OpenGL+QT实现矢量和影像的叠加绘制
一、QT下OpenGL框架的初始化
OpenGL的介绍我在这里就没有必要介绍了,那OpenGL和QT的结合在这里就有必要先介绍一下,也就是怎么使用QT下的OpenGL框架。要想使用QT下的OpenGL框架,就必须要子类化QGLWidget,然后实现。
void initializeGL(); //初始化窗口void paintGL(); //画窗口 void resizeGL( int width, int height ); //重置窗口这三个函数就可以了,第一个函数是初始化OpenGL的函数,函数如下:void GeoGLWidget::initializeGL(){//initglew();glewInit();glClearColor(0.0, 0.0, 0.0, 0.0);//glClearColor(1.0, 1.0, 1.0, 1.0);glShadeModel(GL_FLAT);glEnable(GL_LINE_SMOOTH);glEnable(GL_DEPTH_TEST);glDepthFunc(GL_LEQUAL);glHint (GL_LINE_SMOOTH_HINT, GL_DONT_CARE);//启用顶点数组glEnableClientState(GL_VERTEX_ARRAY);glEnable(GL_BLEND);glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);glEnable(GL_LINE_SMOOTH); const GLubyte* OpenGLVersion= glGetString(GL_VERSION);//返回当前OpenGL实现的版本号const GLubyte* name =glGetString(GL_VENDOR);const GLubyte* pszRender= glGetString(GL_RENDERER);const GLubyte* pszGluVersion= gluGetString(GLU_VERSION);gluGetString(GLU_EXTENSIONS);const GLubyte *glslVersion=glGetString(GL_SHADING_LANGUAGE_VERSION );GLint nMaxStack = 0;glGetIntegerv(GL_MAX_MODELVIEW_STACK_DEPTH,&nMaxStack);printf("%d",nMaxStack);if ( ! GLEW_ARB_vertex_program ){fprintf(stderr, "ARB_vertex_programis missing!\n");}//const GLubyte*glslVersion =glGetString(GL_SHADING_LANGUAGE_VERSION);printf("%s\n",glslVersion);//获得OpenGL扩展信息const GLubyte *extensions= glGetString(GL_EXTENSIONS);GLint nExtensions;glGetIntegerv(GL_NUM_EXTENSIONS, &nExtensions);}第二个函数就是来实现绘图操作的函数void GeoGLWidget::paintGL(){glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glColor3f(0.0, 1.0, 0.0);glLoadIdentity();glEnable(GL_POINT_SMOOTH) ;glLineWidth(2.0f);glFlush();}第三个参数就是窗口大小变化的重绘函数,基本的函数实现如下:void GeoGLWidget::resizeGL( int width, int height ){if ( height == 0 ) { height= 1; }int nWidth = width< height ? width: height;glViewport(0, 0, /*0.5**/(GLint)width, /*0.5**/(GLint)height); glMatrixMode(GL_PROJECTION ); glLoadIdentity();GeoEnvelopegeoEnv;m_poLayer->GetEnvelope(&geoEnv);if (width <= height){glOrtho(geoEnv.minX,geoEnv.maxX,geoEnv.minY,geoEnv.maxY*((GLfloat)height/(GLfloat)width),1.0f,-1.0f);}else{GLdoublefScale = ((GLfloat)width/(GLfloat)height);GLdouble fRight= geoEnv.maxX+(geoEnv.GetWidth()/width);glOrtho(geoEnv.minX,fRight,geoEnv.minY,geoEnv.maxY,1.0f,-1.0f);}}
由于GIS和影像主要是在二维平面上绘图,所以我这边使用正射投影,正射投影函数如下:
GLAPI void GLAPIENTRY glOrtho(GLdouble left, GLdouble right,GLdouble bottom,GLdouble top,GLdouble zNear,GLdouble zFar);
left是指窗口左边的坐标,right为窗口右边的坐标,同理bottom, top就代表上边界和下边界的坐标。zNea和zFar分辨代表近裁剪面和远裁剪面到坐标原点的距离。
这样一个基本的框架就搭建完成了,下一步是如何读取影像和矢量的数据并绘制出来。
二、GIS数据的读取
GIS数据读取采用自己实现的简化版GIS引擎来读取,其实这里引擎部分实现的是符合OpenGIS简单要素访问协议规范的GIS内核,数据读取引擎只不过是将文件中的数据读出来转换为引擎中的数据结构,下一步可以实现简单的插件架构,GIS内核封装为底层核心API,各种数据读取封装为数据驱动插件API,然后在底层核心API和插件API的基础上构建应用程序。
以shapefile为例,通过下面的类实现读取
class GeoShapeLayer : public GeoVectorLayer
GeoShapeLayer是shapefile文件对应的驱动类,GeoVectorLayer是矢量数据图层的一个抽象类,本身只提供接口,具体的功能实现在各种数据格式的图层中,这样就保证了系统的可扩展性和稳定性。
对于影像数据的读取,我这边采用GDAL来读取所支持的类型,对于其他数据格式GDAL不支持的,也可以自己封装数据格式的原生API来实现读取。
class GeoGdalImageLayer : publicGeoImageLayer
GeoGdalImageLayer类就是GDAL支持的数据格式驱动,同样GeoImageLayer是一个抽象类。
下面,就以一个三波段8位的影像数据作为例子实现数据读取。
int GeoGdalImageLayer::ReadData(int nBandCount,int *pBandIndex,int nXstart,int nYstart,int nWidth,int nHeight,int nBufXSize,int nBufYSize,void* poData){assert(nBandCount > 0);assert(pBandIndex != NULL);assert(poData != NULL);if (m_poDataset != NULL){int nDataSize = GDALGetDataTypeSize(GDT_Byte)/8;m_poDataset->RasterIO(GF_Read,nXstart,nYstart,nWidth,nHeight,poData,nBufXSize,nBufYSize,GDT_Byte,nBandCount,pBandIndex,nBandCount*nDataSize,nBufXSize*nBandCount*nDataSize,1*nDataSize);return1;}return 0;}
这样数据的排列格式就是BIP格式,如果只有三个波段,我们也可以理解为RGBRGBRGB排列格式,这样便于OpenGL的绘制。具体的RasterIO函数详解可以参考GDAL的官方文档。好了,数据读取就到这里了。接下来该到绘制部分了吧。
三、OpenGL矢量和影像绘制
这里为了说明问题,我找的矢量数据和影像数据有重叠的部分,都是福州市区的。这里我先读取矢量数据,先确定了OpenGL的正射投影的范围。这个确定了之后,接下来该确定影像的读取范围,是读取整个范围还是一部分,这个需要简单的计算,根据矢量的MBR,下面的代码是确定影像读取范围的行列号。
int bandList[3]= {1,2,3};long nLeft = 0;long nTop = 0;long nRight = 0;long nBottom = 0;m_pImageLayer->WorldToPixel(geoEnv.minX,geoEnv.maxY,nLeft,nTop);m_pImageLayer->WorldToPixel(geoEnv.maxX,geoEnv.minY,nRight,nBottom);if (nLeft < 0){nLeft= 0;}if (nRight >= m_pImageLayer->GetWidth());{nRight= m_pImageLayer->GetWidth()-1;}if (nTop < 0){nTop= 0;}if (nBottom >= m_pImageLayer->GetHeight()){nBottom= m_pImageLayer->GetHeight()-1;}int nReadWidth = nRight-nLeft+1;int nReadHeight = nBottom-nTop+1;
影像的像素范围计算出来后,然后需要将像素范围转换为屏幕像素范围,这主要是为了确定绘图的区域。
//然后计算像素范围对应的地理范围double winMinx = 0;double winMaxx = 0;double winminy = 0;double winmaxy = 0;m_pImageLayer->PixelToWorld(nLeft,nTop,winMinx,winmaxy);m_pImageLayer->PixelToWorld(nRight,nBottom,winMaxx,winminy);
PixelToWorld实现影像像素坐标转为地理坐标。这个影像对应的范围确定之后,然后需要转换为屏幕上对应的像素宽度和高度,这样绘制的位置才能正确无误。
//计算地理区域对应的屏幕像素区域,这就是绘制影像的窗口范围GLdouble dbLeft = 0;GLdouble dbRight = 0;GLdouble dbTop = 0;GLdouble dbBottom = 0;WorldToScreen(winMinx,winmaxy,1.0,&dbLeft,&dbTop);WorldToScreen(winMaxx,winminy,1.0,&dbRight,&dbBottom);m_nDrawLeft= winMinx;m_nDrawTop= winmaxy;int nDrawWidth = fabs(dbRight-dbLeft)+1;int nDrawHeight = fabs(dbBottom-dbTop)+1;m_nDrawWidth= nDrawWidth;m_nDrawHeight= nDrawHeight;
上面确定了影像绘制的起始坐标和宽度,就可以用glRasterPos3d和glDrawPixels来绘制影像了。
注意,OpenGL是从底向上扫描图像,而我们的遥感影像一般是从左上角的像素开始,为了保证影像看上去不是被翻转了的,可以再绘制前先用glPixelZoom函数设置下,具体的代码片段如下:
glPixelStorei(GL_UNPACK_ALIGNMENT,1);glRasterPos3d(m_nDrawLeft,m_nDrawTop,0);glPixelZoom(1.0,-1.0); //从上到下绘制if (m_poData != NULL){glDrawPixels(m_nDrawWidth,m_nDrawHeight,GL_RGB,GL_UNSIGNED_BYTE,m_poData);}
上面的代码片段中WorldToScreen是将世界坐标转换为屏幕像素坐标的过程。这个转换过程主要用到GLU库中的gluProject函数,其声明如下:
int APIENTRY gluProject (GLdouble objx,GLdouble objy,GLdouble objz, const GLdouble modelMatrix[16],const GLdouble projMatrix[16],const GLint viewport[4],GLdouble *winx,GLdouble *winy,GLdouble *winz);
objx,objy,objz代表物体的三维坐标,modelMatrix[16]代表模型视图矩阵、projMatrix[16]代表投影矩阵,viewport[4]为定义的视口变换,最后三个参数就是OpenGL的窗口坐标,注意他的左下角是原点,这是和窗口坐标的不同点,废话少说,上代码吧:
void GeoGLWidget::WorldToScreen(GLdoubleobjx, GLdoubleobjy, GLdoubleobjz, GLdouble*winx, GLdouble*winy){//获得当前的模型变换矩阵double dbModelMatrixs[16];glGetDoublev(GL_MODELVIEW_MATRIX,dbModelMatrixs);//获得投影变换的矩阵double dbProjectionMartixs[16];glGetDoublev(GL_PROJECTION_MATRIX,dbProjectionMartixs);//获得视口坐标GLint viewport[4];glGetIntegerv(GL_VIEWPORT,viewport);//获得opengl的视口坐标GLdouble winX, winY, winZ; gluProject(objx,objy,objz,dbModelMatrixs,dbProjectionMartixs,viewport,&winX,&winY,&winZ);//求得opengl窗口坐标*winx = (GLdouble)winX; *winy = (GLdouble)viewport[3]- (GLdouble)winY;}
不出意外,影像能够绘制在窗口中。

好了,既然影像已经显示出来了,最后要将矢量叠加上进行显示。
矢量的显示最原始的做法是使用glVertex系列函数,然而这种方法对于数据量比较大的话多次调用该函数会导致效率降低,为了提高效率,本文使用缓冲区对象存储顶点数组。
下面是绘制折线的代码片段
//渲染折线glColor3f(0.0, 1.0, 1.0);GeoCoordinate*poPoints = newGeoCoordinate[poRing->GetNumPoint()];poRing->GetPoints(poPoints);std::vector<double>vecVertexs;int n = poRing->GetNumPoint();GLuint*pIndex = newGLuint[n];for(int j = 0; j < n; j ++){vecVertexs.push_back(poPoints[j].x);vecVertexs.push_back(poPoints[j].y);pIndex[j] = j;}delete[]poPoints;GLuint *pBuffer= new GLuint[1];//glGenBuffersARB(1,pBuffer);glGenBuffers(1,pBuffer);glBindBuffer(GL_ARRAY_BUFFER,pBuffer[0]); //绑定对象、glBufferData(GL_ARRAY_BUFFER,sizeof(double)*n*2,&vecVertexs[0],GL_STATIC_DRAW);//分配空间glVertexPointer(2,GL_DOUBLE,0,BUFFER_SET(0)); //指定顶点glDrawElements(GL_LINE_STRIP,poRing->GetNumPoint(),GL_UNSIGNED_INT,pIndex);glFlush();
这样,我们就将影像和矢量叠加上了。如下图所示:

从上面的图可以看出,影像的范围比矢量小,我将投影范围设置为影像的范围,如下图所示:

四、后记
其实这只是最简单的一个demo,在OpenGL的学习和钻研上还需要深入下去。
相关文章:
OpenGL+QT实现矢量和影像的叠加绘制
一、QT下OpenGL框架的初始化 OpenGL的介绍我在这里就没有必要介绍了,那OpenGL和QT的结合在这里就有必要先介绍一下,也就是怎么使用QT下的OpenGL框架。要想使用QT下的OpenGL框架,就必须要子类化QGLWidget,然后实现。 void initia…...
vue基础——java程序员版(vuex)
vuex可以定义共享数据。 1、主要结构 src/store/index.js 是使用vuex的核心js文件。 定义数据:state 修改数据(同步):mutations 修改数据(异步):action调用>mutations 下面定义了一个公共数据msg ,mutations方法setName…...
ubuntu20.04安装 ffmpeg 开发环境
参考:参考1 一些相关软件包,已打包整理好,如下 源码包 1、安装步骤 创建安装目录 sudo mkdir -p /usr/local/ffmpeg/lib 解压源码 tar -jxf ffmpeg-4.3.2.tar.bz2 到指定ffmpeg目录进行配置 cd ffmpeg-4.3.2/ 配置:会报错很多…...
微软开源Garnet高性能缓存服务安装
Garnet介绍 Garnet是一款微软研究院基于C#开发而开源的高性能缓存服务,支持Windows、Linux多平台部署,Garnet兼容Redis服务API,在性能和使用架构上较Redis有很大提升(官方说法),并提供与Redis一样的命令操…...
云计算系统管理(ADMIN)
01. 公司需要将/opt/bjcat3目录下的所有文档打包备份,如何实现? 答案: # tar -czf /tmp/bjcat3.tar.gz /opt/bjcat302. 简述创建crontab计划任务的流程 答案: 利用crontab –e -u 用户名 进入计划任务编辑模式 分 时 日 月 周 …...
Spark spark-submit 提交应用程序
Spark spark-submit 提交应用程序 Spark支持三种集群管理方式 Standalone—Spark自带的一种集群管理方式,易于构建集群。Apache Mesos—通用的集群管理,可以在其上运行Hadoop MapReduce和一些服务应用。Hadoop YARN—Hadoop2中的资源管理器。 注意&…...
IOS面试题编程机制 51-55
51. 在iPhone应用中如何保存数据?有以下几种保存机制: 1).通过web服务,保存在服务器上 2).通过NSCoder固化机制,将对象保存在文件中 3).通过SQlite或CoreData保存在文件数据库中52. 阐述Block 的理解?并写出一个使用Block执行UIVew动画?Block是可以获取其他函数局部变量的…...
话题——AI大模型学习
AI大模型学习 在当前技术环境下,AI大模型学习不仅要求研究者具备深厚的数学基础和编程能力,还需要对特定领域的业务场景有深入的了解。通过不断优化模型结构和算法,AI大模型学习能够不断提升模型的准确性和效率,为人类生活和工作…...
MySQL基础复习
目录 一、简单的命令 二、SQL语句分类 三、简单查询 四、条件查询 五、排序 一、简单的命令 net start 服务名称 net stop 服务名称 mysql -uroot -p123456 显示密码形式 mysql -uroot -p 隐藏密码形式 exit 退出 show databases; 查看MySQL中的数据库有哪些 use test…...
Zookeeper(八)序列化与协议
目录 一 序列化与反序列化1.1 Jute序列化工具1.1 Recor接口1.2 OutputArchive和InputArchive 二 通信协议2.1 请求部分2.1.1 请求头2.2.2 请求体2.1.3 案例分析 2.2 响应部分2.2.1 响应头2.2.2 响应内容2.2.3 案例分析 官网:Apache ZooKeeper 一 序列化与反序列化 …...
人工智能之Tensorflow变量作用域
在TensoFlow中有两个作用域(Scope),一个时name_scope ,另一个是variable_scope。variable_scope主要给variable_name加前缀,也可以给op_name加前缀;name_scope给op_name加前缀。 variable_scope 通过所给的名字创建或…...
ElasticSearch插件安装及配置
Docker安装ElasticSearch docker compose 安装直接看步骤三:新建索引 1、安装elasticsearch (1)下载elasticsearch和kibana docker pull elasticsearch:7.9.1 docker pull kibana:7.9.1(2)配置 mkdir -p /mydata/…...
vue+Echarts实现多设备状态甘特图
目录 1.效果图 2.代码 3.注意事项 Apache ECharts ECharts官网,可在“快速上手”处查看详细安装方法 1.效果图 可鼠标滚轮图表和拉动下方蓝色的条条调节时间细节哦 (注:最后一个设备没有数据,所以不显示任何矩形)…...
STM32使用滴答定时器实现delayms
在STM32上使用SysTick实现jiffies(时间戳)并且实现delay_ms 代码实现: volatile uint32_t jiffies 0; // 用于记录系统运行的jiffies数 void SysTick_Handler(void) {/* 每次SysTick中断,jiffies增加 */jiffies; }uint32_t tick…...
k8s的volumn解析
背景 k8s中有一套自己的存储逻辑,它和docker中的volumn类似,本文就来看一下k8s的volunm的存储设计 k8s的volumn 1.EmptyDir类型的volumn 这种类型的volumn是Pod内的容器共享的,volumn的生命周期和Pod的生命周期是一致的,不过大…...
Golang获取音视频时长信息
文章目录 一、工具简介二、使用golang获取时间长 一、工具简介 这些工具都是与多媒体处理和流媒体相关的开源工具,它们都属于 FFmpeg 多媒体框架。 FFmpeg 是一个用于处理多媒体内容(音频、视频、图像等)的命令行工具。它可以执行各种各样…...
LeetCode 面试经典150题 14.最长公共前缀
题目: 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 ""。 思路: 代码: class Solution {public String longestCommonPrefix(String[] strs) {if (strs.length 0) {return &…...
自注意力机制的理解
一、自注意力要解决什么问题 循环神经网络由于信息传递的容量以及梯度消失问题,只能建立短距离依赖关系。为了建立长距离的依赖关系,可以增加网络的层数或者使用全连接网络。但是全连接网络无法处理变长的输入序列,另外,不同的输…...
win10-误删winsock恢复方法
文件链接放在最前面 链接:https://pan.baidu.com/s/1i9X0HJJOfo63fbtOETc1Xw?pwdlfqx 提取码:lfqx 误删后应该还是可以正常连接网络的,但是重启过后直接以太网和wifi都是无法使用的。下图是我后面网络正常补充的图片 误删后是只有飞行模式…...
c#矩阵求逆
目录 一、矩阵求逆的数学方法 1、伴随矩阵法 2、初等变换法 3、分块矩阵法 4、定义法 二、矩阵求逆C#代码 1、伴随矩阵法求指定3*3阶数矩阵的逆矩阵 (1)伴随矩阵数学方法 (2)代码 (3)计算 2、对…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)
Name:3ddown Serial:FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名:Axure 序列号:8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP...
怎么开发一个网络协议模块(C语言框架)之(六) ——通用对象池总结(核心)
+---------------------------+ | operEntryTbl[] | ← 操作对象池 (对象数组) +---------------------------+ | 0 | 1 | 2 | ... | N-1 | +---------------------------+↓ 初始化时全部加入 +------------------------+ +-------------------------+ | …...
简单介绍C++中 string与wstring
在C中,string和wstring是两种用于处理不同字符编码的字符串类型,分别基于char和wchar_t字符类型。以下是它们的详细说明和对比: 1. 基础定义 string 类型:std::string 字符类型:char(通常为8位)…...
