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.
811 lines
36 KiB
811 lines
36 KiB
using System; |
|
using System.Collections.Concurrent; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Runtime.InteropServices; |
|
using System.Threading.Tasks; |
|
using ESRI.ArcGIS.esriSystem; |
|
using ESRI.ArcGIS.Geometry; |
|
using KGIS.Framework.Utils.ExtensionMethod; |
|
|
|
namespace Kingo.PluginServiceInterface.Helper |
|
{ |
|
/// <summary> |
|
/// 多边形边界对齐工具(针对ArcEngine 10.2.2优化版本) |
|
/// 主要功能:调整目标多边形的顶点坐标,使其与相邻几何体的边界对齐 |
|
/// 优化重点:空间索引结构、COM对象管理、批量坐标操作 |
|
/// </summary> |
|
public static class PolygonBoundaryAligner |
|
{ |
|
#region 公共常量与接口 |
|
private const double DEFAULT_TOLERANCE = 0.001; // 默认捕捉容差(地图单位) |
|
private static readonly object _syncRoot = new object(); // 线程安全锁 |
|
#endregion |
|
|
|
#region 主入口方法 |
|
|
|
/// <summary> |
|
/// 对齐多边形边界的主方法 |
|
/// </summary> |
|
/// <param name="target">目标多边形</param> |
|
/// <param name="adjacentGeometries">相邻几何体集合</param> |
|
/// <param name="tolerance">捕捉容差(默认0.001)</param> |
|
/// <returns>调整后的多边形</returns> |
|
public static IGeometry AlignPolygonBoundary(IGeometry target, List<IGeometry> adjacentGeometries, double tolerance = DEFAULT_TOLERANCE) |
|
{ |
|
// 参数校验 |
|
ValidateParameters(target, adjacentGeometries); |
|
// 克隆原始几何体(避免修改输入参数) |
|
IPolygon originalPolygon = (IPolygon)((IClone)target).Clone(); |
|
IPolygon newPolygon = new PolygonClass(); |
|
newPolygon.SpatialReference = originalPolygon.SpatialReference; |
|
try |
|
{ |
|
// 步骤1:收集相邻几何体的边界要素 |
|
List<IGeometry> adjacentBoundaries = CollectAdjacentBoundaries(adjacentGeometries); |
|
// 步骤2:构建空间索引(顶点索引和线段索引) |
|
ITopologicalOperator topological = originalPolygon as ITopologicalOperator; |
|
var (vertexIndex, segmentIndex) = BuildSpatialIndexes(adjacentBoundaries, topological.Boundary, tolerance); |
|
if (vertexIndex.IsEmpty && segmentIndex.IsEmpty) |
|
{ |
|
return target; |
|
//(vertexIndex, segmentIndex) = BuildSpatialIndexes(adjacentBoundaries, tolerance); |
|
} |
|
// 步骤3:调整多边形顶点 |
|
//IPointCollection points = (IPointCollection)originalPolygon; |
|
List<IPointCollection> points = new List<IPointCollection>(); |
|
IGeometryCollection geometryCollection = (IGeometryCollection)originalPolygon; |
|
for (int i = 0; i < geometryCollection.GeometryCount; i++) |
|
{ |
|
IRing ring = (IRing)geometryCollection.get_Geometry(i); |
|
IPointCollection ringPoints = (IPointCollection)ring; |
|
points.Add(ringPoints); |
|
} |
|
newPolygon = AdjustWithLegacyMethod(points, vertexIndex, segmentIndex, tolerance, adjacentBoundaries); |
|
// 步骤4:拓扑验证与简化 |
|
EnsureTopologyValidity(ref newPolygon); |
|
return (IGeometry)newPolygon; |
|
} |
|
catch (Exception ex) |
|
{ |
|
throw ex; |
|
} |
|
finally |
|
{ |
|
// 显式释放COM对象(重要!防止内存泄漏) |
|
SafeReleaseComObject(originalPolygon); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region 核心处理逻辑 |
|
|
|
/// <summary> |
|
/// 获取调整后的所有环(外环+内环) |
|
/// </summary> |
|
private static IEnumerable<IPolygon> GetAdjustedRings(IPolygon polygon, VertexSpatialIndex vertexIndex, SegmentSpatialIndex segmentIndex, double tolerance, List<IGeometry> boundaries) |
|
{ |
|
IGeometryCollection geomColl = (IGeometryCollection)polygon; |
|
for (int i = 0; i < geomColl.GeometryCount; i++) |
|
{ |
|
if (geomColl.get_Geometry(i) is IRing ring && !ring.IsEmpty) |
|
{ |
|
yield return AdjustRingVertices(ring, vertexIndex, segmentIndex, tolerance, boundaries); |
|
} |
|
} |
|
} |
|
private static IPolygon AdjustRingVertices(IRing ring, VertexSpatialIndex vertexIndex, SegmentSpatialIndex segmentIndex, double tolerance, List<IGeometry> boundaries) |
|
{ |
|
// 获取点集合接口(兼容10.2.2) |
|
IPointCollection points = (IPointCollection)ring; |
|
return AdjustWithLegacyMethod(new List<IPointCollection> { points }, vertexIndex, segmentIndex, tolerance, boundaries); |
|
} |
|
|
|
/// <summary> |
|
/// 兼容方法:传统逐个点操作(ArcEngine 10.0 以下备用) |
|
/// </summary> |
|
private static IPolygon AdjustWithLegacyMethod(List<IPointCollection> allPoints, VertexSpatialIndex vertexIndex, SegmentSpatialIndex segmentIndex, double tolerance, List<IGeometry> boundaries) |
|
{ |
|
IPolygon newPolygon = new PolygonClass(); |
|
IGeometryCollection newGeomColl = (IGeometryCollection)newPolygon; |
|
for (int k = 0; k < allPoints.Count; k++) |
|
{ |
|
int pointCount = allPoints[k].PointCount; |
|
bool modified = false; |
|
var coords = new List<(double X, double Y)>(); |
|
for (int i = 0; i < pointCount; i++) |
|
{ |
|
IPoint p = allPoints[k].get_Point(i); |
|
//string aa1 = GeometryConvertHelper.ConvertIGeoemtryToWKT(p); |
|
try |
|
{ |
|
coords.Add((p.X, p.Y)); |
|
} |
|
finally |
|
{ |
|
Marshal.ReleaseComObject(p); |
|
} |
|
} |
|
// 坐标调整逻辑 |
|
Dictionary<int, (double X, double Y)> keyValuePairs = new Dictionary<int, (double X, double Y)>(); |
|
for (int i = 0; i < coords.Count; i++) |
|
{ |
|
var (x, y) = coords[i]; |
|
var snappedPoints = vertexIndex.FindAllVertices(x, y, tolerance).Distinct().ToList(); |
|
if (snappedPoints != null && snappedPoints.Count == 2) |
|
{ |
|
double distSq0 = (coords[i].X - snappedPoints[0].X) * (coords[i].X - snappedPoints[0].X) + (coords[i].Y - snappedPoints[0].Y) * (coords[i].Y - snappedPoints[0].Y); |
|
double distSq1 = (coords[i].X - snappedPoints[1].X) * (coords[i].X - snappedPoints[1].X) + (coords[i].Y - snappedPoints[1].Y) * (coords[i].Y - snappedPoints[1].Y); |
|
if (distSq0 > distSq1) |
|
{ |
|
coords[i] = snappedPoints[1]; |
|
keyValuePairs.Add(i, snappedPoints[0]); |
|
} |
|
else |
|
{ |
|
coords[i] = snappedPoints[0]; |
|
keyValuePairs.Add(i, snappedPoints[1]); |
|
} |
|
continue; |
|
} |
|
var snappedPoint = vertexIndex.FindClosestVertex(x, y, tolerance); |
|
if (snappedPoint == null) |
|
{ |
|
snappedPoint = segmentIndex.FindClosestPoint(x, y, tolerance); |
|
} |
|
else |
|
{ |
|
var currents = coords.Where(current => current.X != x && current.Y != y).ToList(); |
|
var coordscoords = currents.FirstOrDefault(kvp => Math.Abs(kvp.X - x) < 0.001 && Math.Abs(kvp.Y - y) < 0.001); |
|
if ((coords.Contains(snappedPoint.Value) && coords[0] != snappedPoint.Value) || (coordscoords.X != 0 && coordscoords.Y != 0)) |
|
{ |
|
snappedPoint = segmentIndex.FindClosestPoint(x, y, tolerance); |
|
} |
|
} |
|
if (snappedPoint.HasValue) |
|
{ |
|
if (coords.Contains(snappedPoint.Value) && coords[0] != snappedPoint.Value) |
|
{ |
|
int firstIndex = coords.Select((item, idx) => new { item, idx }). |
|
FirstOrDefault(pair => pair.item.X == snappedPoint.Value.X && pair.item.Y == snappedPoint.Value.Y)?.idx ?? -1; |
|
if (firstIndex != -1 && i > firstIndex) |
|
{ |
|
coords[i] = (0, 0); |
|
continue; |
|
} |
|
} |
|
var currents = coords.Where(current => current.X != x && current.Y != y).ToList(); |
|
var coordscoords = currents.FirstOrDefault(kvp => Math.Abs(kvp.X - x) < 0.1 && Math.Abs(kvp.Y - y) < 0.1); |
|
//var dSnappedPoint = CalculateDistance(snappedPoint.Value.X, snappedPoint.Value.Y, x, y); |
|
//var dCoordscoords = CalculateDistance(coordscoords.X, coordscoords.Y, x, y); |
|
//var gap = Math.Abs(dSnappedPoint - dCoordscoords); |
|
//if (coordscoords.X != 0 && coordscoords.Y != 0 && dSnappedPoint > dCoordscoords && gap > 0.001) |
|
// continue; |
|
coords[i] = snappedPoint.Value; |
|
modified = true; |
|
} |
|
} |
|
// 更新坐标 |
|
if (modified) |
|
{ |
|
allPoints[k].RemovePoints(0, pointCount); |
|
IPoint newPoint = new PointClass(); |
|
if (keyValuePairs.Count > 0) |
|
{ |
|
foreach (var item in keyValuePairs) |
|
{ |
|
coords.Insert(item.Key, item.Value); |
|
} |
|
} |
|
foreach (var (X, Y) in coords) |
|
{ |
|
if (X == 0 || Y == 0) continue; |
|
newPoint.PutCoords(X, Y); |
|
allPoints[k].AddPoint(newPoint); |
|
} |
|
Marshal.ReleaseComObject(newPoint); |
|
} |
|
newGeomColl.AddGeometry((IGeometry)allPoints[k]); |
|
string aa = GeometryConvertHelper.ConvertIGeoemtryToWKT(newPolygon); |
|
Addnode(newPolygon, boundaries); |
|
} |
|
return newPolygon; |
|
//IGeometryCollection geomColl = (IGeometryCollection)newPolygon; |
|
//for (int i = 0; i < geomColl.GeometryCount; i++) |
|
//{ |
|
// IGeometry geometry = geomColl.get_Geometry(i); |
|
// IRing ring1 = geometry as IRing; |
|
// if (ring1 == null || ring1.IsEmpty || ring1.Length < 0.01) continue; |
|
// return ring1; |
|
//} |
|
//return null; |
|
} |
|
public static double CalculateDistance(double x1, double y1, double x2, double y2) |
|
{ |
|
// 计算坐标差值的平方和 |
|
double deltaX = x2 - x1; |
|
double deltaY = y2 - y1; |
|
double distanceSquared = deltaX * deltaX + deltaY * deltaY; |
|
// 开平方根得到距离(使用Math.Sqrt) |
|
return Math.Sqrt(distanceSquared); |
|
} |
|
#endregion |
|
|
|
#region 空间索引构建 |
|
|
|
private static (VertexSpatialIndex vertexIndex, SegmentSpatialIndex segmentIndex) BuildSpatialIndexes(List<IGeometry> boundaries, IGeometry targetBoundary, double tolerance) |
|
{ |
|
double xRange = targetBoundary.Envelope.XMax - targetBoundary.Envelope.XMin; |
|
double yRange = targetBoundary.Envelope.YMax - targetBoundary.Envelope.YMin; |
|
int totalVertices = boundaries.Sum(g => (g as IPointCollection)?.PointCount ?? 0); |
|
double density = totalVertices / (xRange * yRange); // 顶点密度(点/单位面积) |
|
double baseCellSize = Math.Max(xRange, yRange) / 100; |
|
double cellSize = density > 100 ? baseCellSize / 2 : // 高密度:缩小网格 |
|
density < 10 ? baseCellSize * 2 : // 低密度:扩大网格 |
|
baseCellSize; |
|
var vertexIndex = new VertexSpatialIndex(cellSize); |
|
var segmentIndex = new SegmentSpatialIndex(cellSize); |
|
double maxDistance = tolerance * 20; // 设定最大距离阈值 |
|
int threshold = Math.Max(Environment.ProcessorCount * 100, 1000); // 最小阈值1000 |
|
foreach (var geom in boundaries) |
|
{ |
|
if (geom is IPointCollection pointColl) |
|
{ |
|
if (pointColl.PointCount > threshold) |
|
{ |
|
Parallel.For(0, pointColl.PointCount, i => |
|
{ |
|
IPoint point = pointColl.get_Point(i); |
|
try |
|
{ |
|
double distance = CalculateDistance(point, targetBoundary); |
|
if (distance <= maxDistance) |
|
{ |
|
vertexIndex.AddPoint(point); // 线程安全 |
|
} |
|
} |
|
finally |
|
{ |
|
Marshal.ReleaseComObject(point); |
|
} |
|
}); |
|
} |
|
else |
|
{ |
|
for (int i = 0; i < pointColl.PointCount; i++) |
|
{ |
|
IPoint point = pointColl.get_Point(i); |
|
try |
|
{ |
|
// 计算点到目标边界的距离 |
|
double distance = CalculateDistance(point, targetBoundary); |
|
if (distance <= maxDistance) |
|
{ |
|
vertexIndex.AddPoint(point); |
|
} |
|
} |
|
finally |
|
{ |
|
Marshal.ReleaseComObject(point); |
|
} |
|
} |
|
} |
|
} |
|
if (geom is ISegmentCollection segColl) |
|
{ |
|
for (int i = 0; i < segColl.SegmentCount; i++) |
|
{ |
|
ISegment segment = segColl.get_Segment(i); |
|
try |
|
{ |
|
IPolyline polyline1 = new PolylineClass(); |
|
polyline1.FromPoint = segment.FromPoint; |
|
polyline1.ToPoint = segment.ToPoint; |
|
polyline1.SpatialReference = targetBoundary.SpatialReference; |
|
double distancepolyline1 = CalculateDistance(polyline1, targetBoundary); |
|
if (distancepolyline1 <= maxDistance && distancepolyline1 >= 0) |
|
{ |
|
segmentIndex.AddSegment(segment); |
|
} |
|
} |
|
catch |
|
{ |
|
|
|
} |
|
finally |
|
{ |
|
Marshal.ReleaseComObject(segment); |
|
} |
|
} |
|
} |
|
} |
|
return (vertexIndex, segmentIndex); |
|
} |
|
// 计算几何体到目标边界的距离 |
|
private static double CalculateDistance(IGeometry geom, IGeometry targetBoundary) |
|
{ |
|
if (geom == null || targetBoundary == null) return double.MaxValue; |
|
//if (geom is IPoint point && targetBoundary is IPolygon polygon) |
|
//{ |
|
// return CalculateDistanceLocal(point, polygon); // 本地计算 |
|
//} |
|
// 其他类型回退到COM调用 |
|
try |
|
{ |
|
IProximityOperator proximityOp = (IProximityOperator)targetBoundary; |
|
return proximityOp.ReturnDistance(geom); |
|
} |
|
catch |
|
{ |
|
return double.MaxValue; |
|
} |
|
} |
|
#endregion |
|
|
|
#region 空间索引实现类 |
|
|
|
/// <summary> |
|
/// 顶点空间索引(使用双层字典的网格索引) |
|
/// 优化点:提高网格查询速度,减少距离计算次数 |
|
/// </summary> |
|
private class VertexSpatialIndex |
|
{ |
|
private readonly Dictionary<long, Dictionary<long, List<(double X, double Y)>>> _grid; |
|
private readonly double _cellSize; |
|
private int _totalPoints = 0; |
|
public bool IsEmpty => _totalPoints == 0; |
|
public VertexSpatialIndex(double cellSize) |
|
{ |
|
_cellSize = cellSize; |
|
_grid = new Dictionary<long, Dictionary<long, List<(double X, double Y)>>>(); |
|
} |
|
|
|
/// <summary> |
|
/// 添加顶点到索引(自动网格化) |
|
/// </summary> |
|
public void AddPoint(IPoint point) |
|
{ |
|
long xCell = (long)(point.X / _cellSize); |
|
long yCell = (long)(point.Y / _cellSize); |
|
lock (_syncRoot) |
|
{ |
|
if (!_grid.TryGetValue(xCell, out var yDict)) |
|
{ |
|
yDict = new Dictionary<long, List<(double X, double Y)>>(); |
|
_grid[xCell] = yDict; |
|
} |
|
if (!yDict.TryGetValue(yCell, out var points)) |
|
{ |
|
points = new List<(double X, double Y)>(); |
|
yDict[yCell] = points; |
|
} |
|
// 去重逻辑:检查容差范围内是否已有重复点 |
|
bool isDuplicate = false; |
|
double toleranceSq = _cellSize * _cellSize; // 基于网格精度的容差 |
|
foreach (var p in points) |
|
{ |
|
double dx = p.X - point.X; |
|
double dy = p.Y - point.Y; |
|
if (dx * dx + dy * dy <= toleranceSq) // 平方距离比较(避免开方) |
|
{ |
|
isDuplicate = true; |
|
break; |
|
} |
|
} |
|
if (!isDuplicate) |
|
{ |
|
points.Add((point.X, point.Y)); |
|
_totalPoints++; |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// 查找最近顶点(带容差范围) |
|
/// </summary> |
|
public (double X, double Y)? FindClosestVertex(double x, double y, double tolerance) |
|
{ |
|
long centerX = (long)(x / _cellSize); |
|
long centerY = (long)(y / _cellSize); |
|
int radius = (int)Math.Ceiling(tolerance / _cellSize); |
|
double minDist = tolerance * tolerance; |
|
(double X, double Y)? closest = null; |
|
// 网格搜索范围扩展 |
|
for (long dx = -radius; dx <= radius; dx++) |
|
{ |
|
if (!_grid.TryGetValue(centerX + dx, out var yDict)) continue; |
|
for (long dy = -radius; dy <= radius; dy++) |
|
{ |
|
if (!yDict.TryGetValue(centerY + dy, out var points)) continue; |
|
foreach (var (pX, pY) in points) |
|
{ |
|
double dxVal = pX - x; |
|
double dyVal = pY - y; |
|
double distSq = dxVal * dxVal + dyVal * dyVal; |
|
if (distSq < minDist) |
|
{ |
|
minDist = distSq; |
|
closest = (pX, pY); |
|
} |
|
} |
|
} |
|
} |
|
return closest; |
|
} |
|
|
|
/// <summary> |
|
/// 根据坐标获取相邻范围内的所有点 |
|
/// </summary> |
|
/// <param name="x"></param> |
|
/// <param name="y"></param> |
|
/// <param name="tolerance"></param> |
|
/// <returns></returns> |
|
public List<(double X, double Y)> FindAllVertices(double x, double y, double tolerance) |
|
{ |
|
long centerX = (long)(x / _cellSize); |
|
long centerY = (long)(y / _cellSize); |
|
int radius = (int)Math.Ceiling(tolerance / _cellSize); |
|
var result = new List<(double X, double Y)>(); |
|
double toleranceSq = tolerance * tolerance; |
|
// 遍历邻近网格 |
|
for (long dx = -radius; dx <= radius; dx++) |
|
{ |
|
if (!_grid.TryGetValue(centerX + dx, out var yDict)) continue; |
|
for (long dy = -radius; dy <= radius; dy++) |
|
{ |
|
if (!yDict.TryGetValue(centerY + dy, out var points)) continue; |
|
// 筛选所有容差内的点 |
|
foreach (var (pX, pY) in points) |
|
{ |
|
double distSq = (pX - x) * (pX - x) + (pY - y) * (pY - y); |
|
if (distSq <= toleranceSq) |
|
{ |
|
result.Add((pX, pY)); |
|
} |
|
} |
|
} |
|
} |
|
return result; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// 线段空间索引(使用网格索引+包围盒预计算) |
|
/// 优化点:减少不必要的线段距离计算 |
|
/// </summary> |
|
private class SegmentSpatialIndex |
|
{ |
|
private readonly Dictionary<long, Dictionary<long, List<SegmentData>>> _grid; |
|
private readonly List<SegmentData> _allSegments; |
|
private readonly double _cellSize; |
|
public bool IsEmpty => _grid.Count == 0; |
|
public SegmentSpatialIndex(double cellSize) |
|
{ |
|
_cellSize = cellSize; |
|
_grid = new Dictionary<long, Dictionary<long, List<SegmentData>>>(); |
|
_allSegments = new List<SegmentData>(); |
|
} |
|
|
|
/// <summary> |
|
/// 添加线段到索引(自动计算影响网格) |
|
/// </summary> |
|
public void AddSegment(ISegment segment) |
|
{ |
|
var segData = new SegmentData(segment.FromPoint.X, segment.FromPoint.Y, segment.ToPoint.X, segment.ToPoint.Y); |
|
lock (_syncRoot) |
|
{ |
|
_allSegments.Add(segData); // 全局列表存储引用 |
|
// 计算网格范围 |
|
long minXCell = (long)(Math.Min(segData.StartX, segData.EndX) / _cellSize); |
|
long maxXCell = (long)(Math.Max(segData.StartX, segData.EndX) / _cellSize); |
|
long minYCell = (long)(Math.Min(segData.StartY, segData.EndY) / _cellSize); |
|
long maxYCell = (long)(Math.Max(segData.StartY, segData.EndY) / _cellSize); |
|
// 添加到所有覆盖的网格(引用传递,无副本) |
|
for (long x = minXCell; x <= maxXCell; x++) |
|
{ |
|
for (long y = minYCell; y <= maxYCell; y++) |
|
{ |
|
if (!_grid.TryGetValue(x, out var yDict)) |
|
{ |
|
yDict = new Dictionary<long, List<SegmentData>>(); |
|
_grid[x] = yDict; |
|
} |
|
if (!yDict.TryGetValue(y, out var list)) |
|
{ |
|
list = new List<SegmentData>(); |
|
yDict[y] = list; |
|
} |
|
// 避免重复添加(若外部可能重复调用AddSegment) |
|
if (!list.Contains(segData)) |
|
{ |
|
list.Add(segData); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// 查找最近线段点(两阶段查询:先网格后全局) |
|
/// </summary> |
|
public (double X, double Y)? FindClosestPoint(double x, double y, double tolerance) |
|
{ |
|
long centerX = (long)(x / _cellSize); |
|
long centerY = (long)(y / _cellSize); |
|
int radius = (int)Math.Ceiling(tolerance / _cellSize); |
|
var candidates = new HashSet<SegmentData>(); |
|
double minDist = tolerance * tolerance; |
|
(double X, double Y)? closest = null; |
|
// 阶段1:收集附近网格中的候选线段 |
|
for (long dx = -radius; dx <= radius; dx++) |
|
{ |
|
if (!_grid.TryGetValue(centerX + dx, out var yDict)) continue; |
|
for (long dy = -radius; dy <= radius; dy++) |
|
{ |
|
if (yDict.TryGetValue(centerY + dy, out var segList)) |
|
{ |
|
foreach (var seg in segList) |
|
{ |
|
candidates.Add(seg); |
|
} |
|
} |
|
} |
|
} |
|
// 阶段2:如果附近没有候选,则遍历全部线段 |
|
if (candidates.Count == 0) |
|
{ |
|
candidates = new HashSet<SegmentData>(_allSegments); |
|
} |
|
// 计算最近点 |
|
foreach (var seg in candidates) |
|
{ |
|
var (cx, cy, distSq) = CalculateClosestPoint(x, y, seg); |
|
if (distSq < minDist) |
|
{ |
|
minDist = distSq; |
|
closest = (cx, cy); |
|
} |
|
} |
|
return closest; |
|
} |
|
|
|
/// <summary> |
|
/// 计算点到线段的最短距离(向量投影法) |
|
/// </summary> |
|
private static (double X, double Y, double DistanceSq) CalculateClosestPoint( |
|
double x, double y, SegmentData seg) |
|
{ |
|
double dx = seg.EndX - seg.StartX; |
|
double dy = seg.EndY - seg.StartY; |
|
double lengthSq = dx * dx + dy * dy; |
|
|
|
// 处理零长度线段 |
|
if (lengthSq < 1e-10) |
|
{ |
|
double distSq1 = (x - seg.StartX) * (x - seg.StartX) + (y - seg.StartY) * (y - seg.StartY); |
|
return (seg.StartX, seg.StartY, distSq1); |
|
} |
|
|
|
// 计算投影参数 |
|
double t = ((x - seg.StartX) * dx + (y - seg.StartY) * dy) / lengthSq; |
|
t = Math.Max(0, Math.Min(1, t)); // 限制在线段范围内 |
|
|
|
double projX = seg.StartX + t * dx; |
|
double projY = seg.StartY + t * dy; |
|
double distSq = (x - projX) * (x - projX) + (y - projY) * (y - projY); |
|
|
|
return (projX, projY, distSq); |
|
} |
|
|
|
/// <summary> |
|
/// 线段数据结构(引用类型) |
|
/// </summary> |
|
private sealed class SegmentData |
|
{ |
|
public double StartX { get; } |
|
public double StartY { get; } |
|
public double EndX { get; } |
|
public double EndY { get; } |
|
|
|
public SegmentData(double sx, double sy, double ex, double ey) |
|
{ |
|
StartX = sx; |
|
StartY = sy; |
|
EndX = ex; |
|
EndY = ey; |
|
} |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region 辅助方法 |
|
|
|
/// <summary> |
|
/// 参数有效性校验 |
|
/// </summary> |
|
private static void ValidateParameters(IGeometry target, List<IGeometry> adjacentGeometries) |
|
{ |
|
if (target == null) |
|
throw new ArgumentNullException(nameof(target), "目标几何体不能为空"); |
|
|
|
if (adjacentGeometries == null) |
|
throw new ArgumentNullException(nameof(adjacentGeometries), "相邻几何体集合不能为空"); |
|
|
|
if (target.GeometryType != esriGeometryType.esriGeometryPolygon) |
|
throw new ArgumentException("目标几何体必须是多边形类型"); |
|
} |
|
|
|
/// <summary> |
|
/// 收集相邻几何体的边界要素(多线程安全) |
|
/// </summary> |
|
private static List<IGeometry> CollectAdjacentBoundaries(IEnumerable<IGeometry> geometries) |
|
{ |
|
var boundaries = new ConcurrentBag<IGeometry>(); // 线程安全集合 |
|
Parallel.ForEach(geometries.Where(g => g?.IsEmpty == false), geom => |
|
{ |
|
switch (geom.GeometryType) |
|
{ |
|
case esriGeometryType.esriGeometryPolygon: |
|
var polyColl = (IGeometryCollection)geom; |
|
for (int i = 0; i < polyColl.GeometryCount; i++) |
|
{ |
|
if (polyColl.get_Geometry(i) is IRing ring && !ring.IsEmpty) |
|
{ |
|
ring.SpatialReference = geom.SpatialReference; |
|
boundaries.Add(ring); // 线程安全添加 |
|
} |
|
} |
|
break; |
|
case esriGeometryType.esriGeometryPolyline: |
|
boundaries.Add(geom); |
|
break; |
|
} |
|
}); |
|
return boundaries.ToList(); // 转换为普通列表 |
|
} |
|
|
|
/// <summary> |
|
/// 确保多边形拓扑有效性 |
|
/// </summary> |
|
private static void EnsureTopologyValidity(ref IPolygon polygon) |
|
{ |
|
ITopologicalOperator topoOp = (ITopologicalOperator)polygon; |
|
if (!topoOp.IsSimple) |
|
{ |
|
topoOp.Simplify(); |
|
} |
|
|
|
// 二次验证几何完整性 |
|
if (polygon.IsEmpty) |
|
{ |
|
throw new InvalidOperationException("拓扑简化导致几何体无效"); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// 安全释放COM对象(带空值检查) |
|
/// </summary> |
|
private static void SafeReleaseComObject(object comObj) |
|
{ |
|
if (comObj != null && Marshal.IsComObject(comObj)) |
|
{ |
|
Marshal.ReleaseComObject(comObj); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region MyRegion |
|
private static void Addnode(IPolygon newPolygon, List<IGeometry> adjacentGeometries) |
|
{ |
|
double maxDistance = 0.1; // 设定最大距离阈值 |
|
//待插入节点坐标及索引 |
|
Dictionary<int, (double X, double Y)> insertionPoints = new Dictionary<int, (double X, double Y)>(); |
|
//原始图形的点坐标及索引 |
|
Dictionary<int, (double X, double Y)> originalpoints = new Dictionary<int, (double X, double Y)>(); |
|
List<IPolyline> polylines = new List<IPolyline>(); |
|
var coords = new List<(double X, double Y)>(); |
|
try |
|
{ |
|
IPointCollection points = (IPointCollection)newPolygon; |
|
var pointCount = points.PointCount; |
|
for (int i = 0; i < pointCount; i++) |
|
{ |
|
IPoint p = points.get_Point(i); |
|
try |
|
{ |
|
coords.Add((p.X, p.Y)); |
|
originalpoints.Add(i, (p.X, p.Y)); |
|
} |
|
finally |
|
{ |
|
Marshal.ReleaseComObject(p); |
|
} |
|
} |
|
if (newPolygon is ISegmentCollection segColl) |
|
{ |
|
for (int i = 0; i < segColl.SegmentCount; i++) |
|
{ |
|
ISegment segment = segColl.get_Segment(i); |
|
try |
|
{ |
|
IPolyline polyline1 = new PolylineClass(); |
|
polyline1.FromPoint = segment.FromPoint; |
|
polyline1.ToPoint = segment.ToPoint; |
|
polyline1.SpatialReference = newPolygon.SpatialReference; |
|
polylines.Add(polyline1); |
|
} |
|
finally |
|
{ |
|
Marshal.ReleaseComObject(segment); |
|
} |
|
} |
|
} |
|
bool isbreak = false; |
|
foreach (IGeometry geom in adjacentGeometries) |
|
{ |
|
if (geom is IPointCollection pointColl) |
|
{ |
|
for (int i = 0; i < pointColl.PointCount; i++) |
|
{ |
|
IPoint point = pointColl.get_Point(i); |
|
try |
|
{ |
|
foreach (var polyline in polylines) |
|
{ |
|
double distance = CalculateDistance(point, polyline); |
|
if (distance == 0 || distance > 1) continue; |
|
double distanceToPoint = CalculateDistance(point, polyline.ToPoint); |
|
double distanceFromPoint = CalculateDistance(point, polyline.FromPoint); |
|
if (distance == distanceToPoint || distance == distanceFromPoint) continue; |
|
if ((distance < 0.01 && (distanceToPoint < 0.01) || distanceFromPoint < 0.01)) |
|
continue; |
|
if (distance <= maxDistance && distance.ToDouble(5) > 0.00001) |
|
{ |
|
if (coords.Contains((point.X, point.Y))) continue; |
|
var coordscoords = coords.FirstOrDefault(kvp => Math.Abs(kvp.X - point.X) < 0.1 && Math.Abs(kvp.Y - point.Y) < 0.1); |
|
if (coordscoords.X != 0 && coordscoords.Y != 0) continue; |
|
IPoint ToPoint = polyline.ToPoint; |
|
var keyValuePair2 = originalpoints.FirstOrDefault(kvp => Math.Abs(kvp.Value.X - ToPoint.X) < 0.0001 && Math.Abs(kvp.Value.Y - ToPoint.Y) < 0.0001); |
|
var pointindex = keyValuePair2.Equals(default(KeyValuePair<int, (double X, double Y)>)) ? -1 : keyValuePair2.Key; |
|
if (insertionPoints.ContainsKey(pointindex)) continue; |
|
insertionPoints.Add(pointindex, (point.X, point.Y)); |
|
isbreak = true; |
|
break; |
|
} |
|
} |
|
if (isbreak) break; |
|
} |
|
finally |
|
{ |
|
Marshal.ReleaseComObject(point); |
|
} |
|
} |
|
} |
|
if (isbreak) break; |
|
} |
|
if (insertionPoints.Count > 0) |
|
{ |
|
foreach (var item in insertionPoints) |
|
coords.Insert(item.Key, item.Value); |
|
points.RemovePoints(0, pointCount); |
|
IPoint newPoint = new PointClass(); |
|
foreach (var (X, Y) in coords) |
|
{ |
|
newPoint.PutCoords(X, Y); |
|
points.AddPoint(newPoint); |
|
} |
|
Marshal.ReleaseComObject(newPoint); |
|
Addnode(newPolygon, adjacentGeometries); |
|
} |
|
} |
|
catch (Exception) |
|
{ |
|
throw; |
|
} |
|
} |
|
#endregion |
|
} |
|
} |