ArcGIS Pro SDK (八)地理数据库 8 拓扑
文章目录
- [ArcGIS Pro SDK (八)地理数据库 8 拓扑](#ArcGIS Pro SDK (八)地理数据库 8 拓扑)
-
- [1 开放拓扑和进程定义](#1 开放拓扑和进程定义)
- [2 获取拓扑规则](#2 获取拓扑规则)
- [3 验证拓扑](#3 验证拓扑)
- [4 获取拓扑错误](#4 获取拓扑错误)
- [5 标记和不标记为错误](#5 标记和不标记为错误)
- [6 探索拓扑图](#6 探索拓扑图)
- [7 找到最近的元素](#7 找到最近的元素)
环境:Visual Studio 2022 + .NET6 + ArcGIS Pro SDK 3.0
1 开放拓扑和进程定义
csharp
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 获取拓扑规则
csharp
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 验证拓扑
csharp
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 获取拓扑错误
csharp
// 获取当前与拓扑相关的所有错误和异常。
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 标记和不标记为错误
csharp
// 获取所有由于违反"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 探索拓扑图
csharp
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 找到最近的元素
csharp
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。
}
});
}
}