最近在学习arcengine插件开发,经过不断尝试查找资料看说明文档终于让我成功做成了一个小插件。写个文档做个记录,方便后面查看。本文有参考利用C#制作ArcMap插件 - 知乎 (zhihu.com)
功能很简单,找出距离点要素最近的线要素的交点,并且把点移动到最近的交点上去。
插件的界面设计:
首先要在Button代码里设置响应函数,以便启动插件时能打开界面
protected override void OnClick()
{
//
// TODO: Sample code showing how to access button host
//
Form1 f1 = new Form1();
f1.ShowDialog();
ArcMap.Application.CurrentTool = null;
}
protected override void OnUpdate()
{
Enabled = ArcMap.Application != null;
}
接下来要想办法把地图里的图层加载到界面的下拉框里去,这里需要3个函数
//定义getlayername函数,获取已经加载的图层名称
public List<string> getLayerName(IMapDocument pMxdDocument)
{
List<string> layername = new List<string>();
IMap pMap = pMxdDocument.ActiveView.FocusMap;
for (int i = 0; i < pMap.LayerCount; i++)
{
ILayer pLayer = pMap.get_Layer(i);
layername.Add(pLayer.Name.ToString());
}
return layername;
}
//定义getlayeratname函数,通过图层名称获取图层
public ILayer getLayerAtname(string name)
{
ILayer pLayer = null;
IMap pMap = pMxd.ActiveView.FocusMap;
for (int i = 0; i < pMap.LayerCount; i++)
{
ILayer layer = pMap.get_Layer(i);
if (layer.Name.ToString() == name)
{
pLayer = layer;
}
}
return pLayer;
}
//定义一个getlayer函数,导入图层加载到地图窗口
public ILayer GetLayer()
{
OpenFileDialog openfiledialog = new OpenFileDialog();
openfiledialog.Filter = "shp(*.shp|*.shp";
openfiledialog.InitialDirectory = @"D:\";
openfiledialog.Multiselect = false;
ILayer pLayer = null;
if (openfiledialog.ShowDialog() == DialogResult.OK)
{
string pPath = openfiledialog.FileName;
string pFolder = System.IO.Path.GetFileName(pPath);
string pFileName = System.IO.Path.GetFileName(pPath);
IWorkspaceFactory pWorkspaceFactory = new ShapefileWorkspaceFactory();
IWorkspace pWorkspace = pWorkspaceFactory.OpenFromFile(pFolder, 0);
IFeatureWorkspace pFeatureWs = pWorkspace as IFeatureWorkspace;
//根据文件名打开要素
IFeatureClass pFC = pFeatureWs.OpenFeatureClass(pFileName);
IFeatureLayer pFlayer = new FeatureLayerClass();
pFlayer.FeatureClass = pFC;
pFlayer.Name = pFC.AliasName;
pLayer = pFlayer as ILayer;
//加载数据至地图窗口
pMxd.ActiveView.FocusMap.AddLayer(pLayer);
pMxd.ActiveView.Refresh();
}
return pLayer;
}
对于事先没有打开的shp文件,就需要第三个函数,在点图层和线图层的按钮响应函数中引用
//导入点图层
private void Openfile1_Click(object sender, EventArgs e)
{
pPointLayer = GetLayer();
if (pPointLayer != null)
{
comboBox1.Items.Add(pPointLayer.Name.ToString());
comboBox1.SelectedIndex = 0;
}
}
//导入线图层
private void Openfile2_Click(object sender, EventArgs e)
{
pLineLayer = GetLayer();
if(pLineLayer != null)
{
comboBox2.Items.Add(pLineLayer.Name.ToString());
comboBox2.SelectedIndex = 0;
}
}
双击界面中的“确定”按钮,OK_click作为功能实现的主体,大部分代码都集中在这里。调用getlayername函数获取图层名称后,再调用getlayeratname获取图层。
为了获取每条线之间的交点,我定义了两个ifeaturecursor,用于嵌套循环,这里要注意的是featurecursor在光标移动到最后之后并不会重置,所以需要在第一个循环内部定义第二个featurecursor以达到重置的效果。
private void OK_Click(object sender, EventArgs e)
{
//调用getlayeratname根据图层名称获取图层
pPointLayer = getLayerAtname(comboBox1.SelectedItem.ToString());
pLineLayer = getLayerAtname(comboBox2.SelectedItem.ToString());
if (pPointLayer == null || pLineLayer == null)
{
MessageBox.Show("请选择完整图层");
}
else
{
//获取线图层
IFeatureLayer pFeatureLayer = pLineLayer as IFeatureLayer;
IFeatureClass pFeatureclass = pFeatureLayer.FeatureClass;
IFeatureLayer ppfeatureLayer = pPointLayer as IFeatureLayer;
IFeatureClass ppFeatureclass = ppfeatureLayer.FeatureClass;
//判断数据类型是否符合要求
bool type1 = ppFeatureclass.ShapeType == esriGeometryType.esriGeometryMultipoint || ppFeatureclass.ShapeType == esriGeometryType.esriGeometryPoint;
bool type2 = pFeatureclass.ShapeType == esriGeometryType.esriGeometryPolyline || pFeatureclass.ShapeType == esriGeometryType.esriGeometryLine;
if (type1 && type2)
{
IFeatureCursor pfFeaturecursor = pFeatureclass.Search(null, false);
//获取第一个线要素
IFeature firstFeature = pfFeaturecursor.NextFeature();
//存放线交点
IPointCollection xPointCollection = new MultipointClass();
while (firstFeature != null)
{
IGeometry firstLine = firstFeature.Shape;
firstLine.SnapToSpatialReference();
ITopologicalOperator pTopo = firstLine as ITopologicalOperator;
pTopo.Simplify();
IFeatureCursor pFeaturecursor = pFeatureclass.Search(null, false);
//获取第二条线要素
IFeature lineFeature = pFeaturecursor.NextFeature();
lineFeature = pFeaturecursor.NextFeature();
while (lineFeature != null )
{
if (firstFeature.OID < lineFeature.OID)//防止重复计算交点
{
IRelationalOperator pRelation = firstLine as IRelationalOperator;
IGeometry lineGeo = lineFeature.Shape;
lineGeo.SnapToSpatialReference();
if (pRelation.Touches(lineGeo) || pRelation.Crosses(lineGeo))//交叉或者相接
{
//计算两条线的交点
IGeometry xpt = pTopo.Intersect(lineGeo, esriGeometryDimension.esriGeometry0Dimension);
求两个几何对象的重叠部分
两个几何对象的重叠部分,可以有很多种几何类型组合,例如面与面重叠是面,线与线重叠是线或者点,点与点重叠是点,点与面重叠是点,线与面重叠是线等等
参数2是返回结果是多少维的意思,根据经验如果返回结果是点就是0维(esriGeometry0Dimension),线就是1维,面就是2维
xpt.SpatialReference = lineGeo.SpatialReference;
IPointCollection pPointco = xpt as IPointCollection;
xPointCollection.AddPointCollection(pPointco);
}
}
lineFeature = pFeaturecursor.NextFeature();
}
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(pFeaturecursor);
firstFeature = pfFeaturecursor.NextFeature();
}
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(pfFeaturecursor);
//获取点要素
IFeatureCursor pPfeaturecursor = ppFeatureclass.Search(null, false);
IFeature pPFeature = pPfeaturecursor.NextFeature();
while (pPFeature != null)
{
IPoint pt = pPFeature.Shape as IPoint;
IProximityOperator pProximityor = xPointCollection as IProximityOperator;
//寻找线上最近点
IPoint newpt = pProximityor.ReturnNearestPoint(pt, esriSegmentExtension.esriNoExtension);
pPFeature.Shape = newpt;
pPFeature.Store();
pPFeature = pPfeaturecursor.NextFeature();
}
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(pPfeaturecursor);
pMxd.ActiveView.Refresh();
MessageBox.Show("完成!");
}
else
MessageBox.Show("请选择正确的数据!");
}
}
在最后判断线与线相交的时候,Touches和Crosses两种情况都要考虑到。因为当首尾相切的时候,程序并不会判定为cross,而是touch。
还有一种情况比较难处理,那就是有的线自身与自身相交,在这种情况下如何提取交点是比较麻烦的。
在程序编写完成后就可以把add-in插件加载到arcgis里尝试运行了,这里得到的最终效果图: