You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
10 KiB
273 lines
10 KiB
using ESRI.ArcGIS.SystemUI; |
|
using NetTopologySuite.Geometries; |
|
using NetTopologySuite.Index.Strtree; |
|
using NetTopologySuite.Operation.Overlay.Snap; |
|
using NetTopologySuite.Operation.Union; |
|
using System; |
|
using System.Collections.Concurrent; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Threading.Tasks; |
|
|
|
namespace Kingo.PluginServiceInterface.Helper |
|
{ |
|
|
|
/// <summary> |
|
/// 边界吸附处理器 |
|
/// </summary> |
|
public static class BoundarySnapper |
|
{ |
|
/// <summary> |
|
/// 执行边界吸附操作 |
|
/// </summary> |
|
/// <param name="inputParcels">输入图斑集合</param> |
|
/// <param name="tolerance">吸附容差(单位与坐标一致)</param> |
|
/// <returns>修复后的图斑集合</returns> |
|
public static List<Geometry> SnapBoundaries(List<Geometry> inputParcels, double tolerance = 0.001) |
|
{ |
|
// 创建空间索引加速邻近查询 |
|
var spatialIndex = new STRtree<Geometry>(); |
|
foreach (var parcel in inputParcels) |
|
{ |
|
spatialIndex.Insert(parcel.EnvelopeInternal, parcel); |
|
} |
|
spatialIndex.Build(); |
|
|
|
// 复制输入数据以避免修改原始集合 |
|
var outputParcels = new List<Geometry>(inputParcels); |
|
|
|
// 遍历每个图斑进行边界吸附 |
|
foreach (var currentParcel in inputParcels) |
|
{ |
|
// 查询可能与当前图斑相邻的图斑 |
|
var nearbyCandidates = spatialIndex.Query(currentParcel.EnvelopeInternal); |
|
|
|
// 过滤出实际接触的相邻图斑 |
|
var neighbors = nearbyCandidates |
|
.Where(p => p != currentParcel && p.Touches(currentParcel)) |
|
.ToList(); |
|
|
|
foreach (var neighbor in neighbors) |
|
{ |
|
// 使用NTS的GeometrySnapper执行吸附操作 |
|
var snapper = new GeometrySnapper(currentParcel); |
|
Geometry snappedGeometry = (Geometry)snapper.SnapTo(neighbor, tolerance); |
|
|
|
// 如果几何体发生改变,则更新结果集 |
|
if (!snappedGeometry.EqualsExact(currentParcel)) |
|
{ |
|
outputParcels.Remove(currentParcel); |
|
outputParcels.Add(snappedGeometry); |
|
break; // 处理第一个匹配的邻居后跳出循环 |
|
} |
|
} |
|
} |
|
|
|
return outputParcels; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary> |
|
/// 国土变更调查拓扑修复工具 |
|
/// 功能:边界对齐 + 重叠消除 + 拓扑验证 |
|
/// </summary> |
|
public class TopologyRepairEngine |
|
{ |
|
#region 公开接口 |
|
/// <summary> |
|
/// 执行完整拓扑修复流程 |
|
/// </summary> |
|
/// <param name="inputGeometries">输入图斑集合</param> |
|
/// <param name="tolerance">拓扑容差(单位与坐标系一致)</param> |
|
/// <param name="maxIterations">最大修复迭代次数</param> |
|
/// <returns>修复后的拓扑一致图斑集合</returns> |
|
public static List<Geometry> FullRepair(List<Geometry> inputGeometries, double tolerance = 0.001, int maxIterations = 10) |
|
{ |
|
// 阶段1:边界对齐处理 |
|
var snapped = SnapBoundaries(inputGeometries, tolerance, maxIterations); |
|
// 阶段2:全局重叠消除 |
|
var cleaned = ResolveOverlaps(snapped, tolerance); |
|
//// 阶段3:最终验证 |
|
//if (!ValidateTopology(cleaned, tolerance)) |
|
// throw new TopologyException("拓扑修复失败,存在未解决的拓扑错误"); |
|
return cleaned; |
|
} |
|
#endregion |
|
|
|
#region 核心算法 |
|
/// <summary> |
|
/// 多轮次边界吸附处理 |
|
/// </summary> |
|
private static List<Geometry> SnapBoundaries(List<Geometry> input, double tolerance, int maxIterations) |
|
{ |
|
List<Geometry> workingSet = input.Select(g => (Geometry)g.Copy()).ToList(); |
|
bool hasChanges; |
|
int iteration = 0; |
|
do |
|
{ |
|
hasChanges = false; |
|
var spatialIndex = BuildSTRTree(workingSet); |
|
var nextGeneration = new List<Geometry>(); |
|
foreach (var current in workingSet) |
|
{ |
|
var neighbors = spatialIndex.Query(current.EnvelopeInternal) |
|
.Where(g => g != current && g.Touches(current)) |
|
.ToList(); |
|
Geometry processed = current; |
|
foreach (var neighbor in neighbors) |
|
{ |
|
var snapper = new GeometrySnapper(processed); |
|
Geometry snapped = (Geometry)snapper.SnapTo(neighbor, CalculateDynamicTolerance(tolerance, iteration)); |
|
if (!snapped.EqualsTopologically(processed)) |
|
{ |
|
processed = ValidateGeometry(snapped); |
|
hasChanges = true; |
|
break; // 单次只处理一个邻居 |
|
} |
|
} |
|
nextGeneration.Add(processed); |
|
} |
|
workingSet = nextGeneration; |
|
iteration++; |
|
|
|
} while (hasChanges && iteration < maxIterations); |
|
return workingSet; |
|
} |
|
|
|
/// <summary> |
|
/// 全局重叠消除处理 |
|
/// </summary> |
|
private static List<Geometry> ResolveOverlaps(List<Geometry> geometries, double tolerance) |
|
{ |
|
var overlaps = DetectOverlaps(geometries, tolerance); |
|
if (overlaps.Count == 0) return geometries; |
|
var validOverlaps = overlaps |
|
.Where(g => g is Polygon&&!g.IsEmpty) // 仅保留多边形 |
|
.Where(g => g.IsValid)// 检查有效性 |
|
.Cast<GeoAPI.Geometries.IGeometry>() |
|
.ToList(); |
|
var union = CascadedPolygonUnion.Union(validOverlaps); |
|
if(union==null) return geometries; |
|
var result = new List<Geometry>(); |
|
|
|
foreach (var geom in geometries) |
|
{ |
|
if (geom.Intersects(union)) |
|
{ |
|
var diff = geom.Difference(union); |
|
result.AddRange(ExtractValidPolygons((Geometry)diff)); |
|
} |
|
else |
|
{ |
|
result.Add(geom); |
|
} |
|
} |
|
// 递归处理残留重叠 |
|
return ResolveOverlaps(result, tolerance); // 收紧容差 |
|
} |
|
#endregion |
|
|
|
#region 辅助方法 |
|
/// <summary> |
|
/// 构建空间索引 |
|
/// </summary> |
|
private static STRtree<Geometry> BuildSTRTree(IEnumerable<Geometry> geometries) |
|
{ |
|
var tree = new STRtree<Geometry>(); |
|
foreach (var g in geometries) tree.Insert(g.EnvelopeInternal, g); |
|
tree.Build(); |
|
return tree; |
|
} |
|
|
|
/// <summary> |
|
/// 动态容差计算(随迭代次数衰减) |
|
/// </summary> |
|
private static double CalculateDynamicTolerance(double baseTolerance, int iteration) |
|
=> baseTolerance * Math.Pow(0.9, iteration); |
|
|
|
/// <summary> |
|
/// 几何体有效性检查与修复 |
|
/// </summary> |
|
private static Geometry ValidateGeometry(Geometry geom) |
|
{ |
|
if (!geom.IsValid) |
|
{ |
|
// 使用缓冲区法修复常见错误 |
|
var buffered = geom.Buffer(0); |
|
return buffered.IsEmpty ? (Geometry)geom : (Geometry)buffered; |
|
} |
|
return geom; |
|
} |
|
|
|
/// <summary> |
|
/// 检测所有重叠区域 |
|
/// </summary> |
|
private static List<Geometry> DetectOverlaps(List<Geometry> geometries, double minArea) |
|
{ |
|
var overlaps = new List<Geometry>(); |
|
var index = BuildSTRTree(geometries); |
|
|
|
foreach (var geom in geometries) |
|
{ |
|
foreach (var other in index.Query(geom.EnvelopeInternal) |
|
.Where(g => g != geom && g.Intersects(geom))) |
|
{ |
|
var overlap = geom.Intersection(other); |
|
if (overlap.Area > minArea * minArea) |
|
overlaps.Add((Geometry)overlap); |
|
} |
|
} |
|
return overlaps; |
|
} |
|
|
|
/// <summary> |
|
/// 从几何集合中提取有效多边形 |
|
/// </summary> |
|
private static IEnumerable<Geometry> ExtractValidPolygons(Geometry geom) |
|
{ |
|
if (geom is Polygon p) return new[] { p }; |
|
if (geom is GeometryCollection coll) |
|
return coll.Geometries |
|
.Where(g => g is Polygon) |
|
.Select(g => ValidateGeometry((Geometry)g)); |
|
return Enumerable.Empty<Geometry>(); |
|
} |
|
#endregion |
|
|
|
#region 验证逻辑 |
|
/// <summary> |
|
/// 拓扑一致性验证 |
|
/// </summary> |
|
public static bool ValidateTopology(IEnumerable<Geometry> geometries, double tolerance) |
|
{ |
|
var index = BuildSTRTree(geometries); |
|
return geometries.All(g => |
|
{ |
|
// 检查自身有效性 |
|
if (!g.IsValid) return false; |
|
|
|
// 检查相邻关系 |
|
return index.Query(g.EnvelopeInternal) |
|
.Where(other => g != other) |
|
.All(other => |
|
!g.Intersects(other) || |
|
(g.Touches(other) && g.Boundary.Distance(other.Boundary) <= tolerance) |
|
); |
|
}); |
|
} |
|
#endregion |
|
} |
|
|
|
/// <summary> |
|
/// 自定义拓扑异常 |
|
/// </summary> |
|
public class TopologyException : Exception |
|
{ |
|
public TopologyException(string message) : base(message) { } |
|
} |
|
|
|
} |