ArcGIS Runtime SDK for .NET (WPF)

Edit geometries

You edit geometries when you want to change existing geometries or create new ones. However, Geometry objects are immutable. To edit geometries, use geometry builders.

Geometry builders

Geometry builders (or builders for short) create or change geometry. A geometry builder contains the same things a geometry contains—vertices, segments, and parts—allowing its state to be changed as needed. You can create new geometries at any point from the builder’s current state. A builder is available for each type of geometry. Each builder exposes the appropriate methods for modifying a specific type of geometry. In the case of PolylineBuilder and PolygonBuilder that share many members, both inherit from MultipartBuilder, which in turn inherits from GeometryBuilder. Other builders inherit directly from GeometryBuilder.

Because builders represent geometries under construction, they do not use the same immutable collection types as those returned from members of the geometry classes containing a part, part collection, or point collection. Instead, builder members use mutable collection types.

Modify existing geometry

To modify an existing geometry, complete the following steps:

  1. Create the appropriate type of builder and initialize its state by passing in the geometry to be edited to the constructor. For example, modify a polyline by creating an PolylineBuilder and pass in the existing polyline to the builder.
  2. Use the methods on the builder to change the builder’s state. For example, add a new part to a polyline or remove a point from a multipoint.
  3. Call ToGeometry to return a new immutable geometry from the current state of the builder.
  4. Repeat steps 2 and 3 as many times as necessary. By keeping a reference to the builder, you can repeatedly modify the builder state as many times as required, for example, to update a temporary graphic to display the current state to the user.

The following code demonstrates an PolylineBuilder held as a member variable in a custom geometry editor class. The AddPointToEnd method adds another vertex to the end of the polyline and can be called when a user taps at the required location. Each time an update is made, the UpdateRoadGraphic method can be used to update an GraphicsOverlay with the new state of the geometry created so far.

public class RoadGeometryEditor
{
    PolylineBuilder polylineBuilder;
    Graphic roadGraphic;


    public RoadGeometryEditor(GraphicsOverlay roadsOverlay, Feature roadFeature)
    {
        // create a PolylineBuilder for working with road geometry
        // set initial state of the builder based on the Polyline passed in
        var roadPolyline = roadFeature.Geometry as Polyline;
        this.polylineBuilder = new PolylineBuilder(roadPolyline);


        // create a graphic to show the road geometry
        var lineSymbol = new SimpleLineSymbol(SimpleLineSymbolStyle.Solid, Color.FromArgb(0,255,0,0), 1.0);
        this.roadGraphic = new Graphic(roadPolyline, lineSymbol);


        // display the graphic in a graphics overlay in the map view
        roadsOverlay.Graphics.Add(roadGraphic);
    }


    public void AddPointToEnd(MapPoint point)
    {
        // add a point to the end of the last part in the polyline
        this.polylineBuilder.AddPoint(point);
    }


    // a read-only property to get the current Polyline stored in the builder
    public Polyline RoadGeometry
    {            
        get { return this.polylineBuilder.ToGeometry(); }
    }


    // update the line graphic with the geometry currently stored in the polyline builder
    public void UpdateRoadGraphic()
    {            
        this.roadGraphic.Geometry = this.polylineBuilder.ToGeometry();
    }
}

You don't have to initialize a builder with an existing geometry—you can also use builders to create entirely new geometries.

When creating geometries, use IsValidSketch to determine when a builder contains enough information to convert the builder’s state successfully to a geometry.

Tip:

When editing geometries using locations from the screen, and the map is a wraparound map, you may need to normalize the geometry before you save it to a geodatabase or use it in tasks or other operations.

Spatial reference management

When working with geometries and builders, the following principles apply with regard to their SpatialReference:

  • The spatial reference of a builder is always set during builder instantiation and cannot be changed. It can be set to null.
  • You don't need to set the spatial reference of points or segments passed into methods on geometries, collections, or builders. If the spatial reference is not set, the input coordinates will be assumed to be in the same spatial reference as the geometry, collection, or builder.
  • It is an error to add a point or segment having a different spatial reference than that of the geometry, collection, or builder you're adding it to, and an exception will be thrown indicating a mismatch. This includes the case where the spatial reference of the geometry, collection, or builder is null and that of the added point or segment is not null.
  • A MapPoint or Segment returned from a geometry’s member will have the same spatial reference as the geometry it came from.
  • When creating a new Polygon using the constructor, the API first sets the spatial reference parameter for the new Polygon, then the points are added. If there is a mismatch between the spatial reference of the new Polygon and the points being added, this is an error and the constructor will throw an exception.

Tip:

If required for your workflow, use the method on GeometryEngine to project points to a different spatial reference.

Multipoint geometries

Use MultipointBuilder to modify an existing Multipoint. Get and modify the mutable point collection from the builder's Points property, working directly with this collection to add, remove, or insert points. Call ToGeometry to instantiate a new immutable instance of Multipoint from the current state of the builder.

var builder = new MultipointBuilder(SpatialReference.Create(2169));
builder.Points.Add(pointOne);
builder.Points.Add(pointTwo);
builder.Points.Add(pointThree);
// ...
var myMultipoint = builder.ToGeometry();

Multipart geometries

Polygons and polylines can have multiple parts. You can use a multipart polygon to define a group of islands, or a multipart polyline to represent a non-continuous linear feature. Due to the geometric complexity of polygons and polylines, PolylineBuilder and PolygonBuilder are used more frequently than other builders such as PointBuilder. Using the methods on these collections, you can build polygons and polylines by adding points. As with multipoint geometries, use the PointCollection to modify multipart geometries. Or, you can edit the segments in multipart geometries.

// create a new builder for creating a polygon in geographic coordinates 
var builder = new PolygonBuilder(SpatialReferences.Wgs84);


// create a part for the polygon
var partOne = new Part(builder.SpatialReference);


// add a line segment going from (0,0) to (0,7)
partOne.Add(new LineSegment(0, 0, 0, 7));
// add another segment from (0,7) to (3,6)
partOne.Add(new LineSegment(new MapPoint(0, 7), new MapPoint(3, 6)));


// add this part to the polygon builder
builder.AddPart(partOne);


// get the polygon from the builder: the segments will be closed to create a triangle
var myPolygon = builder.ToGeometry();

Polygon created in the example

You can also edit using a mix of point-based and segment-based methods.

// add a new part to the polygon builder
partOne = new Part(builder.SpatialReference);
builder.Parts.Add(partOne);


// add an initial point
partOne.AddPoint(0, 0);            
// add another point to create a segment from (0,0) to (0,7)
partOne.AddPoint(0, 7);
// add a line segment from (0,7) to (5,0)
partOne.Add(new LineSegment(0, 7, 5, 0));

Editing parts

Use the PartCollection methods to add, remove, or insert Parts to a MultipartBuilder. A point-based helper method AddPart(IEnumerable<MapPoint>) is available for the common task of adding an existing set of MapPoints to a PartCollection as a new Part.

var builder = new PolygonBuilder(SpatialReferences.Wgs84);
// create a list of points to define the first part (ring) for the polygon
var partOnePoints = new List<MapPoint>();
partOnePoints.Add(new MapPoint(-112.33, 38.51));
partOnePoints.Add(new MapPoint(-112.53, 38.35));
partOnePoints.Add(new MapPoint(-112.73, 38.18));
// add the points to the builder as a part
builder.AddPart(partOnePoints);

Editing parts using points

The Part is a collection of segments. Working with the point-based method results in defining an ordered series of vertices and, as a consequence, creating straight LineSegments within the builder between those vertices.

The following principles apply when editing parts using points:

  • Use InsertPoint(int, Point) to insert a vertex at a specific index into a part. This will increase the number of segments in the part by one. Connections between segments are preserved—adjacent segments touch at their starting and ending vertices.
    // insert a new vertex into the first part (ring) before the vertex at index position 2 (3rd one)
    builder.Parts[0].InsertPoint(2, new MapPoint(-112.60, 38.22));
  • Use RemovePoint(int) to remove the vertex at a specific index from a part. The number of segments decreases by one, and connections between segments are preserved.
    // remove the first vertex (index position 0) from the first part (ring)
    builder.Parts[0].RemovePoint(0);
  • Use SetPoint(int) to replace a vertex in the collection without the need for a remove followed by an add. The total number of vertices or segments in the collection remains unchanged.
  • You can add a new vertex to the end of a MultipartBuilder using the AddPoint(int, Point) method. This adds a vertex to the end of the last part in the builder. If there are no parts in the builder, this method will create a new part first, then add the new vertex.
  • You do not need to close a part belonging to a polygon.

Caution:

When working with point-based methods that take an index parameter, keep in mind that this is the index of the specific vertex, which is a different index than that used when working with segments. This is because where a segment’s ending vertex is at the same location as the starting vertex of the next segment, it is represented by a single MapPoint in point-based methods.

Editing parts using segments

When you work with the segments in a part, you specify each segment’s starting and ending vertex. This differs from editing with points, where sequential vertices define a series of adjacent segments. Specifying starting and ending vertices for adjacent segments can result in gaps, where the end vertex of one segment does not have exactly the same location as the start vertex of the next adjacent segment. Gaps are not automatically corrected in the builder until you access the builder’s call ToGeometry member to instantiate a new polyline or polygon. At that time, if any adjacent segments in the part do not touch, a short connecting segment will be added automatically to the new geometry to ensure the part forms a continuous edge in the new geometry. To avoid automatically adding segments, re-use the location of the end vertex of the previous segment as the start vertex of the subsequent segment, and use insert, remove, and add methods with care.

Similar to geometries, all segments are immutable and are created by using constructors or static factory methods. Unlike geometries, there are no builders for segments. Segments are fully defined when created, setting all properties at once.

The following principles apply when editing parts using segments:

  • Use Insert(int, Segment) to insert a segment into a part at a specific index. This increases the number of segments in the part by one. Be careful: this will result in a gap at either end of the new segment if the start and end vertices do not share a location with the preceding and following adjacent segments, respectively.
    // create a simple line segment
    var segment = new LineSegment(-111.5, 37.5, -112.0, 38.0, SpatialReferences.Wgs84);
    // insert it as the 2nd segment in the first part
    builder.Parts[0].Insert(1, segment);
  • Use Remove(Segment) or RemoveAt(int) to remove a segment from a part. If you remove anything other than the first or last segment in the part, this will result in a gap between the remaining segments. The number of segments decreases by one, and connections between segments are preserved.
    // remove the third segment from the first part
    builder.Parts[0].RemoveAt(2);
  • Because segments are immutable, existing segments in a part cannot be changed. However, you can replace the existing segment at a specified index with a new segment by calling RemoveAt followed by Insert.
    // remove the third segment from the first part
    builder.Parts[0].RemoveAt(2);
    // create a new line segment
    var newSegment = new LineSegment(-111.5, 37.5, -112.0, 38.0, SpatialReferences.Wgs84);
    // insert it as the new 3rd segment in the part
    builder.Parts[0].Insert(2, segment);
  • You do not need to close a part belonging to a polygon.

Caution:

When working with segment-based methods that take an index parameter, keep in mind that this is the index of the segment within the part and is a different index than that used when working with point-based helper methods. If a segment's ending vertex is at the same location as the starting vertex of the next segment, this location is represented by a single MapPoint in point-based methods. However, if there is a gap, separate MapPoints are returned for the two locations that define the gap.

Topological simplicity in features and graphics

To be saved as a feature in a geodatabase, a geometry must be simplified—that is, be topologically consistent. The rules vary for different types of geometry but include requirements such as polygons not having overlapping parts. Pass a geometry to the GeometryEngine object's Simplify method to return a simplified copy that can be saved in a geodatabase.

Note that in some cases, simplification can change the contents of the geometry, including resulting in an empty geometry. Topological simplicity is different than geometry generalization. For more information, see ITopologicalOperator.Simplify in the ArcObjects help.

The requirements for a geometry that will be used in a graphic are less rigorous than those used in a feature—there is no need to simplify geometries to display them in graphics.

Builders and IsValidSketch

In some workflows, both graphics and features are used. In interactive editing scenarios, for example, a user sketches a geometry on the map. A graphic is used initially to display the state of the sketch during sketching. When the user decides that the sketch is complete, the geometry can be saved to a geodatabase feature. You can use IsValidSketch on a geometry builder to determine when the builder contains enough state to be converted successfully to a geometry. This is a useful tool to enable or disable saving the sketch by the user. To be considered a valid sketch, a polygon builder must contain at least one part with three or more vertices in it, and a polyline builder must contain at least one part with at least two vertices. A multipoint builder must contain at least one point, and for a point or envelope builder, the x and y coordinates must not be NaN. If these conditions are satisfied, these cases should result in a geometry that could be visible if drawn.

Although IsValidSketch may be a good first check when building a geometry, it should not be relied upon to indicate if the eventual geometry will be valid or empty after simplification. Before allowing a user to save a geometry, consider taking a further verification step of calling Simplify. This approach can also be used for other situations that require geometries to be simple, for example, measurement or topological operations.

When importing or exporting geometries, for example, to JSON, if a multipart geometry has insufficient points, additional points can be added to ensure each segment has two endpoints to preserve the stability of other clients that may expect this.

Z-values and m-values

All types of geometry can have z-values. When building geometries, a simple rule determines whether or not the geometry stores z-values. If you call a method that has a z-value parameter, or add a point or segment that has z-values to a builder, the resulting geometry will store z-values—HasZ will return true.

Geometries can also have m-values, used in linear referencing workflows. Using m-values is generally less common than using z-values. Therefore, instead of using constructors, the MapPoint and Envelope classes present static factory methods that allow the creation of geometries with m-values.

Pay special attention to z- and m-values in editing workflows. When syncing edits, sync errors may occur if the z- or m-awareness of the geometry does not match that of the service. Find out more in the ArcGIS REST API help topic Error handling with sync.

Tip:

When saving geometries in geodatabase features, check that HasZ and HasM match the attribute awareness of the feature table being edited.

Related topics