ProConcepts Topology - Esri/arcgis-pro-sdk GitHub Wiki
The Topology API details the workflow for obtaining topologies and their metadata from a geodatabase.
Language: C#
Subject: Geodatabase
Contributor: ArcGIS Pro SDK Team <[email protected]>
Organization: Esri, http://www.esri.com
Date: 10/06/2024
ArcGIS Pro: 3.4
Visual Studio: 2022
-
Abstract
-
Introduction
-
Topology
-
Topology Definition
-
Rules
-
Validation, Errors, and Exceptions
-
Graph and Elements
The ArcGIS.Core.Data.Topology
namespace provides functionality to work with geodatabase and feature service topologies, including mechanisms to access topology metadata such as rules and errors, validate a topology and perform analytic operations via a topology graph.
Almost all of the methods in the Topology API should be called on the Main CIM Thread (MCT). The API reference documentation on the methods that need to run on the MCT are specified as such. These method calls should be wrapped inside the QueuedTask.Run call. Failure to do so will result in ConstructedOnWrongThreadException being thrown. See Working with multithreading in ArcGIS Pro to learn more.
NOTE: This API doesn’t support topology-related DDL operations (such as adding a new feature class to participate in the topology or adding/removing a topology rule).
In geodatabases, a topology is an arrangement that defines how point, line, and polygon features share coincident geometry. Topologies provide a mechanism to perform integrity checks on data and help validate and maintain better feature representations in a geodatabase.
In addition, topologies can be used to model spatial relationships between features. These enable support for various analytic operations, such as finding adjacent features, working with coincident boundaries between features, and navigating along connected features. A typical example of topology is a feature dataset containing public roads, land parcels, and buildings as polygon feature classes and rules: parcels should not be allowed to overlap, neither should public roads and parcels, and buildings should be contained within a parcel. Topologies define these rules and provide validation tools to identify features that violate the rules. Features are explicitly allowed to break the rules through exception settings.
Topologies are implemented as controller datasets in the geodatabase. A feature dataset is used to create a controller dataset, and the feature classes that are to be included in a controller dataset are contained within the feature dataset. Topologies can have multiple feature classes in the same topological role. A feature dataset can have numerous topologies, but a feature class can only belong to one topology. The utility network, network dataset, or parcel fabric are a few examples of other controller datasets.
Map Topology and geodatabase topology differ in the fact that map topology operates on all features in the layers in a map, whereas choosing a geodatabase topology rule limits topological editing to features participating in the selected rule. You can find a sample related to map topology graphs here.
Once the topology has been created with all the feature classes and rules, the topology can be validated. This is not required, but until doing so, the entire topology will be covered by a dirty area and the topological relationships of the features cannot be guaranteed. The areas where the spatial integrity of the topology have not been validated are known as dirty areas.
The Topology
class provides an abstraction of the controller dataset, and the methods in this class provide an entry point to the other areas of the topology API.
As with other datasets in the geodatabase, a Topology
object can be obtained by calling Geodatabase.OpenDataset()
. The Geodatabase.OpenDataset()
routine takes the name of a dataset to open.
public void OpenDataset(Geodatabase geodatabase)
{
Topology topology = geodatabase.OpenDataset<Topology>("datasetName");
}
The Topology
object can be obtained from a feature class as below:
public Topology GetTopologyFromFeatureClass(FeatureClass featureClass)
{
IReadOnlyList<Dataset> controllerDatasets = featureClass.GetControllerDatasets();
Topology topology = null;
foreach (Dataset controllerDataset in controllerDatasets)
{
if (controllerDataset is Topology)
{
topology = controllerDataset as Topology;
}
else
{
controllerDataset.Dispose();
}
}
return topology;
}
The topology also contains dirty areas and errors tables, see Validation, Errors, and Exceptions. The errors can be accessed using GetErrors()
method.
Most of the methods in this class are described in the sections that follow. Additional methods are detailed below:
-
GetState()
returns the state of topology. The states of topology areAnalyzedWithErrors
,AnalyzedWithoutErrors
,Empty
, andUnanalyzed
. -
GetExtent()
returns the maximum extent of the union of all the feature classes that participate in the topology. Note: Unlike a geodatabase topology whose extent dynamically reflects that of its constituent participating feature classes, a feature service topology’s extent remains static for the duration of the lifetime of the object. Therefore, a feature service topology’s GetExtent should not be used as input for ValidationDescription.Extent because the extent may have changed since it was opened. -
GetErrors()
returns a list ofTopologyError
objects as specified inErrorDescription
.
The TopologyDefinition
class provides metadata information about the topology dataset. It contains a schema record of all the properties specified during the topology creation, such as cluster tolerance, topology rules, and feature classes that participate in the topology.
Similar to other definition classes in the geodatabase API, the TopologyDefinition
class can be accessed via the topology dataset itself or from the geodatabase.
-
Get the
TopologyDefinition
from the topology dataset – Usually this is used when the dataset is already open, and the reference is accessibleusing (Topology topology = geodatabase.OpenDataset<Topology>("datasetName")) using (TopologyDefinition definitionViaTopology = topology.GetDefinition()) { }
-
Get the
TopologyDefinition
from a geodatabase – Usually this is used when it is not anticipated that the topology will be openedusing (Geodatabase geodatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri("topology // dataset // location")))) using (TopologyDefinition definitionViaGeodatabase = geodatabase.GetDefinition<TopologyDefinition>("topologyName")) { }
The methods of the TopologyDefinition
class are described below:
-
GetClusterTolerance()
returns the cluster tolerance of the topology. The cluster tolerance is the distance within which edges or vertices are determined to be coincident. -
GetFeatureClassNames()
returns the name of all the feature classes that participate in the topology. -
GetRules()
returns the set of TopologyRule objects defined for the topology. -
GetZClusterTolernace()
returns the Z cluster tolerance of the topology. The z–tolerance is used to distinguish the z–height or elevation of vertices within the tolerance of one another.
A topology rule defines the spatial relationship between features. The spatial relationship could be the relationship between features within a feature class, between features in different feature classes, or between features with different subtypes.
The topology rules are accessed from the topology definition by calling TopologyDefinition.GetRules()
.
using (TopologyDefinition topologyDefinition = topology.GetDefinition())
{
IReadOnlyList<TopologyRule> rules = topologyDefinition.GetRules();
foreach (TopologyRule rule in rules)
{
Console.WriteLine($@"{rule.ID} {rule.RuleType} {rule.OriginClass} {rule.OriginSubtype.GetName()}");
}
}
The members of TopologyRule
class are explained as:
-
DestinationClass
specifies the name of the destination feature class to which this topology rule is assigned. -
DestinationSubtype
defines theSubtype
in the destination feature class. -
ID
specifies theID
of the topology rule. -
OriginClass
specifies the name of the origin feature class to which this topology rule is assigned. -
OriginSubtype
defines theSubType
in the origin feature class. -
RuleType
stipulates the type of this topology rule.
Topology validation is the process of checking the features to identify any violations of the rules that have been defined for the topology. Dirty areas represent the areas where the spatial integrity of the topology has not been validated. The entire dataset is considered dirty when the topology is first created. Then, dirty areas are created whenever a feature’s geometry or subtype is modified or topology rules are added or removed. A topology validation operation also cleans up the dirty areas.
During the validation process, a series of spatial tasks identify shared features and maintain coincidence between adjacent features. The procedure involves snapping features together within a cluster tolerance and testing for rule violations. Any feature that violates one or more topology rules will result in a topology error, which can be marked as an exception. For example, if a rule is defined on a parcel feature class so that it Must Not Have Gaps
, and there is a gap between two parcels, the width of the gap and the size of the cluster tolerance determine if the error should be fixed automatically during validation, or if an error is created that must be marked as an exception or addressed differently.
The ValidateInEditOperation
method on the Topology
class ensures data integrity by validating the features in a topology against a set of topology rules for the area specified in ValidationDescription. The ValidationDescription
class is used to describe the details of a ValidateInEditOperation
operation, and it accepts an Envelope
as a parameter.
If writing a CoreHost application, use the
Validate()
base method on theTopology
class. This routine provides its own transaction management; therefore, it cannot be issued inside an edit operation created byGeodatabase.ApplyEdits()
.
When called on a feature service, the ValidateInEditOperation
method makes a call to a REST endpoint, which runs synchronously. This routine has low processing overhead. Unfortunately, it is also susceptible to server timeouts if processing takes too long. To work around this, the Validate REST endpoint provides an asynchronous version of this routine. Processing is offloaded from to the Geoprocessing Server. While this does cause a performance hit, the Geoprocessing Server is designed to handle long-running jobs and is not susceptible to timeouts. With this service, ArcGIS Pro will poll the service until the operation completes. Because many evaluation calls run against a large number of features, the asynchronous version is the default.
The ServiceSynchronizationType
parameter on TopologyValidationDescription
allows control over which type of the service is called. This uses the ServiceSynchronizationType
enum, which has two values: Synchronous
and Asynchronous
. It's important to note that from a C# developer's perspective, this routine is still a synchronous C# method, regardless of this parameter. Like other methods in the API, it blocks the current thread until the operation completes.
A ValidationResult
describes the result from the validate operation. Areas that violate the topology rules result in topology errors, and these errors need to be fixed.
If a topology does not have dirty areas, calling the validate method returns an empty envelope
ValidationResult result = topology.Validate(new ValidationDescription(topology.GetExtent()));
if (result.AffectedArea.IsEmpty)
{
// Topology has not been edited to create dirty areas
}
This code example shows how to create a new feature in the feature class to make a dirty area that violates the PointProperlyInsideArea
topology rule:
Feature feature = GetFeature("feature class name", “OID”);
Geometry currentGeometry = feature.GetShape();
Geometry newGeometry = GeometryEngine.Instance.Move(currentGeometry, (currentGeometry.Extent.XMax / 8), (currentGeometry.Extent.YMax / 8));
using (FeatureClass featureClass = feature.GetTable())
using (FeatureClassDefinition definition = featureClass.GetDefinition())
using (RowBuffer rowBuffer = featureClass.CreateRowBuffer())
{
rowBuffer[definition.GetShapeField()] = newGeometry;
geodatabase.ApplyEdits(() =>
{
featureClass.CreateRow(rowBuffer);
});
}
After creating a new feature, the topology’s state should be “Unanalyzed” because it has not been validated:
TopologyState topologyState = topology.GetState(); // Unanalyzed
Validate the topology:
// Validate from CoreHost
ValidationResult result = topology.Validate(new ValidationDescription(topology.GetExtent()));
// Alternatively, validate from an ArcGIS Pro add-in
// ValidationResult result = topology.ValidateInEditOperation(new ValidationDescription(topology.GetExtent()));
if (!result.AffectedArea.IsEmpty)
{
// After Validate(), the topology’s state should be “AnalyzedWithErrors” because the topology currently has errors.
topologyState = topology.GetState();
}
The ErrorDescription
describes how topology errors should be retrieved from an area of interest defined in the Extent
. The ErrorType
specifies the type of TopologyError
to be retrieved, and the RuleType
defines the type of TopologyRule
that is violated by the features.
Get all the errors and exceptions due to features violating the PointProperlyInsideArea
topology rule by calling Topology.GetErrors()
TopologyRule pointProperlyInsideAreaRule = topology.GetDefinition().GetRules().First(rule => rule.RuleType == TopologyRuleType.PointProperlyInsideArea);
ErrorDescription errorDescription = new ErrorDescription(topology.GetExtent())
{
TopologyRule = pointProperlyInsideAreaRule
};
IReadOnlyList<TopologyError> errorsDueToViolatingPointProperlyInsideAreaRule = topology.GetErrors(errorDescription);
Mark all errors from features violating the PointProperlyInsideArea
topology rule as exceptions using MarkAsExceptionInEditOperation()
method (Use the base Topology.MarkAsException()
routine only in CoreHost applications)
foreach (TopologyError error in errorsDueToViolatingPointProperlyInsideAreaRule)
{
topology.MarkAsException(error);
// Alternatively, mark exception from the ArcGIS Pro add-in
// topology.MarkAsExceptionInEditOperation(error);
}
By default, ErrorDescription
is initialized to ErrorType.ErrorAndException
. Here we want ErrorType.ErrorOnly
by skipping the exceptions
ErrorDescription errorDescription = new ErrorDescription(topology.GetExtent())
{
ErrorType = ErrorType.ErrorOnly,
TopologyRule = pointProperlyInsideAreaRule
};
IReadOnlyList<TopologyError> errorsAfterMarkedAsExceptions = topology.GetErrors(errorDescription);
Reset all the exceptions as errors by un-marking them as exceptions using UnmarkAsExceptionInEditOperation()
method (again, in CoreHost use the core Topology.UnmarkAsException
routine).
foreach (TopologyError error in errorsDueToViolatingPointProperlyInsideAreaRule)
{
topology.UnmarkAsException(error);
// Alternatively, unmark exception from the ArcGIS Pro add-in
// topology.UnmarkAsExceptionInEditOperation(error);
}
Each geodatabase topology has one associated topology graph. The topology graph is an in-memory planar representation of the geometries in the feature classes participating in the topology. When a TopologyGraph is built via Topology.BuildGraph()
, spatial relationships between features are discovered, analyzed, and established to form an in-memory graph of topological elements.
The lifetime of the TopologyGraph
as input for callBack is scoped to that of the client callback itself. Once the execution flow has returned from the callback, the TopologyGraph
object will be disposed, and attempting to use the TopologyGraph
object outside of the callback scope will raise an ArcGIS.Core.ObjectDisconnectedException.
A TopologyElement
can be either a TopologyNode
or a TopologyEdge
, corresponding to points and lines in the feature space. The topology graph can be traversed by iterating through the connectivity between the topology edges and topology nodes.
Build a topology graph using the extent of the topology dataset by calling Topology.BuildGraph()
// Get a feature from the feature class that participated in topology using an ObjectID
Feature feature = GetFeature("feature class name", ObjectID);
// Build a topology graph using the extent of the topology dataset
topology.BuildGraph(topology.GetExtent(), (topologyGraph) =>
{
// Get all topology edges via the feature
IReadOnlyList<TopologyEdge> allTopologyEdgesViaFeature = topologyGraph.GetEdges(feature);
// Get all topology nodes via the feature
IReadOnlyList<TopologyNode> allTopologyNodesViaFeature = topologyGraph.GetNodes(feature);
TopologyNode topologyNodeViaFeature = allTopologyNodesViaFeature[0];
// Get all edges connected to node in clockwise order via a feature
IReadOnlyList<TopologyEdge> allEdgesConnectedToNodeViaFeature = topologyNodeViaFeature.GetEdges();
// Get all edges connected to node in counterclockwise order via a feature
IReadOnlyList<TopologyEdge> allEdgesConnectedToNodeViaFeatureCounterClockwise = topologyNodeViaFeature.GetEdges(false);
});
Iterates through the topology edges to access topology nodes and parent features
foreach (TopologyEdge edgeConnectedToNodeViaFeature in allEdgesConnectedToNodeViaFeature)
{
// From and to nodes from a topology edge
TopologyNode fromNode = edgeConnectedToNodeViaFeature.GetFromNode();
TopologyNode toNode = edgeConnectedToNodeViaFeature.GetToNode();
// Left and right parent features bounded by the edge
IReadOnlyList<FeatureInfo> leftParentFeaturesBoundedByEdge = edgeConnectedToNodeViaFeature.GetLeftParentFeatures();
IReadOnlyList<FeatureInfo> rightParentFeaturesBoundedByEdge = edgeConnectedToNodeViaFeature.GetRightParentFeatures();
// Left and right parent features not bounded by the edge
IReadOnlyList<FeatureInfo> leftParentFeaturesNotBoundedByEdge = edgeConnectedToNodeViaFeature.GetLeftParentFeatures(false);
IReadOnlyList<FeatureInfo> rightParentFeaturesNotBoundedByEdge = edgeConnectedToNodeViaFeature.GetRightParentFeatures(false);
}
The FindClosestElement<T>
method discovers a topological element in the topology graph closest to the location specified by MapPoint and within the search radius.
// Topology node nearest to the query point within specified search radius of 1 unit
TopologyNode topologyNodeWithInSearchRadius = topologyGraph.FindClosestElement<TopologyNode>(feature.GetShape() as MapPoint, 1.0);
// Topology edge nearest to the query point within specified search radius of 2 units
TopologyEdge topologyEdgeWithInSearchRadius = topologyGraph.FindClosestElement<TopologyEdge>(feature.GetShape() as MapPoint, 2.0);