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.mfbz.cn/a/472241.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

冶炼金属 (第十四届蓝桥杯省赛C++ B组)详解(二分+推公式)

题目描述&#xff1a; 小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。 这个炉子有一个称作转换率的属性 V&#xff0c;V 是一个正整数&#xff0c;这意味着消耗 V 个普通金属 O 恰好可以冶炼出一个特殊金属 X&#xff0c;当普通金属 O 的数目不足 V 时&#…

Spring MVC文件下载配置

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 文件下载 在Spring MVC中通常利用commons-io实现文件下载&#xff0c;示例代码如下&#xff1a; Controller RequestMapping("......") public class DownloadC…

AI大模型-Grok搭建

Grok搭建 硬件要求项目下载Checkpoint下载运行代码 马斯克又搞事情了&#xff0c;正式开源AI大模型Grok-1&#xff0c;免费还可商用&#xff0c;国内AI技术即将迎来重大突破。笔者简单整合了一下&#xff0c;如何搭建Grok-1的思路&#xff0c;供后期自己搭建以及读者学习使用。…

房屋租赁系统|基于JSP技术+ Mysql+Java+ B/S结构的房屋租赁系统设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;ssm&#xff0c;springboot的平台设计与实现项目系统开发资源&#xff08;可…

理解数据库习题

1.选择 &#xff08;1&#xff09;现实世界中客观存在并能相互区别的事物称为&#xff08; &#xff09;。 A.实体 B.实体集 C字段 D 记录 &#xff08;2&#xff09;下列实体类型的联系中&#xff0c;属于一对一联系的是&#xff08; &#xff09;A.教研室对教师的所属联系 …

centos 环境部署

一、安装redis 1. 升级 GCC 最直接的解决方式是升级你的 GCC 编译器到支持 C11 标准的版本。CentOS 7 默认的 GCC 版本较旧&#xff0c;可能不支持 _Atomic。你可以通过以下步骤升级 GCC&#xff1a; 启用 CentOS 的 Software Collections (SCL) 仓库&#xff0c;该仓库提供了…

力扣---完全平方数

思路&#xff1a; 还是比较好想的&#xff0c;g[i]定义为和为 i 的完全平方数的最少数量。那么递推关系式是g[i]min(g[i-1],g[i-4],g[i-9],...)1&#xff0c;数组初始化是g[0]0,g[1]1。注意这里要对g[0]初始化&#xff0c;&#xff08;举个例子&#xff09;因为在遍历到g[4]时&…

docker安装Milvus

docker安装Milvus 拉去CPU版本的milvus镜像 $ sudo docker pull milvusdb/milvus:0.10.0-cpu-d061620-5f3c00 docker pull milvusdb/milvus:0.10.0-cpu-d061620-5f3c00 mkdir -p milvus/conf cd milvus/conf ls wget https://raw.githubusercontent.com/milvus-io/milvus/v0.1…

未来教育趋势:AI个性化培训如何推动企业与员工共赢

AI定制学习&#xff1a;重新定义个性化员工培训的未来 随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;我们正目睹并亲历了AI在培训领域所引发的根本性变革。AI技术的整合不仅革新了知识传递的模式&#xff0c;而且重新塑造了个性化学习的内涵。依托于尖端算…

平替电容笔哪个品牌好?推荐五款 2024 高人气电容笔,入手不亏!

作为一名有着多年测评数码经验的评测师&#xff0c;用过的原装笔和平替电容笔加起来也有不下十款了&#xff0c;看过也有很多朋友因为选错电容笔&#xff0c;导致书写效率低&#xff0c;体验感差的例子也数不胜数。这并非夸大其词&#xff0c;因为不专业产品的最大弊端就是毫无…

segformer多分类语义分割

前言 本期将分享「Segformer」&#xff0c;论文地址https://arxiv.org/abs/2105.15203。 Segformer简介 全局上下文信息: 由于Transformer的自注意力机制&#xff0c;Segformer可以在整个图像范围内捕获上下文信息&#xff0c;而不受局部感受野的限制&#xff0c;这有助于提高分…

开放签开源电子签章白皮书-简版

开放签开源电子签章白皮书-简版 一、摘要&#xff1a; 开放签电子签章团队源自于电子合同SaaS公司&#xff0c;立志于通过开源、开放的模式&#xff0c;结合团队十多年的行业经验&#xff0c;将电子签章产品更简单、更低门槛的推广到各行各业中。让电子签章应用更简单&#x…

动态路由协议-OSPF与LSA简介

一、概述 前面路由基础学习了路由的类型可以按照动静态路由分类&#xff0c;静态路由就是直连和手动配置的路由&#xff0c;而动态路由协议是各路由器相互动态学习的一种路由协议&#xff0c;现在常用的有OSPF和ISIS路由协议&#xff0c;前面在基础篇我们也已经对OSPF有一个简单…

记录我的第一次面试

面试感受 这是我的第一次面试&#xff0c;我感觉我这次面试的很差&#xff0c;很糟糕&#xff0c;十分的糟糕&#xff0c;万分的糟糕。第一次面试&#xff0c;面试了半个小时。我去真的好紧张&#xff0c;脑子里一篇空白。脑子空白还不是最惨的&#xff0c;最惨的是那个八股文…

MySQL的概述与安装

一、数据库的基本概念&#xff1a; 1.1 数据&#xff1a; 1&#xff09; 描述事物的符号记录称为数据&#xff08;Data&#xff09;。数字、文字、图形、图像、声音、档案记录等 都是数据。 2&#xff09;数据是以“记录”的形式按照统一的格式进行存储的&#xff0c;而不是…

基于SSM的宿舍管理系统的设计与实现(JSP,MySQL)

摘 要 随着社会发展、信息技术的普及&#xff0c;人们日常管理工作也发生了巨大的变化。信息化技术之渗透各行业的方方面面。学生宿舍管理作为校园管理工作的重要一环&#xff0c;不仅关系到学生自身的确切利益&#xff0c;同时也是对校园管理工作重大考验。近来年由于在校学生…

ECMAscript6学习

ECMAscript6介绍 ECMA是一个浏览器脚本标准制定的公司&#xff0c;Netscape 创造了 JavaScript 由于商标原因&#xff0c; 后面ECMA公司取名ECMAscript 1 发布&#xff0c;JavaScript 也就是 ECMAscript.到现在最新的版本是6&#xff0c;简称es6. 新增let 与const let 与const …

精酿啤酒:啤酒花的添加时机与风味影响

啤酒花是啤酒酿造过程中不可或缺的成分&#xff0c;它为啤酒带来与众不同的苦味和香味&#xff0c;并增加了啤酒的层次感和复杂性。接下来将详细介绍Fendi Club啤酒在啤酒花的选择、添加时机和风味影响方面的实践和特点。 首先&#xff0c;Fendi Club啤酒选用上好啤酒花&#x…

Python爬虫获取接口数据

Python爬虫获取接口数据 正常人的操作​​​​​​​​​​爬虫的思路标题获取请求信息标题请求转换为代码完整代码请求返回信息执行程序获取静态网页数据的教程,适用于我们要爬取的数据在网页源代码中出现,但是还是有很多的数据是源代码中没有的,需要通过接口访问服务器来获…

docker仓库登录及配置insecure-registries的方法

docker仓库登录及配置insecure-registries的方法 这篇文章主要介绍了docker仓库登录配置insecure-registries的方法,docker客户端如果配置中添加了insecure-registary配置&#xff0c;就不需要在docker 客户端配置上对应证书&#xff0c;如果不配置要在/etc/docker/certs.d/目…