使用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)
[6]你好,三角形 - LearnOpenGL CN (learnopengl-cn.github.io)
使用Shapefile C Library读取shp文件并使用OpenGL绘制的更多相关文章
- [Shapefile C Library]读取shp图形(.net Wapper)
ShapeLib的.net Wapper版可以在官网下载到,在WorldWind中也有使用.ORG据说也是使用的ShapeLib实现的shp文件的读写. 官网:http://shapelib.mapt ...
- C#读取shp文件并获取图形保存到sde要素类中(不使用ESRI的类库,纯c#实现)
说明:首先要将sde要素类发布成对应的要素服务,通过对要素服务的操作,实现数据在sde要素类中的增删 //向服务器发出请求 public string getPostData(string postS ...
- [Shapefile C Library]读写shp图形(C++&.net Wapper)
ShapeLib的.net Wapper版可以在官网下载到,在WorldWind中也有使用.ORG据说也是使用的ShapeLib实现的shp文件的读写. 官网:http://shapelib.mapt ...
- GeoJson的生成与解析,JSON解析,Java读写geojson,geotools读取shp文件,Geotools中Geometry对象与GeoJson的相互转换
GeoJson的生成与解析 一.wkt格式的geometry转成json格式 二.json格式转wkt格式 三.json格式的数据进行解析 四.Java读写geojson 五.geotools读取sh ...
- mfc通过MapWinGIS控件读取shp文件(通过#import实现)
在MFC工程中想使用MapWinGIS组件,有多种方法可以实现, 第一种方法,#Import来实现 1.首先注册MapWinGIS ActiveX组件, 2.新建一个单文档工程:MapGis,为控件添 ...
- GeoTools介绍、环境安装、读取shp文件并显示
GeoTools是一个开放源代码(LGPL)Java代码库,它提供了符合标准的方法来处理地理空间数据,例如实现地理信息系统(GIS).GeoTools库实现了开放地理空间联盟(OGC)规范. Geot ...
- arcgis for android 读取shp文件中文乱码解决方法
设置注册表默认字符,即可解决中文乱码问题. 'dbfDefault' 设置方法1.开始--运行,输入”Regedit“,打开注册表.2.如是用的是 10.x 版本 ArcGIS Desktop,定位到 ...
- mfc通过MapWinGIS控件读取shp文件(不通过#import实现)
1.首先注册MapWinGIS ActiveX组件, 引入MapWinGIS.ocx产生的MapWinGIS_i.h和MapWinGIS_i.c文件,利用CoCreateInstance函数来调用 演 ...
- 结合C++和GDAL实现shapefile(shp)文件的读取
工具:vs2012+GDAL 2.0 数据:中国省界SHP文件bou2_4p.shp 可点击下载 包含头文件: #include "ogrsf_frmts.h" 代码: int ...
- C#、C++用GDAL读shp文件(转载)
C#.C++用GDAL读shp文件 C#用GDAL读shp文件 (2012-08-14 17:09:45) 标签: 杂谈 分类: c#方面的总结 1.目前使用开发环境为VS2008+GDAL1.81 ...
随机推荐
- day35-JSON&Ajax03
JSON&Ajax03 4.jQuery的Ajax请求 原生Ajax请求问题分析: 编写原生的Ajax要写很多的代码,还要考虑浏览器兼容问题,使用不方便 在实际工作中,一般使用JavaScri ...
- [py]残留python.exe导致anaconda python路径无法识别
刚才重下anaconda真是给我整没脾气了 路径啥的都加好了,cmd输入python还是没有,给我跳应用商店去了- 重启也没用 经过一番搜索,找到解决办法: cmd输入"where pyth ...
- Django批量插入(自定义分页器)
目录 一:批量插入 1.常规批量插入数据(时间长,效率低 不建议使用) 2.使用orm提供的bulk_create方法批量插入数据(效率高 减少操作时间) 3.总结 二:自定义分页器 1.自定义分页器 ...
- 接口Interface的作用不止是解耦
简言: 好久没写博客了,今天手痒想写一写.废话少说,我们直入主题,相信大家对接口interface,这个单词一定不陌生.但是要说到它的作用,除了解耦之外,还有什么作用呢?可能大多数人都不是很清楚(大牛 ...
- Window注册表的学习记录
注册表的结构: 概述:注册表是一种树状结构,在很早之前是系统的其他配置信息存放的文件,通常以.ini结尾的文件,因为数量太多不方便管理,后来就整合在一起形成了注册表.你可以按住键盘win+r,然后输入 ...
- 用Dockerfile制作一个java应用镜像,ubuntu基础篇
内容介绍: (1) 本章目的,将一个自行开发的java程序webpay-api,制作为docker自定义镜像,并且进行部署. (2) 实验环境: 物理机:VMware 虚拟机 + CentOS 7.8 ...
- [OpenCV实战]3 透明斗篷
目录 1寻找和存储背景帧 2红色区域检测 3提取红色区域 4背景帧红布区域替换当前帧红布区域. 5工程代码 参考 弄出哈利波特电影里一样效果的透明斗篷.也就是一个视频里,将红布弄成透明.类似下面的效果 ...
- [cocos2d-x]registerScriptHandler和registerScriptTapHandler区别
一 .调用registerScriptHandler 的对象不同相应的响应函数和调用方式也不相同 1. 对象为layer时调用方式为: local function onNodeEvent(event ...
- Java学习笔记:2022年1月6日(补充)
Java学习笔记:2022年1月6日(补充) 摘要:这篇笔记主要记录了2022年1月6日下午的笔记,主要内容为Java语言中的基础操作,以及基础知识点,了解这些后基本上就可以使用Java写算法了. ...
- 關於scanf()的使用
要使用scanf函數進行輸入: 1.如果用scanf()要輸入讀取基本變量的值,需要加&. 2.如果用scanf()讀取的是把字符串讀入字符數組中,則不需要加& 1 #include& ...