ArcGIS Pro 二次開發

本文基於 Windows7 + VS2019 + .NET Framework 4.8 + ArcGIS Pro 2.5.22081 開發和撰寫。

開發環境配置

獲取ArcGIS Pro

ArcGIS Pro可在Esri官網申請21天試用。

安裝VS2019

VS2019的安裝十分簡單,在微軟官網下載VS2019社區版安裝程式,雙擊安裝即可,具體可參考該部落格

安裝ArcGIS Pro SDK

關閉VS拓展自動更新

首先打開安裝好的VS2019,點擊「菜單欄-工具-選項」,在選項中找到「環境-拓展」,關閉拓展自動更新,如下圖所示。關閉拓展自動更新可以防止拓展自動更新後與當前環境不匹配,如我的環境為ArcGIS Pro2.5,如果不關閉自動更新,則下次打開VS時ArcGIS Pro SDK插件將自動更新為2.6版本,與Pro版本不匹配,無法使用。

關閉VS拓展自動更新

正式安裝插件

接下來開始安裝ArcGIS Pro SDK for .NET,需要點擊「菜單欄-拓展-管理拓展」,在彈出的窗口中切換至聯機,搜索「ArcGIS Pro」,找到「ArcGIS Pro SDK for .NET」和「ArcGIS Pro SDK for .NET(Utilities)」兩個插件,安裝並禁用自動更新。點擊安裝後,重啟VS插件即安裝完畢,至此,開發環境配置完成。

需安裝VS插件

創建第一個Pro Add-in

使用模板創建Pro Add-in項目

打開VS2019,選擇「創建新項目」,將「項目類型篩選」設置為「ArcGIS Pro SDK」,找到「ArcGIS Pro 模組載入項」創建項目即可,注意選擇語言為C#而非VB。

使用模板創建新項目

添加一個button

Pro的插件及配置使用DAML文件,即項目下的「config.daml」進行聲明。

daml文件

手動添加button

添加button至顯示

首先,我們來嘗試手動添加一個button。打開「config.daml」文件,在controls標籤下添加一個button標籤。

<controls>
    <button id="AddOneButton" caption="Add one button" className="AddOneButton" loadOnClick="false" smallImage="Images\AddInDesktop16.png" largeImage="Images\AddInDesktop32.png" keytip="AOB">
        <tooltip>Add one button</tooltip>
    </button>
</controls> 

添加完button標籤後,該控制項並不會顯示,只有當控制項被某個group引用時,才會顯示在菜單中,應用方式如下,其中refID為創建button標籤時的id。

<groups>
    <group id="DJ_SuspectTrackingSystem_Group1" caption="Group 1" appearsOnAddInTab="true">
        <button refID="AddOneButton"/>
    </group>
</groups>

至此,當Pro載入時即會在「菜單欄-載入項-Group」中顯示該控制項。修改完成後的「config.daml」文件和Pro中顯示效果如下圖所示。

修改後daml文件
按鈕載入效果

為button添加邏輯程式碼

修改daml後,僅實現了在Pro中顯示button,下面來為button添加邏輯程式碼。

在項目中新建一個類,類名為剛才daml文件中對應button的className屬性,並使其繼承自Pro SDK中的Button類。然後重寫Button的相關方法,如點擊時觸發的OnClick方法,在其中實現邏輯程式碼即可,如下圖。

實現button邏輯程式碼

自動添加button

在我們熟悉了daml文件之後,日常開發即可直接使用VS提供的快捷添加控制項的方式。

在項目上「右擊-添加-新建項」,在彈出的窗口左側選擇「ArcGIS Pro Add-ins」中進行篩選後,選擇「ArcGIS Pro 按鈕」,點擊添加,VS即會自動添加一個button類至項目中,在daml文件的controls中添加button定義,並在默認group中引用該button。

添加button項

添加後daml

生成Addin文件

插件配置完成後,在解決方案上「右鍵-重新生成解決方案」,待解決方案生成完畢後,在解決方案文件夾\bin\Debug目錄下找到*.esriAddinX文件,該文件即為插件安裝文件,雙擊即可為Pro安裝該插件。

Addin安裝文件

調試插件

如果不希望直接為Pro安裝插件,而是在開發過程中需要測試插件效果,直接按快捷鍵F5或點擊運行,VS將啟動Pro,並在Pro中載入修改後的插件,可以在其中對插件進行測試。

開發小tip

  1. 當項目是從其他電腦拷貝而來時,引用地址可能不正確,此時,可以在解決方案上右擊,找到「修復 Pro 引用」選項,點擊後,插件會自動更新引用。若要手動更新引用,dll文件通常存放在Pro安裝路徑\binPro安裝路徑\bin\Extensions目錄下。
  2. 提示”未能解析主引用***,因為它是針對「.NETFramework,Version=v4.8」框架生成的。該框架版本高於當前目標框架”錯誤資訊,則說明生成使用的Framework版本與當前項目應該使用的Framework版本不匹配,在項目上「右擊-屬性-生成程式」,將「目標框架」切換為當前項目版本即可。
    切換框架版本
  3. 不知道什麼原因,對dockpane UI的修改,需要進行以下操作才能生效:
    1. Pro中卸載該載入項
    2. 重新打開Pro,發現載入項沒有再載入(必須重新再打開一次)
    3. 再解決方案上「右擊-重新生成解決方案」
    4. 再次打開Pro,修改即生效

DAML配置

DAML是Desktop Application Markup Language的縮寫。是ESRI基於XML標準自定義的UI配置文件。插件和配置的聲明性部分是在DAML文件中定義的,其中包含了框架元素(主要是插件)的集合,還包括聲明性部分(框架所需的資訊,以便在適當的時候激活或創建相關的對象)。通過這種方式實現介面和功能的分離。

DAML介紹

具體的DAML介紹請查看DAML ID reference。以下僅介紹常用節點。

根節點

即ArcGIS標籤下的節點,大多數情況下無需修改,使用默認即可,常用的有:

  1. defaultAssembly:該配置代表的插件所在的程式集名稱。
  2. defaultNamespace:該配置代表的插件所在的命名空間名稱。
    根節點

AddInInfo節點

AddInInfo節點用於聲明插件的相關資訊,如名稱、描述、圖標等

AddInInfo節點

modules節點

modules節點是最長訪問的節點,插件元素的添加、描述等均在該節點下。modules節點可以包括的元素包括ribbon按鈕、工具、畫廊、組合框、編輯框、調色板和其他控制項,以及應用程式窗格和對接窗格。

Pro 控制項級別

Pro的控制項級別從上到下分為Module-Tab-Group-Menu-Control,相互關係如下圖所示。具體關係控制項層級的概念請參考ProGuide

  1. TabTab
  2. GroupGroup
  3. MenuMenu
  4. ControlControl

一個原始的DAML文件如下圖所示,開發時,先在controls標籤下創建所有要使用的控制項及其描述,然後在需要顯示控制項的group標籤中引用對應的control即可。

原始DAML文件

將control在新tab中顯示

默認情況下,control將添加至「菜單欄-載入項」中,如果需要在單獨的Tab中顯示,則需要在daml文件tabs標籤下新建tab標籤,並在其中添加對需要顯示的group的引用,如下圖。

添加Tab

常用DAML元素

下面只介紹常用的元素及其屬性、方法等,各元素具體使用情景、屬性、方法等請參見官方API文檔。DAML中通常可以包括controls、categories、dockpanes、groups、menus、minitoolbars、panes、toolbars等等各種元素,具體元素可參見官方DAML ID Reference

控制項control

控制項常用屬性

控制項常用方法

控制項常見方法有:

  1. OnClick():單擊控制項時觸發

使用時,在控制項類中重寫對應的方法即可,如:

internal class AddOneButton : ArcGIS.Desktop.Framework.Contracts.Button
{
    protected override void OnClick()
        {
            MessageBox.Show("Hello World");
        }
}

常用控制項

  1. button
  2. comboBox
  3. checkBox
  4. labelControl
  5. tool

停靠窗dockpane

dockpane類為停靠窗口類,其UI由對應的.xaml文件確定,邏輯程式碼由對應的.cs類文件實現。關於DockPane,具體可以參見ProGuide-Dockpanes

雙擊xaml文件即可進入設計窗口,可以通過拖拽的方式將各種控制項添加至dockpane介面中,可以通過可視化或程式碼方式設置控制項和dockpane的各種屬性。

自定義DockPane停靠位置

使用dock,dockWith,autoHide屬性可以設置DockPane的初始停靠狀態。

  1. Dock:該屬性用於設置初始停靠狀態,可選值包括:
    1. left
    2. right
    3. top
    4. bottom
    5. float
    6. group
  2. dockWith:該屬性可以指定要相對停靠的DockPane。

地理處理GeoProcess

可參考ProSnippets-GeoprocessingProConcepts-Geoprocessing

功能實現

很多功能實現可參考ArcGIS Pro SDK community samples

在Addin中直接打開自建的工具箱

若想要實現,點擊插件上的按鈕,自動打開對應的自建工具箱或系統工具箱,需要重寫按鈕的OnClick()方法,在其中使用ArcGIS.Desktop.Core.Geoprocessing.OpenToolDialog(toolPath, param_values)方法打開工具箱,示例如下。若想要不打開工具箱而直接執行工具,則使用ArcGIS.Desktop.Core.Geoprocessing.ExecuteToolAsync(toolPath,param_values)方法非同步執行工具。

如果要使用系統工具箱,則直接用工具箱名\工具名調用即可,如 。如需使用自建工具箱,則使用工具箱路徑\工具箱名(包括後綴)\工具名調用,如@"E:\ArcGIS\MyProject\sixTool\sixTool.pyt\OpenHDFTool"

此外,不論是打開工具還是執行工具,都必須輸入參數,且創建參數時必須至少有一個輸入,否則雖然不會拋出語法錯誤,但是工具不能正常執行,原因未明。

internal class FactorsCorrelationAnalysis : Button
{
    protected override void OnClick()
    {
        //創建工具參數
        //工具必須有參數
        string input_points = "";
        var param_values = Geoprocessing.MakeValueArray(input_points); //創建參數時必須至少有一個輸入,否則工具不會正常顯示

        //獲取sixTools的絕對路徑
        string nowPath = System.Windows.Forms.Application.StartupPath; //獲取啟動程式的可執行文件所在的目錄
        string ArcGISPath = System.IO.Directory.GetParent(nowPath).FullName; //獲取當前目錄的父目錄的絕對路徑
        string toolRelativePath = @"Resources\ArcToolBox\Scripts\sixTool.pyt\FactorsCorrelationAnalysisTool";
        string toolPath = System.IO.Path.Combine(ArcGISPath, toolRelativePath);
        //MessageBox.Show(toolPath);

        //打開工具箱中的工具
        Geoprocessing.OpenToolDialog(toolPath, param_values);//必須輸入工具參數
    }
}

點擊彈出浮動窗口

若要實現點擊按鈕後彈出一個浮動窗口,如ArcGIS自帶的查詢工具,則在項目中添加一個Dockpane停靠窗類,並在「config.daml」文件中設置其「Dock」屬性為「float」即可。

使用Python腳本

Pro中使用Python腳本,本質上就是通過cmd的方式,調用Pro對應的python解釋器來執行腳本,並獲取腳本執行結果。詳細可參考官方sample CallScriptFromNet

//從環境變數中獲取Pro對應的Python解釋器的絕對路徑
var pathProExe = System.IO.Path.GetDirectoryName((new System.Uri(Assembly.GetEntryAssembly().CodeBase)).AbsolutePath);
if (pathProExe == null) return;
pathProExe = Uri.UnescapeDataString(pathProExe);
pathProExe = System.IO.Path.Combine(pathProExe, @"Python\envs\arcgispro-py3");
System.Diagnostics.Debug.WriteLine(pathProExe);
var pathPython = System.IO.Path.GetDirectoryName((new System.Uri(Assembly.GetExecutingAssembly().CodeBase)).AbsolutePath);
if (pathPython == null) return;
pathPython = Uri.UnescapeDataString(pathPython);
System.Diagnostics.Debug.WriteLine(pathPython);

//使用cmd調用python解釋器來執行python腳本
//創建用於執行cmd的字元串以及獲取異常資訊的對象
var myCommand = string.Format(@"/c """"{0}"" ""{1}""""",
    System.IO.Path.Combine(pathProExe, "python.exe"),
    System.IO.Path.Combine(pathPython, "test1.py"));
System.Diagnostics.Debug.WriteLine(myCommand);
var procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", myCommand);

procStartInfo.RedirectStandardOutput = true;
procStartInfo.RedirectStandardError = true;
procStartInfo.UseShellExecute = false;
procStartInfo.CreateNoWindow = true;

//開始執行腳本
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start();

//獲取執行結果並顯示
string result = proc.StandardOutput.ReadToEnd();
string error = proc.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(error)) result += string.Format("{0} Error: {1}", result, error);
System.Windows.MessageBox.Show(result);

創建要屬類

創建要屬類本質上是使用Toolbox中的創建要屬類工具(CreateFeatureclass_management)來創建所需的要屬類。詳細可參考官方sample ConstructingGeometries_CSharp

//根據輸入的要屬類名稱和類型在默認資料庫中創建要屬類的函數
private static async Task CreateLayer(string featureclassName, string featureclassType)
    {
      //創建要素時的參數list
      List<object> arguments = new List<object>
      {
        // store the results in the default geodatabase
        CoreModule.CurrentProject.DefaultGeodatabasePath,
        // name of the feature class
        featureclassName,
        // type of geometry
        featureclassType,
        // no template
        "",
        // no z values
        "DISABLED",
        // no m values
        "DISABLED"
      };
      //非同步,為參數list添加坐標系統參數
      await QueuedTask.Run(() =>
      {
              // spatial reference
              arguments.Add(SpatialReferenceBuilder.CreateSpatialReference(3857));
      });

      //非同步執行創建要屬類工具,創建要素類
      IGPResult result = await Geoprocessing.ExecuteToolAsync("CreateFeatureclass_management", Geoprocessing.MakeValueArray(arguments.ToArray()));
    }
  }

為線要素類添加記錄

執行的操作包括:

  1. 獲取需要編輯的要屬類
  2. 創建編輯器
  3. 創建一個MapPoint List,用於存儲構建線記錄的點
  4. 獲取點要素類游標,迭代點要素類的所有記錄,將構建線記錄的點對象添加至MapPoint List對象中
  5. 使用PolylineBuilder.CreatePolyline()方法,基於MapPoint List對象構建Polyline對象
  6. 使用編輯器對象的Create()方法,基於Polyline對象為線要屬類添加一條記錄
  7. 執行編輯器操作,保存修改
  8. 完成

具體可參考官方sample ConstructingGeometries_CSharp

//為線要素類中添加記錄的函數
//polylineLayer為需要添加記錄的線要素圖層;pointLayer為用於構建線要素的點圖層,也可以執行構建點要素
private Task<bool> ConstructSamplePolylines(FeatureLayer polylineLayer, FeatureLayer pointLayer)
{
    // ()=>{...}或()=>functionNmae() 為.NET3.0以後的新特性 Lambda表達式
    // 也就是說,執行=>後面的函數,把函數返回值作為一個參數傳給當前函數

    // execute the fine grained API calls on the CIM main thread
    return QueuedTask.Run(() =>
    {
        // get the underlying feature class for each layer
        // as 和is都是強制類型轉換運算符。作用類似於C中的  (type)data;  一般使用as,具體不深究
        // 此處未將 polylineLayer.GetTable() 強制轉換為 FeatureClass 類型後,賦值給 polylineFeatureClass 。
        var polylineFeatureClass = polylineLayer.GetTable() as FeatureClass;
        var pointFeatureClass = pointLayer.GetTable() as FeatureClass;

        // retrieve the feature class schema information for the feature classes
        var polylineDefinition = polylineFeatureClass.GetDefinition() as FeatureClassDefinition;
        var pointDefinition = pointFeatureClass.GetDefinition() as FeatureClassDefinition;

        // construct a cursor for all point features, since we want all feature there is no
        // QueryFilter required
        var pointCursor = pointFeatureClass.Search(null, false);
        var is3D = pointFeatureClass.GetDefinition().HasZ();

        // initialize a counter variable
        int pointCounter = 0;
        // initialize a list to hold 5 coordinates that are used as vertices for the polyline
        var lineMapPoints = new List<MapPoint>(5);

        // set up the edit operation for the feature creation
        var createOperation = new EditOperation()
        {
        Name = "Create polylines",
        SelectNewFeatures = false
        };

        // loop through the point features
        while (pointCursor.MoveNext())
        {
            pointCounter++;

            var pointFeature = pointCursor.Current as Feature;
            // add the feature point geometry as a coordinate into the vertex list of the line
            // - ensure that the projection of the point geometry is converted to match the spatial reference of the line
            lineMapPoints.Add(((MapPoint)GeometryEngine.Instance.Project(pointFeature.GetShape(), polylineDefinition.GetSpatialReference())));

            // for every 5 geometries, construct a new polyline and queue a feature create
            if (pointCounter % 5 == 0)
            {
                // construct a new polyline by using the 5 point coordinate in the current list
                var newPolyline = PolylineBuilder.CreatePolyline(lineMapPoints, polylineDefinition.GetSpatialReference());
                // queue the create operation as part of the edit operation
                createOperation.Create(polylineLayer, newPolyline);
                // reset the list of coordinates
                lineMapPoints = new List<MapPoint>(5);
            }
        }

        // execute the edit (create) operation
        return createOperation.ExecuteAsync();
    });
}

待續

ArcGIS Pro SDK 本來不在我的近期學習計劃中,只有由於朋友讓幫忙做一個小插件所以稍微學了一點,使用上面介紹的東西就搞定了。因此,本系列下一次更新可能遙遙無期……


本文參考:

  1. 部落格 //blog.csdn.net/xiangqiang2015/article/details/81741689
  2. 部落格 //blog.csdn.net/DynastyRumble/article/details/104683339
  3. Esri官方wiki //github.com/esri/arcgis-pro-sdk/wiki
  4. Pro API reference //pro.arcgis.com/en/pro-app/sdk/api-reference/index.html#topic10500.html
  5. ArcGIS Pro DAML ID Reference //github.com/Esri/arcgis-pro-sdk/wiki/ArcGIS-Pro-DAML-ID-Reference
  6. ArcGIS Pro Addin guide //github.com/Esri/arcgis-pro-sdk/wiki/ProGuide-Build-Your-First-Add-in
  7. ArcGIS Pro SDK 社區示例 //github.com/Esri/arcgis-pro-sdk-community-samples