VC、OpenGL、ArcGIS Engine开发的二维三维结合的GIS系统
一、前言
众所周知,二维GIS技术发展了近四十年,伴随着计算机软硬件以及关系型数据库的飞速发展,二维GIS技术已日臻完善。在对地理信息的分析功能上有着无可比拟的优势。一些宏观的地理信息,一维的地理信息,如河流、公路等,以及二维的地理信息,如植被、湖泊、人口数量等,在对这些地理信息的分析和处理上,比较适合采用二维GIS系统。二维GIS始于20世纪60年代的机助制图,今天它已经深入社会的各行各业,如土地管理、电力、电信、水利、消防、交通、规划等,但二维GIS有其自身难以克服的缺陷,它本质上是基于抽象符号的系统,不能真实的再现三维客观世界。随着信息技术的快速发展特别是数字地球(Digital Earth)的提出与实施,以及GIS应用深度和广度的不断扩大,二维GIS已经无法满足用户的需求,用户对三维GIS的需求愈发迫切。伴随着计算机显示设备以及存储设备的进步,三维GIS也得到了一定的发展,GIS正在经历一个由二维向三维发展的过程。
三维GIS最大优点是可以真实的再现现实环境中的地理信息,如地形、地貌等。利用三维GIS技术和DEM、纹理数据可以实现真实感地形地貌的生成功能,以及实时漫游功能等,对于一些只有三维GIS技术才能实现的功能,也必须由三维GIS技术实现,如为了更加直观地理解空间查询和分析的结果、提高空间分析的水平,有必要恢复三维空间关系,并进行透视显示。但是三维GIS才起步不久,并没有成熟的空间数据模型给予其强有力的支撑,空间数据库也并不能完全表达空间实体的复杂关系。三维GIS的核心即是地形三维可视化及其查询分析,地形三维可视化技术自20世纪90年代以来一直是地理信息系统领域开发和应用的热点方向之一,地形三维可视化是一门利用数字化高程模型(DEM)显示仿真内容的学科,是图形学研究方向的热门课题,而DEM数据的三维显示是地形三维可视化的基础。近年来,地形三维可视化技术越来越广泛地运用于地理信息系统、防洪决策系统、虚拟环境仿真、国土资源管理等领域。随着科学技术的发展,地形三维可视化逐渐成为当前对河道、湖泊和港口等进行防洪预测、河床演变分析研究的前沿及主要手段,同时也是快速、及时再现地形三维信息及分析的有效手段。现有的三维GIS系统中,系统功能在三维场景可视化、实时漫游等方面取得了较好的成果,但查询分析功能比较弱。然而查询分析功能在三维GIS的实现和应用中具有十分重要的地位,它使三维GIS具有辅助决策支持能力。
综合考虑上述情况及因素,作者认为目前应以开发二维为主、三维为辅的混合型GIS为主要目标,不宜单纯开发三维GIS。在当前GIS产业界,二维GIS已经能够满足大部分实际需求,对三维GIS的需求仍然只占少部分。当前三维GIS在三维数据获取、大数据量处理与存储、三维可视化、三维空间分析方面还不能以较好的性价比满足大规模商业应用的需要。如果完全采用三维GIS,势必将花费高昂的系统建设费用,在二维GIS能够满足需要的情况下,用户没有必要去一味追求高性能。当然,这里并不排除部分单位研制完全的三维GIS以满足一些行业的特定需要,如军事、采矿、石油勘探、地质结构研究等工作。所以发挥二维GIS与三维GIS的优势,进行矢量数据与地形三维可视化的结合性研究有很大的实用价值。如果采用三维可视化的方法集成矢量数据,并实现其相关属性的查询分析,将二维GIS的优势用在地形三维可视化中,使二维GIS与三维GIS得到良好的结合,对军事、民航、气象等行业有非常重要的现实意义。
目前不同的应用目的往往需要二维GIS与三维GIS两种方式交替运作,而不是单一的某一种。因此,结合二维GIS与三维GIS于一体,充分发挥两者的优势,是一个即经济又实用的思想,这也正是本研究的宗旨所在。所以研发一个使矢量数据与地形三维可视化集成的系统,具有重要的现实意义,本研究系统将分为二维和三维两个部分,二维部分采用MFC与ArcGIS Engine实现,三维部分则采用ATL与OpenGL设计成ActiveX控件,ActiveX控件的集成性非常好,可以发布到任何联网的用户终端使用。因此,本论文将三维部分集成到二维部分中,使二维视图与三维视图形成一体化系统,采用的方式是动态加载ActiveX控件,该方式非常灵活,当ActiveX控件版本变化时,不必手动的更新。当然,不能简单的堆砌为一个集二维GIS与三维GIS于一身的显示系统,更重要的是使二维与三维两个部分得以互动,所以,本论文将二维与三维有机的结合,实现了二维与三维的互动。下图说明了本系统的总体设计思路。
本系统是由二维与三维两个部分共同构建的,如下图所示的系统主界面。系统分为左右两个视图,左视图是二维部分,右视图是三维部分,下面将详细阐述二维部分、三维部分以及二维与三维互动的设计与实现,并用某地数据进行了测试。
二、技术路线和功能简介
1、二维部分
本系统二维部分使用VC提供的MFC与ArcGIS Engine组件实现。在MFC中使用ArcGIS Engine的控件、接口和方法必须遵循一定的步骤,并不像VB、DoNet中那样方便,下面详细介绍在MFC中应用ArcGIS Engine的步骤。
1、引入ArcGIS Engine控件库文件(*.ocx)和组件库文件(*.olb)。控件库中定义了与控件相关的接口,组件库中定义了与控件无关的接口。引用语句如下,其中除库文件名(如:esriGeometry.olb)外其余各参数均采用默认即可;
#import <esriGeometry.olb>_ //库文件名
raw_interfaces_only_
raw_native_types_
no_namespace_
named_guids_
exclude("OLE_COLOR", "OLE_HANDLE")
2、加载ArcGIS Engine控件。VC控件工具箱中有一些默认加载的控件,这些控件可以在对话框中进行所见即所得的绘制,但ArcGIS Engine控件并没有加载进去,因此需要手动加载,使得可以在对话框中绘制Map、Scene、Toorbal等控件,VC中提供了两种加载控件的方法;
第一种本论文称之为COM方式,该方式加载的控件图标并不出现在工具箱中。首先右击对话框,选择“插入ActiveX控件”;然后选择“插入ActiveX控件”对话框中要加载的控件,如:ESRI MapControl,这样MapControl即出现在对话框中。
第二种本论文称之为C++方式,该方式加载的控件图标出现在工具箱中,并且向VC工程中添加该控件的VC源文件(*.cpp)和头文件(*.h)。首先,选择工程->增加到工程->Components and Controls菜单,打开“Components and Controls”对话框,选择Registered ActiveX Controls中要插入到工程的控件,如:ESRI MapControl,这样MapControl即出现在VC工具箱中,便可以像默认控件那样,在对话框中绘制MapControl。
3、定义ArcGIS Engine的类对象。ArcGIS Engine包含三种类[62]:抽象类(abstract class)、可实例化类(class)和组件类(cocalss)。抽象类不能用以创建新对象,但可以指定子类;可实例化类不能够直接创建新对象,因为它的构造函数是私有的,但可以通过其他类对象的属性或其他类的方法实例化;组件类指的是能够直接使用通过开发环境中的对象定义语法来创建对象的类,组件类可以直接被创建或者实例化。
VC中均采用智能指针来声明ArcGIS Engine接口,可以被实例化的类在VC中具有三种实例化方式:
第一种本论文称之为类标识方式。在类对象声明时,直接使用类唯一标识(CLSID)进行构造,如:IFieldsPtr pFlds(CLSID_Fields);
第二种本论文称之为ATL方式。在类对象声明时,使用CocreateInstance方法进行构造,如:CComPtr<IFields> pFlds;pFlds.CoCreateInstance(CLSID_Fields);
第三种本论文称之为COM方式。在类对象声明时,使用CreateInstance方法进行构造,如:IFeatureLayerPtr pFlds;HRESULT hr = pFlds.CreateInstance(CLSID_Fields)。
经过以上步骤,即可以应用ArcGIS Engine提供的接口、方法进行本系统二维部分的程序设计。二维的功能如下图所示,其中数据转换部分实现了由Shapefile向TIN及Raster的转换和由TIN或Raster生成等高线。二维AE部分实现了DEM及SHP文件的显示,以及对DEM分层设色、生成坡度、通视分析、TIN的三维显示、夸张系数设置等功能。
下面是二维的一些贴图。
2、三维部分
本系统三维部分使用VC提供的ATL与OpenGL实现。ATL是活动模板库(Active Template Library)的简称,是VC中为了支持COM而提供的轻便类库,用ATL可以容易的定制COM组件,并不需要自己写模块定义文件(*.def)。在ATL中使用OpenGL的步骤如下。
1、引入OpenGL函数库。首先,选择工具->选项菜单,分别选择目录->Include files和目录->Library files,将相应目录添加进去;然后,在stdafx.h文件中加入如下语句,引入OpenGL头文件和库文件(*.lib)。
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")
#pragma comment(lib, "glaux.lib")
#include <gl"gl.h>
#include <gl"glu.h>
#include <gl"glaux.h>
2、在具体的实现过程中,由于OpenGL函数通过“绘制场景”(Rendring ContextRC)完成三维图形的绘制。Windows下的窗口和设备场景支持位图格式属性,该属性与RC存在位图结构上的一致。只要在创建RC时将它与一个DC(Device Context)相关联(RC只能由一个已经建立了位图格式的DC来创建),OpenGL的函数就可以通过RC对应的DC绘制到相应的显示设备上,相应的步骤如下:
1)设置显示设备DC的位图格式属性。通过填写一个PIXELFORMATDESCRIPTOR的结构来完成,该结构决定了OpenGL绘图的物理设备属性,另外,DC有可能只支持部分位图格式,因此首先使用函数ChoosePixelFormat选择与DC支持的指定位图格式最接近的位图格式,然后使用函数SetPixelFormt设置DC的位图格式。
2)创建绘制环境RC与DC的联系,利用DC创建绘制场景RC(wglCreateContext),以便在DC与RC之间创建关联,此时需要使用函数wglMakeContexte。
3)调用OpenGL函数进行绘制。
4)释放相关内存。绘图完备后,需要调用函数wglMakeContext设置当前线程的RC为NULL,从而断开当前线程和该RC的关联,并由此断开与DC的关联。
一般地,在使用单个RC的应用程序中,相应的WM_CREATE消息时创建RC,当WM_CLOSE或WM_DESTROY到来时再删除它。在使用OpenGL命令往窗口中绘制图形之前,必须先建立一个RC,并使之成为现行RC。OpenGL命令无需提供RC,OpenGL将简单地忽略所有的绘图命令。
经过以上步骤,即可以应用ATL及OpenGL从底层实现本系统三维部分的程序设计。三维OpenGL部分的功能如下图所示,三维部分采用Roam算法实现DEM的三维可视化以及纹理的叠加,并实现了在三维场景中的实时漫游和Shapefile叠加到纹理表面,可以清晰的看到Shapefile叠加到纹理表面后的是有起伏的,这是因为Shapefile是绘制在纹理图像上的,避免了低于地形或高于地形的情况,为有关部门的规划管理提供了支持。
下面是一些三维贴图。
1、叠加shapefile的效果图
2、漫游浏览的效果图(不知为何变成黑白色了,晕)
3、Roam算法生成的Dem格网图
三、二维与三维集成及互动1三维ActiveX控件集成到二维部分
欲将ActiveX控件集成到二维部分,首先必须将二维部分分割成左右两个视图,然后将ActiveX控件插入到其中一个视图中,详细实现如下。
1、分割MFC视图(View)。首先建立一个继承自CFormView的视图COpenGL,该视图即是三维ActiveX控件的载体;然后建立一个继承自CSplitterWnd的类CMySplitter,该类用来创建左右两个视图;最后重载CMainFrame的OnCreateClient函数,在该函数中调用CSplitterWnd的CreateStatic函数创建两个视图,并用RUNTIME_CLASS将COpenGL作为其中一个视图显示。
2、动态加载ActiveX控件。首先如上节的方法插入三维ActiveX控件;然后在COpenGL类的OnCreate函数中使用CWnd类的CreateControl动态的加载ActiveX控件。
2 二维与三维互通信
二维视图与三维视图的互通信是实现二维与三维的互动的基础,二维与三维互通信分为以下两步。
1、视图间互通信。它不是简单的函数调用问题,因为MFC的默认视图类(CView)并不能安全的与除文档类(CDocument)以外的其余视图类进行通信,所以,必须使得所有视图类均先与文档类进行通信,这就需要重载CDocument::OnOpenDocument函数,将COpenGL类的指针加入其中即可以实现视图间的通信。
2、MFC视图与ActiveX控件通信。ActiveX控件所提供的方法均没有返回值,而实现MFC视图与ActiveX控件的通信又必须得到方法的返回值,解决这个矛盾有三种方法:
一是将方法的参数设置为指针类型;
二是提供相应方法的属性,在属性内部调用ActiveX的类函数;
三是将方法声明成事件,并可以与默认的ActiveX事件挂钩。
这三种方法各有千秋,需要结合应用,如:坡度坡向计算时,需要得到该点的坡度坡向两个数值,即可将该方法的参数声明为指针类型;距离量算时,只需得到距离一个参数值,即可将其声明为属性;坐标查询时,需要在MFC视图中截获鼠标单击的消息,因此要将该方法声明为响应鼠标单击的事件。
添加ActiveX事件是一项非常困难的工作,需要用接口定义语言—IDL(Interface Definition Language)手动编写接口定义文件(*.idl)。下面以添加鼠标单击事件Click为例说明添加事件的步骤。首先,应用guidgen.exe获得一个唯一标识码作为Click事件的接口_IEvent的ID,然后将_IEvent声明为[default, source]dispinterface,编译idl文件,并添加该_IEvent的Click事件的连接点(Connection Point),系统会自动产生一个继承自IConnectionPointImpl接口的CProxy_IEvents类,并在该类中加入Fire_Click方法,该方法即为Click事件。这样只是添加了Click事件,还必须将其与ActiveX默认的鼠标单击事件OnLButtonDown挂接,才能响应鼠标单击的消息,因此必须在OnLButtonDown中调用Fire_Click方法,一切处理均在OnLButtonDown中实现,而后传入Fire_Click中。
3 设计思路
本研究的核心思想即是有机的结合二维GIS与三维GIS,并将两者统一应用到一个系统中,使在三维中难以实现或算法复杂的功能应用二维GIS实现,而在二维中不能实现或不够精确的功能应用三维GIS实现,使二维GIS与三维GIS优势互补。
本文即应用该思想设计一个二维与三维结合的系统,在本系统中,要达到矢量数据和三维场景之间的一一映射,必须建立两者之间坐标系的唯一对应或对应地理目标名称[63](唯一的ID)的一一对应,一旦获得对象的唯一标识,就可以获得对象实体的全部信息。本论文选择坐标对应方式,获得三场景中任意一点对应的地面点坐标是进行有关空间信息的查询操作和地形分析的前提,因此在三维部分中最为重要的就是如何获得三维坐标,对点的空间位置查询是其余地形空间信息查询的基础,本论文改进了现有的三维坐标查询算法(见3.3.4节),使查询三维坐标更加方便准确。通过建立二维与三维两部分坐标的对应和消息响应机制可以实现矢量数据与三维场景的互动。如:
1、用户在三维场景中漫游时,在二维场景中显示出相应的位置和视野(FOV);
2、在二维场景中改变观察者位置的时候,相应地在三维场景中跳到对应的位置。在三维场景中改变观察者的位置,在二维场景中视点也跳到对应的位置;
3、二维中进行目标属性信息查询的时候,三维场景的对应目标高亮度显示;
4、三维中进行目标属性信息查询的时候,二维场景的对应目标高亮度显示;
5、由于二维GIS宏观性、整体性、简洁性的特点,在二维中实现剖面图绘制、通视分析、坡度图绘制等宏观的功能;
6、由于三维GIS局部性、现实性、直观性的特点,在三维中实现地形的真实再现、面积量算、距离量算、单点坡度坡向计算等微观功能;
二维GIS与三维GIS结合应用,即克服了二维GIS的抽象多义性,又避免了三维场景漫游的方向迷失感。本研究充分发挥了两者的优势,通过二维GIS与三维GIS的互动实现GIS功能,而不局限于用单一的方式来实现,这样即增强了系统的灵活性,又使问题简单化。
4 功能介绍
经过以上两步,二维与三维互动的准备工作已经完成,二维与三维互动的根本思想即是通过二维与三维的互通信,使得二维与三维互相关联,形成一个有机的整体。实现了二维与三维的互动的功能。其中坐标查询是最为关键的功能,是实现其他功能的基础与前提。下面详细说明这些功能实现的主要思想。
4.1、坐标查询。二维中只具有 坐标,三维中具有 坐标,因此欲得到二维中鼠标点击位置的三维坐标,必须将二维中的 传入三维中,经过改进算法的计算即可;而三维中坐标与二维中对应只需将三维中的 坐标传入二维中。这样就实现了二维与三维互动的坐标查询功能,如下图所示,经验证获得的坐标正确,且实现了二维与三维的坐标对应。
4.2、场景定位。在二维中想了解某个位置的详细情况时,可以直接将二维坐标传入三维中,计算出三维坐标后,在进行局部放大到相应位置的三维视图。如果想获得二维中某条线路或某个区域的具体信息,可以将该线路或区域传入三维中绘制在地形纹理上进行仔细观察,其中区域绘制有透明和非透明两种,透明采用遍历活性边表的扫描线算法进行填充,非透明采用GDI的FillRgn函数进行填充;而三维中漫游时,如果迷失方向,则可以将三维坐标传入二维中进行定位标识,如下图所示,二维中的任意位置、线路及区域均可在三维中作相应的绘制,均具备有地形相符的起伏;而三维中漫游时亦可随时定位到二维中。
4.3、距离量算。如果在三维GIS中测量曲面距离,必须与网格线进行多次求交,得到相应的交点再计算,这样势必大大增加系统负担,而二维GIS中恰好可以应用IPointCollection接口直接获得与网格线的交点集合,但二维GIS中的距离是投影距离,而曲面距离中每一段距离是空间距离,所以,如果是三维中两点的曲面距离,首先需将起终点传入二维GIS中,获得与网格线的交点集合再传回三维GIS中进行计算即可;而如果是二维中两点间的曲面距离,即可首先得到交点(x,y)坐标集合,然后传入三维中,获得网格点集合的(x,y,z)坐标,再进行计算即可。下图即为按照该方法得到的投影、直线、曲面三种距离。
还有体积、剖面图、三维二维加载shapefile等功能,此处就不再一一赘述。完结啦,^_^