ArcGIS Pro SDK (八)地理數據庫 8 拓撲
文章目錄
- ArcGIS Pro SDK (八)地理數據庫 8 拓撲
- 1 開放拓撲和進程定義
- 2 獲取拓撲規則
- 3 驗證拓撲
- 4 獲取拓撲錯誤
- 5 標記和不標記為錯誤
- 6 探索拓撲圖
- 7 找到最近的元素
環境:Visual Studio 2022 + .NET6 + ArcGIS Pro SDK 3.0
1 開放拓撲和進程定義
public void OpenTopologyAndProcessDefinition()
{// 從文件地理數據庫中打開拓撲并處理拓撲定義。using (Geodatabase geodatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(@"C:\TestData\GrandTeton.gdb"))))using (Topology topology = geodatabase.OpenDataset<Topology>("Backcountry_Topology")){ProcessDefinition(geodatabase, topology);}// 打開要素服務拓撲并處理拓撲定義。const string TOPOLOGY_LAYER_ID = "0";using (Geodatabase geodatabase = new Geodatabase(new ServiceConnectionProperties(new Uri("https://sdkexamples.esri.com/server/rest/services/GrandTeton/FeatureServer"))))using (Topology topology = geodatabase.OpenDataset<Topology>(TOPOLOGY_LAYER_ID)){ProcessDefinition(geodatabase, topology);}
}private void ProcessDefinition(Geodatabase geodatabase, Topology topology)
{// 類似于Core.Data API中其余Definition對象,打開數據集的定義有兩種方式 -- 通過拓撲數據集本身或通過地理數據庫。using (TopologyDefinition definitionViaTopology = topology.GetDefinition()){OutputDefinition(geodatabase, definitionViaTopology);}using (TopologyDefinition definitionViaGeodatabase = geodatabase.GetDefinition<TopologyDefinition>("Backcountry_Topology")){OutputDefinition(geodatabase, definitionViaGeodatabase);}
}private void OutputDefinition(Geodatabase geodatabase, TopologyDefinition topologyDefinition)
{Console.WriteLine($"拓撲聚類容差 => {topologyDefinition.GetClusterTolerance()}");Console.WriteLine($"拓撲Z值聚類容差 => {topologyDefinition.GetZClusterTolerance()}");IReadOnlyList<string> featureClassNames = topologyDefinition.GetFeatureClassNames();Console.WriteLine($"有 {featureClassNames.Count} 個要素類參與了拓撲:");foreach (string name in featureClassNames){// 打開每個參與拓撲的要素類。using (FeatureClass featureClass = geodatabase.OpenDataset<FeatureClass>(name))using (FeatureClassDefinition featureClassDefinition = featureClass.GetDefinition()){Console.WriteLine($"\t{featureClass.GetName()} ({featureClassDefinition.GetShapeType()})");}}
}
2 獲取拓撲規則
using (TopologyDefinition topologyDefinition = topology.GetDefinition())
{IReadOnlyList<TopologyRule> rules = topologyDefinition.GetRules();Console.WriteLine($"拓撲定義了 {rules.Count} 條拓撲規則:");Console.WriteLine("ID \t 源類 \t 源子類 \t 目標類 \t 目標子類 \t 規則類型");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();}
}
3 驗證拓撲
public void ValidateTopology()
{using (Geodatabase geodatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(@"C:\TestData\GrandTeton.gdb"))))using (Topology topology = geodatabase.OpenDataset<Topology>("Backcountry_Topology")){// 如果拓撲當前沒有臟區域,調用Validate()將返回一個空的包絡線。ValidationResult result = topology.Validate(new ValidationDescription(topology.GetExtent()));Console.WriteLine($"在未編輯的拓撲上驗證后的'受影響區域' => {result.AffectedArea.ToJson()}");// 現在創建一個故意違反“PointProperlyInsideArea”拓撲規則的要素。這個動作將創建臟區域。Feature newFeature = null;try{// 獲取Campsite要素類中ObjectID為2的要素。然后從這個要素稍微修改后創建一個新的幾何體,并用它創建一個新的要素。using (Feature featureViaCampsites2 = GetFeature(geodatabase, "Campsites", 2)){Geometry currentGeometry = featureViaCampsites2.GetShape();Geometry newGeometry = GeometryEngine.Instance.Move(currentGeometry, (currentGeometry.Extent.XMax / 8),(currentGeometry.Extent.YMax / 8));using (FeatureClass campsitesFeatureClass = featureViaCampsites2.GetTable())using (FeatureClassDefinition definition = campsitesFeatureClass.GetDefinition())using (RowBuffer rowBuffer = campsitesFeatureClass.CreateRowBuffer()){rowBuffer[definition.GetShapeField()] = newGeometry;geodatabase.ApplyEdits(() =>{newFeature = campsitesFeatureClass.CreateRow(rowBuffer);});}}// 在'Campsites'參與要素類中創建新要素后,拓撲的狀態應為“未分析”,因為尚未驗證。Console.WriteLine($"應用編輯后拓撲狀態 => {topology.GetState()}");// 現在驗證拓撲。結果包絡線對應于臟區域。result = topology.Validate(new ValidationDescription(topology.GetExtent()));Console.WriteLine($"在剛編輯后驗證的拓撲上的'受影響區域' => {result.AffectedArea.ToJson()}");// 在Validate()之后,拓撲的狀態應為“有錯誤的分析”,因為拓撲當前存在錯誤。Console.WriteLine($"驗證拓撲后的拓撲狀態 => {topology.GetState()}");// 如果沒有臟區域,則結果包絡線應為空。result = topology.Validate(new ValidationDescription(topology.GetExtent()));Console.WriteLine($"在剛驗證過的拓撲上的'受影響區域' => {result.AffectedArea.ToJson()}");}finally{if (newFeature != null){geodatabase.ApplyEdits(() =>{newFeature.Delete();});newFeature.Dispose();}}// 刪除新創建的要素后再次驗證。topology.Validate(new ValidationDescription(topology.GetExtent()));}
}private Feature GetFeature(Geodatabase geodatabase, string featureClassName, long objectID)
{using (FeatureClass featureClass = geodatabase.OpenDataset<FeatureClass>(featureClassName)){QueryFilter queryFilter = new QueryFilter(){ObjectIDs = new List<long>() { objectID }};using (RowCursor cursor = featureClass.Search(queryFilter)){System.Diagnostics.Debug.Assert(cursor.MoveNext());return (Feature)cursor.Current;}}
}
4 獲取拓撲錯誤
// 獲取當前與拓撲相關的所有錯誤和異常。IReadOnlyList<TopologyError> allErrorsAndExceptions = topology.GetErrors(new ErrorDescription(topology.GetExtent()));
Console.WriteLine($"錯誤和異常數目 => {allErrorsAndExceptions.Count}");Console.WriteLine("源類名稱 \t 源對象ID \t 目標類名稱 \t 目標對象ID \t 規則類型 \t 是否異常 \t 幾何類型 \t 幾何寬度 & 高度 \t 規則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}");
}
5 標記和不標記為錯誤
// 獲取所有由于違反“PointProperlyInsideArea”拓撲規則而引起的錯誤。using (TopologyDefinition topologyDefinition = topology.GetDefinition())
{TopologyRule pointProperlyInsideAreaRule = topologyDefinition.GetRules().First(rule => rule.RuleType == TopologyRuleType.PointProperlyInsideArea);ErrorDescription errorDescription = new ErrorDescription(topology.GetExtent()){TopologyRule = pointProperlyInsideAreaRule};IReadOnlyList<TopologyError> errorsDueToViolatingPointProperlyInsideAreaRule = topology.GetErrors(errorDescription);Console.WriteLine($"有 {errorsDueToViolatingPointProperlyInsideAreaRule.Count} 個要素違反了'PointProperlyInsideArea'拓撲規則.");// 將違反“PointProperlyInsideArea”拓撲規則的所有錯誤標記為異常。foreach (TopologyError error in errorsDueToViolatingPointProperlyInsideAreaRule){topology.MarkAsException(error);}// 現在驗證所有違反“PointProperlyInsideArea”拓撲規則的錯誤是否確實已標記為異常。//// 默認情況下,ErrorDescription初始化為ErrorType.ErrorAndException。在這里我們想要ErrorType.ErrorOnly。errorDescription = new ErrorDescription(topology.GetExtent()){ErrorType = ErrorType.ErrorOnly,TopologyRule = pointProperlyInsideAreaRule};IReadOnlyList<TopologyError> errorsAfterMarkedAsExceptions = topology.GetErrors(errorDescription);Console.WriteLine($"在將所有錯誤標記為異常后,有 {errorsAfterMarkedAsExceptions.Count} 個要素違反了'PointProperlyInsideArea'拓撲規則.");// 最后,通過取消標記為異常將所有異常重置為錯誤。foreach (TopologyError error in errorsDueToViolatingPointProperlyInsideAreaRule){topology.UnmarkAsException(error);}IReadOnlyList<TopologyError> errorsAfterUnmarkedAsExceptions = topology.GetErrors(errorDescription);Console.WriteLine($"在將所有異常重置為錯誤后,有 {errorsAfterUnmarkedAsExceptions.Count} 個要素違反了'PointProperlyInsideArea'拓撲規則.");
}
6 探索拓撲圖
public void ExploreTopologyGraph()
{using (Geodatabase geodatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(@"C:\TestData\GrandTeton.gdb"))))using (Topology topology = geodatabase.OpenDataset<Topology>("Backcountry_Topology")){// 使用拓撲數據集的范圍構建拓撲圖。topology.BuildGraph(topology.GetExtent(),(topologyGraph) =>{using (Feature campsites12 = GetFeature(geodatabase, "Campsites", 12)){IReadOnlyList<TopologyNode> topologyNodesViaCampsites12 = topologyGraph.GetNodes(campsites12);TopologyNode topologyNodeViaCampsites12 = 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){TopologyNode fromNode = edgeConnectedToNodeViaCampsites12.GetFromNode();TopologyNode toNode = edgeConnectedToNodeViaCampsites12.GetToNode();bool fromNodeIsTheSameAsTopologyNodeViaCampsites12 = (fromNode == topologyNodeViaCampsites12);bool toNodeIsTheSameAsTopologyNodeViaCampsites12 = (toNode == topologyNodeViaCampsites12);System.Diagnostics.Debug.Assert(fromNodeIsTheSameAsTopologyNodeViaCampsites12 || toNodeIsTheSameAsTopologyNodeViaCampsites12,"連接到'topologyNodeViaCampsites12'的每個邊的FromNode或ToNode應與'topologyNodeViaCampsites12'本身相同。");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);}}}});}
}private void EnsureShapeIsNotEmpty(FeatureInfo featureInfo)
{using (Feature feature = featureInfo.GetFeature()){System.Diagnostics.Debug.Assert(!feature.GetShape().IsEmpty, "要素的形狀不應為空。");}
}
7 找到最近的元素
public void FindClosestElement()
{using (Geodatabase geodatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(@"C:\TestData\GrandTeton.gdb"))))using (Topology topology = geodatabase.OpenDataset<Topology>("Backcountry_Topology")){// 使用拓撲數據集的范圍構建拓撲圖。topology.BuildGraph(topology.GetExtent(), (topologyGraph) =>{MapPoint queryPointViaCampsites12 = null;using (Feature campsites12 = GetFeature(geodatabase, "Campsites", 12)){queryPointViaCampsites12 = campsites12.GetShape() as MapPoint;}double searchRadius = 1.0;TopologyElement topologyElementViaCampsites12 = topologyGraph.FindClosestElement<TopologyElement>(queryPointViaCampsites12, searchRadius);System.Diagnostics.Debug.Assert(topologyElementViaCampsites12 != null, "在searchRadius范圍內應該有一個與'queryPointViaCampsites12'對應的拓撲元素.");IReadOnlyList<FeatureInfo> parentFeatures = topologyElementViaCampsites12.GetParentFeatures();Console.WriteLine("生成'topologyElementViaCampsites12'的父要素:");foreach (FeatureInfo parentFeature in parentFeatures){Console.WriteLine($"\t{parentFeature.FeatureClassName}; OID: {parentFeature.ObjectID}");}TopologyNode topologyNodeViaCampsites12 = topologyGraph.FindClosestElement<TopologyNode>(queryPointViaCampsites12, searchRadius);if (topologyNodeViaCampsites12 != null){// 在searchRadius單位內存在一個最近的TopologyNode。}TopologyEdge topologyEdgeViaCampsites12 = topologyGraph.FindClosestElement<TopologyEdge>(queryPointViaCampsites12, searchRadius);if (topologyEdgeViaCampsites12 != null){// 在searchRadius單位內存在一個最近的TopologyEdge。}});}
}