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的学习和钻研上还需要深入下去。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/759981.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java算法总结之插入排序(详解)

程序代码园发文地址&#xff1a;Java算法总结之插入排序&#xff08;详解&#xff09;-程序代码园小说,Java,HTML,Java小工具,程序代码园,http://www.byqws.com/ ,Java算法总结之插入排序&#xff08;详解&#xff09;http://www.byqws.com/blog/3148.html?sourcecsdn 插入排…

Oracle数据库删除归档脚本(实例)

Oracle数据库删除归档脚本&#xff08;实例&#xff09; #!/bin/sh applied_days2 cat /dev/null>applied_arch.lst sqlplus -s "/as sysdba" <<EOF>/dev/null set feedback off set pages 0 set head off set timing off set echo off spool applied_a…

IDEA 多个git仓库项目放一个窗口

1、多个项目先通过新建module或者CtrlAltShiftS 添加module引入 2、重点是右下角有时候git 分支视图只有一个module的Repositories。这时候需要去设置把多个git仓库添加到同一个窗口才能方便提交代码。 3、如果Directory Mappings已经有相关项目配置&#xff0c;但是灰色的&…

常量与变量

1 常量和符号常量 在程序运行过程中,其值不能被改变的量称为常量。常量区分为不同的类型,如12、0、-3为整型常量&#xff0c;4.6、-1.23为实型常量&#xff0c;a、d为字符常量。常量一般从其字面形式即可判别。这种常量称为字面常量或直接常量。 也可以用一个标识符代表一个常…

【NBUOJ刷题笔记】递推_递归+分治策略1

0. 前言 PS&#xff1a;本人并不是集训队的成员&#xff0c;因此代码写的烂轻点喷。。。本专题一方面是巩固自己的算法知识&#xff0c;另一方面是给NBU学弟学妹们参考解题思路&#xff08;切勿直接搬运抄袭提交作业&#xff01;&#xff01;&#xff01;&#xff09;最后&…

【消息队列开发】 虚拟主机设计——操作绑定

文章目录 &#x1f343;前言&#x1f332;添加绑定&#x1f333;删除绑定⭕总结 &#x1f343;前言 本次开发任务&#xff1a; 实现对绑定的添加与绑定 &#x1f332;添加绑定 对于绑定的操作相较于前面对交换机和队列的操作就会麻烦一点了 我们分为以下七步来实现&#x…

【xr806开发板使用】连接wifi例程实现

##开发环境 win10 WSL ##1、环境配置 参考&#xff1a;https://aijishu.com/a/1060000000287513 首先下载安装wsl 和ubuntu https://docs.microsoft.com/zh-cn/windows/wsl/install &#xff08;1&#xff09;安装repo&#xff1a; 创建repo安装目录&#xff1a; mkdir ~/…

AI预测福彩3D第15弹【2024年3月21日预测--新算法重新开始计算第12次测试】

今天继续对第一套算法进行测试&#xff0c;废话不多说&#xff0c;直接上结果。 最终&#xff0c;经过研判分析&#xff0c;2024年3月21日福彩3D的七码预测结果如下&#xff1a; 百位&#xff1a;5 4 7 1 2 0 6 十位&#xff1a;3 5 1 2 0 4 6(9换6) 个位&#xff1a;2 0 1 3 4…

修改网站源码,给电子商城的商品添加图片时商品id为0的原因

修改网站源码&#xff0c;给电子商城的商品添加图片时商品id为0的原因。花了几个小时查找原因。后来&#xff0c;由于PictureControl.class.php是复制CourseControl.class.php而来&#xff0c;于是对比了这两个文件&#xff0c;在CourseControl.class.php找到了不一样的关键几条…

【算法】差分算法(空调)

可用于求一个数组要变为另一个数组最少要改变多少次的次数 Farmer John 的 N 头奶牛对他们牛棚的室温非常挑剔。 有些奶牛喜欢温度低一些&#xff0c;而有些奶牛则喜欢温度高一些。 Farmer John 的牛棚包含一排 N 个牛栏&#xff0c;编号为 1…N&#xff0c;每个牛栏里有一头…

【Linux系列】计算机系统中的架构与发行版:理解与区分

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

一文读懂MES和ERP的区别

MES&#xff08;Manufacturing Execution System&#xff09;系统是制造执行系统&#xff0c;位于上层的计划管理系统与生产过程的直接工业控制系统之间&#xff0c;是面向车间层的管理信息系统&#xff0c;能够对整个车间制造过程进行优化&#xff0c;实时收集生产过程中的数据…

LeetCode-60题:排列序列解法二(原创)

【题目描述】 给出集合 [1,2,3,...,n]&#xff0c;其所有元素共有 n! 种排列。按大小顺序列出所有排列情况&#xff0c;并一一标记&#xff0c;当 n 3 时, 所有排列如下&#xff1a;"123" 、"132" 、"213" 、"231"、"312"、…

数字化转型:传统行业的新出路?

近年来&#xff0c;数字化转型已成为各行各业的热议话题。许多传统行业正面临着巨大的挑战&#xff0c;例如市场竞争加剧、成本上升、利润率下降等。数字化转型被视为传统行业破局的关键。那么&#xff0c;数字化转型究竟是不是传统行业的新出路呢&#xff1f; 传统行业面临的挑…

Qt 坐标位置转换

Qt 坐标位置转换 文章目录 Qt 坐标位置转换常见的位置坐标转换Qt窗体中常用坐标的区别与获取途径当前光标相对于屏幕的绝对位置当前光标相对于当前窗口的位置鼠标事件发生的位置窗体的位置判断鼠标光标是否悬浮在某个子控件上 从事Qt快一年了 &#xff0c;在做坐标转换的时候容…

OpenCV4.9.0在Android 开发简介

查看&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;使用 Clojure 进行 OpenCV 开发简介 下一篇&#xff1a;暂无 引言&#xff1a; OpenCV是一个跨平台计算机视觉库&#xff0c;广泛用于图像处理、计算机视觉和机器学习等领域…

python网络爬虫实战教学——urllib的使用(3)

文章目录 专栏导读1、urlsplit2、urlunsplit3、urljoin4、urlencode 专栏导读 ✍ 作者简介&#xff1a;i阿极&#xff0c;CSDN 数据分析领域优质创作者&#xff0c;专注于分享python数据分析领域知识。 ✍ 本文录入于《python网络爬虫实战教学》&#xff0c;本专栏针对大学生、…

日志集中审计系列(1)--- LogAuditor接收DAS设备syslog日志

日志集中审计系列(1)--- LogAuditor接收DAS设备syslog日志 前言拓扑图设备选型组网需求配置思路操作步骤结果验证前言 近期有读者留言:“因华为数通模拟器仅能支持USG6000V的防火墙,无法支持别的安全产品,导致很多网络安全的方案和产品功能无法模拟练习,是否有真机操作的…

群发短信后的实际效果统计分析

群发短信后的实际效果统计分析是评估短信营销效果的关键环节&#xff0c;有助于企业了解短信发送的效果&#xff0c;并据此优化营销策略。以下是对群发短信实际效果统计分析的详细探讨&#xff1a; 首先&#xff0c;企业需要关注短信的发送成功率。这包括短信是否成功发送至目标…

TinTin Web3 Bounty 挑战杯开启,Sui 向你发出挑战邀请!

2024 年开年最火的是什么&#xff1f; 对 Web3 来说&#xff0c;Bounty 任务应该是普通人获得行业“一杯羹”的重要捷径&#xff01; 通过深入学习各类 Web3 技术&#xff0c;凭借实战锻炼开发创新项目&#xff0c;就有机会获得各大生态项目方的 Bounty 奖励。 TinTinLand 社…