publicvoidOpenTopologyAndProcessDefinition(){// Open a geodatabase topology from a file geodatabase and process the topology definition.using(Geodatabasegeodatabase=newGeodatabase(newFileGeodatabaseConnectionPath(newUri(@"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=newGeodatabase(newServiceConnectionProperties(newUri("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(stringnameinfeatureClassNames){// 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(TopologyRuleruleinrules){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=newGeodatabase(newFileGeodatabaseConnectionPath(newUri(@"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(newValidationDescription(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(newValidationDescription(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(newValidationDescription(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(newValidationDescription(topology.GetExtent()));}}privateFeatureGetFeature(Geodatabasegeodatabase,stringfeatureClassName,longobjectID){using(FeatureClassfeatureClass=geodatabase.OpenDataset<FeatureClass>(featureClassName)){QueryFilterqueryFilter=newQueryFilter(){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(newErrorDescription(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(TopologyErrorerrorinallErrorsAndExceptions){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=newErrorDescription(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(TopologyErrorerrorinerrorsDueToViolatingPointProperlyInsideAreaRule){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=newErrorDescription(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(TopologyErrorerrorinerrorsDueToViolatingPointProperlyInsideAreaRule){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=newGeodatabase(newFileGeodatabaseConnectionPath(newUri(@"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(TopologyEdgeedgeConnectedToNodeViaCampsites12inallEdgesConnectedToNodeViaCampsites12){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(FeatureInfofeatureInfoinleftParentFeaturesBoundedByEdge){System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(featureInfo.FeatureClassName));System.Diagnostics.Debug.Assert(featureInfo.ObjectID>0);EnsureShapeIsNotEmpty(featureInfo);}IReadOnlyList<FeatureInfo>leftParentFeaturesNotBoundedByEdge=edgeConnectedToNodeViaCampsites12.GetLeftParentFeatures(false);foreach(FeatureInfofeatureInfoinleftParentFeaturesNotBoundedByEdge){System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(featureInfo.FeatureClassName));System.Diagnostics.Debug.Assert(featureInfo.ObjectID>0);EnsureShapeIsNotEmpty(featureInfo);}IReadOnlyList<FeatureInfo>rightParentFeaturesBoundedByEdge=edgeConnectedToNodeViaCampsites12.GetRightParentFeatures();foreach(FeatureInfofeatureInfoinrightParentFeaturesBoundedByEdge){System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(featureInfo.FeatureClassName));System.Diagnostics.Debug.Assert(featureInfo.ObjectID>0);EnsureShapeIsNotEmpty(featureInfo);}IReadOnlyList<FeatureInfo>rightParentFeaturesNotBoundedByEdge=edgeConnectedToNodeViaCampsites12.GetRightParentFeatures(false);foreach(FeatureInfofeatureInfoinrightParentFeaturesNotBoundedByEdge){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=newGeodatabase(newFileGeodatabaseConnectionPath(newUri(@"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()asMapPoint;}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(FeatureInfoparentFeatureinparentFeatures){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.}});}}