ArcGIS Engine二次开发
——提高篇
1 缩略图(鹰眼)
鹰眼功能是GIS的主要功能之一,当地图范围很大时,它可以很好的为用户指明当前地图的范围。在本小节中我们将学习如何制作这种鹰眼。
1.1 添加控件
新建一个C#.Net项目,项目名称为OverView,将Form1的名字设置为MainForm,并添加ToolbarControl 、两个MapControl和LicenceControl等四个控件。布局如下图所示。左边的axMapControl1用于地图数据显示和操作,右边axMapControl2用于鹰眼显示。
图 1 界面布局
在ToolbarControl 加载添加数据按钮和地图浏览的功能按钮,如下图所示,并将ToolbarControl的伙伴控件设为axMapControl1。
图 2添加按钮
1.2 代码添加及解释
鹰眼用来显示主窗体当前视图范围在全景视图中的位置,在ArcMap中使用一个线框在鹰眼视图中标识。当主视图中的视图范围改变时,鹰眼中的线框随之改变,当拖动鹰眼视图中的红线框时,主视图中的视图范围也随之改变。
下面开始实现鹰眼功能,添加using ESRI.ArcGIS.Carto、using ESRI.ArcGIS.Geometry、 using ESRI.ArcGIS.Display三个引用。首先在axMapControl1中视图范围改变时鹰眼窗体要做出对应的响应,即绘制线框并显示,在OnExtentUpdated事件中添加代码如下:
private void axMapControl1_OnExtentUpdated(object sender,
ESRI.ArcGIS.Controls.IMapControlEvents2_OnExtentUpdatedEvent e)
{
//创建鹰眼中线框
IEnvelope pEnv = (IEnvelope)e.newEnvelope;
IRectangleElement pRectangleEle = new RectangleElementClass();
IElement pEle = pRectangleEle as IElement;
pEle.Geometry = pEnv;
//设置线框的边线对象,包括颜色和线宽
IRgbColor pColor = new RgbColorClass();
pColor.Red = 255;
pColor.Green = 0;
pColor.Blue = 0;
pColor.Transparency = 255;
// 产生一个线符号对象
ILineSymbol pOutline = new SimpleLineSymbolClass();
pOutline.Width = 2;
pOutline.Color = pColor;
// 设置颜色属性
pColor.Red = 255;
pColor.Green = 0;
pColor.Blue = 0;
pColor.Transparency = 0;
// 设置线框填充符号的属性
IFillSymbol pFillSymbol = new SimpleFillSymbolClass();
pFillSymbol.Color = pColor;
pFillSymbol.Outline = pOutline;
IFillShapeElement pFillShapeEle = pEle as IFillShapeElement;
pFillShapeEle.Symbol = pFillSymbol;
// 得到鹰眼视图中的图形元素容器
IGraphicsContainer pGra = axMapControl2.Map as IGraphicsContainer;
IActiveView pAv = pGra as IActiveView;
// 在绘制前,清除 axMapControl2 中的任何图形元素
pGra.DeleteAllElements();
// 鹰眼视图中添加线框
pGra.AddElement((IElement)pFillShapeEle, 0);
// 刷新鹰眼
pAv.PartialRefresh(esriViewDrawPhase.esriViewGraphics, null, null);
}
当鼠标点击鹰眼窗体时,主窗体Extent随之改变。在axMapControl2的OnMouseDown事件中添加代码如下:
private void axMapControl2_OnMouseDown(object sender,
ESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseDownEvent e)
{
if (this.axMapControl2.Map.LayerCount != 0)
{
// 按下鼠标左键移动矩形框
if (e.button == 1)
{
IPoint pPoint = new PointClass();
pPoint.PutCoords(e.mapX, e.mapY);
IEnvelope pEnvelope = this.axMapControl1.Extent;
pEnvelope.CenterAt(pPoint);
this.axMapControl1.Extent = pEnvelope;
this.axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);}
// 按下鼠标右键绘制矩形框
else if (e.button == 2)
{
IEnvelope pEnvelop = this.axMapControl2.TrackRectangle();
this.axMapControl1.Extent = pEnvelop;
this.axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);}
}
}
当鼠标在鹰眼窗体移动时,主窗体Extent随之改变。在axMapControl2的OnMouseMove事件中添加代码如下:
private void axMapControl2_OnMouseMove(object sender,
ESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseMoveEvent e)
{
// 如果不是左键按下就直接返回
if (e.button != 1) return;
IPoint pPoint = new PointClass();
pPoint.PutCoords(e.mapX, e.mapY);
this.axMapControl1.CenterAt(pPoint);
this.axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);
}
下面代码用于实现axMapControl2与axMapControl1的数据的同步更新,获取主视图中视图范围最大的图层作为鹰眼中的视图。这个更新由两部分组成,一个是对axMapControl1添加地图文档(mxd文件)的响应,通过axMapControl1的OnMapReplace事件实现,一个是对axMapControl1添加单个图层的响应,通过axMapControl1的OnFullExtentUpdated事件实现。我们获取主视图中的视图范围最大的图层写成一个独立的函数,方便调用。
private ILayer GetOverviewLayer(IMap map)
{
//获取主视图的第一个图层
ILayer pLayer = map.get_Layer(0);
//遍历其他图层,并比较视图范围的宽度,返回宽度最大的图层
ILayer pTempLayer = null;
for (int i = 1; i < map.LayerCount;i++ )
{
pTempLayer = map.get_Layer(i);
if (pLayer.AreaOfInterest.Width < pTempLayer.AreaOfInterest.Width)
pLayer = pTempLayer;
}
return pLayer;
}
然后在axMapControl1的OnMapReplaced事件中调用。
private void axMapControl1_OnMapReplaced(object sender,
IMapControlEvents2_OnMapReplacedEvent e)
{
//获取鹰眼图层
this.axMapControl2.AddLayer(this.GetOverviewLayer(this.axMapControl1.Map));// 设置 MapControl 显示范围至数据的全局范围
this.axMapControl2.Extent = this.axMapControl1.FullExtent;
// 刷新鹰眼控件地图
this.axMapControl2.Refresh();
}
在axMapControl1的OnFullExtentUpdated添加代码,用于实现在主视图添加图层时,实现对鹰眼视图的更新。代码如下:
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
private void axMapControl1_OnFullExtentUpdated(object sender,
ESRI.ArcGIS.Controls.IMapControlEvents2_OnFullExtentUpdatedEvent e)
{
//获取鹰眼图层
this.axMapControl2.AddLayer(this.GetOverviewLayer(this.axMapControl1.Map));// 设置 MapControl 显示范围至数据的全局范围
this.axMapControl2.Extent = this.axMapControl1.FullExtent;
// 刷新鹰眼控件地图
this.axMapControl2.Refresh();
}
本例的示例数据无特别要求,使用前面章节实例数据即可。运行程序,添加地图数据,可以在主视图进行相关操作,鹰眼视图同步响应,在鹰眼视图可以移动红线框可以同步更新主视图的视图范围,在鹰眼视图单击右键拉框可以重新绘制红线框,效果如下:
图 3鹰眼效果
1.3 MyGIS中添加鹰眼
在上一讲中的最后一节,我们创建了一个简单的GIS系统MyGIS,这里,我们讲鹰眼功能嵌入到我们的系统中。在这里我们对实现的思路做一个介绍,请您自己动手完善MyGIS。
首先需要修改一下MyGIS窗体的控件布局,我们讲鹰眼视图放到图层管理器的下方,需要在控件容器SpliterContainer1的Panel1中添加一个水平分隔的SpliterContainer,然后将图层管理器空间TOCControl和鹰眼视图MapControl分别置于上下的容器中,并将其属性Dock分别设为Fill。
另外,在此种窗体布局情况下,直接在TOCControl控件属性中设置伙伴控件无效,如图所示。我们需要在MainForm的Load事件中为TOCControl设置伙伴控件为axMapControl1。添加代码如下:
private void Form1_Load(object sender, EventArgs e)
{
//设置axTOCControl1的伙伴控件
this.axTOCControl1.SetBuddyControl(axMapControl1.Object);
}
图 4 TOCControl控件属性中设置伙伴控件
然后依次添加本例中的代码,即可完成,运行效果如下图所示:
图 5 MyGIS中鹰眼的运行效果
1.4 小结
在本小节中,我们实现了鹰眼功能并讲鹰眼加入了MyGIS,这部分的重点是鹰眼视图和主视图之间的事件交互。推荐您仔细结合例子程序查看代码,如果需要获得进一步的信息,请查看帮助系统。如果您对这一小节的内容比较熟悉了,就可以开始学习本章最后一小节的内容了。在下一小节中,我们将尝试添加缓冲区分析功能。
2 缓冲区分析
缓冲区分析指为了识别某一地理实体或空间物体对其周围地物影响度而在其周围建立的具有一定宽度的区域,以确定哪些实体落在了被影响的区域范围之内。
缓冲区分析与缓冲区查询不同,缓冲区查询是不破坏原有空间目标的关系,只是检索到该缓冲区范围内涉及到的目标。而缓冲区分析是根据设定的距离条件对一类地物建立缓冲区多边形,存储到一个新的图层中。然后再将新的图层与需要进行缓冲区分析的图层进行叠置分析,得到所需要的结果。因此,缓冲区分析实际上进行了两步的操作,第一步是建立缓冲区图层,第二步是进行叠置剪裁分析。
缓冲区分析适用于点、线、面对象,如点状的居民点、线状的河流和面状的作物分布区等,只要地理实体能对周围一定区域形成影响即可使用这种分析方法。
图 6点、线、面的缓冲区分析
ArcGIS的ArcToolBox中的分析工具提供了缓冲区分析的功能,本节实习我们首先使用Geoprocessor方法实现一个简单的缓冲区分析功能,然后将缓冲区分析功能添加到我们的MyGIS项目中。
程序运行前首先需要在D盘下新建一个名为Temp的文件夹,存放叠置分析生成的文件。
2.1 Geoprocessor实现缓冲区分析
为了降低开发难度和提高开发效率,ArcGIS Engine中添加了GeoProcessor类,使用Geoprocessor能帮助用户直接实现一些简单的工具性的功能,所有在ArcToolBox中的功能,基本都可以用Geoprocessor编程实现。本节我们使用Geoprocessor实现缓冲区分析的功能。二次开发
2.1.1 添加控件
新建一个C#.Net项目,项目名称为Buffer,将Form1的名字设置为MainForm,并添加ToolbarControl 、MapControl、TOCControl、LicenceControl和Button等五个控件。并将ToolbarControl 、TOCControl的伙伴控件设为MapControl,Button控件的Name属性设定为btnBuffer,Text属性设定为“缓冲区分析”。控件布局效果如下图所示。
图 7控件布局效果
在ToolbarControl 加载添加数据按钮和地图浏览的功能按钮,如下图所示。
图 8添加按钮
2.1.2 代码添加及解释
首先添加如下四个命名空间的引用。
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geoprocessor;
using ESRI.ArcGIS.Geoprocessing;
using ESRI.ArcGIS.esriSystem;
在使用Geoprocessor工具实现缓冲区分析时,需要首先定义一个Geoprocessor对象,因为命名空间“ESRI.ArcGIS.Geoprocessing”也包含Geoprocessor类,为了避免混淆,我们使用命
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
名空间来定义Geoprocessor,然后设置Geoprocessor中的环境参数,这里我们使用默认参数。然后定义一个操作类Buffer,并设置参数,生成缓冲区的参数包含原始图层,缓冲半径和输出路径,最后使用已定义的Geoprocessor对象执行即可。双击“生成缓存区”按钮,添加代码如下:
private void btnBuffer_Click(object sender, EventArgs e)
{
//判断MapControl中是否包含图层
if (this.axMapControl1.LayerCount == 0)
return;
//获取MapControl中第一个图层
ILayer pLayer = this.axMapControl1.Map.get_Layer(0);
//输出路径,可以自行指定
string strOutputPath = @"D:\Buffer.shp";
//缓冲半径
double dblDistace = 1.0;
//获取一个geoprocessor的实例,避免与命名空间Geoprocessing中的Geoprocessor发生引用错误
ESRI.ArcGIS.Geoprocessor.Geoprocessor gp = new
ESRI.ArcGIS.Geoprocessor.Geoprocessor();
//OverwriteOutput为真时,输出图层会覆盖当前文件夹下的同名图层
gp.OverwriteOutput = true;
//创建一个Buffer工具的实例
ESRI.ArcGIS.AnalysisTools.Buffer buffer = new ESRI.ArcGIS.AnalysisTools.Buffer(pLayer, strOutputPath, dblDistace);
//执行缓冲区分析
IGeoProcessorResult results = null;
results = gp.Execute(buffer, null) as IGeoProcessorResult;
//判断缓冲区是否成功生成
if (results.Status != esriJobStatus.esriJobSucceeded)
MessageBox.Show("图层" + pLayer.Name + "缓冲区生成失败!");
else
{
MessageBox.Show("缓冲区生成成功!");
//将生成图层加入MapControl
int index = strOutputPath.LastIndexOf("\\");
this.axMapControl1.AddShapeFile(strOutputPath.Substring(0, index),
strOutputPath.Substring(index));
}
}
运行程序,添加一个图层(多个图层时本例中默认选择的图层为第一个图层),点击“生
成缓冲区”,运行结果如图。
图 9缓冲区生成效果
2.1.3 小结
本例中,我们使用Geoprocessor工具实现了缓冲区分析。从中我们可以得到Geoprocessor工具使用的一般方法,在使用Geoprocessor时,一般需先定义一个Geoprocessor对象,然后设置该对象的参数,如本例中的OverwriteOutput,再定义一个具体的操作类,如本例中的Buffer类,在设置完操作类的参数后,则通过Geoprocessor的Excute函数来执行。至此,我们已经实现了一个简单的缓冲区分析的功能,从中我们学习了Geoprocessor的使用方法。
下一节我们讲对缓冲区份分析功能做进一步的改进,使其具有更强的适用性,并将这个功能添加到MyGIS中。
2.2 MyGIS中添加缓冲区分析
我们在使用缓冲区分析时,需要设定原始的图层,缓冲半径以及生成缓冲区的保存路径。本节我们将在上一节的基础上进一步实现缓冲区分析,实现缓冲图层,缓冲半径和保存路径的可选设置。
2.2.1 添加控件
打开项目MyGIS,在MyGIS的主菜单添加一个新的菜单项“空间分析”,并添加子菜单“缓冲区分析”,Name属性修改为“menuBuffer”。
项目中添加一个新的窗体,名称为“BufferForm”,Name属性设为“缓冲区分析”,添加四个Label、一个ComboBox、两个TextBox、三个Button控件,控件属性设置如下:
表 1控件属性设置
2.2.2 代码添加及解释
该项目需添加如下引用:
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.Geoprocessor;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geoprocessing;
using ESRI.ArcGIS.esriSystem;
首先声明两个成员变量,用于保存地图数据和输出文件的路径。
//接收MapControl中的数据
private IHookHelper mHookHelper = new HookHelperClass();
//缓冲区文件输出路径
public string strOutputPath;
重写BufferForm的构造函数,添加一个参数,用于接收MapControl中的数据。
//重写构造函数,添加参数hook,用于传入MapControl中的数据
public BufferForm(object hook)
{
InitializeComponent();
this.mHookHelper.Hook = hook;
}
添加一个自定义函数,用于根据图层名称获取要素图层并返回。
private IFeatureLayer GetFeatureLayer(string layerName)
{
IFeatureLayer pFeatureLayer = null;
//遍历图层,获取与名称匹配的图层
for (int i = 0; i < this.mHookHelper.FocusMap.LayerCount; i++)
{
ILayer pLayer = this.mHookHelper.FocusMap.get_Layer(i);
if (pLayer.Name == layerName)
{
pFeatureLayer = pLayer as IFeatureLayer;
}
}
if (pFeatureLayer != null)
return pFeatureLayer;
else
return null;
}
BufferForm在载入时需要加载当前MapControl中的图层名称到cboLayers,读取当前地图的地图单位,设置缓冲区文件的默认输出路径,这里我们将默认输出路径设为“D:\Temp\”。
private void BufferForm_Load(object sender, EventArgs e)
{
//传入数据为空时返回
if (null == mHookHelper || null == mHookHelper.Hook || 0 ==
mHookHelper.FocusMap.LayerCount)
return;
//获取图层名称并加入cboLayers
for (int i = 0; i < this.mHookHelper.FocusMap.LayerCount; i++)
{
ILayer pLayer = this.mHookHelper.FocusMap.get_Layer(i);
cboLayers.Items.Add(pLayer.Name);
}
//cboLayers控件中默认显示第一个选项
if (cboLayers.Items.Count > 0)
cboLayers.SelectedIndex = 0;
//设置生成文件的默认输出路径和名称
string tempDir = @"D:\Temp\";
txtOutputPath.Text = System.IO.Path.Combine(tempDir, ((string)cboLayers.SelectedItem + "_buffer.shp"));
//设置默认地图单位
lblUnits.Text = Convert.ToString(mHookHelper.FocusMap.MapUnits);
}
private void btnOutputLayer_Click(object sender, EventArgs e)
{
//定义输出文件路径
SaveFileDialog saveDlg = new SaveFileDialog();
//检查路径是否存在
saveDlg.CheckPathExists = true;
saveDlg.Filter = "Shapefile (*.shp)|*.shp";
//保存时覆盖同名文件
saveDlg.OverwritePrompt = true;
saveDlg.Title = "输出路径";
//对话框关闭前还原当前目录
saveDlg.RestoreDirectory = true;
saveDlg.FileName = (string)cboLayers.SelectedItem + "_buffer.shp";
//读取文件输出路径到txtOutputPath
DialogResult dr = saveDlg.ShowDialog();
if (dr == DialogResult.OK)
txtOutputPath.Text = saveDlg.FileName;
}
双击“分析”按钮,添加代码如下:
private void btnBuffer_Click(object sender, EventArgs e)
{
//缓冲距离
double bufferDistance;
//输入的缓冲距离转换为double
double.TryParse(txtBufferDistance.Text.ToString(),out bufferDistance);
//判断输出路径是否合法
if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(txtOutputPath.Text)) ||".shp" != System.IO.Path.GetExtension(txtOutputPath.Text))
{
MessageBox.Show("输出路径错误!");
return;
}
//判断图层个数
if (mHookHelper.FocusMap.LayerCount == 0)
return;
//获取图层
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
IFeatureLayer pFeatureLayer = GetFeatureLayer((string)cboLayers.SelectedItem);if (null == pFeatureLayer)
{
MessageBox.Show("图层" + (string)cboLayers.SelectedItem + "不存在!\r\n");return;
}
//获取一个geoprocessor的实例
Geoprocessor gp = new Geoprocessor();
//OverwriteOutput为真时,输出图层会覆盖当前文件夹下的同名图层
gp.OverwriteOutput = true;
//缓冲区保存路径
strOutputPath = txtOutputPath.Text;
//创建一个Buffer工具的实例
ESRI.ArcGIS.AnalysisTools.Buffer buffer = new
ESRI.ArcGIS.AnalysisTools.Buffer(pFeatureLayer, strOutputPath, bufferDistance.ToString());//执行缓冲区分析
IGeoProcessorResult results = null;
results = (IGeoProcessorResult)gp.Execute(buffer, null);
//判断缓冲区是否成功生成
if (results.Status != esriJobStatus.esriJobSucceeded)
MessageBox.Show("图层" + pFeatureLayer.Name + "缓冲区生成失败!");
else
{
this.DialogResult = DialogResult.OK;
MessageBox.Show("缓冲区生成成功!");
}
}
双击“取消”按钮,添加代码如下:
private void btnCancel_Click(object sender, EventArgs e)
{
this.Dispose();
}
进入MyGIS的主窗体,双击菜单中的“缓冲区分析”,添加代码如下:
BufferForm bufferForm = new BufferForm(this.axMapControl1.Object);
if (bufferForm.ShowDialog() == DialogResult.OK)
{
//获取输出文件路径
string strBufferPath = bufferForm.strOutputPath;
//缓冲区图层载入到MapControl
int index = strBufferPath.LastIndexOf("\\");
this.axMapControl1.AddShapeFile(strBufferPath.Substring(0, index),
strBufferPath.Substring(index));
}
图 10缓冲区分析效果
如果运行过程中出现错误“正试图在 OS 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码,这样做会导致应用程序挂起。”,请采用如下方法解决:
把vs2005菜单的 调试->异常->Managed Debuggin Assistants->LoaderLock 的选中状态去掉即可!如果异常(exception)这一项没有的话,在工具---自定义---命令选项卡,选择左边“调试”,找到右边“异常”拖到菜单上。
2.2.3 小结
缓冲区分析是GIS空间分析的基本功能,这一节我们完成了缓冲区分析的功能,实现了缓冲区分析文件、缓冲半径和输出路径的可选设置,希望您仔细体会并掌握Geoprocessor工具开发空间分析功能的基本方法。
3 叠置分析
叠置分析是GIS中一种常见的分析功能,它是将有关主题层组成的各个数据层面进行叠置产生一个新的数据层面,其结果综合了原来两个或多个层面要素所具有的属性,同时叠置分析不仅生成了新的空间关系,而且还将输入的多个数据层的属性联系起来产生了新的属性关系。
ArcGIS中的叠置分析包含Union(叠置求并)、Intersect(叠置求交)、Identify(叠置标识)、Erase(叠置擦除)、Symmetrical Difference (叠置相交取反)、Update(叠置更新)等。这一小节,我们以叠置求交为例,介绍叠置分析的开发。叠置求交是保留两个图层公共部分的空间图形,并综合两个叠加图层的属性。如下图,反映了叠置求交的原理。
图 11叠置求交
本节实习将介绍这种方法实现缓冲区分析,我们首先使用Geoprocessor方法实现一个简单的缓冲区分析功能,然后将缓冲区分析功能添加到我们的MyGIS项目中。
同样,ArcGIS的ArcToolBox中的分析工具提供了缓冲区分析的功能,本节实习我们首先使用Geoprocessor方法实现一个简单的缓冲区分析功能,然后将缓冲区分析功能添加到我们的MyGIS项目中。
程序运行前首先需要在D盘下新建一个名为Temp的文件夹,存放叠置分析生成的文件。
3.1 Geoprocessor实现叠置分析
叠置分析我们同样使用Geoprocessor工具来实现。
3.1.1 添加控件
新建一个C#.Net项目,项目名称为OverLay,将Form1的名字设置为MainForm,并添加ToolbarControl 、MapControl、TOCControl、LicenceControl和Button等五个控件。并将ToolbarControl 、TOCControl的伙伴控件设为MapControl,Button控件的Name属性设定为btnIntersect,Text属性设定为“叠置求交”。控件布局效果如下图所示。
图 12控件布局效果
在ToolbarControl 加载添加数据按钮和地图浏览的功能按钮,如下图所示。
图 13添加按钮
3.1.2 代码添加及解释
首先添加如下引用:
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.AnalysisTools;
using ESRI.ArcGIS.Geoprocessor;
using ESRI.ArcGIS.Geoprocessing;
与缓冲区分析的实现类似,在使用Geoprocessor工具实现叠置分析时,需要首先定义一个Geoprocessor对象,因为命名空间“ESRI.ArcGIS.Geoprocessing”也包含Geoprocessor类,为了避免混淆,我们使用命名空间来定义Geoprocessor,然后设置Geoprocessor中的环境参数,这里我们使用默认参数。然后定义一个操作类,这里为Intersect,然后设置其操作参数,这里我们仅设置输入的要素,最后使用已定义的Geoprocessor对象执行即可。双击“生成缓存区”按钮,添加代码如下:
private void btnIntersect_Click(object sender, EventArgs e)
{
//添加两个以上图层时才允许叠置
if (this.axMapControl1.LayerCount < 2)
return;
ESRI.ArcGIS.Geoprocessor.Geoprocessor gp = new
ESRI.ArcGIS.Geoprocessor.Geoprocessor();
//OverwriteOutput为真时,输出图层会覆盖当前文件夹下的同名图层
gp.OverwriteOutput=true;
//创建叠置分析实例
Intersect intersectTool = new Intersect();
//获取MapControl中的前两个图层
ILayer pInputLayer1 = this.axMapControl1.get_Layer(0);
ILayer pInputLayer2 = this.axMapControl1.get_Layer(1);
//转换为object类型
object inputfeature1 = pInputLayer1;
object inputfeature2 = pInputLayer2;
//设置参与叠置分析的多个对象
IGpValueTableObject pObject = new GpValueTableObjectClass();
pObject.SetColumns(2);
pObject.AddRow(ref inputfeature1);
pObject.AddRow(ref inputfeature2);
intersectTool.in_features = pObject;
//设置输出路径
string strTempPath = @"D:\Temp\";
string strOutputPath = strTempPath + pInputLayer1.Name + "_" + pInputLayer2.Name + "_Intersect.shp";
intersectTool.out_feature_class=strOutputPath;
//执行叠置分析
IGeoProcessorResult result = null;
result = gp.Execute(intersectTool, null) as IGeoProcessorResult;
//判断叠置分析是否成功
if (result.Status != ESRI.ArcGIS.esriSystem.esriJobStatus.esriJobSucceeded)
MessageBox.Show("叠置求交失败!");
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
else{
MessageBox.Show("叠置求交成功!");int index = strOutputPath.LastIndexOf("\\");
this.axMapControl1.AddShapeFile(strOutputPath.Substring(0, index), strOutputPath.Substring(index));}}
运行程序,添加叠置分析的数据,至少为两个图层,点击“叠置求交”,运行结果如图。
图 14叠置求交效果
3.1.3 小结
学习完这一小节,细心的同学会发现使用Geoprocessor实现叠置分析与实现缓冲区分析的基本思路是一致的,只是不同的操作方法设置了不同的参数。另外,注意在进行叠置分析时要通过IGpValueTableObject接口加载多个要素。请您仔细体会缓冲区分析和叠置求交分析的实现过程的相似点与不同点。下一小节我们将对叠置分析进行进一步学习,并将多种叠置分析功能添加到MyGIS中。
3.2 MyGIS中添加叠置分析
这一小节我们将在上一节开发的叠置求交的基础上,实现叠置分析中三种最常用的叠置方式,Union(叠置求并)、Intersect(叠置求交)和Identify(叠置标识)。Intersect(叠置求交)在上节已经介绍,下面简要介绍一下Union(叠置求并)和Identify(叠置标识)。
叠置求并(Union)保留了两个叠置图层的空间图形和属性信息,进行叠置求和的两个图层须是多边形图层。输入图层的一个多边形被叠加图层中的多边形弧段分割成多个多边形,输出图层综合了两个图层的属性。所有要素都将被写入到输出要素类,输出结果具有来自与其叠置的输入要素的属性。
图 15叠置求并(Union)
Identify(叠置标识)是以输入图层为界,保留边界以内两个多变形的所有多边形,出入图层切割后的多边形也被赋予叠加图层的属性。如下图所示。
图 16叠置标识(Identify)
在通过ArcEngine中的Geoprocessor实现这三种叠置分析时,我们将实现输入图层和叠置图层的可选设置,叠置方式的可选设置,输出路径的可选设置。
3.2.1 添加控件
打开项目MyGIS,在MyGIS的主菜单 “空间分析”中添加子菜单“叠置分析”,Name属性修改为“menuOverlay”。
项目中添加一个新的窗体,名称为“OverlayForm”,Name属性设为“叠置分析”,添加四个Label、一个ComboBox、四个TextBox、五个Button控件和一个GroupBox,控件属性设置如下:
表 2控件属性设置
3.2.2 代码添加及解释
该工程需要添加如下引用:
using ESRI.ArcGIS.Controls; using ESRI.ArcGIS.AnalysisTools; using ESRI.ArcGIS.Geoprocessing;
首先声明一个成员变量,用于保存叠置分析输出文件的路径。
public string strOutputPath;
OverlayForm在载入时需要加载三种叠置方式到cboOverlay中,并且需要设置缓冲区文件的默认输出路径,这里我们将默认输出路径设为“D:\Temp\”。
private void OverlayForm_Load(object sender, EventArgs e){
//加载叠置方式
this.cboOverLay.Items.Add("求交(Intersect)");this.cboOverLay.Items.Add("求并(Union)");this.cboOverLay.Items.Add("标识(Identity)");this.cboOverLay.SelectedIndex = 0;//设置默认输出路径
string tempDir = @"D:\Temp\";txtOutputPath.Text = tempDir;}
private void btnInputFeat_Click(object sender, EventArgs e){
//定义OpenfileDialog
OpenFileDialog openDlg = new OpenFileDialog();openDlg.Filter = "Shapefile (*.shp)|*.shp";openDlg.Title = "选择第一个要素";//检验文件和路径是否存在openDlg.CheckFileExists=true;openDlg.CheckPathExists=true;//初试化初试打开路径
openDlg.InitialDirectory = @"D:\Temp\";//读取文件路径到txtFeature1中
if (openDlg.ShowDialog()==DialogResult.OK){
this.txtInputFeat.Text=openDlg.FileName;}}
类似的,双击叠置要素的文件选择按钮,添加代码如下;
private void btnOverlayFeat_Click(object sender, EventArgs e){
//定义OpenfileDialog
OpenFileDialog openDlg = new OpenFileDialog();openDlg.Filter = "Shapefile (*.shp)|*.shp";openDlg.Title = "选择第二个要素";//检验文件和路径是否存在openDlg.CheckFileExists = true;openDlg.CheckPathExists = true;//初试化初试打开路径
openDlg.InitialDirectory = @"D:\Temp\";//读取文件路径到txtFeature2中
if (openDlg.ShowDialog() == DialogResult.OK){
this.txtOverlayFeat.Text = openDlg.FileName;}}
双击输出图层的路径选择按钮,添加代码如下:
private void btnOutputLayer_Click(object sender, EventArgs e){
//定义输出文件路径
SaveFileDialog saveDlg = new SaveFileDialog();//检查路径是否存在
saveDlg.CheckPathExists = true;saveDlg.Filter = "Shapefile (*.shp)|*.shp";
//保存时覆盖同名文件
saveDlg.OverwritePrompt = true;saveDlg.Title = "输出路径";//对话框关闭前还原当前目录saveDlg.RestoreDirectory = true;
saveDlg.FileName = (string)cboOverLay.SelectedItem + ".shp";
//读取文件输出路径到txtOutputPathDialogResult dr = saveDlg.ShowDialog();if (dr == DialogResult.OK)
txtOutputPath.Text = saveDlg.FileName;}
双击“分析”按钮,添加代码如下。
private void btnOverLay_Click(object sender, EventArgs e){
//判断是否选择要素
if (this.txtInputFeat.Text==""||this.txtInputFeat.Text==null||this.txtOverlayFeat.Text==""||this.txtOverlayFeat.Text==null){
txtMessage.Text="请设置叠置要素!";return;}
ESRI.ArcGIS.Geoprocessor.Geoprocessor gp = new ESRI.ArcGIS.Geoprocessor.Geoprocessor();
//OverwriteOutput为真时,输出图层会覆盖当前文件夹下的同名图层gp.OverwriteOutput = true;
//设置参与叠置分析的多个对象
object inputFeat = this.txtInputFeat.Text;
object overlayFeat = this.txtOverlayFeat.Text;IGpValueTableObject pObject = new GpValueTableObjectClass();pObject.SetColumns(2);pObject.AddRow(ref inputFeat);pObject.AddRow(ref overlayFeat);
//获取要素名称
string str = System.IO.Path.GetFileName(this.txtInputFeat.Text);int index = str.LastIndexOf(".");string strName = str.Remove(index);
//设置输出路径
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
strOutputPath = txtOutputPath.Text;
//叠置分析结果
IGeoProcessorResult result = null;
//创建叠置分析实例,执行叠置分析
string strOverlay=cboOverLay.SelectedItem.ToString();try{
//添加处理过程消息
txtMessage.Text = "开始叠置分析……"+"\r\n";switch (strOverlay){
case "求交(Intersect)":
Intersect intersectTool = new Intersect();//设置输入要素
intersectTool.in_features = pObject;//设置输出路径
strOutputPath += strName + "_" + "_intersect.shp";intersectTool.out_feature_class = strOutputPath;//执行求交运算
result = gp.Execute(intersectTool, null) as IGeoProcessorResult;break;case "求并(Union)":
Union unionTool = new Union();//设置输入要素
unionTool.in_features = pObject;//设置输出路径
strOutputPath += strName + "_" + "_union.shp";unionTool.out_feature_class = strOutputPath;//执行联合运算
result = gp.Execute(unionTool, null) as IGeoProcessorResult;break;
case "标识(Identity)":
Identity identityTool = new Identity();//设置输入要素
identityTool.in_features = inputFeat;identityTool.identity_features = overlayFeat;//设置输出路径
strOutputPath += strName + "_" + "_identity.shp";identityTool.out_feature_class = strOutputPath;//执行标识运算
result = gp.Execute(identityTool, null) as IGeoProcessorResult;break;
}}
catch (System.Exception ex){
//添加处理过程消息
txtMessage.Text += "叠置分析过程出现错误:" + ex.Message+"\r\n";}
//判断叠置分析是否成功
if (result.Status != ESRI.ArcGIS.esriSystem.esriJobStatus.esriJobSucceeded)txtMessage.Text+="叠置失败!";else{
this.DialogResult = DialogResult.OK;
txtMessage.Text += "叠置成功!";}}
细心的同学可能会发现,Union和Intersect设置输入要素和叠置要素的方式是一致的,它们是将两种要素读入到IGpValueTableObject中,然后赋值给in_features,而Identity工具是针对in_features和 identity_features分别赋值。因为在ArcGIS的叠置分析中Union和Intersect两种工具可以针对两个以上的图层进行叠置运算,而Identity工具是针对两个要素的运算,其实质是使用叠置要素对输入要素进行更新的一个过程。
另外,Identity工具需要本机中具有ArcInfo级别的Licence权限,如果你的当前电脑没有安装ArcInfo,请在实现的过程中将Identity的相关代码进行屏蔽,如果装有ArcInfo,在运行程序之前,首先需要打开ArcGIS LicenceManager的服务。我们通过以下方式设置Licence权限。 进入到MyGIS的MainForm窗体的设计器界面,右键单击LicenceControl,选择菜单中的“属性”选项。选择Products中的ArcInfo选项。
图 17 LicenceControl设置
运行程序,点击菜单“叠置分析”,弹出叠置分析参数设置窗口,添加叠置分析要素文件,并设置输出路径如下。
图 18叠置分析参数设置
点击分析,可得到如下结果。
图 19叠置分析效果
3.2.3 小结
本节我们系统讲解了叠置分析功能的开发并进一步完善了MyGIS系统。当然,我们的工程中的叠置分析的功能还存在一些不足之处,比如叠置求交和叠置联合是针对两个或以上要素类进行的操作,我们这里仅实现了两个要素类的操作。如果您有兴趣,可以自己动手进行扩展。同时,您也可以尝试自己通过ArcEngine来实现ArcGIS中的其他分析操作,并添加到MyGIS中。
4.1 添加控件
将ToolbarControl 、TOCControl的伙伴控件设为MapControl,ToolbarControl 加载添加数据按钮和地图浏览的功能按钮。控件布局效果如下图所示。
图 20界面效果
控件属性设置如下:
表 3控件属性设置
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
4.2 添加引用和代码
using System.Windows.Forms;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Display;
定义如下成员变量。
private IMap mMap;//地图
private IDisplayFeedback mDisplayFeedback;//用于鼠标与控件进行可视化交互private IFeature mPanFeature;//移动的要素
带参数的构造函数和默认构造函数如下:
public Edit(IFeatureLayer editLayer, IMap map)
{
mCurrentLayer = editLayer;
this.mMap = map;
}
/// <summary>
/// 默认构造函数
/// </summary>
public Edit()
{
}
/// <summary>
/// </summary>
/// <returns></returns>
public bool IsEditing()
{
return mIsEditing;
}
/// <summary>
/// </summary>
/// <returns></returns>
public bool HasEdited()
{
return mHasEditing;
}
/// <summary>
/// </summary>
public void StartEditing()
{
//获取要素工作空间
IFeatureClass pFeatureClass = mCurrentLayer.FeatureClass;
IWorkspace pWorkspace = (pFeatureClass as IDataset).Workspace;
mWorkspaceEdit = pWorkspace as IWorkspaceEdit;
if (mWorkspaceEdit == null)
return;

if (!mWorkspaceEdit.IsBeingEdited())
{
mWorkspaceEdit.StartEditing(true);
mIsEditing = true;
}
}
/// <summary>
/// </summary>
/// <param name="save">true时保存,false时不保存</param>
public void SaveEditing(bool save)
{
if (!save)
{
mWorkspaceEdit.StopEditing(false);
}
else if (save && mHasEditing && mIsEditing)
{
mWorkspaceEdit.StopEditing(true);
}
mHasEditing = false;
}
/// <summary>
/// </summary>
/// <param name="save"></param>
public void StopEditing(bool save)
{
this.SaveEditing(save);
mIsEditing = false;
}
下面添加鼠标与地图的交互事件,包括创建要素时鼠标的MouseDown事件、MouseMove事件和DoubleClick事件,移动要素时鼠标的PanMouseDown事件、MouseMove事件和MouseUp事件。
创建要素时首先在MouseDown事件中获取鼠标点击的点位,若图层为点图层,则直接创建要素,若为线图层或面图层,则作为第一个节点,以后每次点击都会添加一个节点,直到双击鼠标完成要素的创建。创建要素时的MouseDown事件在这里定义为CreateMouseDown。代码如下:
public void CreateMouseDown(double mapX, double mapY)
{
//鼠标点击位置
IPoint pPoint = new PointClass();
pPoint.PutCoords(mapX, mapY);
INewLineFeedback pNewLineFeedback;
INewPolygonFeedback pNewPolygonFeedback;
if (mIsEditing)
{
//针对线和多边形,判断交互状态,第一次时要初始化,再次点击则直接添加节点if(mDisplayFeedback==null)
{
//根据图层类型创建不同要素
switch (mCurrentLayer.FeatureClass.ShapeType)
{
case esriGeometryType.esriGeometryPoint:
//添加点要素
AddFeature(pPoint);
break;
case esriGeometryType.esriGeometryPolyline:
mDisplayFeedback = new NewLineFeedbackClass();
//获取当前屏幕显示
mDisplayFeedback.Display = ((IActiveView)this.mMap).ScreenDisplay;pNewLineFeedback = mDisplayFeedback as INewLineFeedback;//开始追踪
pNewLineFeedback.Start(pPoint);
break;
case esriGeometryType.esriGeometryPolygon:
mDisplayFeedback = new NewPolygonFeedbackClass();
mDisplayFeedback.Display = ((IActiveView)this.mMap).ScreenDisplay;pNewPolygonFeedback = mDisplayFeedback as
INewPolygonFeedback;
//开始追踪
pNewPolygonFeedback.Start(pPoint);
break;
}
}
else //第一次之后的点击则添加节点
{
if (mDisplayFeedback is INewLineFeedback)
{
pNewLineFeedback = mDisplayFeedback as INewLineFeedback;pNewLineFeedback.AddPoint(pPoint);
}
else if (mDisplayFeedback is INewPolygonFeedback)
{
pNewPolygonFeedback = mDisplayFeedback as INewPolygonFeedback;pNewPolygonFeedback.AddPoint(pPoint);
}
}
}
}
MouseMove事件在创建要素时和移动要素时可以共用,代码如下:
public void MouseMove(double mapX, double mapY)
{
if (mDisplayFeedback == null)
return;
//获取鼠标移动点位,并移动至当前点位
IPoint pPoint = new PointClass();
pPoint.PutCoords(mapX, mapY);
mDisplayFeedback.MoveTo(pPoint);
}
创建要素时的DoubleClick事件代码如下:
public void CreateDoubleClick(double mapX, double mapY)
{
if (mDisplayFeedback == null)
return;
IGeometry pGeometry=null;
IPoint pPoint = new PointClass();
pPoint.PutCoords(mapX, mapY);
INewLineFeedback pNewLineFeedback;
INewPolygonFeedback pNewPolygonFeedback;
IPointCollection pPointCollection;
if (mIsEditing)
{
if (mDisplayFeedback is INewLineFeedback)
{
pNewLineFeedback = mDisplayFeedback as INewLineFeedback;//添加点击点
pNewLineFeedback.AddPoint(pPoint);
//结束Feedback
IPolyline pPolyline = pNewLineFeedback.Stop();
pPointCollection = pPolyline as IPointCollection;
//至少两点时才创建线要素
if (pPointCollection.PointCount < 2)
MessageBox.Show("至少需要两点才能建立线要素!", "提示");else
pGeometry = pPolyline as IGeometry;
}
else if (mDisplayFeedback is INewPolygonFeedback)
{
pNewPolygonFeedback = mDisplayFeedback as INewPolygonFeedback;//添加点击点
pNewPolygonFeedback.AddPoint(pPoint);
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
//结束Feedback
IPolygon pPolygon = pNewPolygonFeedback.Stop();
pPointCollection = pPolygon as IPointCollection;
//至少三点才能创建线要素
if (pPointCollection.PointCount < 3)
MessageBox.Show("至少需要两点才能建立线要素!", "提示");
else
pGeometry = pPolygon as IGeometry;
}
mDisplayFeedback.Display = ((IActiveView)this.mMap).ScreenDisplay;
//不为空时添加
if (pGeometry!=null)
{
AddFeature(pGeometry);
//创建完成将DisplayFeedback置为空
mDisplayFeedback = null;
}
}
}
移动要素的MouseDown事件定义为PanMouseDown,代码如下:
public void PanMouseDown(double mapX, double mapY)
{
//清除地图选择集
mMap.ClearSelection();
//获取鼠标点击位置
IPoint pPoint = new PointClass();
pPoint.PutCoords(mapX, mapY);
IActiveView pActiveView = mMap as IActiveView;
//获取点击到的要素
mPanFeature = SelectFeature(pPoint);
if (mPanFeature== null)
return;
//获取要素形状
IGeometry pGeometry = mPanFeature.Shape;
IMovePointFeedback pMovePointFeedback;
IMoveLineFeedback pMoveLineFeedback;
IMovePolygonFeedback pMovePolygonFeedback;
//根据要素类型定义移动方式
switch (pGeometry.GeometryType)
{
case esriGeometryType.esriGeometryPoint:
mDisplayFeedback = new MovePointFeedbackClass();
//获取屏幕显示
mDisplayFeedback.Display = pActiveView.ScreenDisplay;
//开始追踪
pMovePointFeedback = mDisplayFeedback as IMovePointFeedback;pMovePointFeedback.Start((IPoint)pGeometry, pPoint);
break;
case esriGeometryType.esriGeometryPolyline:
mDisplayFeedback = new MoveLineFeedbackClass();
mDisplayFeedback.Display = pActiveView.ScreenDisplay;
//开始追踪
pMoveLineFeedback = mDisplayFeedback as IMoveLineFeedback;
pMoveLineFeedback.Start((IPolyline)pGeometry, pPoint);
break;
case esriGeometryType.esriGeometryPolygon:
mDisplayFeedback = new MovePolygonFeedbackClass();
mDisplayFeedback.Display = pActiveView.ScreenDisplay;
//开始追踪
pMovePolygonFeedback = mDisplayFeedback as IMovePolygonFeedback;pMovePolygonFeedback.Start((IPolygon)pGeometry, pPoint);
break;
}
}
移动要素的MouseUp事件定义为PanMouseUp,代码如下:
public void PanMouseUp(double mapX, double mapY)
{
if (mDisplayFeedback == null)
return;
//获取点位
IActiveView pActiveView = mMap as IActiveView;
IPoint pPoint = new PointClass();
pPoint.PutCoords(mapX, mapY);
IMovePointFeedback pMovePointFeedback;
IMoveLineFeedback pMoveLineFeedback;
IMovePolygonFeedback pMovePolygonFeedback;
IGeometry pGeometry;
//根据移动要素类型选择移动方式
if (mDisplayFeedback is IMovePointFeedback)
{
pMovePointFeedback = mDisplayFeedback as IMovePointFeedback;
//结束追踪
pGeometry = pMovePointFeedback.Stop();
//更新要素
UpdateFeature(mPanFeature, pGeometry);
}
else if (mDisplayFeedback is IMoveLineFeedback)
{
pMoveLineFeedback = mDisplayFeedback as IMoveLineFeedback;
//结束追踪
pGeometry = pMoveLineFeedback.Stop();
//更新要素
UpdateFeature(mPanFeature, pGeometry);
}
else if (mDisplayFeedback is IMovePolygonFeedback)
{
pMovePolygonFeedback = mDisplayFeedback as IMovePolygonFeedback;pGeometry = pMovePolygonFeedback.Stop();
UpdateFeature(mPanFeature, pGeometry);
}
mDisplayFeedback = null;
pActiveView.Refresh();
}
另外,本例中使用到了之前定义的ConvertPixelToMapUnits()函数,用于实现屏幕距离向地图距离的转化,请自行添加该函数。
下面我们开始主窗体的实现,首先添加ESRI.ArcGIS.Carto的引用,定义Edit类的对象作为主窗体的成员变量。在主窗体的载入事件中添加如下代码:
private void MainForm_Load(object sender, EventArgs e)
{
cboTasks.Items.Add("新建");
cboTasks.Items.Add("移动");
cboTasks.SelectedIndex = 0;
this.cboTasks.Enabled = false;
this.btnSave.Enabled = false;
this.btnStopEditing.Enabled = false;
}
双击“更新图层”添加如下代码:
private void btnRefreshLayers_Click(object sender, EventArgs e)
{
//清空原有选项
cboLayers.Items.Clear();
//没有添加图层时返回
if (this.axMapControl1.Map.LayerCount == 0)
{
MessageBox.Show("MapControl中未添加图层!", "提示");
return;
}
//加载图层
for (int i = 0; i < this.axMapControl1.Map.LayerCount; i++)
{
ILayer pLayer = this.axMapControl1.get_Layer(i);
cboLayers.Items.Add(pLayer.Name);
}
this.axMapControl1.Refresh();
cboLayers.SelectedIndex = 0;
}
private void btnStartEditing_Click(object sender, EventArgs e)
{
if (this.axMapControl1.Map.LayerCount == 0)
return;
if (this.cboLayers.Items.Count == 0)
{
return;
}
IMap pMap = this.axMapControl1.Map;
IFeatureLayer pFeatureLayer = this.axMapControl1.get_Layer(cboLayers.SelectedIndex) as IFeatureLayer;
if (mEdit == null)
{
mEdit = new Edit(pFeatureLayer, pMap);
}
mEdit.StartEditing();
this.btnStartEditing.Enabled = false;
this.cboTasks.Enabled = true;
this.btnStopEditing.Enabled = true;
this.btnSave.Enabled = true;
}
双击“保存”添加如下代码:
private void btnSave_Click(object sender, EventArgs e)
{
if (mEdit == null)
return;
if (mEdit.IsEditing()&&mEdit.HasEdited())
{
mEdit.SaveEditing(true);
}
}
private void btnStopEditing_Click(object sender, EventArgs e)
{
if (mEdit == null)
return;
if (mEdit.HasEdited())
{
if (dr == DialogResult.OK)
mEdit.SaveEditing(true);
else
mEdit.SaveEditing(false);
}
}
在MapControl的MouseDown事件中添加如下代码:
private void axMapControl1_OnMouseDown(object sender,
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
ESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseDownEvent e)
{
//判断是否鼠标左键
if (e.button != 1)
return;
if (mEdit.IsEditing())
{
switch (cboTasks.SelectedIndex)
{
case 0:
mEdit.CreateMouseDown(e.mapX, e.mapY);
break;
case 1:
mEdit.PanMouseDown(e.mapX, e.mapY);
break;
}
}
}
MapControl的MouseMove事件中添加如下代码:
private void axMapControl1_OnMouseMove(object sender,
ESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseMoveEvent e)
{
if (mEdit.IsEditing())
{
switch (cboTasks.SelectedIndex)
{
case 0:
case 1:
mEdit.MouseMove(e.mapX, e.mapY);
break;
}
}
}
MapControl的Mouseup事件中添加如下代码:
private void axMapControl1_OnMouseUp(object sender,
ESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseUpEvent e)
{
//判断是否鼠标左键
if (e.button != 1)
return;
if (mEdit.IsEditing())
{
switch (cboTasks.SelectedIndex)
{
case 0:
break;
case 1:
mEdit.PanMouseUp(e.mapX, e.mapY);
break;
}
}
}
MapControl的OnDoubleClick事件中添加如下代码:
private void axMapControl1_OnDoubleClick(object sender,
ESRI.ArcGIS.Controls.IMapControlEvents2_OnDoubleClickEvent e)
{
//判断是否鼠标左键
if (e.button !=1)
return;
if (mEdit.IsEditing())
{
switch (cboTasks.SelectedIndex)
{
case 0:
mEdit.CreateDoubleClick(e.mapX, e.mapY);
break;
case 1:
break;
}
}
}
图 21新建要素效果
选择“选择任务”中的“移动”,选中要素并移动,效果如下图所示:
图 22移动要素效果
4.3 小结
5 专题图制作
如何使用不同的符号来修饰地图上的不同要素,使得地图能够清晰、美观和直接地表达用户所需的不同类型的信息,直接关系到地图的使用状况。ArcMap提供了多种方法来制作不同类型的专题图,这些专题图是可以依据要素的一个或多个属性的不同而设置不同类型的符号,从而达到区分不同类型要素的目的。在实际项目中用户也经常使用要素着色的功能,如制作价格分布图、土地利用图等。常用的着色方法主要有简单着色法、唯一值着色法、数据分级着色法、依比例符号着色法、质量图着色法和点密度着色法。
FeatureRenderer是一个抽象类,其子类负责进行不同类型的着色运算。它们都实现了IFeatureRenderer接口,这个接口定义了进行地图着色运算的公共属性和方法。使用不同的着色对象时,用户击要确定是哪个图层击要被着色,而着色对象只是要素图层的一个属性而已,程序员可以通过IGeoFeatureLayer::Renderer属性获得一个图层的着色对象。
5.1 添加控件
新建一个C#.Net项目,项目名称为MapRender,将Form1的名字设置为MainForm,并添加ToolbarControl 、MapControl、TOCControl、LicenceControl和Button等五个控件。并将ToolbarControl 、TOCControl的伙伴控件设为MapControl。控件布局效果如下图所示。
图 23控件布局效果
在ToolbarControl 加载添加数据按钮和地图浏览的功能按钮,如下图所示。
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
图 24工具栏设置效果
添加Buttom按钮的属性设置如下 :
表 4控件属性设置
5.2 代码添加及解释
该工程需要添加如下引用:
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Display
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.esriSystem;
在axMapControl1控件中添加STATES.shp图层,如图所示:
图 25添加初始化图层
1. 简单着色代码添加
添加btnSimpIeRenderer按钮的Click事件代码:
private void btnSimpIeRenderer_Click(object sender, EventArgs e)
{
IMap pMap = axMapControl1.Map;
//这里为面状图层为例
IGeoFeatureLayer pGeoFeatureLayer = pMap.get_Layer(0) as
IGeoFeatureLayer;
//新建一个填充符号
IFillSymbol pSimpleFills;
pSimpleFills = new SimpleFillSymbolClass();
IRgbColor color = new RgbColorClass();
color.Red = 120;
color.Green = 110;
color.Blue = 0;
pSimpleFills.Color = color;
//新建线符号
ILineSymbol pLineSymbol= new SimpleLineSymbolClass();
color.Red = 255;
color.Green = 0;
color.Blue = 0;
pLineSymbol.Color = color;
pLineSymbol.Width = 3;
//线符号作为该填充符号的外边缘
pSimpleFills.Outline = pLineSymbol;
ISimpleRenderer pSimpleRenderer;
pSimpleRenderer = new SimpleRendererClass();
pSimpleRenderer.Symbol = (ISymbol)pSimpleFills;
//以POP1990字段作为要素透明设置的属性
ITransparencyRenderer pTransRenderer;
pTransRenderer = pSimpleRenderer as ITransparencyRenderer;
pTransRenderer.TransparencyField = "POP1990";
pGeoFeatureLayer.Renderer = pTransRenderer as IFeatureRenderer;//刷新显示
axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);
}
着色的效果如下图所示:
图 26根据POP1990字段透明的简单着效果
代码注释:
ITransparencyRenderer pTransRenderer;
pTransRenderer = pSimpleRenderer as ITransparencyRenderer;
pTransRenderer.TransparencyField = "POP1990";
效果中符号的颜色都是一样的,这里主要根据 POP1990这个字段的属性值来对其符号实现透明度设置。
2. 分级着色代码添加
在前面代码的基础上添加如下引用:
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.esriSystem;
然后添加btnCIassBreakRenderer按钮的Click事件代码:
private void btnCIassBreakRenderer_Click(object sender, EventArgs e)
{
IGeoFeatureLayer pGeoFeatureL;
ITable pTable;
IClassifyGEN pClassify;
ITableHistogram pTableHistogram;
IBasicHistogram pHistogram;
object dataFrequency;
object dataValues;
double[] Classes;
int ClassesCount;
IClassBreaksRenderer pClassBreaksRenderer;
IHsvColor pFromColor;
IHsvColor pToColor;
IAlgorithmicColorRamp pAlgorithmicCR;
IEnumColors pEnumColors;
bool ok;
IColor pColor;
ISimpleFillSymbol pSimpleFillS;
int lbreakIndex;
string strPopField = "POP1990";
int numDesiredClasses = 5;
IMap pMap = axMapControl1.Map;
pMap.ReferenceScale = 0;
pGeoFeatureL = (IGeoFeatureLayer)pMap.get_Layer(0);
//从pTable的POP1990字段中得到信息给dataValues和 dataFrequency两个数组pTable = (ITable)pGeoFeatureL.FeatureClass;
pTableHistogram = new BasicTableHistogramClass();
pHistogram = (IBasicHistogram)pTableHistogram;
pTableHistogram.Field = strPopField;
pTableHistogram.Table = pTable;
pHistogram.GetHistogram(out dataValues, out dataFrequency);
//下面是分级方法,用于根据获取的值计算出符合要求的数据
pClassify = new EqualIntervalClass();
try
{
pClassify.Classify(dataValues, dataFrequency, ref
numDesiredClasses);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
//返回一个数组
Classes = (double[])pClassify.ClassBreaks;
ClassesCount = Classes.GetUpperBound(0);
pClassBreaksRenderer = new ClassBreaksRendererClass();
pClassBreaksRenderer.Field = strPopField;
//设置着色对象的分级数目
pClassBreaksRenderer.BreakCount = ClassesCount;
pClassBreaksRenderer.SortClassesAscending = true;
//产生分级着色需要的颜色带对象的起止颜色对象
pFromColor = new HsvColorClass();
pFromColor.Hue = 60;
pFromColor.Saturation = 100;
pFromColor.Value = 96;
pToColor = new HsvColorClass();
pToColor.Hue = 0;
pToColor.Saturation = 100;
pToColor.Value = 96;
//产生颜色带对象
pAlgorithmicCR = new AlgorithmicColorRampClass();
pAlgorithmicCR.Algorithm = esriColorRampAlgorithm.esriHSVAlgorithm;pAlgorithmicCR.FromColor = pFromColor;
pAlgorithmicCR.ToColor = pToColor;
pAlgorithmicCR.Size = ClassesCount;
pAlgorithmicCR.CreateRamp(out ok);
//获得颜色
pEnumColors = pAlgorithmicCR.Colors;
//分类着色对象中的symbol和break的下标是从0开始
for (lbreakIndex = 0; lbreakIndex <= ClassesCount - 1; lbreakIndex++){
pColor = pEnumColors.Next();
pSimpleFillS = new SimpleFillSymbolClass();
pSimpleFillS.Color = pColor;
pSimpleFillS.Style = esriSimpleFillStyle.esriSFSSolid;
//这里是构造不同颜色着色的方法
pClassBreaksRenderer.set_Symbol(lbreakIndex,
(ISymbol)pSimpleFillS);
//着色对象的断点
pClassBreaksRenderer.set_Break(lbreakIndex, Classes[lbreakIndex + 1]);
}
pGeoFeatureL.Renderer = (IFeatureRenderer)pClassBreaksRenderer;
axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);
}
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
着色的效果如下图所示:
图 27根据POP1990字段的分级着效果
代码注释:代码中先通过一个ITableHistogram对象先同一个要素类获得POP1990字段的所有值和值频率,即datavalues和datafrequency,这两个数组记录了分级的基本数据;在取得两个数组后,再使用IClassifyGEN对象对他们进行分级,得到Classes和ClassesCount,前者是分级临街点值的数据,后者是分级数目。最后分局得到的分级数目和颜色带对象,分别设置ClassBreakRenderer对象的不同符号,产生不同的效果。注意:在C#.NET中必须使用IClassifyGEN这个借口定义的对象才能实现分级。二次开发
3. 唯一值着色代码添加
添加btnUniqueValueRenderer按钮的Click事件代码:
private void btnUniqueValueRenderer_Click(object sender, EventArgs e)
{
IGeoFeatureLayer m_pGeoFeatureL;
IUniqueValueRenderer pUniqueValueR;
IFillSymbol pFillSymbol;
IColor pNextUniqueColor;
IEnumColors pEnumRamp;
ITable pTable;
int lfieldNumber;
IRow pNextRow;
IRowBuffer pNextRowBuffer;
ICursor pCursor;
IQueryFilter pQueryFilter;
string codeValue;
IRandomColorRamp pColorRamp;
string strNameField = "State_Name";
IMap pMap = axMapControl1.Map;
pMap.ReferenceScale = 0;
m_pGeoFeatureL = (IGeoFeatureLayer)pMap.get_Layer(0);
pUniqueValueR = new UniqueValueRendererClass();
pTable = (ITable)m_pGeoFeatureL;
lfieldNumber = pTable.FindField(strNameField);
if (lfieldNumber == -1)
{
MessageBox.Show("未能找到字段 " + strNameField);
return;
}
//只用一个字段进行单值着色
pUniqueValueR.FieldCount = 1;
//用于区分着色的字段
pUniqueValueR.set_Field(0, strNameField);
//产生一个随机的颜色条,用的是HSV颜色模式
pColorRamp = new RandomColorRampClass();
pColorRamp.StartHue = 0;
pColorRamp.MinValue = 99;
pColorRamp.MinSaturation = 15;
pColorRamp.EndHue = 360;
pColorRamp.MaxValue = 100;
pColorRamp.MaxSaturation = 30;
//任意产生100个颜色,如果知道要素的数目可以产生精确的颜色个数
pColorRamp.Size = 100;
bool ok = true;
pColorRamp.CreateRamp(out ok);
pEnumRamp = pColorRamp.Colors;
pNextUniqueColor = null;
//产生查询过滤器对象
pQueryFilter = new QueryFilterClass();
pQueryFilter.AddField(strNameField);
//根据某个字段在表中找出指向所有行的游标对象
pCursor = pTable.Search(pQueryFilter, true);
pNextRow = pCursor.NextRow();
//遍历所有的要素
while (pNextRow != null)
{
pNextRowBuffer = pNextRow;
//找出Row为“STATES_NAME”的值,即不同的州名
codeValue = (string)pNextRowBuffer.get_Value(lfieldNumber);//获取随机颜色带中的任意一个颜色
pNextUniqueColor = pEnumRamp.Next();
if (pNextUniqueColor == null)
{
pEnumRamp.Reset();
pNextUniqueColor = pEnumRamp.Next();
}
pFillSymbol = new SimpleFillSymbolClass();
pFillSymbol.Color = pNextUniqueColor;
//将每次都得的要素字段值和修饰它的符号值放入着色对象中
pUniqueValueR.AddValue(codeValue, strNameField, (ISymbol)
pFillSymbol);
pNextRow = pCursor.NextRow();
}
m_pGeoFeatureL.Renderer = (IFeatureRenderer)pUniqueValueR;
axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);
}
着色的效果如下图所示:
图 28根据STATES_NAME字段的唯一值着效果
4. 唯一值着色代码添加
在前面代码的基础上添加引用
using stdole;
然后添加btnSymbolRenderer按钮的Click事件代码:
private void btnSymbolRenderer_Click(object sender, EventArgs e)
{
IGeoFeatureLayer pGeoFeatureLayer;
IFeatureLayer pFeatureLayer;
IProportionalSymbolRenderer pProportionalSymbolR;
ITable pTable;
IQueryFilter pQueryFilter;
ICursor pCursor;
IFillSymbol pFillSymbol;
ICharacterMarkerSymbol pCharaterMarkerS;
IDataStatistics pDataStatistics;
IStatisticsResults pStatisticsResult;
stdole.StdFont pFontDisp;
IRotationRenderer pRotationRenderer;
IMap pMap = axMapControl1.Map;
pMap.ReferenceScale = 0;
pFeatureLayer = (IGeoFeatureLayer)pMap.get_Layer(0);
pGeoFeatureLayer = (IGeoFeatureLayer)pFeatureLayer;
pTable = (ITable)pGeoFeatureLayer;
pQueryFilter = new QueryFilterClass();
pQueryFilter.AddField("");
pCursor = pTable.Search(pQueryFilter, true);
//使用statistics对象来计算最大最小值
pDataStatistics = new DataStatisticsClass();
pDataStatistics.Cursor = pCursor;
//设置要统计的字段名称
pDataStatistics.Field = "POP1990";
//获取统计的结果
pStatisticsResult = pDataStatistics.Statistics;
if (pStatisticsResult == null)
{
MessageBox.Show("获取对象失败!");
return;
}
//设置符号的背景填充色
pFillSymbol = new SimpleFillSymbolClass();
IRgbColor backColor = new RgbColorClass();
backColor.Red = 239;
backColor.Green = 228;
backColor.Blue = 190;
pFillSymbol.Color = backColor;
//设置依比例符号的符号类型
pCharaterMarkerS = new CharacterMarkerSymbolClass();
pFontDisp = new stdole.StdFontClass();
pFontDisp.Name = "ESRI Business";
pFontDisp.Size = 20;
pCharaterMarkerS.Font = (IFontDisp)pFontDisp;
pCharaterMarkerS.CharacterIndex = 90;
IRgbColor color = new RgbColorClass();
color.Red = 0;
color.Green = 0;
color.Blue = 0;
pCharaterMarkerS.Color = color;
// 创建一个新的依比例变化的符号来对pop1990字段进行渲染符号化
pCharaterMarkerS.Size = 8;
pProportionalSymbolR = new ProportionalSymbolRendererClass();
pProportionalSymbolR.ValueUnit = esriUnits.esriUnknownUnits;
pProportionalSymbolR.Field = "POP1990";
pProportionalSymbolR.FlanneryCompensation = false;
pProportionalSymbolR.MinDataValue = pStatisticsResult.Minimum;pProportionalSymbolR.MaxDataValue = pStatisticsResult.Maximum;pProportionalSymbolR.BackgroundSymbol = pFillSymbol;
pProportionalSymbolR.MinSymbol = (ISymbol)pCharaterMarkerS;
pProportionalSymbolR.LegendSymbolCount = 5;
pProportionalSymbolR.CreateLegendSymbols();
//根据pop1990的值来设置符号的旋转角度
pRotationRenderer = (IRotationRenderer)pProportionalSymbolR;
pRotationRenderer.RotationField = "POP1990";
pRotationRenderer.RotationType =
esriSymbolRotationType.esriRotateSymbolGeographic;
//设置states图层为依比例变化符号渲染的图层并刷新显示
pGeoFeatureLayer.Renderer = (IFeatureRenderer)pProportionalSymbolR;
axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);
}
着色的效果如下图所示:
图 29根据POP1990字段的依比例着效果
5. 质量图着色代码添加
这里以柱状图为例,先添加引用如下:
然后添加btnChartRenderer按钮的Click事件代码:
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
private void btnChartRenderer_Click(object sender, EventArgs e)
{
IGeoFeatureLayer pGeoFeatureL;
IFeatureLayer pFeatureLayer;
ITable pTable;
ICursor pCursor;
IQueryFilter pQueryFilter;
IRowBuffer pRowBuffer;
int numFields = 2;
int[] fieldIndecies = new int[numFields];
int lfieldIndex;
double dmaxValue;
bool firstValue;
double dfieldValue;
IChartRenderer pChartRenderer;
IRendererFields pRendererFields;
IFillSymbol pFillSymbol;
IMarkerSymbol pMarkerSymbol;
ISymbolArray pSymbolArray;
IChartSymbol pChartSymbol;
//设置需要符号化的字段信息
string strPopField1 = "POP1990";
string strPopField2 = "POP1999";
IMap pMap = axMapControl1.Map;
pMap.ReferenceScale = pMap.MapScale;
pFeatureLayer = (IGeoFeatureLayer)pMap.get_Layer(0);
pGeoFeatureL = (IGeoFeatureLayer)pFeatureLayer;
pTable = (ITable)pGeoFeatureL;
pGeoFeatureL.ScaleSymbols = true;
pChartRenderer = new ChartRendererClass();
//设置柱状图中所需要绘制的属性
pRendererFields = (IRendererFields)pChartRenderer;
pRendererFields.AddField(strPopField1, strPopField1);
pRendererFields.AddField(strPopField2, strPopField2);
pQueryFilter = new QueryFilterClass();
pQueryFilter.AddField(strPopField1);
pQueryFilter.AddField(strPopField2);
pCursor = pTable.Search(pQueryFilter, true);
fieldIndecies[0] = pTable.FindField(strPopField1);
fieldIndecies[1] = pTable.FindField(strPopField2);
firstValue = true;
//创建一个存储字段索引号的数组来对字段进行标识
fieldIndecies[0] = pTable.FindField(strPopField1);
fieldIndecies[1] = pTable.FindField(strPopField2);
firstValue = true;
dmaxValue = 0;
//迭代访问每一个要素
pRowBuffer = pCursor.NextRow();
while (pRowBuffer != null)
{
for (lfieldIndex = 0; lfieldIndex <= numFields - 1; lfieldIndex++){
// 迭代访问要素的字段值并对最大值进行更新和标记
dfieldValue =
(double)pRowBuffer.get_Value(fieldIndecies[lfieldIndex]);
if (firstValue)
{
// 将最大值dmaxValue初始化为第一个值
dmaxValue = dfieldValue;
firstValue = false;
}
else
{
if (dfieldValue > dmaxValue)
{
// 获取新的最大值时进行更新
dmaxValue = dfieldValue;
}
}
}
pRowBuffer = pCursor.NextRow();
}
if (dmaxValue <= 0)
{
MessageBox.Show("获取要素失败");
return;
}
// 设置需要渲染的柱状图符号
IBarChartSymbol pBarChartSymbol;
pBarChartSymbol = new BarChartSymbolClass();
pChartSymbol = (IChartSymbol)pBarChartSymbol;
pBarChartSymbol.Width = 12;
pMarkerSymbol = (IMarkerSymbol)pBarChartSymbol;
//设置柱状图的最大值
pChartSymbol.MaxValue = dmaxValue;
//设置柱状图的最大渲染高度
pMarkerSymbol.Size = 80;
//为每个柱状设置符号
pSymbolArray = (ISymbolArray)pBarChartSymbol;
// 为每个柱状符号添加颜色
pFillSymbol = new SimpleFillSymbolClass();
IRgbColor color = new RgbColorClass();
color.Red = 213;
color.Green = 212;
color.Blue = 252;
pFillSymbol.Color = color;
pSymbolArray.AddSymbol((ISymbol)pFillSymbol);
pFillSymbol = new SimpleFillSymbolClass();
color.Red = 193;
color.Green = 252;
color.Blue = 179;
pFillSymbol.Color = color;
pSymbolArray.AddSymbol((ISymbol)pFillSymbol);
//设置渲染符号为柱状图
pChartRenderer.ChartSymbol = (IChartSymbol)pBarChartSymbol;
pChartRenderer.Label = "Population";
//设置柱状图的背景颜色
pFillSymbol = new SimpleFillSymbolClass();
color.Red = 239;
color.Green = 228;
color.Blue = 190;
pFillSymbol.Color = color;
pChartRenderer.BaseSymbol = (ISymbol)pFillSymbol;
//设置overpoaster属性为false,使柱状图显示在polygon多边形要素的pChartRenderer.UseOverposter = false;
pChartRenderer.CreateLegend();
//更新柱状图和刷新显示
pGeoFeatureL.Renderer = (IFeatureRenderer)pChartRenderer;
axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);
}
着色的效果如下图所示:
图 30根据POP1990和POP1999字段的柱状图着效果
1. 简单着色代码添加
添加btnDotDensityRenderer按钮的Click事件代码:
private void btnDotDensityRenderer_Click(object sender, EventArgs e)
{
IGeoFeatureLayer pGeoFeatureL;
IDotDensityRenderer pDotDensityRenderer;
IDotDensityFillSymbol pDotDensityFillS;
IRendererFields pRendererFields;
ISymbolArray pSymbolArray;
ISimpleMarkerSymbol pSimpleMarkerS;
string strPopField = "POP1990";
IMap pMap = axMapControl1.Map;
pGeoFeatureL = (IGeoFeatureLayer)pMap.get_Layer(0);
pDotDensityRenderer = new DotDensityRendererClass();
pRendererFields = (IRendererFields)pDotDensityRenderer;
//添加点密度着色的字段名
pRendererFields.AddField(strPopField, strPopField);
pDotDensityFillS = new DotDensityFillSymbolClass();
//设置着色符号的大小和颜色
pDotDensityFillS.DotSize = 4;
IRgbColor color = new RgbColorClass();
color.Red = 0;
color.Green = 0;
color.Blue = 0;
pDotDensityFillS.Color = color;
//设置着色符号的背景颜色
color.Red = 239;
color.Green = 228;
color.Blue = 190;
pDotDensityFillS.BackgroundColor = color;
pSymbolArray = (ISymbolArray)pDotDensityFillS;
//设置点符号样式
pSimpleMarkerS = new SimpleMarkerSymbolClass();
pSimpleMarkerS.Style = esriSimpleMarkerStyle.esriSMSCircle;
pSimpleMarkerS.Size = 4;
color.Red = 128;
color.Green = 128;
color.Blue = 255;
pSimpleMarkerS.Color = color;
pSymbolArray.AddSymbol((ISymbol)pSimpleMarkerS);
pDotDensityRenderer.DotDensitySymbol = pDotDensityFillS;
//设置一个点代表的值
pDotDensityRenderer.DotValue = 200000;
//创建图例
pDotDensityRenderer.CreateLegend();
pGeoFeatureL.Renderer = (IFeatureRenderer)pDotDensityRenderer;
axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);
}
着色的效果如下图所示:
图 31根据POP1990字段的点密度着效果
5.3 小结
地图专题图制作是GIS中数据显示和分析的重要环节,涉及到的对象和接口也比较多,上面实例只是简单的实现了五种专题图的显示。读者也可以尝试在当前工程中添加TOCControl控件,然后在每次专题图生成代码的最后添加如下代码:
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
axTOC1.SetBuddyControl(axMap1.Object ) ;axTOC1.Refresh();
在ArcGIS中,网络是由一组边和接点按照一定的拓扑关系彼此连接而成的,边是具有一定长度和物流的网络要素,接点是两条或两条以上边的交汇处,实现两条边之间的物流的转换,边与接点是网络的两类基本组成要素。ArcGIS所涉及的网络,是由一系列要素类别组成的,可以度量并能够用图形形式表达的网络,所以又称为几何网络(Geometric Network)。组成几何网络的各要素被限制存在于网络中,作为网络要素(Network Feature)。ArcGIS自动维护几何网络要素之间的基于几何的一致性拓扑关系。本节中的网络分析包括两个模块,网络生成和进行网络分析。
本小节中的网络分析是针对网络生成中的数据进行操作的。路径与权重的设置注意文档中的”注意”。
6.1 生成几何网络
在进行网络分析功能实现之前,我们需要使用ArcGIS来创建一个几何网络文件。
1) 创建Geodatabase。打开ArcCatalog,在“\GIS设计与开发\例子数据\Network”文件夹下单击右键,选择“New”,“Personal Geodatabase”,名称设为“USA_Highway_Network_GDB”。
2) 创建Feature Dataset。右键单击“USA_Highway_Network_GDB”,选择“New”“Feature ,
Dataset”。在弹出窗口设置要素数据集的名字为“high”。在弹出的坐标系统设置界面单击“Import”按钮,选择文件“USHIGH.shp”,即将该文件的坐标系统导入到数据集中,如图所示。然后单击“Next”,直到Finish,完成要素数据集的创建。
图 32创建Feature Dataset
图 33坐标系统设置
3) 添加FeatureClass。右键单击要素数据集“high”,选择“Import”,“FeatureClass(Single)”,在弹出对话框中设置“Input Features”为“USHIGH.shp”,输出要素名称为“high”,输出路径为默认。单击“OK”,完成添加。
图 34添加FeatureClass
图 35输出设置
4) 创建几何网络。右击“high”数据集,选择“New”,“Geometric Network”,准备开始生成网络。
图 36创建几何网络
进入几何网络生成向导。
图 37几何网络生成向导
选择几何网络生成方式,这里选择根据已存在要素创建。
图 38选择几何网络生成方式
选择生成几何网络的要素,并设置名称,这里保留默认名称不做修改。
图 39选择生成几何网络的要素
选择几何网络中是否保留原要素的属性值,这里选择“Yes”。
图 40选择几何网络属性值
选择网络中的边的类型为复杂边(complex edges)。所谓复杂边,就是在生成网络过程中,不会因为节点和边的连接关系而打断该边。而简单边会因为有中间节点的存在被分割成为多个要素。
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
图 41网络中的边的类型
图 42设置要素间捕捉容差
设置网络数据的权重,将Weight Name设为“Length”,“Type”设为“Double”。
图 43设置网络数据的权重
将上一步设置的权重名称与要素类中的字段关联,这里选择LENGTH。
图 44权重字段关联
几何网络参数设置完成。点击“Finish”即可完成。
图 45几何网络设置信息
打开Catalog下文件夹,展开后创建的文件如下图所示。
图 46创建几何网络后的文件
通过以上步骤生成的几何网络指定了“high”要素类中的“LENGTH”字段作为该几何网络的权。执行程序后,在Personal Geodatabase中的“high”要素集中生成了“high_NET_Junctions”接点要素类和“ushigh”几何网络。
6.2 网络分析的实现
我们下面动手实现一个简单的最短路径分析。用户通过在MapControl点击选择路径经过的节点,双击完成节点的选择,然后最短路径会以红色线条显示到MapControl中。
6.2.1 添加控件
新建一个C#项目,项目名称为Network,将Form1的名字设置为MainForm,Text属性设为“网络分析”。并添加、MapControl、TOCControl、LicenceControl三个控件。并将TOCControl的伙伴控件设为MapControl。控件布局效果如下图所示。
图 47控件布局效果
6.2.2 代码添加及解释
添加如下引用。
using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.DataSourcesGDB; using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.Geometry; using ESRI.ArcGIS.NetworkAnalysis; using ESRI.ArcGIS.Display;
添加如下成员变量。
//几何网络
private IGeometricNetwork mGeometricNetwork;//给定点的集合
private IPointCollection mPointCollection;//获取给定点最近的Network元素private IPointToEID mPointToEID;
//返回结果变量
private IEnumNetEID mEnumNetEID_Junctions;private IEnumNetEID mEnumNetEID_Edges;private double mdblPathCost;
编写一个自定义函数,用于实现路径分析。在ArcEngine中,使用ITraceFlowSolverGEN接口实现路径分析计算,通过查询接口INetSolver获取几何网络的Network,使用INetFlag获取路径求解的边(PutEdgeOrigins)或点(PutJunctionOrigins),最后使用该接口的FindPath
方法执行路径分析。实现代码如下:
private void SolvePath(string weightName){
//创建ITraceFlowSolverGEN
ITraceFlowSolverGEN pTraceFlowSolverGEN = new TraceFlowSolverClass();INetSolver pNetSolver = pTraceFlowSolverGEN as INetSolver;//初始化用于路径计算的Network
INetwork pNetWork = mGeometricNetwork.Network;pNetSolver.SourceNetwork = pNetWork;
//获取分析经过的点的个数
int intCount = mPointCollection.PointCount;if (intCount < 1)return;
INetFlag pNetFlag;
//用于存储路径计算得到的边
IEdgeFlag[] pEdgeFlags = new IEdgeFlag[intCount];
IPoint pEdgePoint = new PointClass();int intEdgeEID;
IPoint pFoundEdgePoint;double dblEdgePercent;
//用于获取几何网络元素的UserID, UserClassID,UserSubIDINetElements pNetElements = pNetWork as INetElements;int intEdgeUserClassID;int intEdgeUserID;int intEdgeUserSubID;for (int i = 0; i < intCount; i++){
pNetFlag = new EdgeFlagClass();//获取用户点击点
pEdgePoint = mPointCollection.get_Point(i);//获取距离用户点击点最近的边
mPointToEID.GetNearestEdge(pEdgePoint, out intEdgeEID, out pFoundEdgePoint, out dblEdgePercent);
if (intEdgeEID <= 0)continue;
//根据得到的边查询对应的几何网络中的元素UserID, UserClassID,UserSubIDpNetElements.QueryIDs(intEdgeEID, esriElementType.esriETEdge,
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
out intEdgeUserClassID, out intEdgeUserID, out intEdgeUserSubID);
if (intEdgeUserClassID <= 0 || intEdgeUserID <= 0)
continue;
pNetFlag.UserClassID = intEdgeUserClassID;
pNetFlag.UserID = intEdgeUserID;
pNetFlag.UserSubID = intEdgeUserSubID;
pEdgeFlags[i] = pNetFlag as IEdgeFlag;
}
//设置路径求解的边
pTraceFlowSolverGEN.PutEdgeOrigins(ref pEdgeFlags);
//路径计算权重
INetSchema pNetSchema = pNetWork as INetSchema;
INetWeight pNetWeight = pNetSchema.get_WeightByName(weightName);
if (pNetWeight == null)
return;
//设置权重,这里双向的权重设为一致
INetSolverWeights pNetSolverWeights = pTraceFlowSolverGEN as INetSolverWeights;pNetSolverWeights.ToFromEdgeWeight = pNetWeight;
pNetSolverWeights.FromToEdgeWeight = pNetWeight;
object[] arrResults = new object[intCount - 1];
//执行路径计算
pTraceFlowSolverGEN.FindPath(esriFlowMethod.esriFMConnected,
esriShortestPathObjFn.esriSPObjFnMinSum,
out mEnumNetEID_Junctions, out mEnumNetEID_Edges, intCount - 1, ref arrResults);
//获取路径计算总代价(cost)
mdblPathCost = 0;
for (int i = 0; i < intCount - 1; i++)
mdblPathCost += (double)arrResults[i];
}
编写自定义函数,实现路径分析结果到几何要素的转换,用于地图的显示。主要通过IEIDHelper接口完成转换,首先使用该接口获取几何网络和空间参考,然后查询组成路径的几何要素的信息返回到接口IEnumEIDInfo中,最后通过IEnumEIDInfo接口获取要素的几何要素。代码如下:
private IPolyline PathToPolyLine()
{
IPolyline pPolyLine = new PolylineClass();
IGeometryCollection pNewGeometryCollection = pPolyLine as IGeometryCollection;
if (mEnumNetEID_Edges == null)
return null;
IEIDHelper pEIDHelper = new EIDHelperClass();
//获取几何网络
pEIDHelper.GeometricNetwork = mGeometricNetwork;
//获取地图空间参考
ISpatialReference pSpatialReference = this.axMapControl1.Map.SpatialReference;pEIDHelper.OutputSpatialReference = pSpatialReference;
pEIDHelper.ReturnGeometries = true;
//根据边的ID获取边的信息
IEnumEIDInfo pEnumEIDInfo = pEIDHelper.CreateEnumEIDInfo(mEnumNetEID_Edges);int intCount = pEnumEIDInfo.Count;
pEnumEIDInfo.Reset();
IEIDInfo pEIDInfo;
IGeometry pGeometry;
for (int i = 0; i < intCount;i++ )
{
pEIDInfo = pEnumEIDInfo.Next();
//获取边的几何要素
pGeometry = pEIDInfo.Geometry;
pNewGeometryCollection.AddGeometryCollection((IGeometryCollection)pGeometry);}
return pPolyLine;
}
下面开始网络分析的实现。首先是网络数据的读取与加载,我们在MainForm的Load事件中实现。此处直接加载我们在上一节自己生成的几何网络数据,首先获取工作空间“USA_Highway_Network_GDB.mdb”,然后获取其中的要素数据集合“high”,进而得到其中的几何网络数据“high_net”,使用接口IGeometricNetwork获取。然后使用IFeatureClassContainer接口获取几何网络中的要素类,添加到MapControl中。值得注意的是,路径分析中,IPointToEID的源地图和几何网络以及容差在此完成设置。代码如下:
private void MainForm_Load(object sender, EventArgs e)
{
//获取几何网络文件路径
//注意修改此路径为当前存储路径
string strPath = @"E:\GIS设计与开发\例子数据
\Network\USA_Highway_Network_GDB.mdb";
//打开工作空间
IWorkspaceFactory pWorkspaceFactory = new AccessWorkspaceFactory();
IFeatureWorkspace pFeatureWorkspace = pWorkspaceFactory.OpenFromFile(strPath, 0) as IFeatureWorkspace;
//获取要素数据集
//注意名称的设置要与上面创建保持一致
IFeatureDataset pFeatureDataset = pFeatureWorkspace.OpenFeatureDataset("high");
//获取network集合
INetworkCollection pNetWorkCollection = pFeatureDataset as INetworkCollection;//获取network的数量,为零时返回
int intNetworkCount = pNetWorkCollection.GeometricNetworkCount;
if (intNetworkCount < 1)
return;
//FeatureDataset可能包含多个network,我们获取指定的network
//注意network的名称的设置要与上面创建保持一致
mGeometricNetwork = pNetWorkCollection.get_GeometricNetworkByName("high_net");
//将Network中的每个要素类作为一个图层加入地图控件
IFeatureClassContainer pFeatClsContainer = mGeometricNetwork as
IFeatureClassContainer;
//获取要素类数量,为零时返回
int intFeatClsCount=pFeatClsContainer.ClassCount;
if (intFeatClsCount < 1)
return;
IFeatureClass pFeatureClass;
IFeatureLayer pFeatureLayer;
for (int i = 0; i < intFeatClsCount;i++ )
{
//获取要素类
pFeatureClass = pFeatClsContainer.get_Class(i);
pFeatureLayer = new FeatureLayerClass();
pFeatureLayer.FeatureClass = pFeatureClass;
pFeatureLayer.Name = pFeatureClass.AliasName;
//加入地图控件
this.axMapControl1.AddLayer((ILayer)pFeatureLayer, 0);
}
//计算snap tolerance为图层最大宽度的1/100
//获取图层数量
int intLayerCount=this.axMapControl1.LayerCount;
IGeoDataset pGeoDataset;
IEnvelope pMaxEnvelope=new EnvelopeClass();
for (int i = 0; i < intLayerCount;i++ )
{
//获取图层
pFeatureLayer = this.axMapControl1.get_Layer(i) as IFeatureLayer;
pGeoDataset = pFeatureLayer as IGeoDataset;
//通过Union获得较大图层范围
pMaxEnvelope.Union(pGeoDataset.Extent);
}
double dblWidth = pMaxEnvelope.Width;
double dblHeight = pMaxEnvelope.Height;
double dblSnapTol;
if (dblHeight < dblWidth)
dblSnapTol = dblWidth * 0.01;
else
dblSnapTol = dblHeight * 0.01;
//设置源地图,几何网络以及捕捉容差
mPointToEID = new PointToEIDClass();
mPointToEID.SourceMap = this.axMapControl1.Map;
mPointToEID.GeometricNetwork = mGeometricNetwork;
mPointToEID.SnapTolerance = dblSnapTol;
}
下面添加MapControl的OnMouseDown事件,完成用户单击时的响应。用户单击时获取用户单击的点,并存储到点集中。
private void axMapControl1_OnMouseDown(object sender,
ESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseDownEvent e)
{
//记录鼠标点击的点
IPoint pNewPoint = new PointClass();
pNewPoint.PutCoords(e.mapX, e.mapY);
if (mPointCollection == null)
mPointCollection = new MultipointClass();
//添加点,before和after标记添加点的索引,这里不定义
object before = Type.Missing;
object after = Type.Missing;
mPointCollection.AddPoint(pNewPoint, ref before, ref after);
}
最后添加,MapControl的OnDoubleClick事件的响应,完成路径分析,并将所得路径转换为几何要素绘制到屏幕上。代码如下:
private void axMapControl1_OnDoubleClick(object sender,
ESRI.ArcGIS.Controls.IMapControlEvents2_OnDoubleClickEvent e)
{
try
{
//路径计算
//注意权重名称与设置保持一致
SolvePath("LENGTH");
//路径转换为几何要素
IPolyline pPolyLineResult = PathToPolyLine();
//获取屏幕显示
IActiveView pActiveView = this.axMapControl1.ActiveView;
IScreenDisplay pScreenDisplay = pActiveView.ScreenDisplay;
//设置显示符号
ILineSymbol pLineSymbol = new CartographicLineSymbolClass();
IRgbColor pColor = new RgbColorClass();
pColor.Red = 255;
pColor.Green = 0;
pColor.Blue = 0;
//设置线宽
pLineSymbol.Width =4;
//设置颜色
pLineSymbol.Color = pColor as IColor;
//绘制线型符号
pScreenDisplay.StartDrawing(0, 0);
pScreenDisplay.SetSymbol((ISymbol)pLineSymbol);
pScreenDisplay.DrawPolyline(pPolyLineResult);
pScreenDisplay.FinishDrawing();
}
catch (System.Exception ex)
{
MessageBox.Show("路径分析出现错误:" + "\r\n" + ex.Message);
}
//点集设为空
mPointCollection = null;
}
至此,我们已经完成了路径分析的功能,编译程序并运行,通过鼠标单击选择若干网络节点,最后通过双击完成节点选择。生成结果如图所示。
37ArcGIS Engine二次开发——提高篇_arcgis二次开发
图 48路径分析效果
6.3 MyGIS中添加网络分析
打开项目MyGIS,在MyGIS的主菜单添加一个新的菜单项“空间分析”,并添加子菜单“网络分析”,Name属性修改为“menuNetwork”。
请你参照前面的实现自己来完成代码的添加,注意在menuNetwork_Click事件中将当前工具mTool设为"Network",并在MapControl的OnMouseDown事件中修改响应的代码。
编译运行后效果如下图示。
图 49运行效果
6.4 小结
至此你已经完成了网络分析的全部内容,但我们仅仅只是介绍了一种网络分析分方法。事实上本小节所用到的网络分析功能是管线网络(ESRI.ArcGIS.NetworkAnalysis),例如电力网,供水网等。这种网络的特点是没有方向性,不会有方向限制。而AE所提供了另一种网络分析(ESRI.ArcGIS.NetworkAnalyst),这种分析方法针对于路网分析,在这种分析中,
你可以限制道路的左转限制,是否可以调头等设置。在路网分析中同样存在最优路径选取的内容。但内容相对复杂些,下图是AE所提供的路网分析的一个实例:
图 50网络分析示例
ESRI.ArcGIS.NetworkAnalyst 命名空间下提供了大量的方法针对路网数据,我们可以获得最优的路径,同时可以获得各个服务点的服务范围,可以翻译道路,可以推算路程的时间等。有兴趣的同学,在课后可以借助帮助和文档,进行深入的研究。
7 总结
至此,你已经完成了关于ArcEngine二次开发的全部入门学习。当然该文档的作用仅仅是带领你迈入ArcEngine的开发大门。想要开发出功能完善的GIS程序,作为程序员我们应当不断的深入了解。希望你有时间耐心查看这个文档,并逐步学习接口,和接口之间的调用接口。这份文档为初稿,当中不免有不足之处或错误之处,敬请您谅解。祝大家实习顺利!
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/tongxinshuyu/article-23094-1.html
炒作而已
我是小海浪