publicvoidOpenTopologyAndProcessDefinition(){// Open a geodatabase topology from a file geodatabase and process the topology definition.using(Geodatabasegeodatabase=new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(@"C:\TestData\GrandTeton.gdb"))))using(Topologytopology= geodatabase.OpenDataset<Topology>("Backcountry_Topology")){
ProcessDefinition(geodatabase, topology);}// Open a feature service topology and process the topology definition.conststringTOPOLOGY_LAYER_ID="0";using(Geodatabasegeodatabase=new Geodatabase(new ServiceConnectionProperties(new Uri("https://sdkexamples.esri.com/server/rest/services/GrandTeton/FeatureServer"))))using(Topologytopology= geodatabase.OpenDataset<Topology>(TOPOLOGY_LAYER_ID)){
ProcessDefinition(geodatabase, topology);}}privatevoidProcessDefinition(Geodatabasegeodatabase,Topologytopology){// Similar to the rest of the Definition objects in the Core.Data API, there are two ways to open a dataset's // definition -- via the Topology dataset itself or via the Geodatabase.using(TopologyDefinitiondefinitionViaTopology= topology.GetDefinition()){
OutputDefinition(geodatabase, definitionViaTopology);}using(TopologyDefinitiondefinitionViaGeodatabase=
geodatabase.GetDefinition<TopologyDefinition>("Backcountry_Topology")){
OutputDefinition(geodatabase, definitionViaGeodatabase);}}privatevoidOutputDefinition(Geodatabasegeodatabase,TopologyDefinitiontopologyDefinition){
Console.WriteLine($"Topology cluster tolerance => {topologyDefinition.GetClusterTolerance()}");
Console.WriteLine($"Topology Z cluster tolerance => {topologyDefinition.GetZClusterTolerance()}");IReadOnlyList<string>featureClassNames= topologyDefinition.GetFeatureClassNames();
Console.WriteLine($"There are {featureClassNames.Count} feature classes that are participating in the topology:");foreach(string name in featureClassNames){// Open each feature class that participates in the topology.using(FeatureClassfeatureClass= geodatabase.OpenDataset<FeatureClass>(name))using(FeatureClassDefinitionfeatureClassDefinition= featureClass.GetDefinition()){
Console.WriteLine($"\t{featureClass.GetName()} ({featureClassDefinition.GetShapeType()})");}}}
GetTopologyRules
using(TopologyDefinitiontopologyDefinition= topology.GetDefinition()){IReadOnlyList<TopologyRule>rules= topologyDefinition.GetRules();
Console.WriteLine($"There are {rules.Count} topology rules defined for the topology:");
Console.WriteLine("ID \t Origin Class \t Origin Subtype \t Destination Class \t Destination Subtype \t Rule Type");foreach(TopologyRule rule in rules){
Console.Write($"{rule.ID}");
Console.Write(!String.IsNullOrEmpty(rule.OriginClass)?$"\t{rule.OriginClass}":"\t\"\"");
Console.Write(rule.OriginSubtype !=null?$"\t{rule.OriginSubtype.GetName()}":"\t\"\"");
Console.Write(!String.IsNullOrEmpty(rule.DestinationClass)?$"\t{rule.DestinationClass}":"\t\"\"");
Console.Write(rule.DestinationSubtype !=null?$"\t{rule.DestinationSubtype.GetName()}":"\t\"\"");
Console.Write($"\t{rule.RuleType}");
Console.WriteLine();}}
ValidateTopology
publicvoidValidateTopology(){using(Geodatabasegeodatabase=new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(@"C:\TestData\GrandTeton.gdb"))))using(Topologytopology= geodatabase.OpenDataset<Topology>("Backcountry_Topology")){// If the topology currently does not have dirty areas, calling Validate() returns an empty envelope.ValidationResultresult= topology.Validate(new ValidationDescription(topology.GetExtent()));
Console.WriteLine($"'AffectedArea' after validating a topology that has not been edited => {result.AffectedArea.ToJson()}");// Now create a feature that purposely violates the "PointProperlyInsideArea" topology rule. This action will// create dirty areas.FeaturenewFeature=null;try{// Fetch the feature in the Campsites feature class whose objectID is 2. Then create a new geometry slightly// altered from this and use it to create a new feature.using(FeaturefeatureViaCampsites2= GetFeature(geodatabase,"Campsites",2)){GeometrycurrentGeometry= featureViaCampsites2.GetShape();GeometrynewGeometry= GeometryEngine.Instance.Move(currentGeometry,(currentGeometry.Extent.XMax /8),(currentGeometry.Extent.YMax /8));using(FeatureClasscampsitesFeatureClass= featureViaCampsites2.GetTable())using(FeatureClassDefinitiondefinition= campsitesFeatureClass.GetDefinition())using(RowBufferrowBuffer= campsitesFeatureClass.CreateRowBuffer()){
rowBuffer[definition.GetShapeField()]=newGeometry;
geodatabase.ApplyEdits(()=>{newFeature= campsitesFeatureClass.CreateRow(rowBuffer);});}}// After creating a new feature in the 'Campsites' participating feature class, the topology's state should be // "Unanalyzed" because it has not been validated.
Console.WriteLine($"The topology state after an edit has been applied => {topology.GetState()}");// Now validate the topology. The result envelope corresponds to the dirty areas.result= topology.Validate(new ValidationDescription(topology.GetExtent()));
Console.WriteLine($"'AffectedArea' after validating a topology that has just been edited => {result.AffectedArea.ToJson()}");// After Validate(), the topology's state should be "AnalyzedWithErrors" because the topology currently has errors.
Console.WriteLine($"The topology state after validate topology => {topology.GetState()}");// If there are no dirty areas, the result envelope should be empty.result= topology.Validate(new ValidationDescription(topology.GetExtent()));
Console.WriteLine($"'AffectedArea' after validating a topology that has just been validated => {result.AffectedArea.ToJson()}");}finally{if(newFeature!=null){
geodatabase.ApplyEdits(()=>{ newFeature.Delete();});
newFeature.Dispose();}}// Validate again after deleting the newly-created feature.
topology.Validate(new ValidationDescription(topology.GetExtent()));}}private Feature GetFeature(Geodatabasegeodatabase,stringfeatureClassName,longobjectID){using(FeatureClassfeatureClass= geodatabase.OpenDataset<FeatureClass>(featureClassName)){QueryFilterqueryFilter=new QueryFilter(){ObjectIDs=newList<long>(){ objectID }};using(RowCursorcursor= featureClass.Search(queryFilter)){
System.Diagnostics.Debug.Assert(cursor.MoveNext());return(Feature)cursor.Current;}}}
GetTopologyErrors
// Get all the errors and exceptions currently associated with the topology.IReadOnlyList<TopologyError>allErrorsAndExceptions= topology.GetErrors(new ErrorDescription(topology.GetExtent()));
Console.WriteLine($"errors and exceptions count => {allErrorsAndExceptions.Count}");
Console.WriteLine("OriginClassName \t OriginObjectID \t DestinationClassName \t DestinationObjectID \t RuleType \t IsException \t Shape type \t Shape width & height \t Rule ID \t");foreach(TopologyError error in allErrorsAndExceptions){
Console.WriteLine($"'{error.OriginClassName}' \t{error.OriginObjectID}\t '{error.DestinationClassName}' \t"+$"{error.DestinationObjectID}\t{error.RuleType}\t{error.IsException}\t{error.Shape.GeometryType}\t"+$"{error.Shape.Extent.Width},{error.Shape.Extent.Height}\t{error.RuleID}");}
MarkAndUnmarkAsErrors
// Get all the errors due to features violating the "PointProperlyInsideArea" topology rule.using(TopologyDefinitiontopologyDefinition= topology.GetDefinition()){TopologyRulepointProperlyInsideAreaRule= topologyDefinition.GetRules().First(rule => rule.RuleType == TopologyRuleType.PointProperlyInsideArea);ErrorDescriptionerrorDescription=new ErrorDescription(topology.GetExtent()){TopologyRule=pointProperlyInsideAreaRule};IReadOnlyList<TopologyError>errorsDueToViolatingPointProperlyInsideAreaRule= topology.GetErrors(errorDescription);
Console.WriteLine($"There are {errorsDueToViolatingPointProperlyInsideAreaRule.Count} feature violating the 'PointProperlyInsideArea' topology rule.");// Mark all errors from features violating the 'PointProperlyInsideArea' topology rule as exceptions.foreach(TopologyError error in errorsDueToViolatingPointProperlyInsideAreaRule){
topology.MarkAsException(error);}// Now verify all the errors from features violating the 'PointProperlyInsideArea' topology rule have indeed been// marked as exceptions.//// By default, ErrorDescription is initialized to ErrorType.ErrorAndException. Here we want ErrorType.ErrorOnly.errorDescription=new ErrorDescription(topology.GetExtent()){ErrorType= ErrorType.ErrorOnly,TopologyRule=pointProperlyInsideAreaRule};IReadOnlyList<TopologyError>errorsAfterMarkedAsExceptions= topology.GetErrors(errorDescription);
Console.WriteLine($"There are {errorsAfterMarkedAsExceptions.Count} feature violating the 'PointProperlyInsideArea' topology rule after all the errors have been marked as exceptions.");// Finally, reset all the exceptions as errors by unmarking them as exceptions.foreach(TopologyError error in errorsDueToViolatingPointProperlyInsideAreaRule){
topology.UnmarkAsException(error);}IReadOnlyList<TopologyError>errorsAfterUnmarkedAsExceptions= topology.GetErrors(errorDescription);
Console.WriteLine($"There are {errorsAfterUnmarkedAsExceptions.Count} feature violating the 'PointProperlyInsideArea' topology rule after all the exceptions have been reset as errors.");}
ExploreTopologyGraph
publicvoidExploreTopologyGraph(){using(Geodatabasegeodatabase=new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(@"C:\TestData\GrandTeton.gdb"))))using(Topologytopology= geodatabase.OpenDataset<Topology>("Backcountry_Topology")){// Build a topology graph using the extent of the topology dataset.
topology.BuildGraph(topology.GetExtent(),(topologyGraph)=>{using(Featurecampsites12= GetFeature(geodatabase,"Campsites",12)){IReadOnlyList<TopologyNode>topologyNodesViaCampsites12= topologyGraph.GetNodes(campsites12);TopologyNodetopologyNodeViaCampsites12= topologyNodesViaCampsites12[0];IReadOnlyList<TopologyEdge>allEdgesConnectedToNodeViaCampsites12= topologyNodeViaCampsites12.GetEdges();IReadOnlyList<TopologyEdge>allEdgesConnectedToNodeViaCampsites12CounterClockwise= topologyNodeViaCampsites12.GetEdges(false); System.Diagnostics.Debug.Assert(allEdgesConnectedToNodeViaCampsites12.Count == allEdgesConnectedToNodeViaCampsites12CounterClockwise.Count);foreach(TopologyEdge edgeConnectedToNodeViaCampsites12 in allEdgesConnectedToNodeViaCampsites12){TopologyNodefromNode= edgeConnectedToNodeViaCampsites12.GetFromNode();TopologyNodetoNode= edgeConnectedToNodeViaCampsites12.GetToNode();boolfromNodeIsTheSameAsTopologyNodeViaCampsites12=(fromNode==topologyNodeViaCampsites12);booltoNodeIsTheSameAsTopologyNodeViaCampsites12=(toNode==topologyNodeViaCampsites12); System.Diagnostics.Debug.Assert(fromNodeIsTheSameAsTopologyNodeViaCampsites12||toNodeIsTheSameAsTopologyNodeViaCampsites12,"The FromNode *or* ToNode of each edge connected to 'topologyNodeViaCampsites12' should be the same as 'topologyNodeViaCampsites12' itself.");IReadOnlyList<FeatureInfo>leftParentFeaturesBoundedByEdge= edgeConnectedToNodeViaCampsites12.GetLeftParentFeatures();foreach(FeatureInfo featureInfo in leftParentFeaturesBoundedByEdge){ System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(featureInfo.FeatureClassName)); System.Diagnostics.Debug.Assert(featureInfo.ObjectID >0); EnsureShapeIsNotEmpty(featureInfo);}IReadOnlyList<FeatureInfo>leftParentFeaturesNotBoundedByEdge= edgeConnectedToNodeViaCampsites12.GetLeftParentFeatures(false);foreach(FeatureInfo featureInfo in leftParentFeaturesNotBoundedByEdge){ System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(featureInfo.FeatureClassName)); System.Diagnostics.Debug.Assert(featureInfo.ObjectID >0); EnsureShapeIsNotEmpty(featureInfo);}IReadOnlyList<FeatureInfo>rightParentFeaturesBoundedByEdge= edgeConnectedToNodeViaCampsites12.GetRightParentFeatures();foreach(FeatureInfo featureInfo in rightParentFeaturesBoundedByEdge){ System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(featureInfo.FeatureClassName)); System.Diagnostics.Debug.Assert(featureInfo.ObjectID >0); EnsureShapeIsNotEmpty(featureInfo);}IReadOnlyList<FeatureInfo>rightParentFeaturesNotBoundedByEdge= edgeConnectedToNodeViaCampsites12.GetRightParentFeatures(false);foreach(FeatureInfo featureInfo in rightParentFeaturesNotBoundedByEdge){ System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(featureInfo.FeatureClassName)); System.Diagnostics.Debug.Assert(featureInfo.ObjectID >0); EnsureShapeIsNotEmpty(featureInfo);}}}});}}privatevoidEnsureShapeIsNotEmpty(FeatureInfofeatureInfo){using(Featurefeature= featureInfo.GetFeature()){
System.Diagnostics.Debug.Assert(!feature.GetShape().IsEmpty,"The feature's shape should not be empty.");}}
FindClosestElement
publicvoidFindClosestElement(){using(Geodatabasegeodatabase=new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(@"C:\TestData\GrandTeton.gdb"))))using(Topologytopology= geodatabase.OpenDataset<Topology>("Backcountry_Topology")){// Build a topology graph using the extent of the topology dataset.
topology.BuildGraph(topology.GetExtent(),(topologyGraph)=>{MapPointqueryPointViaCampsites12=null;using(Featurecampsites12= GetFeature(geodatabase,"Campsites",12)){queryPointViaCampsites12= campsites12.GetShape()as MapPoint;}doublesearchRadius=1.0;TopologyElementtopologyElementViaCampsites12= topologyGraph.FindClosestElement<TopologyElement>( queryPointViaCampsites12, searchRadius); System.Diagnostics.Debug.Assert(topologyElementViaCampsites12!=null,"There should be a topology element corresponding to 'queryPointViaCampsites12' within the 'searchRadius' units.");IReadOnlyList<FeatureInfo>parentFeatures= topologyElementViaCampsites12.GetParentFeatures(); Console.WriteLine("The parent features that spawn 'topologyElementViaCampsites12' are:");foreach(FeatureInfo parentFeature in parentFeatures){ Console.WriteLine($"\t{parentFeature.FeatureClassName}; OID: {parentFeature.ObjectID}");}TopologyNodetopologyNodeViaCampsites12= topologyGraph.FindClosestElement<TopologyNode>(queryPointViaCampsites12, searchRadius);if(topologyNodeViaCampsites12!=null){// There exists a TopologyNode nearest to the query point within searchRadius units.}TopologyEdgetopologyEdgeViaCampsites12= topologyGraph.FindClosestElement<TopologyEdge>(queryPointViaCampsites12, searchRadius);if(topologyEdgeViaCampsites12!=null){// There exists a TopologyEdge nearest to the query point within searchRadius units.}});}}