OpenGL+QT实现矢量和影像的叠加绘制

news2024/7/2 4:04:42
一、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.coloradmin.cn/o/1533203.html

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

相关文章

【算法训练营】STL算法 Stack 栈的压入、弹出序列+最小栈

Stack刷题 1.最小栈2.栈的压入、弹出序列 1.最小栈 题目链接: 最小栈 题目描述 解决思路 创建一个辅助栈 只保存最小的元素 代码 class MinStack { public:MinStack() {}void push(int val) {// 只要是压栈&#xff0c;先将元素保存到_elem中_stack.push(val);//然后判断…

Unicode转码 [ASIS 2019]Unicorn shop1

打开题目 我们买最贵的试试看&#xff0c;结果提示只能输入一个字符 抓包分析一下看看 从中可以发现源代码是如何处理price的 使用的是unicodedata.numeric() 但是我们查看页面源代码&#xff0c;发现页面的编码是utf-8编码 所以&#xff0c;前端html使用的是utf-8&#xff0…

npm ERR! code ELIFECYCLE 解决办法

npm ERR! code ELIFECYCLE 解决办法 问题分析可能原因解决方法 问题 使用Vue脚手架构建项目的时候出现npm ERR! code ELIFECYCLE 分析可能原因 vue-cli-service 并没有加入到环境变量里 解决方法 ./node_modules/.bin/vue-cli-service serve

Go语言超全详解(入门级)

文章目录 1. Go语言的出现2. go版本的hello world3. 数据类型3.0 定义变量3.0.1 如果变量没有初始化3.0.2 如果变量没有指定类型3.0.3 :符号3.0.4 多变量声明3.0.5 匿名变量3.0.6 变量作用域 3.1 基本类型3.2 指针3.2.1 指针声明和初始化3.2.2 空指针 3.3 数组3.3.1 声明数组3.…

IDEA插件MyBatisCodeHelper-Pro的破解与使用

下载链接: https://pan.baidu.com/s/1M9818XstvQNeZPJACrhXcw 提取码: gs83 举例&#xff1a;IDEA 2023.3激活&#xff0c;setting->plugin->右上角齿轮&#xff08;设置&#xff09;->Install plugin from disk->选择上面下载的 MybatisCodeHelperNew-3.2.2.zip&…

『Python爬虫』极简入门

本文简介 点赞 收藏 关注 学会了 声明&#xff1a;请勿使用爬虫技术获取公民隐私数据、数据以及企业或个人不允许你获取的数据。 本文介绍如何使用 Python 写一只简单的爬虫&#xff0c;作为入门篇&#xff0c;这个程序不会很复杂&#xff0c;但至少可以讲明爬虫是个什么东…

Flume-transaction机制源码分析

一、整体流程 FileChannel主要是由WAL预写日志和内存队列FlumeEventQueue组成。 二、Transaction public interface Transaction {// 描述transaction状态enum TransactionState { Started, Committed, RolledBack, Closed }void begin();void commit();void rollback();voi…

用户中心项目(数据库表设计 + 用户注册后端)

文章目录 1.数据库表设计1.IDEA连接MySQL1.选择database&#xff0c;添加数据源2.填写信息&#xff0c;然后点击测试连接3.查找指定数据库4.查看某个表的DDL5.新建查询 2.删除测试的user表3.创建一个新的user表4.创建user表 2.注册功能1.换了台电脑&#xff0c;重新打开后端项目…

深度学习-2.9梯度不稳定和Glorot条件

梯度不稳定和Glorot条件 一、梯度消失和梯度爆炸 对于神经网络这个复杂系统来说&#xff0c;在模型训练过程中&#xff0c;一个最基础、同时也最常见的问题&#xff0c;就是梯度消失和梯度爆炸。 我们知道&#xff0c;神经网络在进行反向传播的过程中&#xff0c;各参数层的梯…

Cesium for Unreal注意事项

一、Cesium for Unreal使用WGS84坐标系统 原因&#xff1a;在百度、高德、谷歌拾取的坐标经纬度设置在Cesium for Unreal项目中时位置不准确是因为这些厂商使用的坐标系不一样。高德是GCJ02&#xff0c;百度是在GCJ02的基础上再加密&#xff0c;谷歌是WGS84就是原始gps坐标&am…

蓝桥杯Python B组练习——完美的代价

一、题目 问题描述   回文串&#xff0c;是一种特殊的字符串&#xff0c;它从左往右读和从右往左读是一样的。小龙龙认为回文串才是完美的。现在给你一个串&#xff0c;它不一定是回文的&#xff0c;请你计算最少的交换次数使得该串变成一个完美的回文串。   交换的定义是…

分布式游戏服务器

1、概念介绍 分布式游戏服务器是一种专门为在线游戏设计的大型系统架构。这种架构通过将游戏服务器分散部署到多台计算机&#xff08;节点&#xff09;上&#xff0c;实现了数据的分散存储和计算任务的并行处理。每个节点都负责处理一部分游戏逻辑和玩家请求&#xff0c;通过高…

环境安装篇 之 安装kubevela

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

GIS设计与开发的学习笔记

目录 一、简答题 1.GeoDatabase数据模型结构类型与四种关系。 2.组件式GIS的基本思想是什么&#xff1f; 3.请简述创建空间书签的实现逻辑。 4.请问与地理要素编辑相关的类有哪些&#xff1f;&#xff08;列举至少五个类&#xff09; 5.利用ArcGIS Engine提供的栅格运算工…

【LabVIEW FPGA入门】局部变量和全局变量

局部变量 无法访问某前面板对象或需要在程序框图节点之间传递数据时&#xff0c;可创建前面板对象的局部变量。创建局部变量后&#xff0c;局部变量仅仅出现在程序框图上&#xff0c;而不在前面板上。 局部变量可对前面板上的输入控件或显示件进行数据读写。写入局部变量相当于…

借还款管理神器,高效记录借还款信息,让财务明细不再遗漏

在快节奏的现代生活中&#xff0c;借还款管理成为我们日常财务处理的重要一环。无论是个人生活还是企业运营&#xff0c;都需要一个高效、准确、便捷的方式来记录和追踪借还款信息。传统的记账方式往往容易出错、繁琐且耗时&#xff0c;难以满足现代人的需求。现在&#xff0c;…

数据库系统概论(超详解!!!) 第四节 关系数据库标准语言SQL(Ⅰ)

1.SQL概述 SQL&#xff08;Structured Query Language&#xff09;结构化查询语言&#xff0c;是关系数据库的标准语言 SQL是一个通用的、功能极强的关系数据库语言 SQL的动词 基本概念 基本表 &#xff1a;本身独立存在的表&#xff1b; SQL中一个关系就对应一个基本表&am…

一、初识 Web3

瑾以此系列文章&#xff0c;献给那些出于好奇并且想要学习这方面知识的开发者们 在多数时间里&#xff0c;我们对 web3 的理解是非常模糊的 就好比提及什么是 web1 以及 web2&#xff0c;相关概念的解释是&#xff1a; 1. 从 Web3 的开始 Web3&#xff0c;也被称为Web3.0&…

飞腾+FPGA+AI电力行业智能数据采集与分析网闸解决方案

行业痛点: 安全物联网闸在监控平台中的具体作用&#xff1a;35KV变电站是煤矿的动力核心&#xff0c;采矿人员上下井、煤炭提升输送、矿井通风等核心设备均依靠变电站提供电源。监控中心及时掌握变电站的运行状态对煤矿的安全生产非常重要。如若外部通过监控网络来控制变电站会…

ByteMD - 掘金社区 MarkDown 编辑器的免费开源的版本,可以在 Vue / React / Svelte 中使用

各位元宵节快乐&#xff0c;今天推荐一款字节跳动旗下掘金社区官方出品的 Markdown 编辑器 JS 开发库。 ByteMD 是一个用于 web 开发的 Markdown 编辑器 JavaScript 库&#xff0c;是字节跳动&#xff08;也就是掘金社区&#xff09;出品的 Markdown 格式的富文本编辑器&#…