使用Shapefile C Library读取shp文件并使用OpenGL绘制

1. 概述

坐标数据是空间数据文件的核心,空间数据的数据量往往是很大的。数据可视化是GIS的一个核心应用,绘制海量的坐标数据始终是一个考验设备性能的难题,使用GPU进行绘制可有效减少CPU的负载,提升绘制时的速度

shapefile是空间数据文件常用的格式,Shapefile C Library(也叫shapelib)提供了编写简单的 C/C++ 程序以读取、写入和更新ESRI Shapefile 以及关联的属性文件 (.dbf) 的功能

shapelib的官网:Shapefile C Library (maptools.org)

shapelib的GitHub地址:OSGeo/shapelib: Official repository of shapelib (github.com)

本文基于C++语言,使用shapelib库来读取shp文件,并使用基于GLFW和GLAD来使用OpenGL绘制空间数据

2. 环境准备

本文的系统环境为Ubuntu 20.04.3 LTS,关于C++语言的OpenGL环境搭建可参考:

shapelib在Linux上安装参考其GitHub指导:

具体步骤为:

  • $ git clone https://github.com/OSGeo/shapelib.git
    
  • $ cmake build .
    
  • $ make
    
  • $ make install
    

Windows平台上的安装可以参考:

本文使用的数据为云南的县界,数据信息如图所示:

image-20220707002503270

注意:

  • 本文数据编码是UTF-8
  • 本文数据Geometry是Polygon
  • 本文数据坐标系是投影坐标系

3. 读取shp文件

参考博客:

官方API文档:

这里笔者需要读取shp文件的四至范围和每个Geometry的坐标数据

代码如下:

#include <shapefil.h>

int main()
{
    //读取shp
    const char * pszShapeFile = "../云南县界/云南县界.shp";
    SHPHandle hShp= SHPOpen(pszShapeFile, "r");
    int nShapeType, nVertices;
    int nEntities = 0;
    double* minB = new double[4];
    double* maxB = new double[4];
    SHPGetInfo(hShp, &nEntities, &nShapeType, minB, maxB);
    printf("ShapeType:%d\n", nShapeType);
    printf("Entities:%d\n", nEntities);
    printf("Xmin, Ymin: %f,%f\n", minB[0], minB[1]);
    printf("Xmax, Ymax: %f,%f\n", maxB[0], maxB[1]);
    for (int i = 0; i < nEntities;i++)
    {
        int iShape = i;
        SHPObject *obj = SHPReadObject(hShp, iShape);
        printf("--------------Feature:%d------------\n",iShape);
        int parts = obj->nParts;
        int verts=obj->nVertices;
        printf("nParts:%d\n", parts);
        printf("nVertices:%d\n", verts);
        for (size_t i = 0; i < verts; i++)
        {
            double x=obj->padfX[i];
            double y = obj->padfY[i];
            printf("%f,%f;", x,y);
        }
        printf("\n");
    }
    SHPClose(hShp);
}

结果输出:

Xmin, Ymin: 348122 2.34118e+06
Xmax, Ymax: 1.23349e+06 3.23468e+06
ShapeType:5
Entities:125
......

4. OpenGL绘制

OpenGL的绘制流程参考:

该网站上绘制两个三角形的示例代码:

本文所使用的方法是每个Geometry绑定一个VAO和VBO,然后进行绘制

全部代码如下:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <shapefil.h>
#include <vector>
#include <iostream>

void framebuffer_size_callback(GLFWwindow *window, int width, int height);
void processInput(GLFWwindow *window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
double XMAX, YMAX, XMIN, YMIN;
std::vector<int> size;

const char *vertexShaderSource = "#version 330 core\n"
                                 "layout (location = 0) in vec3 aPos;\n"
                                 "void main()\n"
                                 "{\n"
                                 "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
                                 "}\0";
const char *fragmentShaderSource = "#version 330 core\n"
                                   "out vec4 FragColor;\n"
                                   "void main()\n"
                                   "{\n"
                                   "   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
                                   "}\n\0";

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow *window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // build and compile our shader program
    // ------------------------------------
    // vertex shader
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // check for shader compile errors
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n"
                  << infoLog << std::endl;
    }
    // fragment shader
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // check for shader compile errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n"
                  << infoLog << std::endl;
    }
    // link shaders
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    // check for linking errors
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success)
    {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n"
                  << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    const char *pszShapeFile = "../云南县界/云南县界.shp";
    SHPHandle hShp = SHPOpen(pszShapeFile, "r");
    int nShapeType, nVertices;
    int nEntities = 0;
    double *minB = new double[4];
    double *maxB = new double[4];
    SHPGetInfo(hShp, &nEntities, &nShapeType, minB, maxB);
    std::cout <<"Xmin, Ymin: "<< minB[0] << " " << minB[1] << std::endl;
    std::cout <<"Xmax, Ymax: "<< maxB[0] << " " << maxB[1] << std::endl;
    XMAX = maxB[0];
    XMIN = minB[0];
    YMAX = maxB[1];
    YMIN = minB[1];
    printf("ShapeType:%d\n", nShapeType);
    printf("Entities:%d\n", nEntities);

    unsigned int VBOs[nEntities], VAOs[nEntities];
    glGenVertexArrays(nEntities, VAOs); // we can also generate multiple VAOs or buffers at the same time
    glGenBuffers(nEntities, VBOs);
    for (int i = 0; i < nEntities; i++)
    {
        std::vector<double> arr;
        int iShape = i;
        SHPObject *obj = SHPReadObject(hShp, iShape);
        // printf("--------------Feature:%d------------\n",iShape);
        int parts = obj->nParts;
        int verts = obj->nVertices;
        // printf("nParts:%d\n", parts);
        // printf("nVertices:%d\n", verts);
        for (size_t i = 0; i < verts; i++)
        {
            double x = (obj->padfX[i] - XMIN) / (XMAX - XMIN) * 2 - 1;
            double y = (obj->padfY[i] - YMIN) / (YMAX - YMIN) * 2 - 1;
            // double x = obj->padfX[i];
            // double y = obj->padfY[i];
            // printf("%f,%f;", x, y);
            arr.push_back(x);
            arr.push_back(y);
            arr.push_back(0.0f);
        }
        
        size.push_back(arr.size()/3);
        double vertices[arr.size()];
        std::copy(arr.begin(), arr.end(), vertices);

        glBindVertexArray(VAOs[i]);
        glBindBuffer(GL_ARRAY_BUFFER, VBOs[i]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        glVertexAttribPointer(0, 3, GL_DOUBLE, GL_FALSE, 3 * sizeof(double), (void *)0); // Vertex attributes stay the same
        glEnableVertexAttribArray(0);
    }
    SHPClose(hShp);

    // uncomment this call to draw in wireframe polygons.
    // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----
        processInput(window);

        // render
        // ------
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glUseProgram(shaderProgram);
        for (int i = 0; i < size.size(); i++)
        {
            glBindVertexArray(VAOs[i]);
            glDrawArrays(GL_LINE_STRIP, 0, size[i]);
            // std::cout<<VAOs[i]<<"  "<<size[i]<<std::endl;
        }

        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // optional: de-allocate all resources once they've outlived their purpose:
    // ------------------------------------------------------------------------
    glDeleteVertexArrays(size.size(), VAOs);
    glDeleteBuffers(size.size(), VBOs);
    glDeleteProgram(shaderProgram);

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

本文基于CMake构建,CMakeLists.txt代码如下:

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息
project (Demo)

# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
# aux_source_directory(.. DIR_SRCS)

find_package(shapelib)
find_package(glfw3 REQUIRED)
find_package( OpenGL REQUIRED )
include_directories( ${OPENGL_INCLUDE_DIRS} )

# 指定生成目标
# add_executable(Demo ${DIR_SRCS})
add_executable(Demo ../glad.c ../shapelib_opengl.cpp)

target_link_libraries(${PROJECT_NAME} ${shapelib_LIBRARIES} ${OPENGL_LIBRARIES} glfw)

注意

  • shapelib_opengl.cpp是上述代码的文件名

构建:

$ cmake build .

编译:

$ make

运行:

./Demo

结果如图所示:

image-20220713145548871

5. 参考资料

[1]OSGeo/shapelib: Official repository of shapelib (github.com)

[2]Shapefile C Library (maptools.org)

[3]shapelib库 VS2017X64编译并调用 | 码农家园 (codenong.com)

[4]GIS开源库shapeLib的使用方法(一) - cjingzm - 博客园 (cnblogs.com)

[5][Shapefile C Library]读写shp图形(C++&.net Wapper) - 归山须尽丘壑美 - 博客园 (cnblogs.com)

[6]你好,三角形 - LearnOpenGL CN (learnopengl-cn.github.io)

posted @ 2022-07-12 23:51  当时明月在曾照彩云归  阅读(1360)  评论(0编辑  收藏  举报