使用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平台上的安装可以参考:
本文使用的数据为云南的县界,数据信息如图所示:
注意:
- 本文数据编码是UTF-8
- 本文数据Geometry是Polygon
- 本文数据坐标系是投影坐标系
3. 读取shp文件
参考博客:
- shapelib库 VS2017X64编译并调用_moneymyone的博客-CSDN博客
- [Shapefile C Library]读写shp图形(C++&.net Wapper) - 归山须尽丘壑美 - 博客园 (cnblogs.com)
官方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
结果如图所示:
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)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律