このシリーズでは「ArcGIS Pro SDK for .NET を使用した機能開発」のシリーズ記事として、ArcGIS Proを拡張するためのアドイン開発時によく使う便利なクラスやメソッド、また、それらを用いた実践的な開発をご紹介しています。本シリーズで実装しているソースはすべて GitHub に格納してありますので、ArcGIS Pro SDK for .NET(以下 Pro SDK)を使用する開発の参考にしてください。
本シリーズのこれまでの記事では、以下のような機能の実装方法をご紹介しました。
第 1 弾: マップとの対話的な操作により選択したフィーチャの属性表示を行う機能
第 2 弾: フィーチャの強調表示、フィーチャへのズーム機能
第 3 弾: レイヤーのレンダラーの設定
第 4 弾となる今回は、アノテーションを操作する機能の実装方法をご紹介します。
・Pro SDK を使用してArcGIS Pro のアドイン開発を行っている方
・Pro SDK を使用したArcGIS Pro のアドイン開発をこれから行う、もしくは、検討している方
※「ArcGIS Pro SDK for .NET を使った独力での開発」でご紹介した Pro SDK のハンズオンで予習しておくと、本シリーズをよりスムーズに理解できると思います。
GIS の成果物である「地図」において、最も重要な要素の一つは地物の名称などが見やすく記載されているかどうかです。ArcGIS Pro では Maplex ラベル エンジンを使用することで高度なラベル制御が可能ですが、地図調製の現場ではアノテーションを作成して更に細かな制御が必要な場合が多いでしょう。
アノテーションの操作に係る機能をカスタマイズすることで、こうした細かな注記表現の制御の作業効率を向上させることが期待できます。
本記事でご紹介するサンプルでは、選択したアノテーションをコピーしたり回転したりする機能を Pro SDK を使って拡張します。
※本記事ではドッキング ウインドウの「アノテーション操作」タブの機能を実装します。「ジオメトリ変換」タブの機能は今後の連載でご紹介します。
本記事で使用する主な API カテゴリ
本記事では「編集」、「ジオデータベース」、「ジオメトリ」を中心に使用します。
※API カテゴリに関しては「ArcGIS Pro SDK for .NET を使った独力での開発」をご参照ください。
本記事で使用するソースは GitHub に格納しています。ソースに関して実装のポイントとなる部分を抜粋して解説しますので、ダウンロードをお願いします。
この手順では、選択されたアノテーション フィーチャの角度を取得し、ドッキング ウインドウ内のテキストボックス内に取得した角度を表示するように機能を拡張します。
1.1 MainDockPane.xaml の設定
「アノテーション操作」タブ アイテムの「TextBox」の「Text」属性を「MainDockPaneViewModel.cs」の「Angle」プロパティにバインドさせます。
<TextBox x:Name="angle_textBox" Grid.Row="0" Grid.Column="1" Margin="5,5,5,5" TextAlignment ="Right" MaxLength="4" Text="{Binding Path = Angle, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
また、「選択」ボタンの「Command」属性を「MainDockPaneViewModel.cs」の「AnnotationAngle」プロパティにバインドさせます。
<Button Content="選択" Grid.Column="3" HorizontalAlignment="Stretch" Command="{Binding Path=AnnotationAngle}"Style="{DynamicResource Esri_Button}"></Button>
1.2 テキスト ボックスとバインドするプロパティを作成
「MainDockPaneViewModel.cs」に Angle プロパティを作成します。次の手順で実装しますが、アノテーション フィーチャを選択した際に取得した角度を格納します。
private double _angle; public double Angle { get { return _angle; } set { SetProperty(ref _angle, value, () => Angle); } }
1.3 MainDockPaneViewModel.cs でイベント ハンドラーを登録
「選択」ボタンを押すと 「ExexuteAnnotationAngle()」メソッドが実行されるように実装します。第一回目の手順2.2で紹介したように「GetPlugInWrapper()」を使用し、マップ ツール と「選択」ボタンをバインドしています。
// アノテーション タブの選択ボタンを押すと ExexuteAnnotationAngle() が実行される _annotationAngle = new RelayCommand(() => ExexuteAnnotationAngle(), () => true); /// <summary> /// アノテーション操作タブの選択ボタンをクリック /// </summary> private ICommand _annotationAngle; public ICommand AnnotationAngle => _annotationAngle; public void ExexuteAnnotationAngle() { var cmd = FrameworkApplication.GetPlugInWrapper("AddInSamples_IdentifyFeatures") as ICommand; if (cmd.CanExecute(null)) cmd.Execute(null); }
1.4 イベント ハンドラーの実装
第一回目の手順 5.3 で実装した OnMapSelectionChanged() を拡張し、「アノテーション操作」タブの「選択」ボタンを使用した場合に、アノテーション フィーチャの角度を取得するように if 文で制御します。前回までの記事で既に実装済みの部分は else で処理します。
/// <summary> /// マップ内の選択状態が変わった場合に発生 /// </summary> private void OnMapSelectionChanged(MapSelectionChangedEventArgs args) { var mapView = MapView.Active; if (mapView == null) return; RemoveFromMapOverlay(); // アノテーション操作タブの場合 if (_tabPage == 2) { QueuedTask.Run(() => { var selection = mapView.Map.GetSelection(); // 選択しているフィーチャクラスがある場合 if (selection.Count > 0) { var featureLayer = selection.FirstOrDefault().Key as BasicFeatureLayer; // アノテーションの場合 if (featureLayer.GetType().Name == "AnnotationLayer") { var table = featureLayer.GetTable(); var feature = featureLayer.GetSelection(); // OBJECTIDを指定 QueryFilter queryFilter = new QueryFilter { WhereClause = "ObjectId =" + feature.GetObjectIDs().First(), }; // 複数選択した場合は最初に取得したアノテーションの角度を使用する using (RowCursor rowCursor = table.Search(queryFilter)) { rowCursor.MoveNext(); using (Row row = rowCursor.Current) { // 角度取得 Angle = Convert.ToDouble(row["Angle"]); } } } } }); } else { (後略)
この手順では、選択したアノテーション フィーチャの角度を任意の角度に回転する機能を実装します。
2.1 MainDockPane.xaml の設定
「回転」ボタンの「Command」属性を「MainDockPaneViewModel.cs」の「RotateAnnotation」プロパティにバインドさせます。
<Button Content="回転" Grid.Column="4" HorizontalAlignment="Stretch" Command="{Binding Path=RotateAnnotation}" Style="{DynamicResource Esri_Button}"></Button>
2.2 MainDockPaneViewModel.cs でイベント ハンドラーを登録
「実行」ボタンを押すと「ExecuteRenderingClick()」メソッドが実行されるように実装します。
// アノテーション タブの回転ボタンを押すと ExecuteRotateAnnotation() が実行される _rotateAnnotation = new RelayCommand(() => ExecuteRotateAnnotation(), () => true);
2.3 イベント ハンドラーの実装
選択したフィーチャをループ処理し、アノテーション フィーチャであった場合、テキスト ボックスに入力されている値を使用して角度を更新します。フィーチャの属性情報等にアクセスする際は Inspector クラスを使用します。EditOperation クラスの Modify メソッドを使用することでジオメトリや属性情報を更新し、Execute メソッドで実行します。EditOperation クラスは編集する際に必ず使用するクラスです。主要な機能として以下の3つがあります。
詳細については Editing のコンセプト (英語)をご参照下さい。
/// <summary> /// アノテーション操作タブの回転ボタンをクリック /// </summary> // アノテーションの回転 private ICommand _rotateAnnotation; public ICommand RotateAnnotation => _rotateAnnotation; private void ExecuteRotateAnnotation() { var mapView = MapView.Active; if (mapView == null) return; try { QueuedTask.Run(() => { var selection = mapView.Map.GetSelection(); // 選択しているフィーチャクラスがある場合 if (selection.Count > 0) { var editOperation = new EditOperation(); // フィーチャクラスごとにループ foreach (var mapMember in selection) { var layer = mapMember.Key as BasicFeatureLayer; // アノテーションの場合 if (layer.GetType().Name == "AnnotationLayer") { using (var rowCursor = layer.GetSelection().Search(null)) { var inspector = new Inspector(); while (rowCursor.MoveNext()) { using (var row = rowCursor.Current) { // 角度更新 inspector.Load(layer, row.GetObjectID()); var annoProperties = inspector.GetAnnotationProperties(); annoProperties.Angle = _angle; inspector.SetAnnotationProperties(annoProperties); editOperation.Modify(inspector); } } } } } editOperation.Execute(); } }); } catch { MessageBox.Show("アノテーションの角度変更に失敗しました。"); } }
この手順では、選択したアノテーション フィーチャをコピーする機能を実装します。
3.3 MainDockPane.xaml の設定
「コピー」ボタンの「Command」属性を「MainDockPaneViewModel.cs」の「RotateAnnotation」プロパティにバインドさせます。
<Button Content="コピー" Grid.Column="5" HorizontalAlignment="Stretch" Command="{Binding Path=CopyAnnotation}" Style="{DynamicResource Esri_Button}"></Button>
3.2 MainDockPaneViewModel.cs でイベント ハンドラーを登録
「実行」ボタンを押すと「ExecuteRenderingClick()」メソッドが実行されるように実装します。
// アノテーション タブのコピーボタンを押すと ExecuteCopyAnnotation() が実行される _copyAnnotation = new RelayCommand(() => ExecuteCopyAnnotation(), () => true);
3.3 イベント ハンドラーの実装
選択したフィーチャをループ処理し、アノテーション フィーチャであった場合に処理を実行する点は上述した 2.3 の処理と同じです。
コピーする処理は下に記載するメソッドを別に作成しています。大まかな処理の流れは以下の通りです。
private void InsertAnnotation(AnnotationFeature annofeat) { var selectedFeatures = MapView.Active.Map.GetSelection().Where(kvp => kvp.Key is AnnotationLayer).ToDictionary(kvp => (AnnotationLayer)kvp.Key, kvp => kvp.Value); var layer = selectedFeatures.Keys.FirstOrDefault(); // コピーするアノテーション用に行を作成 AnnotationFeatureClass annotationFeatureClass = layer.GetFeatureClass() as AnnotationFeatureClass; RowBuffer rowBuffer = annotationFeatureClass.CreateRowBuffer(); Feature feature = annotationFeatureClass.CreateRow(rowBuffer) as Feature; // コピーするアノテーションを作成 AnnotationFeature copyAnnoFeat = feature as AnnotationFeature; copyAnnoFeat.SetStatus(AnnotationStatus.Placed); copyAnnoFeat.SetAnnotationClassID(0); // コピー元のアノテーションの重心にポイントを作成 Envelope shape = annofeat.GetShape().Extent; var x = shape.Center.X; var y = shape.Center.Y; var mapPointBuilder = new MapPointBuilder(layer.GetSpatialReference()); mapPointBuilder.X = x; mapPointBuilder.Y = y; MapPoint mapPoint = mapPointBuilder.ToGeometry(); // コピー元のアノテーションのテキストを作成 var annoGraphich = annofeat.GetGraphic() as CIMTextGraphic; CIMTextGraphic cimTextGraphic = new CIMTextGraphic(); // 作成したポイントとアノテーションをコピー先のアノテーションにコピー cimTextGraphic.Text = annoGraphich.Text; cimTextGraphic.Shape = mapPoint; // シンボル設定 var symbolRef = new CIMSymbolReference(); symbolRef.SymbolName = annoGraphich.Symbol.SymbolName; symbolRef.Symbol = annoGraphich.Symbol.Symbol; cimTextGraphic.Symbol = symbolRef; // コピー copyAnnoFeat.SetGraphic(cimTextGraphic); copyAnnoFeat.Store(); }
アノテーションを操作する際に使用されている CIMTextGraphic はアノテーションのテキストやフォーマットに係る属性を含んでいます。詳細は Annotation のコンセプト (英語) 及び Editing Annotation のコンセプト (英語) をご参照ください。
今回実装したような CIMTextGraphic を操作する仕組みを流用することで、アノテーションのテキストを動的に変更することや、任意のフォント設定でアノテーションを作成・更新することも可能になるでしょう。
本記事ではアノテーション フィーチャの角度を取得し、回転、コピーする処理を実装、解説しました。これらの操作はカスタマイズしなくとも可能ですが、Pro SDK を活用することでより効率的に実行できるようになります。細かなアノテーション操作が必要な地図調製などの業務向けには、本記事でご紹介した内容をご参考にしていただき、利用頻度の高い操作等を効率的に行えるようカスタマイズしていただければと思います。
次回はジオメトリ操作タブ内に機能を実装していきます。