利用C#製作ArcMap插件

之前只是了解到Arcgis10.1版本後才只能利用python通過Add-in製作ArcMap的插件,是我了解的東西太少了,視野不夠開闊,得努力補上。前段時間在尋找畢業論文題目的時候,非常感謝知友 你們壇哥哥的分享,我才了解到原來Arcgis10.0之後就可以利用Add-in製作ArcMap插件,只不過10.0版本只能利用Java,VB,C#語言進行開發,10.1版本後才推出可以利用python製作插件。

無奈現在實習公司要求的Arcgis版本就是10.0,只能暫時通過C#先去了解Add-in相關的知識。剛好也在學Arcgis engine相關的東西,所以想把之前利用python做的點線拓撲腳本工具以插件的形式實現,看看兩者有什麼區別,接下來就讓我們一起了解C#環境下的Arcgis Add-in吧。

C#環境下開發插件,跟Arcgis engine二次開發差不多,兩者所用到的類庫,方法等都是一樣的,唯一不一樣的就是二次開發是基於VS環境進行調試,插件的的調試時基於ArcMap環境。

下面的實驗我是在Win10+Arcgis10.0+VS2010環境下實現的

創建項目

打開VS2010→新建項目→選擇Arcgis下拉菜單里的Desktop Add-Ins→選擇你插件的類型(如圖1)。我這裡是是基於ArcMap的插件,所以選擇對應的ArcMap Add-in,.net框架選擇3.5版本(其他版本貌似會報錯),設置好名稱路徑等信息後點擊【確定】按鈕完成一個插件項目的創建。進入到如圖2的界面。

圖1

歡迎頁面信息設置:插件名稱,製作公司,製作人,插件描述,圖片的選擇等,這些都不是重點,設置好了點擊【Next】按鈕進入插件類型的設置,如圖3所示。

圖2

插件的類型可以在頁面的左側進行選擇,有按鈕和工具,組合框等,英語不太好,我也沒完全看懂,也不是很熟悉,也是剛剛開始學習;詳細的內容可以參考Esri官網幫助文檔(英文版)有關Add-in的詳細描述,像我這種應用渣渣,只能看網友翻譯的中文版,也非常感謝網友的無私分享,讓我這些英語也有機會學習那麼高大上的東西。

設置好工具類型,類名,工具提示,描述等就可以完成插件的創建,進入到如圖4的界面。

圖3

這裡可以看到在解決方案資源管理窗口生成的我們剛剛設置的文件,其中Config.esriaddinx文件就是根據我們剛剛設置輸入的內容自動生成的代碼,這我們一般不用修改。接下來就是根據需求,進行代碼的書寫(這裡才是重點啊)。

圖4

創建好插件項目後的內容跟Arcgis engine二次開發的內容差不多了,只是在調試的時候有一點點區別而已。根據我們的功能,需要提供一個窗體,窗體的設置如下圖5:

圖5

功能的代碼實現

自動生成的 點線拓撲.cs文件里又兩個函數Onclick()和OnUpdate()需要我們重寫,因為我需要的功能效果是點擊ArcMap插件按鈕後彈出點線拓撲窗口,所以我這裡只需在Onclick()加入顯示窗體的代碼即可。OnUpdate()保持默認。

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; }

添加相關的引用:Arcgis很多功能都封裝在類庫裡面,用用到某個功能前要先引用對應的類庫。在解決方案窗口選擇【引用】右擊選擇添加引用功能,然後選擇相應的引用,如圖6所示。

圖6

點線拓撲窗體的功能設置

Combox:顯示圖層的名稱,當地圖窗口有圖層時,可以通過下拉功能選擇圖層,地圖窗口為空則需要通過打開按鈕導入數據;

打開文件按鈕:導入數據,名稱在combox中顯示,數據載入到地圖窗口中;

確定按鈕:完成數據的選擇後,進行數據的判斷,是否為空,數據的類型是否符合要求,然後再進行點線拓撲處理功能。

窗體的主要代碼如下:

public partial class Form1 : Form { IMapDocument pMxd = null; ILayer pPointLayer = null; ILayer PLineLayer = null; public Form1() { //獲得地圖窗口的地圖文檔 pMxd = ArcMap.Document as IMapDocument; InitializeComponent(); } //定義一個GetLayer()函數,通過導入圖層並載入到地圖窗口中 public ILayer GetLayer() { System.Windows.Forms.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.GetDirectoryName(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; } //定義一個函數fetLayerName()獲得地圖窗口中的圖層名稱 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; } private void Form1_Load(object sender, EventArgs e) { //窗體載入時,判斷當前地圖窗口是否為空,不為空則把圖層名稱顯示在Combox中 if (pMxd != null) { //調用getLayerName()函數獲得圖層名列表 List<string> name = getLayerName(pMxd); for (int i = 0; i < name.Count; i++) { comboBox1.Items.Add(name[i]); comboBox2.Items.Add(name[i]); } } } //導入點圖層 private void button1_Click(object sender, EventArgs e) { pPointLayer = GetLayer(); //調用GetLayer()獲得點要素 if (pPointLayer != null) { comboBox1.Items.Add(pPointLayer.Name.ToString()); comboBox1.SelectedIndex = 0; } } //導入線圖層 private void button2_Click(object sender, EventArgs e) { PLineLayer = GetLayer();//調用GetLayer()獲得線要素 if (PLineLayer != null) { comboBox2.Items.Add(PLineLayer.Name.ToString()); comboBox2.SelectedIndex = 0; } } //進行拓撲 private void button3_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 pFeaturecursor = pFeatureclass.Search(null, false); IFeature firstFeature = pFeaturecursor.NextFeature(); //獲得第一個線要素 IGeometry Newline = firstFeature.Shape; //以第一個線要素 為基礎進行線要素合併 IFeature pFeature = pFeaturecursor.NextFeature(); while (pFeature != null) { ITopologicalOperator pTopolog = Newline as ITopologicalOperator; Newline = pTopolog.Union(pFeature.Shape); //線要素合併 pFeature = pFeaturecursor.NextFeature(); } //判斷每一個點是否與合併後的線要素相交 //構建一個關係分析對象 IRelationalOperator pRelation = Newline as IRelationalOperator; //獲得點要素 IFeatureCursor pPfeaturecusor = ppFeatureclass.Search(null, false); IFeature pPFeature = pPfeaturecusor.NextFeature(); while (pPFeature != null) { IPoint pt = pPFeature.Shape as IPoint; if (pRelation.Disjoint(pPFeature.Shape)) { //不相交 IProximityOperator pProximityor = Newline as IProximityOperator; IPoint newpts = new PointClass(); IPoint newpt = pProximityor.ReturnNearestPoint(pt, esriSegmentExtension.esriNoExtension); //獲得最近點newpt //根據最近點把對應的點移動在線上 pPFeature.Shape = newpt; pPFeature.Store(); //移動後保存 } pPFeature = pPfeaturecusor.NextFeature(); } pMxd.ActiveView.Refresh(); MessageBox.Show("完成!"); } else { MessageBox.Show("請選擇正確的數據!"); } } }

調試

調試前要把項目的啟動操作選擇 啟動外部程序(Arcmap程序)項目名稱右鍵選擇屬性顯示屬性設置頁面,如圖7所示

圖7

接下來啟動調試的時候,程序會啟動Arcmap程序,然後就可以像其他C#項目那樣進行斷點調試等操作。但是我這次啟動調試的時候報錯:

錯誤 21 Failed to register Add-in 點線拓撲.esriAddIn.

去百度了一下才發現出現這個錯誤的原因可能是圖2,圖3裡面的相關設置出現了中文,所以設置的時候盡量不要出現中文吧。

按照網上的解決方案,我重新進行了設置(不出現中文),然後進行調試,然後VS就自動啟動了Arcmap程序,接下來我們就在Arcmap裡面載入剛剛創建的插件,如果沒錯的話就可以跟其他工具那樣進行運行,出錯了就如一般C#程序那樣調試即可,如下:

載入插件:自定義→自定義模式→打開自定義窗口(如圖9)→選擇【命令】→點擊Add-in Control→在右邊的窗口可以看到我們創建的插件 My Button→選擇 My Button拖放至Arcmap的工具欄(如圖10紅圈內)。

圖8

圖9

圖10

點擊圖10紅圈內的的工具,即可打開我們的插件,如圖11;當地圖窗口沒有圖層時,combox下拉顯示的內容為空的,單擊打開按鈕,即可導入數據,如圖12所示。

圖11

圖12

導入數據後就可以按確定按鈕就是點線拓撲處理(有沒有像Arcmap里的工具一樣方便???),如圖13,14所示

圖13

圖14

當地圖窗口有圖層時,可在combox里選擇對應的數據即可,如圖15所示。

圖15

如果選擇的數據的類型不符合要求就會報錯,如圖16所示

圖16

插件共享:打開解決方案的文件夾,我們可以看到程序生成的插件程序(如圖17 ArcMapAddin2.Esri AddIn File文件就是生成插件文件),直接複製這個程序發送給別人就可以共享,按照上面的插件載入方式載入到Arcmap就可以使用,不過要求Arcgis的版本要一致。

圖17

Arcgis10.0 點線拓撲工具下載地址:

總結:

C#通過Add-in製作插件,python自定義腳本工具這兩種方式實現同樣的功能,單單從代碼量來說,python更為簡單,所以10.1版本後python已經取代C#等語言,成為Arcgis插件開發的主要語言。不過如果你有AE基礎,而沒有時間去學python的話,利用C#+add-in開發也未嘗不可;C#之所以會要那麼多代碼,是因為我通過winform構造相應的控制項,但python里就已經直接封裝好一個函數GetParameterAsText(index),直接調用函數就可以實現很多功能。不過好像C#裡面,Esri也把常用的控制項封裝在ESRI.ArcGIS.Framework類庫裡面,因為才剛剛開始接觸到Add-in,所以還沒去了解ESRI.ArcGIS.Framework類庫裡面的東西,現在暫時就通過winform自己去實現相要的功能。

C#+Add-in進行Arcgis插件的開發,相對於AE 二次開發來說,會節省很多功夫,而且插件可以直接移植至Arcmap裡面直接使用,非常方便 。python腳本工具箱,python+add-in插件,C#+add-in插件,AE二次開發,四者都是殊途同歸一樣東西——Arcgis Object,有相通的地方,也各有千秋,最後還是根據項目的需求去考慮用哪種方式比較方便實用。

參考

Esri官方幫助文檔

博客:ArcGIS擴展開發(一)--為 ArcGIS Desktop建立Add-in插件

歡迎大家一起交流,一起學習,一起進步!

推薦閱讀:

中國地理專業好的大學有哪些?
地理信息系統的學生畢業之後做內業數據處理什麼的有沒有前途?還是二次開發有前途?
怎樣下載地圖用於GIS?
測繪這個行業到底應該往哪邊靠??
求推薦:想考地理信息系統的研有哪些相關的書看了會比較有幫助!!?

TAG:GIS地理信息系统 | ArcGIS | GIS软件工程师 |