ProGuide Annotation Editing Tools - Esri/arcgis-pro-sdk GitHub Wiki
Language: C#
Subject: Editing
Contributor: ArcGIS Pro SDK Team <[email protected]>
Organization: Esri, http://www.esri.com
Date: 10/06/2024
ArcGIS Pro: 3.4
Visual Studio: 2022
This ProGuide demonstrates how to build tools for modifying annotation features. Two tools will be discussed; the first map tool illustrates how to modify the baseline geometry of an annotation feature. (The baseline geometry of an annotation feature is the geometry which the annotation text sits on). The second map tool illustrates how to modify text attributes of an annotation feature.
The code used in this ProGuide can be found at Anno Tools Sample.
The accompanying ProGuide Annotation Construction Tools demonstrates how to build tools for creating annotation features.
Important please consult ProConcepts Editing Annotation which covers special considerations for authors of editing tools for annotation and ProConcepts Annotation for general reference information on Annotation.
Prerequisites
- Download and install the sample data required for this guide as instructed in ArcGIS Pro SDK Community Samples Releases.
- If you previously completed the ProGuide Annotation Construction Tools open the AnnoTools project in Visual Studio. Otherwise create a new ArcGIS Pro Module Add-in, and name the project AnnoTools. If you are not familiar with the ArcGIS Pro SDK, you can follow the steps in the ProGuide Build your first add-in to get started.
Step 1
This map tool demonstrates how to modify the baseline geometry of an annotation feature. Remember that the shape of an annotation feature is always a polygon; the bounding box of the annotation text; and is maintained by the application software. It is the baseline geometry of the annotation text that can be modified by developers. Access this baseline geometry and other text formatting properties using the AnnotationProperties class from an Inspector object.
Add a new ArcGIS Pro Add-ins | ArcGIS Pro Map Tool item to the add-in project, and name the item AnnoModifyGeometry. Modify the Config.daml file tool item as follows:
- Change the caption to "Modify Geometry"
- Change the tool heading to "Modify Anno Geometry" and the ToolTip text to "Click and drag over annotation features to modify their geometry."
<tool id="AnnoTools_AnnoModifyGeometry" caption="Modify Geometry"
className="AnnoModifyGeometry"
loadOnClick="true" condition="esri_mapping_mapPane"
smallImage="Images\GenericButtonRed16.png" largeImage="Images\GenericButtonRed32.png" >
<tooltip heading="Modify Anno Geometry">Click and drag over annotation features to modify their geometry.<disabledText /></tooltip>
</tool>
Step 2
Open the AnnoModifyGeometry.cs file and review the code. The constructor sets up the map tool to sketch a rectangle on the map. There are also two methods stubbed out by default; OnToolActivateAsync and OnSketchCompleteAsync. We will be modifying the OnSketchCompleteAsync method to retrieve the annotation features that intersect the sketch geometry, obtain their baseline geometry, rotate those geometries 90 degrees and modify the features with the rotated geometries.
First, retrieve the annotation features intersecting the sketch using the MapView.GetFeatures method. Refine the features returned to be only features from annotation layers using the AnnotationLayer class. Iterate through the remaining features. The following code accomplishes this.
// execute on the MCT
return QueuedTask.Run(() =>
{
// find features under the sketch
var features = MapView.Active.GetFeatures(geometry);
if (features.Count == 0)
return false;
EditOperation op = null;
// for each set of features that are of type Annotationlayer
foreach (var annofeatures in features.ToDictionary<AnnotationLayer>())
{
// are there features?
var annoLayer = annofeatures.Key;
var featOids = annofeatures.Value;
if (featOids.Count == 0)
continue;
foreach (var oid in featOids)
{
...
}
}
});
Step 3
Now the results have been restricted to annotation layers, the individual annotation feature can be loaded into an Inspector object and the annotation properties retrieved using the Inspector.GetAnnotationProperties method. Use the Shape property to retrieve the CIMTextGraphic geometry. Review the following code which achieves this. Copy it to the interior of the for loop
.
// load the inspector with the feature
var insp = new Inspector();
insp.Load(annoLayer, oid);
// get the annotation properties
var annoProperties = insp.GetAnnotationProperties();
// get the cimTextGraphic geometry
Geometry textGraphicGeometry = annoProperties.Shape;
Step 4
If the geometry retrieved is a polyline, rotate it 90 degrees around it's centroid using the GeometryEngine.Instance.Centroid and GeometryEngine.Instance.Rotate methods.
// if cimTextGraphic geometry is not a polyline, ignore
Polyline baseLine = textGraphicGeometry as Polyline;
if (baseLine == null)
continue;
// rotate the baseline 90 degrees
var origin = GeometryEngine.Instance.Centroid(baseLine);
Geometry rotatedBaseline = GeometryEngine.Instance.Rotate(baseLine, origin, System.Math.PI / 2);
Step 5
Next assign the rotated geometry back to the annotation properties, set the updated properties back on the Inspector object using Inspector.SetAnnotationProperties, create the edit operation and call the Modify method.
// set the updated geometry back to the annotation properties
annoProperties.Shape = rotatedBaseline;
// assign the annotation properties back to the inspector
insp.SetAnnotationProperties(annoProperties);
// create the edit operation
if (op == null)
{
op = new EditOperation();
op.Name = "Update annotation baseline";
op.SelectModifiedFeatures = true;
op.SelectNewFeatures = false;
}
op.Modify(insp);
As an alternative, you could pass the updated shape directly to the EditOperation.Modify method. In the same way that calling Modify for a normal point, line or polygon feature updates it's shape, calling Modify for an annotation feature and passing the rotated geometry will cause the CIMTextGraphic geometry of the feature to be updated.
op.Modify(annoLayer, oid, rotatedBaseline);
Finally after iterating through all the features returned from MapView.Active.GetFeatures
execute the edit operation.
// execute the operation
if ((op != null) && !op.IsEmpty)
return op.Execute();
return
false;
The entire OnSketchCompleteAsync method should look like the following
protected override Task<bool> OnSketchCompleteAsync(Geometry geometry)
{
// execute on the MCT
return QueuedTask.Run(() =>
{
// find features under the sketch
var features = MapView.Active.GetFeatures(geometry);
if (features.Count == 0)
return false;
EditOperation op = null;
// for each set of features that are of type Annotationlayer
foreach (var annofeatures in features.ToDictionary<AnnotationLayer>())
{
// are there features?
var annoLayer = annofeatures.Key;
var featOids = annofeatures.Value;
if (featOids.Count == 0)
continue;
// for each feature
foreach (var oid in featOids)
{
// Remember - the shape of an annotation feature is a polygon - the bounding box of the annotation text.
// We need to update the cimTextGraphic geometry.
// load the inspector with the feature
var insp = new Inspector();
insp.Load(annoLayer, oid);
// get the annotation properties
var annoProperties = insp.GetAnnotationProperties();
// get the cimTextGraphic geometry
Geometry textGraphicGeometry = annoProperties.Shape;
// if cimTextGraphic geometry is not a polyline, ignore
Polyline baseLine = textGraphicGeometry as Polyline;
if (baseLine == null)
continue;
// rotate the baseline 90 degrees
var origin = GeometryEngine.Instance.Centroid(baseLine);
Geometry rotatedBaseline = GeometryEngine.Instance.Rotate(baseLine, origin, System.Math.PI / 2);
// set the updated geometry back to the annotation properties
annoProperties.Shape = rotatedBaseline;
// assign the annotation properties back to the inspector
insp.SetAnnotationProperties(annoProperties);
// create the edit operation
if (op == null)
{
op = new EditOperation();
op.Name = "Update annotation baseline";
op.SelectModifiedFeatures = true;
op.SelectNewFeatures = false;
}
op.Modify(insp);
// OR
// pass the updated geometry directly
// op.Modify(annoLayer, oid, rotatedBaseline);
// OR
// use the Dictionary methodology
//Dictionary<string, object> newAtts = new Dictionary<string, object>();
//newAtts.Add("SHAPE", rotatedBaseline);
//op.Modify(annoLayer, oid, newAtts);
}
}
// execute the operation
if ((op != null) && !op.IsEmpty)
return op.Execute();
return false;
});
}
Step 6
Build the sample and fix any compile errors. Debug the add-in and start ArcGIS Pro. Open the SampleAnno.aprx project. Validate the UI by activating the Add-In tab.
Activate the tool and drag a rectangle on the map around one or more of the annotation features. Verify that the annotation text is rotated 90 degrees.
In Visual Studio, add a breakpoint in the OnSketchCompleteAsync method after the AnnotationProperties are retrieved.
// get the annotation properties
var annoProperties = insp.GetAnnotationProperties();
// get the cimTextGraphic geometry
Geometry textGraphicGeometry = annoProperties.Shape; <-- add breakpoint here
Run the tool again and investigate the AnnotationProperties object. Note the numerous properties available to alter in the object. We will explore this further in the second map tool of this guide.
Stop debugging and return to Visual Studio.
Step 7
The second map tool in this ProGuide will demonstrate how to update text attributes of an annotation feature. As with the previous tool, the recommended pattern to modify text attributes is to use the AnnotationProperties
class from an Inspector object to update the CIMTextGraphic object of the annotation feature, then pass the updated Inspector to the EditOperation.Modify method.
Add a new ArcGIS Pro Add-ins | ArcGIS Pro Map Tool item to the add-in project, and name the item AnnoModifySymbol. Modify the Config.daml file tool item as follows:
- Change the caption to "Modify Symbol"
- Change the tool heading to "Modify Anno Symbol" and the ToolTip text to "Click and drag over annotation features to modify their text and symbol."
<tool id="AnnoTools_AnnoModifySymbol" caption="Modify Symbol"
className="AnnoModifySymbol"
loadOnClick="true" condition="esri_mapping_mapPane"
smallImage="Images\GenericButtonRed16.png" largeImage="Images\GenericButtonRed32.png" >
<tooltip heading="Modify Anno Symbol">Click and drag over annotation features to modify their text and symbol.<disabledText /></tooltip>
</tool>
Compile the add-in. Debug and start ArcGIS Pro. Open the SampleAnno.aprx project. Validate the UI by activating the Add-In tab. You should now see two tools on the ribbon.
Close ArcGIS Pro and return to Visual Studio.
Step 8
Open the AnnoModifySymbol.cs file in Visual Studio. Examine the code. As per the previous map tool all updates will be made in the OnSketchCompleteAsync method. Replace the contents of the method with the following code.
// execute on the MCT
return QueuedTask.Run(() =>
{
// find features under the sketch
var features = MapView.Active.GetFeatures(geometry);
if (features.Count == 0)
return false;
EditOperation op = null;
foreach (var annofeatures in features.ToDictionary<AnnotationLayer>())
{
// are there features?
var annoLayer = annofeatures.Key;
var featOids = annofeatures.Value;
if (featOids.Count == 0)
continue;
.....
}
// execute the operation
if ((op != null) && !op.IsEmpty)
return op.Execute();
return
false;
});
This should look familiar to you from the previous map tool as we are doing exactly the same thing; finding the annotation features under the sketch. This time however, we will update all of the features at the same time, rather than iterating in a loop.
First create the edit operation. Then load all the features into an Inspector and retrieve the AnnotationProperties for the set. Add the following code to your add-in after the line which checks the feature count.
// create the edit operation
if (op == null)
{
op = new EditOperation();
op.Name = "Update annotation symbol";
op.SelectModifiedFeatures = true;
op.SelectNewFeatures = false;
}
// load an inspector
var insp = new Inspector();
insp.Load(annoLayer, featOids);
// get the annotation properties
var annoProperties = insp.GetAnnotationProperties();
Step 9
Once the AnnotationProperties are retrieved, we can update the required attributes. In this scenario change the text to "Hello World" and the symbol color to Red. We will also alter the horizontal alignment. Use the SetAnnotationProperties method on the Inspector object to set the updated attributes. This will cause all the features loaded into the Inspector to be updated with the same attribute values. Finally pass the Inspector to the EditOperation.Modify method.
// change the text
annoProperties.TextString = "Hello World";
// you can use a textstring with embedded formatting information
//annoProperties.TextString = "My <CLR red = \"255\" green = \"0\" blue = \"0\" >Special</CLR> Text";
// change font color to red
annoProperties.Color = ColorFactory.Instance.RedRGB;
// change the horizontal alignment
annoProperties.HorizontalAlignment = HorizontalAlignment.Center;
// set the annotation properties back on the inspector
insp.SetAnnotationProperties(annoProperties);
// call modify
op.Modify(insp);
The entire OnSketchCompleteAsync method should look like the following
protected override Task<bool> OnSketchCompleteAsync(Geometry geometry)
{
// execute on the MCT
return QueuedTask.Run(() =>
{
// find features under the sketch
var features = MapView.Active.GetFeatures(geometry);
if (features.Count == 0)
return false;
EditOperation op = null;
foreach (var annofeatures in features.ToDictionary<AnnotationLayer>())
{
// are there features?
var annoLayer = annofeatures.Key;
var featOids = annofeatures.Value;
if (featOids.Count == 0)
continue;
// create the edit operation
if (op == null)
{
op = new EditOperation();
op.Name = "Update annotation symbol";
op.SelectModifiedFeatures = true;
op.SelectNewFeatures = false;
}
// load an inspector
var insp = new Inspector();
insp.Load(annoLayer, featOids);
// get the annotation properties
var annoProperties = insp.GetAnnotationProperties();
// change the text
annoProperties.TextString = "Hello World";
// you can use a textstring with embedded formatting information
//annoProperties.TextString = "My <CLR red = \"255\" green = \"0\" blue = \"0\" >Special</CLR> Text";
// change font color to red
annoProperties.Color = ColorFactory.Instance.RedRGB;
// change the horizontal alignment
annoProperties.HorizontalAlignment = HorizontalAlignment.Center;
// set the annotation properties back on the inspector
insp.SetAnnotationProperties(annoProperties);
// call modify
op.Modify(insp);
}
// execute the operation
if ((op != null) && !op.IsEmpty)
return op.Execute();
return
false;
});
}
Step 10
Build the sample and fix any compile errors. Debug the add-in and start ArcGIS Pro. Open the SampleAnno.aprx project. Activate the Modify Anno Symbol tool and drag a rectangle on the map around one or more of the annotation features. Verify that the annotation text is altered and the symbol color changes to red.
In Visual Studio, add a breakpoint in the OnSketchCompleteAsync method after the AnnotationProperties are retrieved.
// get the annotation properties
var annoProperties = insp.GetAnnotationProperties();
// change the text
annoProperties.TextString = "Hello World"; <-- add breakpoint here
Run the tool again and when the breakpoint hits, explore other properties you might wish to alter. Further investigation and code modifications are left to the developer.
Stop debugging and return to Visual Studio.
The Anno Tools Sample illustrating this ProGuide contains additional tools demonstrating how to add leader lines and callouts to the CIMTextGraphic. It also illustrates how to create new annotation templates.