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、对…...
DeepSeek-OCR-2性能压测报告:深求·墨鉴单节点QPS与延迟实测分析
DeepSeek-OCR-2性能压测报告:深求墨鉴单节点QPS与延迟实测分析 1. 引言:为什么需要性能压测? 最近,一款名为“深求墨鉴”的文档解析工具在技术圈里悄悄火了起来。它基于DeepSeek-OCR-2引擎,号称能将扫描文档、书籍图…...
如何永久保存微信聊天记录:WeChatMsg终极指南与数据守护方案
如何永久保存微信聊天记录:WeChatMsg终极指南与数据守护方案 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we…...
突破平台局限:AirPods跨平台体验增强方案全解析
突破平台局限:AirPods跨平台体验增强方案全解析 【免费下载链接】AirPodsDesktop ☄️ AirPods desktop user experience enhancement program, for Windows and Linux (WIP) 项目地址: https://gitcode.com/gh_mirrors/ai/AirPodsDesktop AirPodsDesktop是一…...
where.exe 是什么openclaw 龙虾调用原理faclaw[AI人工智能(八十一)]—东方仙盟
一、where.exe 是什么?where.exe 是 Windows 系统自带的命令行工具,作用是在系统 PATH 环境变量中查找指定程序 / 文件的位置,相当于 Linux/macOS 里的 which 命令。它的核心功能:输入 where.exe <程序名>,会返回…...
3大跨平台游戏开发库部署方案:从环境搭建到性能优化的全流程指南
3大跨平台游戏开发库部署方案:从环境搭建到性能优化的全流程指南 【免费下载链接】raylib A simple and easy-to-use library to enjoy videogames programming 项目地址: https://gitcode.com/GitHub_Trending/ra/raylib 跨平台游戏开发库raylib凭借其轻量级…...
保姆级教程:在Ubuntu 20.04上搞定Carla 0.9.13编译版安装(附国内镜像加速方案)
Ubuntu 20.04下Carla 0.9.13编译版全流程安装指南 最近在自动驾驶仿真领域,Carla作为开源仿真平台的热度持续攀升。但很多开发者在Ubuntu系统上安装Carla编译版时,总会遇到各种"拦路虎"——从Python版本冲突到资源下载失败,每一步…...
MacBook安装OpenClaw全记录:Phi-3-vision-128k-instruct多模态初体验
MacBook安装OpenClaw全记录:Phi-3-vision-128k-instruct多模态初体验 1. 为什么选择OpenClawPhi-3组合 去年第一次听说OpenClaw时,我就被这个"能直接操作电脑的AI助手"吸引了。作为一个经常需要处理多模态内容的创作者,传统AI工具…...
手把手教你用B站NFT工具设置小钻石头像(含最新工具下载与使用指南)
手把手教你用B站NFT工具设置小钻石头像(含最新工具下载与使用指南) 在数字藏品风靡的当下,B站推出的NFT小钻石头像成为了许多用户展示个性的新选择。不同于传统的头像设置,NFT头像不仅具有独特的收藏价值,还能在B站社…...
Ollama调用translategemma-27b-it部署指南:Kubernetes集群水平扩展实践
Ollama调用translategemma-27b-it部署指南:Kubernetes集群水平扩展实践 1. 项目简介与核心价值 translategemma-27b-it是Google基于Gemma 3模型系列构建的先进翻译模型,专门处理55种语言之间的翻译任务。这个模型最大的特点是既能处理文本翻译…...
lychee-rerank-mm行业方案:文旅部门景区图片库按游客搜索词智能排序
Lychee-rerank-mm行业方案:文旅部门景区图片库按游客搜索词智能排序 1. 项目背景与价值 文旅部门的景区图片库通常包含成千上万张照片,从自然风光到人文景观,从特色建筑到文化活动。当游客通过搜索词查找图片时,如何快速找到最相…...
