ProConcepts Editing Annotation - Esri/arcgis-pro-sdk GitHub Wiki
This concepts document covers special considerations for authors of editing tools for annotation. It augments the overall annotation concepts covered in ProConcepts Annotation.
ArcGIS.Core.dll
ArcGIS.Desktop.Editing.dll
Language: C#
Subject: Annotation
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 concepts document augments the overall annotation concepts covered in ProConcepts Annotation. Annotation features differ from other geodatabase features in a few small but fundamental ways. It is important to keep these in mind when developing custom editing tools that create or modify annotation features.
Firstly an annotation feature class stores polygon geometry. This polygon is the bounding box of the text of the annotation feature. The bounding box is calculated from the text string, font, font size, angle orientation and other text formatting attributes of the feature. It is automatically updated by the application each time the annotation attributes are modified. You should never need to access or modify an annotation feature's polygon shape.
The text attributes of an annotation feature are represented as a CIMTextGraphic. The CIMTextGraphic contains the text string, text formatting attributes (such as alignment, angle, font, color, etc), and other information (such as callouts and leader lines). It also has a shape which represents the baseline geometry that the annotation text string sits upon. For annotation features this CIMTextGraphic shape can be a point, polyline (typically a two point line or Bezier curve), multipoint or geometryBag. It is this shape that you will typically interact with when developing annotation tools. For example when creating annotation features, the geometry passed to the EditOperation.Create
method is the CIMTextGraphic geometry.
Another key concept when interacting with annotation is the annotation feature class schema. By default, annotation feature classes are created with a series of fields with descriptive information about the feature and its symbolization. While these fields are created for new feature classes, not all these fields are required. Optional text formatting attributes can be deleted by the user if they exist; they are no longer designated as protected or system fields. In ArcGIS Pro, the only fields guaranteed to exist in an annotation schema are AnnotationClassID, SymbolID, Element, FeatureID or FeatureGlobalID (if using a GlobalID relationship), ZOrder and Status along with the system ObjectID and Shape fields. All other fields which store text formatting attributes (such as TextString, FontName, VerticalAlignment, HorizontalAlignment etc) are optional. They are not guaranteed to exist in the physical schema. Additionally, the ArcGIS Pro annotation model no longer has Bold and Italic fields. They have been replaced with a FontStyle field. When the annotation descriptive fields exist, they are kept in sync with the contents of the CIMTextGraphic of the AnnotationFeature. Updating the CIMTextGraphic will update the field in the row corresponding to the property. Likewise, updating the field value will update the CIMTextGraphic. If you update both the field and the CIMTextGraphic in one operation and they conflict, the CIMTextGraphic will take precedence.
Due to the complexities of having to cater for possible differences in the annotation schema, the recommended way to access or modify annotation features is to use the AnnotationProperties class which is obtained from an Inspector object.
The final concept specific to annotation feature classes is the label and symbol classes defined at the feature class level. Each annotation feature class has a set of label and symbol classes associated with it. Label classes are synonymous with annotation classes in ArcGIS 10x. A label class can contain a query expression and placement properties (to include a labeling expression) to define how a subset of annotation in the feature class display and the default symbology to be applied when creating new annotation.
For example, if you have an annotation feature class for cities, you could have label classes of varying text sizes and scale ranges for small, medium, and large cities—all managed within a single annotation feature class. Label classes save you from having to define and maintain multiple annotation feature classes.
An annotation feature class also contains a collection of one or more text symbols that you define. Every time you create a new annotation feature, you assign it one of these predefined symbols. The symbol contains properties that describe how the annotation feature is drawn, such as font, size, and color. For example, if you have annotation for small, medium, and large cities, create three text symbols of varying font sizes to assign to the annotation. Each annotation feature stores a value in the SymbolID field which refers to one of these three text symbols. Because the annotation feature stores the symbol ID of the predefined symbol rather than each individual symbol property, ArcGIS is able to reduce storage requirements and maximize display and query performance. Committing to a limited list of symbols can help you promote standards for any new annotation features you create.
You can retrieve the label and symbol classes for an annotation feature class by accessing its AnnotationFeatureClassDefinition. This can be achieved using code similar to the following
// must be executed on the MCT - wrap in a QueuedTask.Run
// get the anno layer
AnnotationLayer annoLayer = CurrentTemplate.Layer as AnnotationLayer;
if (annoLayer == null)
return false;
// get the anno feature class
var fc = annoLayer.GetFeatureClass() as ArcGIS.Core.Data.Mapping.AnnotationFeatureClass;
if (fc == null)
return false;
// get the featureclass definition which contains the label and symbol collections
var annoDefinition = fc.GetDefinition() as ArcGIS.Core.Data.Mapping.AnnotationFeatureClassDefinition;
//Get the CIM definitions for the labelling and symbol identifiers
var labels = annoDefinition.GetLabelClassCollection();
var symbols = annoDefinition.GetSymbolCollection();
You can use values in these collections to set the AnnotationClassID and SymbolID fields when creating new annotation features if required.
Of course, over time, you may discover that the text symbols you created do not contain the properties you need for one or more annotation features. For example, you may require a smaller font size to fit annotation into a congested area. One approach is to create a new text symbol with the new properties, then assign the new text symbol to the annotation features. However, creating a new symbol for every unique set of properties you require could result in a long list of symbols that is difficult to work with.
Instead ArcGIS Pro allows you to modify symbol properties on a feature-by-feature basis. When editing, you can select annotation and change any symbol property for that annotation. These overrides are managed by comparing edits made to the CIMTextGraphic's symbol to the symbol referenced. If the feature can be stored with overrides, it will be. This allows for CIMTextSymbol properties to be freely changed without worrying about storage implications. If the overrides cannot be stored separately, the CIMTextGraphic will be disconnected from the symbol collection and the annotation feature will store the entire CIMTextSymbol. When this happens, the symbol is referred to as 'bloated'. Note that AnnotationFeatureClassDefinition properties can designate that overrides are not allowed or that all symbols must reference a symbol collection symbol. If those properties are set and edits are made that violate those constraints, the edits will fail.
Since so many of the text formatting fields in the data schema for an annotation feature class are optional, you should not use the Inspector[fieldName]
methodology for accessing and updating annotation data. Instead, use the AnnotationProperties class to access the baseline geometry and other text formatting properties of an annotation feature. The AnnotationProperties class is retrieved via the Inspector.GetAnnotationProperties method. If you are modifying the annotation attributes, update the required properties on the AnnotationProperties object, then use the Inspector.SetAnnotationProperties method to assign any altered properties back on to the Inspector object.
Use this pattern for creating annotation features which require attributes different from default template values, modifying annotation attributes and also for creating annotation templates. Snippets covering all three scenarios are displayed below.
Note: The AnnotationFeatureClassDefinition AreSymbolOverridesAllowed()
method can be accessed to verify if symbol overrides are allowed on the given annotation feature class. If symbol override are not allowed, edits to the annotation text graphic will fail. Similarly the AnnotationFeatureClassDefinition IsSymbolIDRequired()
method should also be checked to determine if all symbolds must reference a symbol collection symbol. If symbol IDs are required, edits to the annotation text graphic which cause a disconnect from the symbol collection will also fail.
This code snippets below assume that annoFeatureClassDef.AreSymbolOverridesAllowed() is true and annoFeatureClassDef.IsSymbolIDRequired() is false.
If you wish to create construction tools for annotation features which assign attribute overrides then use the overload on the EditOperation.Create method. This takes an Inspector object as a parameter, allowing you to override the default properties of an annotation template. The general pattern for creating features in this scenario should be as follows: obtain the inspector object from the CurrentTemplate, assign the AnnotationClassID and SymbolID fields (required fields), retrieve the AnnotationProperties class, update the necessary attributes including the shape, assign the AnnotationProperties back to the inspector and call EditOperation.Create. The following code illustrates this.
// must be executed on the MCT - wrap in a QueuedTask.Run
// use the template's inspector object
var inspector = CurrentTemplate.Inspector;
// use the inspector[fieldName] methodology to set the AnnotationClassid and SymbolID fields
// these are fixed fields in the annotation schema and are guaranteed to exist.
inspector["AnnotationClassID"] = label.ID;
// set the symbolID too
inspector["SymbolID"] = symbolID;
// get the annotation properties
var annoProperties = inspector.GetAnnotationProperties();
// use the annotation properties to set the other attributes
annoProperties.TextString = "My annotation feature";
annoProperties.Color = ColorFactory.Instance.GreenRGB;
annoProperties.VerticalAlignment = ArcGIS.Core.CIM.VerticalAlignment.Top;
annoProperties.Underline = true;
// set the geometry to be the sketched geometry
// when creating annotation features the shape to be passed
// in the create operation is the CIMTextGraphic shape
annoProperties.Shape = geometry;
// set the annotation properties back on the inspector
inspector.SetAnnotationProperties(annoProperties);
// Create an edit operation
var createOperation = new EditOperation();
createOperation.Name = string.Format("Create {0}", CurrentTemplate.Layer.Name);
createOperation.SelectNewFeatures = true;
// create and execute using the inspector
createOperation.Create(CurrentTemplate.Layer, inspector);
return createOperation.Execut();
Modifying annotation features should follow similar patterns to modifying point, line or polygon features. That is; create an Inspector object, load the appropriate feature, modify the necessary attributes, then call EditOperation.Modify passing the updated Inspector object. The only difference for annotation features is that you use the AnnotationProperties class to modify the annotation specific attributes. This is illustrated below.
// must be executed on the MCT - wrap in a QueuedTask.Run
// 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;
// increase the font size
annoProperties.FontSize = 48;
// rotate the cimTextGraphic - but only if it's a polyline
Polyline baseLine = textGraphicGeometry as Polyline;
if (baseLine != null)
{
// 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
op = new EditOperation();
op.Name = "Update annotation baseline";
op.SelectModifiedFeatures = true;
op.SelectNewFeatures = false;
// call modify
op.Modify(insp);
// call execute
Similar to editing of other geometry types, you can load multiple annotation features into the inspector, assign values via the AnnotationProperties class and have those values be applied to all features loaded in the inspector.
If the property you are wishing to modify does not exist on the AnnotationProperties class - for example you wish to add a callout or leader line or modify some more specific text formatting attribute you can still access the CIMTextGraphic itself by using the TextGraphic property. Once you have modified the CIMTextGraphic, use the AnnotationProperties.LoadFromTextGraphic method to assign the text graphic back to the AnnotationProperties, followed by setting the annotation properties back on the Inspector.
// must be executed on the MCT - wrap in a QueuedTask.Run
// 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
CIMTextGraphic textGraphic = annoProperties.TextGraphic;
// change text
textGraphic.Text = "Hello world";
// set x,y offset via the symbol
var symbol = textGraphic.Symbol.Symbol;
var textSymbol = symbol as CIMTextSymbol;
textSymbol.OffsetX = 2;
textSymbol.OffsetY = 3;
textSymbol.HorizontalAlignment = HorizontalAlignment.Center;
// load the updated textGraphic
annoProperties.LoadFromTextGraphic(textGraphic);
// assign the annotation properties back to the inspector
insp.SetAnnotationProperties(annoProperties);
// create the edit operation
op = new EditOperation();
op.Name = "Update annotation baseline";
op.SelectModifiedFeatures = true;
op.SelectNewFeatures = false;
// call modify
op.Modify(insp);
// call execute
The Inspector and AnnotationProperties classes are also the recommended pattern for creating annotation templates. Rather than dealing directly with the CIM as in previous releases, the CreateTemplate method facilitates template creation with a populated Inspector object. You can also easily assign the template name, description, tags, default tool and tool filter with the same call. This extension method can be used for all geometry types; it is not specifically for creation of annotation templates.
// must be executed on the MCT - wrap in a QueuedTask.Run
// load the schema
insp = new Inspector();
insp.LoadSchema(annoLayer);
// set up the AnnotationClassID, SymbolID fields - required fields
// ok to access them this way - they are guaranteed to exist
insp["AnnotationClassID"] = label.ID;
insp["SymbolID"] = symbolID;
// set up some text properties
AnnotationProperties annoProperties = insp.GetAnnotationProperties();
annoProperties.FontSize = 36;
annoProperties.TextString = "My Annotation feature";
annoProperties.VerticalAlignment = VerticalAlignment.Top;
annoProperties.HorizontalAlignment = HorizontalAlignment.Justify;
// assign the properties back to the inspector
insp.SetAnnotationProperties(annoProperties);
// set up tags
var tags = new[] { "Annotation", "tag1", "tag2" };
// set up default tool - use daml-id rather than guid
string defaultTool = "esri_editing_SketchStraightAnnoTool";
// tool filter is the tools to filter OUT
var toolFilter = new[] { "esri_editing_SketchCurvedAnnoTool" };
// create a new CIM template - new extension method
var newTemplate = annoLayer.CreateTemplate("My new template", "sample description", insp, defaultTool, tags, toolFilter);
The ProGuide Annotation Construction Tools and ProGuide Annotation Editing Tools give examples of building construction and editing tools using concepts discussed above. Refer to ProConcepts Annotation for general reference information on Annotation.