1、Wuhan University School of Remote Sensing and Information Engineering张文张文、黄长青、黄长青武汉大学遥感信息工程学院武汉大学遥感信息工程学院基于C#.NET和AO/AE的GIS设计与开发12023年1月6日Wuhan University School of Remote Sensing and Information Engineering2Part 5:2023年1月6日35.1 ArcGIS Engine简介简介5.2 ArcGIS Engine开发起步开发起步 5.3 属性查询属性查询5.4 空间查询空间查询5.5
2、BaseCommand开发实例开发实例5.6 BaseTool开发实例开发实例5.7 通过代码添加图层通过代码添加图层5.8 构建一个简单的构建一个简单的GIS应用应用5.9 问题解决方法及帮助文档的使用问题解决方法及帮助文档的使用2023年1月6日5.1 ArcGIS Engine简介简介lArcGIS Engine的功能十分强大。作为ArcGIS Engine开发者,您可以实现以下列出的和其他更多的功能,这包括:用多个图层来显示地图,例如道路、水系、边界等地图的漫游和缩放地图上要素的确认地图上要素的查询和定位根据属性值显示注记根据航片或者卫片显示图像绘制几何图形,例如点、线、弧、多边形添加
3、描述性的文字42023年1月6日沿着线选择或者选择矩形、某一范围内、多边形等内部的要素选择距离在某一范围内的要素利用SQL语言来寻找和选择要素利用专题地图来渲染要素,如唯一值法、分级法和点状密度法动态显示实时地图或者时序数据根据地理编码来寻找位置转换您地图的坐标系根据几何操作来生成缓冲区、计算差值,或者进行求交、求并等运算编辑要素形状或者旋转地图创建并更新要素的几何形状及其属性52023年1月6日编辑要素(Editing Features)lArcGIS Engine开发包让您构建出能创建、修改和删除geodatabase中矢量要素或者shapefile文件的应用程序。标准的ArcGIS En
4、gine Runtime能够让编辑shapefile文件或者personal geodatabase中简单要素的程序运行。然而,要构建企业级geodatabase的全部功能,就需要ArcGIS Engine Runtime中的Geodatabase Update选项。62023年1月6日空间建模和分析l空间建模和通过增加ArcGIS Engine Runtime中的Spatial选项,您能够扩展ArcGIS Engine的功能。这个选项(Spatial选项)能提供一系列强劲的空间建模和分析功能。您能够创建、查询、表达和分析基于象元的栅格数据,能够集成栅格和矢量数据进行分析,能够从已有数据挖掘出
5、新的信息,能够从多层数据中查询信息,能够在ArcGIS Engine应用程序中完美集成基于象元的栅格数据和矢量数据。72023年1月6日三维可视化等lArcGIS Engine Runtime中的3D选项让您通过使用Scene和Globe控件来有效的表达和分析区域及全球数据。l例如,您能够:显示Scene和Globe文档展示交互性的透视图,包括漫游、缩放、旋转、倾斜、模拟飞行以便于表达和分析显示真实世界的表面要素,例如楼房展示视域和可见范围分析、场景高度内插、剖面分析和最短距离分析82023年1月6日l这个例子将引导您创建第一个简单的地图显示程序,并添加基本的缩放和漫游功能。如果您之前没有接触
6、过ArcGIS Engine的开发,那么这个例子是您迈入ArcGIS Engine二次开发大门的极好例子,如果您之前没有接触lC#.NET,也无需担心,这个例子将从零开始引导您一步一步完成任务。5.2 ArcGIS Engine开发起步第一个简单的地图显示程序92023年1月6日l首先打开Microsoft Visual Studio 2005,点击菜单栏中的“文件”“新建项目”,在弹出的对话框中选择新建一个C#的Windows应用程序,之后更改项目名称为“MapView”,更改文件的路径为个人实习文件夹,点击“确定”即可。创建一个新的工程图图23 新建项目对话框新建项目对话框102023年1
7、月6日l点击编译器最左侧的“工具箱”,在弹出的选择项中找到“ArcGIS Windows Forms”项,单击其中的MapControl,之后在Form1的空白处单击鼠标左键不放并拖拽鼠标,直到调整MapControl到合适的大小再松开鼠标(您也可以直接在工具箱中双击MapControl,该控件则会自动加入到Form1中)。用同样的方法,再将LicenseControl添加到Form1中。添加控件及引用图图24 工具箱工具箱112023年1月6日l如果您在工具箱中找不到MapControl,则请依次尝试以下两种解决方案。首先单击工具栏,待工具箱弹出之后,在工具箱的任意位置上单击鼠标右键,从弹出
8、菜单中选择“重置工具箱”。如果这一步操作之后仍然无法看到MapControl,则请滑动工具栏右侧的滚动条至最底部,找到“常规”选项卡,然后在“常规”选项卡上单击鼠标右键,在弹出菜单中单击“选择项(I)”,在弹出的对话框中找到“AxLicenseControl”和“AxMapControl”,将这两项前的复选框打上勾,最后点击确定即可(如果在“.NET Framework组件”这个面板中找不到这两项,则选择“COM 组件”面板,在“ESRI LicenseControl”和“ESRI MapControl”前面打勾)。图图25 重置工具箱重置工具箱122023年1月6日图图26 选择项选择项图图
9、27 选择工具箱项选择工具箱项132023年1月6日图图28 选择工具箱项选择工具箱项142023年1月6日添加好MapControl和LicenseControl之后,调整Form1和MapControl的位置与大小,如下图所示:图图29 窗体布局窗体布局152023年1月6日在MapControl上单击鼠标右键,选择“属性”,则会弹出MapControl的属性设置面板,在之前也介绍过,通过这个面板可以完成许多简单的工作。如图所示,点击“Map”面板,之后点击 按钮,在弹出的对话框中选择下图所示的路径(注:笔者的ArcGIS安装在D盘,若您的ArcGIS装在别的盘符,请做相应修改),再在此路
10、径下选择“States”,点击“Open”。之后在MapControl的属性页上点击“确定”即可。添加地图图图30 文件添加路径文件添加路径162023年1月6日l至此,我们已经完成了一个最简单的地图显示程序,下面一起来看一下成果吧。点击“启动调试”按钮(或者在“调试”菜单下选择相应命令,或者按键盘的F5键),可以得到如下的运行结果。图图31 “启动调试启动调试”按钮按钮172023年1月6日图图32 初次运行结果初次运行结果182023年1月6日l我们没有书写任何代码,就得到了一个最简单的地图显示程序。但这个程序还不能与用户交互,下一步我们需要添加一些代码,让程序能响应用户的鼠标,完成放大和
11、全图显示的功能。添加基本的代码图图33 MapControl控件支持的所有方法控件支持的所有方法192023年1月6日l双击MapControl控件,可以进入代码编辑界面。从窗口上方的下拉列表框中,我们能够看到MapControl能够响应的所有事件(关于每个事件的详细使用方法等请参见帮助系统,第六章对帮助系统有更加详细的介绍)。双击MapControl进入代码编辑界面的时候,默认的是“OnMouseDown”事件,下一步就需要在这个事件中添加响应鼠标的相关代码。l请您在Private Sub AxMapControl1_OnMouseDown函数中添加如下代码:if(e.button=1)th
12、is.axMapControl1.Extent=this.axMapControl1.TrackRectangle();else if(e.button=2)this.axMapControl1.Extent=this.axMapControl1.FullExtent;202023年1月6日l再次运行程序,鼠标左键在地图上拉框可以实现地图的放大功能,而右键单击地图则会还原地图的全图显示。图图34 任意比例尺放大功能任意比例尺放大功能212023年1月6日l如果将代码替换如下,则能实现左键放大,右键漫游的功能。if(e.button=1)this.axMapControl1.Extent=thi
13、s.axMapControl1.TrackRectangle();else if(e.button=2)this.axMapControl1.Pan();222023年1月6日l下面我们依次来看看这些代码都代表什么意思。首先看来第一段:if(e.button=1)this.axMapControl1.Extent=this.axMapControl1.TrackRectangle();else if(e.button=2)this.axMapControl1.Extent=this.axMapControl1.FullExtent;l这个代码是一个If Else条件语句,关于“e”的详细定义及
14、其中包含的各参数,请参考帮助中与“IMapControlEvents2,OnMouseDownEventHandler delegate”关键字相关的内容。代码解释232023年1月6日l可以根据e中包含的“button”值来判断鼠标的单击操作是来自何处,若button值为1,则为鼠标左键,2代表鼠标右键,4代表鼠标中键。当判断得到是鼠标左键单击时,执行“AxMapControl1.Extent=AxMapControl1.TrackRectangle”这条语句,其中,等号右侧是调用了“TrackRectangle”方法,这个方法是在地图上拖拽出一个矩形,之后将这个矩形赋值给当前地图的显示区域
15、(Extent),这样就实现了地图的放大功能。类似的,若鼠标右键单击,则将全图范围赋值给当前的显示范围,实现了地图的全图显示功能。l第二段代码与第一段结构一样,只是在右键的相应事件上略有不同,这是调用了“Pan”方法,实现了地图的漫游功能。242023年1月6日l通过这个例子,我们制作出了一个最简单的地图浏览程序MapView,并能响应一些基本的鼠标操作。在MapControl的属性页中,其实还有许多内容您可以尝试,例如在“General”面板中可以直接加入地图文件(*.mxd或者*.mxt),您也可以利用刚才的方式一次性多加入一些图层而不仅仅加入“States”一个,同时可以更改各图层的叠放
16、次序,也可以在“Data”面板中设置地图的旋转角度(Rotation)等,您还可以设置MapControl的显示方式,是否支持地图的预览功能,边框样式等等。您可以做一些尝试,看看能得到哪些有趣的结果,这些尝试对您今后熟悉ArcGIS Engine的开发是有一定帮助的。如果需要重置MapControl,只需要点击“Data”面板中的“Reset”按钮。当您完成了这个例子,并做了一些积极的尝试之后,您就可以接着学习下一个小节的内容了。小结252023年1月6日l查询是GIS中非常重要的一个功能,下面将分别介绍属性查询和空间查询的制作方法。5.3 属性查询添加控件l如果上一小节的工程已经关闭,则将其
17、打开,如果您之后又在MapControl中添加了一些别的数据,请将其删除,只保留一个“states”图层,请务必注意这一步,这直接关系到您下面的工作能否顺利进行。用之前讲过的方式,在窗体中添加一个Label和一个TextBox。将Label的“Text”属性修改为“StateName”,结果如下:图图35 Label控件和控件和TextBox控件控件262023年1月6日l首先添加引用。点击菜单栏上的“项目”“添加引用”,在弹出的对话框中同时选择“ESRI.ArcGIS.Carto”和“ESRI.ArcGIS.Geodatabase”(选择的时候按下Ctrl键以同时选择多个),点击确定。l之后
18、双击TextBox1控件,进入代码编辑界面。在代码编辑区域的最上方输入以下内容:lusing ESRI.ArcGIS.Carto;lusing ESRI.ArcGIS.Geodatabase;如下图所示:添加引用和代码图图36 引用添加位置引用添加位置272023年1月6日图图37 添加引用对话框添加引用对话框282023年1月6日l之后在控件之后在控件TextBox的事件中选择的事件中选择KeyUp,在,在KeyUp事件中添加以下代码:事件中添加以下代码:图图38 KeyPress方法方法292023年1月6日 if(e.KeyCode=Keys.Enter)/定义图层,要素游标,查询过滤器
19、,要素 IFeatureLayer pFeatureLayer;IFeatureCursor pFeatureCursor;IQueryFilter pQueryFilter;IFeature pFeature;/获取图层 pFeatureLayer=this.axMapControl1.Map.get_Layer(0)as IFeatureLayer;/如果图层名称不是states,程序退出 if(pFeatureLayer.Name!=states)return;/清除上次查询结果 this.axMapControl1.Map.ClearSelection();/pQueryFilter的
20、实例化 pQueryFilter=new QueryFilterClass();/设置查询过滤条件 pQueryFilter.WhereClause=STATE_NAME=+txtStateName.Text+;302023年1月6日/查询 pFeatureCursor=pFeatureLayer.Search(pQueryFilter,true);/获取查询到的要素 pFeature=pFeatureCursor.NextFeature();/判断是否获取到要素 if(pFeature!=null)/选择要素 this.axMapControl1.Map.SelectFeature(pFea
21、tureLayer,pFeature);/放大到要素 this.axMapControl1.Extent=pFeature.Shape.Envelope;else /没有得到pFeature的提示 MessageBox.Show(没有找到名为+txtStateName.Text+的州,提示);312023年1月6日l运行程序,分别向编辑框中输入“Texas”和“RS”,键入回车,如下图所示:图图39 Texas州查询结果州查询结果322023年1月6日图图40 RS查询结果查询结果332023年1月6日 if(e.KeyCode=Keys.Enter)上述代码是一个判断语句,即当用户输入回车的
22、时候,开始进行查询。下面两行代码是定义查询的范围,默认为上一小节中添加的图层“states”,如果找不到这个图层则自动退出。/获取图层 pFeatureLayer=this.axMapControl1.Map.get_Layer(0)as IFeatureLayer;/如果图层名称不是states,程序退出 if(pFeatureLayer.Name!=states)return;/清除上次查询结果 this.axMapControl1.Map.ClearSelection();l这一部分是生成一个新的查询器,选择条件(WhereClause)就是检索是否有与用户输入相符的州,并将结果从查询得
23、到的pCursor中读取出来。代码解释342023年1月6日/判断是否获取到要素 if(pFeature!=null)/选择要素 this.axMapControl1.Map.SelectFeature(pFeatureLayer,pFeature);/放大到要素 this.axMapControl1.Extent=pFeature.Shape.Envelope;else /没有得到pFeature的提示 MessageBox.Show(没有找到名为+txtStateName.Text+的州,提示);这一部分是一个判断语句,若查询得到的结果为空,则刷新地图,弹出对话框通知用户没有查询到结果,并
24、退出程序。如果查询得到的结果不为空,则将这个结果加入地图的选择集,并将地图的显示范围定为查询结果的外轮廓,这样得到的州将高亮显示同时居中放大到屏幕中心。352023年1月6日l这一部分中,我们接触到了基本的属性查询。但是在这个例子中,我们不能实现对属性表中任意属性字段的查询(在这个程序中,我们只能查询州名STATE_NAME,而不能对别的字段进行查询),而且这个查询不支持模糊查询。为了使查询变的更加丰富,更加人性化,请您参考IQueryFilter接口中WhereClause属性的设置方法,拓展WhereClause可以得到许多有趣的结果。在书写代码的过程中,对任何有疑问的地方,或者您想要拓展
25、的位置,都可以在帮助系统中查询相关的接口和属性,查看最原始的定义,帮助系统中的解释和定义对于您熟悉ArcObjects,熟悉ArcGIS Engine的二次开发以及后续的工作都是十分重要的,请一定不要忽视这个环节。如果您已经尝试了一些变化,或者对本小节的内容比较熟悉了,则可以进入下一小节的学习。小结362023年1月6日l上一小节我们已经学习了如何进行属性查询,在这一小节中,我们将继续学习GIS中的另一种查询方式空间查询。l如果上一小节的工程已经关闭,则将其打开,如果您之后又在MapControl中添加了一些别的数据,请将其删除,只保留一个“states”图层,请务必注意这一步,这直接关系到您
26、下面的工作能否顺利进行。我们接着上一小节的内容继续完善。在窗体中添加一个Button,将其Text属性修改为“点查询”。5.4 空间查询添加控件图图41 添加添加“点查询点查询”按钮按钮372023年1月6日l利用上一小节讲的方式,添加引用“ESRI.ArcGIS.Geometry”,并在类中添加一个全局变量nMouseFlag,如下图所示:添加引用和代码图图42 添加引用添加引用“ESRI.ArcGIS.Geometry”382023年1月6日l之后在类中添加一个公共函数,用来根据屏幕像素计算实际的地理距离 private double ConvertPixelToMapUnits(IAct
27、iveView activeView,double pixelUnits)double realWorldDiaplayExtent;int pixelExtent;double sizeOfOnePixel;double mapUnits;/获取设备中视图显示宽度,即像素个数 pixelExtent=activeView.ScreenDisplay.DisplayTransformation.get_DeviceFrame().right-activeView.ScreenDisplay.DisplayTransformation.get_DeviceFrame().left;/获取地图坐标
28、系中地图显示范围 realWorldDiaplayExtent=activeView.ScreenDisplay.DisplayTransformation.VisibleBounds.Width;/每个像素大小代表的实际距离 sizeOfOnePixel=realWorldDiaplayExtent/pixelExtent;/地理距离 mapUnits=pixelUnits*sizeOfOnePixel;return mapUnits;392023年1月6日此后,双击Button1,进入Button的Click事件,向其中添加如下代码:/标记点查询 mMouseFlag=1;/设置鼠标形状
29、this.axMapControl1.MousePointer=ESRI.ArcGIS.Controls.esriControlsMousePointer.esriPointerCrosshair;(注:上两行代码应该书写在一行,由于空间有限,这里写为两行)最后将MapControl控件的OnMouseDown事件中已有的代码清除,替换为以下代码:if(mMouseFlag=1)IFeatureLayer pFeatureLayer;IFeatureClass pFeatureClass;/获取图层和要素类,为空时返回 pFeatureLayer=this.axMapControl1.Map.
30、get_Layer(0)as IFeatureLayer;if(pFeatureLayer.Name!=states)return;pFeatureClass=pFeatureLayer.FeatureClass;if(pFeatureClass=null)return;IActiveView pActiveView;IPoint pPoint;double length;402023年1月6日 /获取视图范围 pActiveView=this.axMapControl1.ActiveView;/获取鼠标点击屏幕坐标 pPoint=pActiveView.ScreenDisplay.Displ
31、ayTransformation.ToMapPoint(e.x,e.y);/2个像素大小的屏幕距离转换为地图距离 length=ConvertPixelToMapUnits(pActiveView,2);ITopologicalOperator pTopoOperator;IGeometry pGeoBuffer;ISpatialFilter pSpatialFilter;/根据缓冲半径生成空间过滤器 pTopoOperator=pPoint as ITopologicalOperator;pGeoBuffer=pTopoOperator.Buffer(length);pSpatialFilt
32、er=new SpatialFilterClass();pSpatialFilter.Geometry=pGeoBuffer;/根据图层类型选择缓冲方式 switch(pFeatureClass.ShapeType)case esriGeometryType.esriGeometryPoint:pSpatialFilter.SpatialRel=esriSpatialRelEnum.esriSpatialRelContains;break;case esriGeometryType.esriGeometryPolyline:pSpatialFilter.SpatialRel=esriSpati
33、alRelEnum.esriSpatialRelCrosses;break;case esriGeometryType.esriGeometryPolygon:pSpatialFilter.SpatialRel=esriSpatialRelEnum.esriSpatialRelIntersects;break;412023年1月6日/定义空间过滤器的空间字段 pSpatialFilter.GeometryField=pFeatureClass.ShapeFieldName;IQueryFilter pQueryFilter;IFeatureCursor pFeatureCursor;IFeat
34、ure pFeature;/利用要素过滤器查询要素 pQueryFilter=pSpatialFilter as IQueryFilter;pFeatureCursor=pFeatureLayer.Search(pQueryFilter,true);pFeature=pFeatureCursor.NextFeature();int fieldIndex;if(pFeature!=null)/选择指定要素 this.axMapControl1.Map.ClearSelection();this.axMapControl1.Map.SelectFeature(ILayer)pFeatureLaye
35、r,pFeature);this.axMapControl1.Refresh();fieldIndex=pFeature.Fields.FindField(STATE_NAME);MessageBox.Show(查找到“+pFeature.get_Value(fieldIndex)+”,提示);422023年1月6日运行程序,结果如右图所示:图图43 空间查询空间查询运行结果运行结果432023年1月6日距离转换函数请您自行参看帮助系统中对相关接口的具体定义和解释。Button1的Click事件中是将nMouseFlag设置为1,并将鼠标在MapControl上的形状改变为十字丝状。/获取视图
36、范围 pActiveView=this.axMapControl1.ActiveView;/获取鼠标点击屏幕坐标pPoint=pActiveView.ScreenDisplay.DisplayTransformation.ToMapPoint(e.x,e.y);/屏幕距离转换为地图距离 length=ConvertPixelToMapUnits(pActiveView,2);上述代码是在MapControl的OnMouseDown事件中,当您单击鼠标左键的时候,将屏幕上的点转换成地图上的点(关键在于坐标值的转换),方便后续操作。/根据缓冲半径生成空间过滤器 pTopoOperator=pPoi
37、nt as ITopologicalOperator;pGeoBuffer=pTopoOperator.Buffer(length);pSpatialFilter=new SpatialFilterClass();pSpatialFilter.Geometry=pGeoBuffer;代码解释442023年1月6日这一部分是将2个像素的距离转换成实际的地理距离,并以这个距离为半径,上一步生成的点为中心,生成一个缓冲区。452023年1月6日l上述代码是设置pSpatialFilter的各项参数,供后续查询,包括空间查询的几何形状(之前生成的缓冲区),空间查询的方式(相交,包含等)以及Shape字
38、段。这两句代码是找出“STATE_NAME”所在的列数,并将其显示出来。462023年1月6日l在本节中我们完成更多的空间查询功能,其中有点查询、线查询、矩形查询、圆查询l新建一个C#.Net工程,向工程中添加控件,如下图所示:l其中包括MapControl,4个Button,一个TextBox进一步完善空间查询472023年1月6日l通过在控件属性中添加地图的方法,向Mapcontrol中添加例子数据。(例子数据是位于World文件夹下的Continents.lyr)如下图所示:l下面我们在MainForm的代码页添加空间查询的函数。本例中我们需要添加ESRI.ArcGIS.Carto、ES
39、RI.ArcGIS.Geometry、ESRI.ArcGIS.Geodatabase、ESRI.ArcGIS.Controls四个个命名空间,并且我们仍然需要上节中的ConvertPixelToMapUnits(IActiveView activeView,double pixelUnits)函数,请自行添加。l空间查询函数代码如下:l /l /空间查询l /l /MapControll /空间查询方式l /字段名称l /查询得到的要素名称482023年1月6日lprivate string QuerySpatial(AxMapControl mapControl,IGeometry geom
40、etry,string fieldName)l ll 492023年1月6日502023年1月6日512023年1月6日在设计页面双击点查询按钮,进入点击按钮响应事件填写如下代码。相应的线查询、矩形查询、圆查询添加同样的代码,但nMouseFlag得值要有所改变。线查询:nMouseFlag=2矩形查询:nMouseFlag=3圆查询:nMouseFlag=4为MapControl控件添加OnMouseDown事件,填入以下代码522023年1月6日532023年1月6日542023年1月6日l点击运行,运行效果如下图所示:552023年1月6日l仔细研读代码,您会发现,在这部分中我们并没有用
41、到什么新的知识,只是在结构上做了调整,应为空间查询都是需要使用一个IGeometry对象进行空间求交进行查询的。所以我们将公共的代码放在公共的模块中进行调用。有心的同学可能发现,我们为了判断用户在MapControl上的操作,我们引入了一个全局变量nMouseFlag,程序中多一个全局变量,对程序的结构的封闭性就有所破坏,能不能去掉这个全局变量而是Mapcontrol自主判断是哪个功能进行操作呢?答案是肯定的,我们可以使用BaseCommand和BaseTool来完成这个工作,详细的用法在3.4和3.5小节将会介绍。562023年1月6日小结l在这一小节中,我们学习了如何进行简单的空间查询。空
42、间查询不仅包括点查询,还包括线查询,矩形查询,多边形查询等(为了实现这些功能,可以参考MapControl中的TrackRectangle等方法)。对于这一小节的代码,强烈建议您参看帮助系统中对相关接口的解释和定义,以进一步熟悉接口的使用,这对后面的学习以及掌握ArcGIS Engine二次开发是极有好处的。如果您对这一部分比较熟悉了,可以进入下一小节。在第四章中,我们介绍了控件命令(Control Commands),并提到ArcGIS Engine允许用户自定义开发一些控件命令,在下两小节中,我们将具体学习如何开发。572023年1月6日5.5 BaseCommand开发实例l在这一小节中
43、,我们将学习BaseCommand的开发步骤。BaseCommand的功能与Button的功能类似,是当鼠标点击按钮的时候,MapControl控件会对其中的命令做出相应而无需额外的操作。在这一小节中,我们将制作一个“定比例尺放大”的按钮,当鼠标单击按钮时,地图将居中放大一倍。添加控件l如果上一小节的程序已经关闭,则重新打开,同时保证MapControl控件中加载了至少一个图层。在主窗体(frmMain)中添加一个Button,将其Text属性更改为“居中放大”。3.3.5小结小结582023年1月6日添加添加BaseCommand592023年1月6日602023年1月6日添加代码添加代码612023年1月6日622023年1月6日632023年1月6日小结小结642023年1月6日End,Thanks!652023年1月6日