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.rhkb.cn/news/280683.html

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

相关文章

流畅的 Python 第二版(GPT 重译)(十一)

第二十章&#xff1a;并发执行器 抨击线程的人通常是系统程序员&#xff0c;他们心中有着典型应用程序员终其一生都不会遇到的用例。[…] 在 99%的用例中&#xff0c;应用程序员可能会遇到的情况是&#xff0c;生成一堆独立线程并将结果收集到队列中的简单模式就是他们需要了解…

sqllab第三十四关通关笔记

知识点&#xff1a; 宽字节注入单引号闭合注意&#xff1a;不能直接在输入框进行宽字节注入&#xff0c;会被url编码&#xff0c;除非输入原始字符&#xff08;%df已经是url编码了&#xff0c;直接输入会二次编码&#xff09;错误注入 payload:username1%dforextractvalue(1,c…

【G3D笔记】AI生成式3D算法相关环境安装爬坑笔记

【G3D笔记】AI生成式3D算法相关环境安装爬坑笔记) 1、 RayMarching1.1 error C1189: #error: You need C++17 to compile PyTorch1.2 raymarching安装环境版本测试1.3 host_config.h(231): fatal error C1083: 无法打开包括文件: “crtdefs.h”2、Tiny-Cuda-nn2.1 HTTP/2 stre…

SpringCloudAlibaba系列之Seata实战

目录 环境准备 1.下载seata安装包 2.修改配置文件 3.准备seata所需配置文件 4.初始化seata所需数据库 5.运行seata 服务准备 分布式事务测试 环境准备 1.下载seata安装包 Seata-Server下载 | Apache Seata 本地环境我们选择稳定版的二进制下载。 下载之后解压到指定目录…

Mysql:行锁,间隙锁,next-key锁?

注&#xff1a;以下讨论基于InnoDB引擎。 文章目录 问题引入猜想1&#xff1a;只加了一行写锁&#xff0c;锁住要修改的这一行。语义问题数据一致性问题 猜想2&#xff1a;要修改的这一行加写锁&#xff0c;扫描过程中遇到其它行加读锁猜想3&#xff1a;要修改的这一行加写锁&…

微信小程序Skyline模式自定义tab组件胶囊与原生胶囊平齐,安卓和ios均自适应

进入下面小程序可以体验效果&#xff1a; APP.JS // app.js App({onLaunch() {;(async ()>{const systemInfo wx.getSystemInfoSync()this.globalData.system systemInfoconst menuInfo wx.getMenuButtonBoundingClientRect()this.globalData.menu menuInfo})()},glob…

【Java - 框架 - Mybatis】(01) 普通Java项目使用Mybatis操作Mysql - 快速上手

普通Java项目使用Mybatis操作Mysql - 快速上手 说明 通过软件"IntelliJ IDEA"创建"Maven"项目完成&#xff1b;通过"Mybatis"框架操纵"MySQL"数据库完成操作&#xff1b; 环境 “Java"版本"1.8.0_202”&#xff1b;“Wi…

arm-linux实现onvif server+WS-UsernameToken令牌验证

目录 一、环境搭建 1、安装openssl 2、安装bison 3、安装flex 二、gsoap下载 三、编译x86版本gsoap 四、编译arm-linux版本gsoap 1、交叉编译openssl 1.1、下载openssl 1.2、交叉编译 2、交叉编译zlib 2.1、下载zlib 2.2、交叉编译 3、交叉编译gsoap 3.1、编译过…

【鸿蒙HarmonyOS开发笔记】通知模块之发布进度条类型通知

概述 进度条通知也是常见的通知类型&#xff0c;主要应用于文件下载、事务处理进度显示。HarmonyOS提供了进度条模板&#xff0c;发布通知应用设置好进度条模板的属性值&#xff0c;如模板名、模板数据&#xff0c;通过通知子系统发送到通知栏显示。 目前系统模板仅支持进度条…

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

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

国内IP地址切换排行榜软件大全

随着互联网的飞速发展&#xff0c;IP地址切换技术在日常工作和生活中扮演着越来越重要的角色。无论是为了网络安全、访问特定地区网站&#xff0c;还是进行市场调研、网络爬虫等&#xff0c;IP地址切换都成为了不可或缺的工具。虎观代理将为您介绍国内较受欢迎的IP地址切换软件…

springBoot项目,无配置中心,怎么实现类似功能

实现EnvironmentPostProcessor import cn.hutool.http.HttpUtil; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.YamlPropertySourceLoader; import org.springfr…

FPGA学习_Xilinx7系列FPGA基本结构

文章目录 前言一、7系列FPGA介绍1.1、芯片编号 二、基本组成单元2.1、可编程逻辑块CLB&#xff08;Configable Logic Block&#xff09;2.2、可编程输入输出单元&#xff08;IOB&#xff09;2.3、嵌入式块RAM&#xff08;Block RAM&#xff09;2.4、底层内嵌功能单元2.5、内嵌专…

酷开系统提供畅享娱乐平台,酷开科技带来独属科技的温暖

现如今&#xff0c;智能电视早已渗透进千家万户&#xff0c;但大家对智能电视的操作系统却知之甚少&#xff0c;操作系统对于智能电视来说&#xff0c;就相当于人的大脑。购买电视&#xff0c;就像是买手机&#xff0c;购买的时候可能会被颜色、大小所吸引&#xff0c;但是操作…

【word技巧】Word不能复制粘贴是什么原因?

Word文档打开之后&#xff0c;发现文档的内容无法复制粘贴&#xff0c;这是什么原因&#xff1f;今天小编总结了一下原因。 一、 首先&#xff0c;可能是因为我们接收到的文件&#xff0c;在我们自己软件打开的时候会出现兼容性问题。如果除了Word以外&#xff0c;其他格式文…

VUE-组件间通信(一)props

props 1、单向绑定 props是父组件给子组件传输数据 当父组件的属性变化时&#xff0c;将传导给子组件&#xff0c;但是反过来不会 2、使用示例 子组件&#xff08;类似于方法&#xff09; <template> <div><h2>姓名:{{ name }}</h2><h2>性别:{{…

环境安装篇 之 安装kubevela

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 环境安装 系列文章&#xff0c;介绍 oam规范标准实施项目 kubevela 的安装详细步骤kubevela 官方安装文档&#xff1a;https://kubevela.io/zh/docs/installation/kubernetes/ 1.CentOS 安装kubevela 1.1.前提…

【Python】新手入门学习:详细介绍里氏替换原则(LSP)及其作用、代码示例

【Python】新手入门学习&#xff1a;详细介绍里氏替换原则&#xff08;LSP&#xff09;及其作用、代码示例 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyT…

多模态大语言模型的 (R) 演变:调查

目录 1. Introduction2. 赋予LLMs多模态能力2.1 大型语言模型2.2 视觉编码器2.3 视觉到语言适配器2.4 多模式训练 3. 使用 MLLM 处理视觉任务 连接文本和视觉模式在生成智能中起着至关重要的作用。因此&#xff0c;受大型语言模型成功的启发&#xff0c;大量研究工作致力于多模…

垃圾回收-垃圾回收算法

垃圾回收分为标记阶段和清除阶段。 在堆里存放着几乎所有的Java对象实例&#xff0c;在GC执行垃圾回收之前&#xff0c;首先需要区分出内存中哪些是存活对象&#xff0c;哪些是已经死亡的对象。只有被标记为己经死亡的对象&#xff0c;GC才会在执行垃圾回收时&#xff0c;释放…