当前位置: 首页 > news >正文

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的介绍我在这里就没有必要介绍了&#xff0c;那OpenGL和QT的结合在这里就有必要先介绍一下&#xff0c;也就是怎么使用QT下的OpenGL框架。要想使用QT下的OpenGL框架&#xff0c;就必须要子类化QGLWidget&#xff0c;然后实现。 void initia…...

vue基础——java程序员版(vuex)

​ vuex可以定义共享数据。 1、主要结构 src/store/index.js 是使用vuex的核心js文件。 定义数据&#xff1a;state 修改数据(同步)&#xff1a;mutations 修改数据(异步)&#xff1a;action调用>mutations 下面定义了一个公共数据msg &#xff0c;mutations方法setName…...

ubuntu20.04安装 ffmpeg 开发环境

参考&#xff1a;参考1 一些相关软件包&#xff0c;已打包整理好&#xff0c;如下 源码包 1、安装步骤 创建安装目录 sudo mkdir -p /usr/local/ffmpeg/lib 解压源码 tar -jxf ffmpeg-4.3.2.tar.bz2 到指定ffmpeg目录进行配置 cd ffmpeg-4.3.2/ 配置&#xff1a;会报错很多…...

微软开源Garnet高性能缓存服务安装

Garnet介绍 Garnet是一款微软研究院基于C#开发而开源的高性能缓存服务&#xff0c;支持Windows、Linux多平台部署&#xff0c;Garnet兼容Redis服务API&#xff0c;在性能和使用架构上较Redis有很大提升&#xff08;官方说法&#xff09;&#xff0c;并提供与Redis一样的命令操…...

云计算系统管理(ADMIN)

01. 公司需要将/opt/bjcat3目录下的所有文档打包备份&#xff0c;如何实现&#xff1f; 答案&#xff1a; # tar -czf /tmp/bjcat3.tar.gz /opt/bjcat302. 简述创建crontab计划任务的流程 答案&#xff1a; 利用crontab –e -u 用户名 进入计划任务编辑模式 分 时 日 月 周 …...

Spark spark-submit 提交应用程序

Spark spark-submit 提交应用程序 Spark支持三种集群管理方式 Standalone—Spark自带的一种集群管理方式&#xff0c;易于构建集群。Apache Mesos—通用的集群管理&#xff0c;可以在其上运行Hadoop MapReduce和一些服务应用。Hadoop YARN—Hadoop2中的资源管理器。 注意&…...

IOS面试题编程机制 51-55

51. 在iPhone应用中如何保存数据?有以下几种保存机制: 1).通过web服务,保存在服务器上 2).通过NSCoder固化机制,将对象保存在文件中 3).通过SQlite或CoreData保存在文件数据库中52. 阐述Block 的理解?并写出一个使用Block执行UIVew动画?Block是可以获取其他函数局部变量的…...

话题——AI大模型学习

AI大模型学习 在当前技术环境下&#xff0c;AI大模型学习不仅要求研究者具备深厚的数学基础和编程能力&#xff0c;还需要对特定领域的业务场景有深入的了解。通过不断优化模型结构和算法&#xff0c;AI大模型学习能够不断提升模型的准确性和效率&#xff0c;为人类生活和工作…...

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 案例分析 官网&#xff1a;Apache ZooKeeper 一 序列化与反序列化 …...

人工智能之Tensorflow变量作用域

在TensoFlow中有两个作用域&#xff08;Scope&#xff09;&#xff0c;一个时name_scope ,另一个是variable_scope。variable_scope主要给variable_name加前缀&#xff0c;也可以给op_name加前缀&#xff1b;name_scope给op_name加前缀。 variable_scope 通过所给的名字创建或…...

ElasticSearch插件安装及配置

Docker安装ElasticSearch docker compose 安装直接看步骤三&#xff1a;新建索引 1、安装elasticsearch &#xff08;1&#xff09;下载elasticsearch和kibana docker pull elasticsearch:7.9.1 docker pull kibana:7.9.1&#xff08;2&#xff09;配置 mkdir -p /mydata/…...

vue+Echarts实现多设备状态甘特图

目录 1.效果图 2.代码 3.注意事项 Apache ECharts ECharts官网&#xff0c;可在“快速上手”处查看详细安装方法 1.效果图 可鼠标滚轮图表和拉动下方蓝色的条条调节时间细节哦 &#xff08;注&#xff1a;最后一个设备没有数据&#xff0c;所以不显示任何矩形&#xff09;…...

STM32使用滴答定时器实现delayms

在STM32上使用SysTick实现jiffies&#xff08;时间戳&#xff09;并且实现delay_ms 代码实现&#xff1a; volatile uint32_t jiffies 0; // 用于记录系统运行的jiffies数 void SysTick_Handler(void) {/* 每次SysTick中断&#xff0c;jiffies增加 */jiffies; }uint32_t tick…...

k8s的volumn解析

背景 k8s中有一套自己的存储逻辑&#xff0c;它和docker中的volumn类似&#xff0c;本文就来看一下k8s的volunm的存储设计 k8s的volumn 1.EmptyDir类型的volumn 这种类型的volumn是Pod内的容器共享的&#xff0c;volumn的生命周期和Pod的生命周期是一致的&#xff0c;不过大…...

Golang获取音视频时长信息

文章目录 一、工具简介二、使用golang获取时间长 一、工具简介 这些工具都是与多媒体处理和流媒体相关的开源工具&#xff0c;它们都属于 FFmpeg 多媒体框架。 FFmpeg 是一个用于处理多媒体内容&#xff08;音频、视频、图像等&#xff09;的命令行工具。它可以执行各种各样…...

LeetCode 面试经典150题 14.最长公共前缀

题目&#xff1a; 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 ""。 思路&#xff1a; 代码&#xff1a; class Solution {public String longestCommonPrefix(String[] strs) {if (strs.length 0) {return &…...

自注意力机制的理解

一、自注意力要解决什么问题 循环神经网络由于信息传递的容量以及梯度消失问题&#xff0c;只能建立短距离依赖关系。为了建立长距离的依赖关系&#xff0c;可以增加网络的层数或者使用全连接网络。但是全连接网络无法处理变长的输入序列&#xff0c;另外&#xff0c;不同的输…...

win10-误删winsock恢复方法

文件链接放在最前面 链接&#xff1a;https://pan.baidu.com/s/1i9X0HJJOfo63fbtOETc1Xw?pwdlfqx 提取码&#xff1a;lfqx 误删后应该还是可以正常连接网络的&#xff0c;但是重启过后直接以太网和wifi都是无法使用的。下图是我后面网络正常补充的图片 误删后是只有飞行模式…...

c#矩阵求逆

目录 一、矩阵求逆的数学方法 1、伴随矩阵法 2、初等变换法 3、分块矩阵法 4、定义法 二、矩阵求逆C#代码 1、伴随矩阵法求指定3*3阶数矩阵的逆矩阵 &#xff08;1&#xff09;伴随矩阵数学方法 &#xff08;2&#xff09;代码 &#xff08;3&#xff09;计算 2、对…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

Fabric V2.5 通用溯源系统——增加图片上传与下载功能

fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...