このシリーズでは「ArcGIS Pro SDK for .NET を使用した機能開発」のシリーズ記事として、ArcGIS Pro を拡張するためのアドイン開発時によく使う便利なクラスやメソッド、また、それらを用いた実践的な開発をご紹介します。本シリーズで実装するソースはすべて Github に格納してありますので、ぜひ ArcGIS Pro SDK for .NET(以下 Pro SDK)を使用する開発の参考にしてください。
本シリーズ第1回目となる今回は「マップとの対話的な操作により選択したフィーチャの属性表示を行う機能」を実装する方法をご紹介します。
・Pro SDK を使用してArcGIS Pro のアドイン開発を行っている方
・Pro SDK を使用したArcGIS Pro のアドイン開発をこれから行う、もしくは、検討している方
※「ArcGIS Pro SDK for .NET を使った独力での開発」でご紹介した Pro SDK のハンズオンで予習しておくと、本シリーズをよりスムーズに理解できると思います。
Tips:マップとの対話的な操作とは?
マップとの対話的な操作とは、マップをクリックしたり、ジオメトリをスケッチしたり、マップに対して何らかの操作をしてそこから結果を得ることです。以下動画でマップをクリックした地点の座標を表示させていますが、これはマップをクリックして(マップに対する操作)、そこの座標を取得する(結果を得る)というマップとの対話的な操作の一例です。
「マップ ツール」というアイテムを使用する必要があります。上記動画の「MapTool1」というボタンがそれにあたります。マップ ツールの実装方法については後述します。
マップ ツールで選択したフィーチャの属性をドッキング ウインドウに配置した DataGrid に表示させる機能を実装します(以下「完成イメージ」参照)。ドッキング ウインドウの実装方法については後述します。
※本記事ではドッキング ウインドウの「対話的操作」タブ内の一部機能の実装を行います(当該タブ内のその他の機能は次回の連載にてご紹介します)。他タブの機能も今後の連載でご紹介します。
Tips:なぜマップ ツールをドッキング ウインドウへ配置する必要があるのか?
Pro SDK で追加するアイテムはデフォルトで「アドイン」タブに配置されます。確かにそれでも問題ないこともあると思いますが、追加したアイテムをドッキング ウインドウに配置することにより、より操作性の高い機能を開発することができます。マップ ツールをドッキング ウインドウに配置(ドッキング ウインドウ右上の「選択」ボタン)することによって、見た目のバランスや操作の導線を考慮した開発をすることができるようになります。
1.マップ ツールの作成
2.マップ ツールをドッキング ウインドウへ配置
3.アクティブなマップ ビューに存在するレイヤーをコンボ ボックスに格納
4.マップ ツールを使用して選択したフィーチャの属性を DataGrid に表示
5.マップ ビューに対するイベント クラスの利用
本記事で使用する主な API カテゴリ
本記事では「マップ ビュー」、「ジオデータベース」を中心に使用します。
※API カテゴリに関しては「ArcGIS Pro SDK for .NET を使った独力での開発」をご参照ください。
本記事で使用するソースは Github に格納しています。ソースに関して実装のポイントとなる部分を抜粋して解説しますので、ダウンロードをお願いします。
ボタンなどのアイテムに対して、配置場所などを定義するための設定ファイル。プロジェクト作成時にデフォルトで作成されます。
2.IdentifyFeatures.cs
マップ ツール。作成手順に関しては「1.マップツールの作成」をご参照ください。
3.MainDockPane.xaml
ドッキング ウインドウ。作成手順に関しては「Pro SDK を使用した ArcGIS Pro の拡張④:アドインの開発」をご参照ください。
4.MainDockPaneViewModel.cs
ViewModel。ドッキング ウインドウ(MainDockPane.xaml)作成時に自動的に作成されます。
前述した通りですが、マップ ツールはマップに対して対話的な操作を行うためのアイテムです。マップをクリックしたり、ジオメトリをスケッチする際にイベントを発生させることができます。
1.1 マップ ツールをプロジェクトに追加
「プロジェクトを右クリック > 追加 > 新しい項目 > ArcGIS Pro マップ ツール」の手順でマップ ツールをプロジェクトに追加します(本記事ではIdentifyFeatures.cs と名前を付けて保存しています)。
1.2 IdentifyFeatures.cs(マップ ツール)に処理を追加
今回実装する処理ではフィーチャの選択はポリゴンで行うため、「SketchType」 プロパティに 「SketchGeometryType.Polygon」 を設定します。また、「OnSketchCompleteAsync」メソッド(当該ファイルにデフォルトで作成されているメソッド)の処理を以下のように拡張します。このように実装することで、マップ ツールでスケッチしたポリゴンに交差するフィーチャを選択することができます。
public IdentifyFeatures() { IsSketchTool = true; // ポリゴンをスケッチする SketchType = SketchGeometryType.Polygon; SketchOutputMode = SketchOutputMode.Map; } protected override Task<bool> OnSketchCompleteAsync(Geometry geometry) { return QueuedTask.Run(() => { // スケッチしたジオメトリに交差するフィーチャを選択する var mapView = MapView.Active; mapView.SelectFeatures(geometry); return true; }); }
これで「マップ ツールの作成」は完了です。次の手順で作成したマップ ツールをドッキング ウインドウに配置します。
「選択」ボタンを押した際にマップ ツールが起動するように実装します。
2.1 MainDockPane.xaml の設定
「選択」ボタンの 「Command」 属性を 「MainDockPaneViewModel.cs」 の 「SelectionTool」プロパティとバインドします。
<Button Grid.Column="2" Content="選択" Command="{Binding Path= SelectionTool}" Style="{DynamicResource Esri_Button}"></Button>
2.2 MainDockPaneViewModel.cs でイベントハンドラを登録
「選択」ボタンを押すと 「ExecuteSelectionTool()」メソッドが実行されるように実装します(マップツールが起動します)。当該メソッド内では 「"AddInSamples_IdentifyFeatures"」(マップ ツールのDAMLID)を引数として 「GetPlugInWrapper()」 メソッドが実行されます。このように 「GetPlugInWrapper()」 を使用することでマップ ツール と「選択」ボタンをバインドすることができるようになります。
protected MainDockPaneViewModel() { // 選択ボタンを押すとExecuteSelectionTool()が実行される _selectionTool = new RelayCommand(() => ExecuteSelectionTool(), () => true); } private RelayCommand _selectionTool; public ICommand SelectionTool => _selectionTool; internal static void ExecuteSelectionTool() { // 作成したマップ ツールのDAMLIDを指定 var cmd = FrameworkApplication.GetPlugInWrapper("AddInSamples_IdentifyFeatures") as ICommand; if (cmd.CanExecute(null)) // マップツール起動 cmd.Execute(null); }
2.3 Config.daml の設定
「アドイン」 タブにマップ ツールが表示されないように以下のようにマップ ツールの要素をコメントアウトします。
<group id="AddInSamples_Group1" caption="Group 1" appearsOnAddInTab="true"> <button refID="AddInSamples_MainDockPane_ShowButton" size="large" /> <!—-<tool refID="AddInSamples_IdentifyFeatures" size="large" />--> </group>
これで「マップツールをドッキング ウインドウへ配置」は完了です。
コンボ ボックスの選択肢がアクティブなマップ ビューに含まれているレイヤーを動的に表示するように実装します。
3.1 MainDockPane.xaml の設定
コンボ ボックスの 「Itemsource」 と 「SelectedItem」 属性を 「MainDockPaneViewModel.cs」 の「FeatureLayers」 と 「SelectedFeatureLayer」プロパティとバインドさせます。
<ComboBox Grid.Column="1" ItemsSource="{Binding FeatureLayers}" SelectedItem="{Binding SelectedFeatureLayer}"/>
3.2 コンボ ボックスとバインドするプロパティを作成
「MainDockPaneViewModel.cs」 に「FeatureLayers」と「SelectedFeatureLayer」プロパティ(「3.1」でバインドさせたプロパティ)を作成します。各々の役割は以下の通りです。
・FeatureLayers・・・アクティブなマップ ビューに存在するレイヤーを格納
・SelectedFeatureLayer・・・コンボ ボックスで選択しているレイヤーを格納
private ObservableCollection<BasicFeatureLayer> _featureLayers = new ObservableCollection<BasicFeatureLayer>(); private BasicFeatureLayer _selectedFeatureLayer; public ObservableCollection<BasicFeatureLayer> FeatureLayers { get { return _featureLayers; } set { SetProperty(ref _featureLayers, value, () => FeatureLayers); } } public BasicFeatureLayer SelectedFeatureLayer { get { return _selectedFeatureLayer; } set { SetProperty(ref _selectedFeatureLayer, value, () => SelectedFeatureLayer); } }
3.3 GetLayers() メソッドの実装
「3.2」で作成した「FeatureLayers」プロパティにアクティブなマップ ビューに存在するレイヤーを格納します。
private void GetLayers() { // アクティブなマップビューを取得 var mapView = MapView.Active; if (mapView == null) return; // コンボ ボックスに格納されているレイヤーをクリア FeatureLayers.Clear(); // コンボ ボックスにレイヤーを格納 foreach (var featureLayer in mapView.Map.Layers.OfType<BasicFeatureLayer>()) { FeatureLayers.Add(featureLayer); } }
これで「アクティブなマップ ビューに存在するレイヤーをコンボ ボックスに格納」は完了です。
マップ ツールを使用して選択したフィーチャの属性を表示する DataGrid を実装します。
4.1 MainDockPane.xaml の設定
DataGrid の「ItemsSourse」 属性と 「MainDockPaneViewModel.cs」 の 「SelectedFeatureDataTable」 プロパティをバインドします。
<DataGrid ItemsSource="{Binding Path=SelectedFeatureDataTable, Mode=OneWay}" Grid.ColumnSpan="2"> </DataGrid>
4.2 DataGrid とバインドするプロパティを作成
「MainDockPaneViewModel.cs」 に「SelectedFeatureDataTable」プロパティを作成します。
private DataTable _selectedFeatureDataTable; public DataTable SelectedFeatureDataTable { get { return _selectedFeatureDataTable; } set { SetProperty(ref _selectedFeatureDataTable, value, () => SelectedFeatureDataTable); } }
これで「マップ ツールを使用して選択したフィーチャの属性を DataGrid に表示」は完了です。
マップ ツールの他にマップ ビューに対するイベント クラスを使用すると、より高度なマップとの対話的な操作ができるようになります。今回使用するイベント クラスは以下の四つです。
マップ内の選択状態が変わった場合に発生
アクティブなマップを切り替えた場合に発生
マップにレイヤーを追加した場合に発生
マップからレイヤーを削除した場合に発生
5.1 イベントの登録
「MainDockPaneViewModel.cs」 のコンストラクタに以下三つのイベントを登録します。
protected MainDockPaneViewModel() { // マップツール _selectionTool = new RelayCommand(() => ExecuteSelectionTool(), () => true); // イベントの登録 ActiveMapViewChangedEvent.Subscribe(OnActiveMapViewChanged); LayersAddedEvent.Subscribe(OnLayerAdded); LayersRemovedEvent.Subscribe(OnLayerRemoved); }
もう一つのイベント「MapSelectionChangedEvent」を「OnShow」メソッド内で登録します。
「MainDockPaneViewModel.cs」が継承している「DockPane」抽象クラスの「OnShow」メソッドをオーバーライドすることでドッキング ウインドウが表示/非表示されるタイミングでイベントの登録/解除を行うことができるようになります。今回はドッキング ウインドウが非表示の場合は MapSelectionChangedEvent を解除するように実装します。
private SubscriptionToken _mapSelectionChangedEvent = null; protected override void OnShow(bool isVisible) { if (isVisible && _mapSelectionChangedEvent == null) { // イベントの登録 _mapSelectionChangedEvent = MapSelectionChangedEvent.Subscribe(OnMapSelectionChanged); } if (!isVisible && _mapSelectionChangedEvent != null) { // イベントの解除 MapSelectionChangedEvent.Unsubscribe(_mapSelectionChangedEvent); _mapSelectionChangedEvent = null; } }
5.2 イベントハンドラの実装①
以下のイベントハンドラを実装します。
・OnLayerAdded()
マップにレイヤーを追加した時に実行されます。追加したレイヤーをコンボ ボックスに格納します。
・OnLayerRemoved()
マップからレイヤーを削除した時に実行されます。削除したレイヤーをコンボ ボックスから削除します。また、DataGrid をクリアして選択しているフィーチャを解除します。
・OnActiveMapViewChanged()
アクティブなマップビューが変わった時に実行されます。「3.3」で実装した「GetLayers()」メソッドを実行することでコンボ ボックスにアクティブなマップ ビューに存在するレイヤーを格納します。
private void OnLayerAdded(LayerEventsArgs args) { // 追加したレイヤーをコンボボックスに追加 foreach (var featureLayer in args.Layers.OfType<BasicFeatureLayer>().ToList()) { FeatureLayers.Add(featureLayer); } } private void OnLayerRemoved(LayerEventsArgs args) { // 削除したレイヤーをコンボボックスから削除 foreach (var featureLayer in args.Layers.OfType<BasicFeatureLayer>().ToList()) { if (_featureLayers.Contains(featureLayer)) { FeatureLayers.Remove(featureLayer); } } // DataGridをクリアして選択しているフィーチャを解除 Clear(); } private void OnActiveMapViewChanged(ActiveMapViewChangedEventArgs args) { // レイヤーを取得 GetLayers(); // DataGridをクリアして選択しているフィーチャを解除 Clear(); }
5.3 イベントハンドラの実装②
以下のイベントハンドラを実装します。
・OnMapSelectionChanged()
マップ ツールを使用して選択したフィーチャのフィールド情報と属性を取得し、それらを DataGrid に格納します。
private void OnMapSelectionChanged(MapSelectionChangedEventArgs args) { QueuedTask.Run(() => { // レイヤーを選択していない場合 if (_selectedFeatureLayer == null) return; var listColumnNames = new List<KeyValuePair<string, string>>(); var listValues = new List<List<string>>(); // 選択したフィーチャを処理する using (var rowCursor = _selectedFeatureLayer.GetSelection().Search(null)) { bool bDefineColumns = true; while (rowCursor.MoveNext()) { var anyRow = rowCursor.Current; if (bDefineColumns) { // 選択したフィーチャのフィールドを取得 foreach (var fld in anyRow.GetFields().Where(fld => fld.FieldType != FieldType.Geometry)) { listColumnNames.Add(new KeyValuePair<string, string>(fld.Name, fld.AliasName)); } } // 選択したフィーチャの属性を取得 var newRow = new List<string>(); foreach (var fld in anyRow.GetFields().Where(fld => fld.FieldType != FieldType.Geometry)) { newRow.Add((anyRow[fld.Name] == null) ? string.Empty : anyRow[fld.Name].ToString()); } listValues.Add(newRow); bDefineColumns = false; } } // DataGridにカラムを設定 SelectedFeatureDataTable = new DataTable(); foreach (var col in listColumnNames) { SelectedFeatureDataTable.Columns.Add(new DataColumn(col.Key, typeof(string)) { Caption = col.Value }); } // DataGridに選択したフィーチャの属性を格納 foreach (var row in listValues) { var newRow = SelectedFeatureDataTable.NewRow(); newRow.ItemArray = row.ToArray(); SelectedFeatureDataTable.Rows.Add(newRow); } if (_selectedFeatureDataTable.Rows.Count > 0) { // ズーム ZoomToSelection(); } NotifyPropertyChanged(() => SelectedFeatureDataTable); }); }
5.4 SelectedFeatureLayer に処理を追加
「3.2」で実装した「SelectedFeatureLayer」のセッターに「OnMapSelectionChanged(null);」 を追加します。これによってフィーチャ選択後(複数のフィーチャクラスのフィーチャを選択)にコンボ ボックスのレイヤーを変更し、変更後のレイヤーのフィーチャが選択されている場合、その属性が DataGrid に表示されるようになります(以下動画参照)。
public BasicFeatureLayer SelectedFeatureLayer { get { return _selectedFeatureLayer; } set { SetProperty(ref _selectedFeatureLayer, value, () => SelectedFeatureLayer); OnMapSelectionChanged(null); } }
これで「マップ ビューに対するイベント クラスの利用」は完了です。
このようにマップ ツールとイベント クラスを利用することによって、ArcGIS Pro 単独では実現できない高度なマップとの対話的な操作を実現することができるようになります。マップ ツールにはその他にも様々なメソッドが用意されています。また、その他様々なイベント クラスも用意されていますので、Pro SDK を使用して開発を行う際はぜひ使ってみてください。
次回は「マップとの対話的な操作その2」です。本記事でご紹介した機能を拡張します。