ProGuide Annotation Editing Tools - kataya/arcgis-pro-sdk GitHub Wiki

Language:      C#
Subject:       Editing
Contributor:   ArcGIS Pro SDK Team <[email protected]>
Organization:  Esri, http://www.esri.com
Date:          11/24/2020
ArcGIS Pro:    2.7
Visual Studio: 2017, 2019

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

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. Instead, it is the baseline geometry of the annotation text that is 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 the geometry 90 degrees and modify the features with the rotated geometry.

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 layer in the features retrieved
    foreach (var annoLayer in features.Keys.OfType<AnnotationLayer>())
    {
      // are there features?
      var featOids = features[layer];
      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(layer, 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 layer in the features retrieved
    foreach (var annoLayer in features.Keys.OfType<AnnotationLayer>())
    {
      // are there features?
      var featOids = features[annoLayer];
      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(layerKey, oid, rotatedBaseline);

        // OR 
        // use the Dictionary methodology

        //Dictionary<string, object> newAtts = new Dictionary<string, object>();
        //newAtts.Add("SHAPE", rotatedBaseline);
        //op.Modify(layer, 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.

annoModifyGeometry

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.

annoModifySymbol

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 annoLayer in features.Keys.OfType<AnnotationLayer>())
    {
      // are there features?
      var featOids = features[annoLayer];
      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 annoLayer in features.Keys.OfType<AnnotationLayer>())
    {
      // are there features?
      var featOids = features[annoLayer];
      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.

⚠️ **GitHub.com Fallback** ⚠️