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.
855 lines
36 KiB
855 lines
36 KiB
using System; |
|
using System.Collections.Concurrent; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Threading.Tasks; |
|
using ESRI.ArcGIS.Geometry; |
|
using KGIS.Framework.AE; |
|
namespace Kingo.PluginServiceInterface.Helper |
|
{ |
|
/// <summary> |
|
/// 尖锐角检查与处理逻辑 |
|
/// </summary> |
|
public class SharpAngle |
|
{ |
|
/// <summary> |
|
/// 处理几何图形中的尖锐角(小于指定角度阈值) |
|
/// </summary> |
|
/// <param name="geometry"></param> |
|
/// <param name="distance"></param> |
|
/// <param name="angleThreshold">默认小于10度为尖锐角</param> |
|
/// <returns></returns> |
|
public static IGeometry ProcessSharpAngles(IGeometry geometry, double distance, double angleThreshold = 10, int recursionCount = 0) |
|
{ |
|
// 预处理:移除几何图形中的重复顶点,确保顶点唯一性 |
|
//geometry = RemoveDuplicatePoints(geometry); |
|
// 存储构成最小角度顶点集合的列表(通常包含三个顶点:构成尖角的三个点) |
|
List<IPoint> pointList = new List<IPoint>(); |
|
double angle = 0; |
|
// 获取当前几何图形中的最小内角,并获取对应的三个顶点(尖角顶点及其相邻顶点) |
|
double internalAngle = GetMinAngle1(geometry, ref pointList); |
|
// 仅当找到有效三角形顶点时处理(三点构成一个尖角) |
|
if (pointList.Count == 3) |
|
{ |
|
// 获取构成尖角的三个顶点 |
|
// a - 前驱顶点,b - 尖角顶点,c - 后继顶点 |
|
IPoint a = pointList[0]; |
|
IPoint b = pointList[1]; |
|
IPoint c = pointList[2]; |
|
IPolygon triangle = CreateTriangle(a, b, c); |
|
if (triangle != null) |
|
{ |
|
var area = triangle as IArea; |
|
if (area != null && area.Area < 0.1) |
|
{ |
|
if (!FeatureAPI.IsInterSect(triangle, geometry)) |
|
{ |
|
geometry = FeatureAPI.Union(geometry, triangle); |
|
} |
|
else |
|
{ |
|
// 从原始几何体中减去三角形区域,实现尖角切割 |
|
geometry = FeatureAPI.Difference(geometry, triangle); |
|
} |
|
} |
|
else |
|
{ |
|
// 在ab边上距离顶点b指定距离的位置生成新点d |
|
IPoint d = GetPointAlongEdge(a, b, distance); |
|
// 在cb边上距离顶点b指定距离的位置生成新点e |
|
//IPoint e = GetPointAlongEdge(c, b, distance); |
|
// 计算向量db的分量 |
|
double dbX = b.X - d.X; |
|
double dbY = b.Y - d.Y; |
|
// 计算分子和分母 |
|
double numerator = dbX * (b.X - d.X) + dbY * (b.Y - d.Y); |
|
double denominator = dbX * (c.X - b.X) + dbY * (c.Y - b.Y); |
|
// 避免分母为0(若分母为0,说明bc边与db向量平行,无法形成直角) |
|
if (Math.Abs(denominator) < 1e-9) |
|
{ |
|
// 处理边界:无法在bc边上找到e点,可返回原逻辑或抛出异常 |
|
return CreateTriangle(d, b, GetPointAlongEdge(c, b, distance)); // 原逻辑生成e点 |
|
} |
|
double t = -numerator / denominator; |
|
// 限制t在[0,1]范围内(确保e点在bc边上) |
|
t = Math.Max(0, Math.Min(1, t)); |
|
// 生成e点(位于bc边上) |
|
IPoint e = new PointClass() |
|
{ |
|
X = b.X + t * (c.X - b.X), |
|
Y = b.Y + t * (c.Y - b.Y) |
|
}; |
|
// 用d-b-e三点创建三角形几何体(用于切割尖角区域) |
|
triangle = CreateTriangle(d, b, e); |
|
// 从原始几何体中减去三角形区域,实现尖角切割 |
|
//geometry = FeatureAPI.Difference(geometry, triangle); |
|
if (triangle != null) |
|
{ |
|
if (!FeatureAPI.IsInterSect(triangle, geometry)) |
|
{ |
|
geometry = FeatureAPI.Union(geometry, triangle); |
|
} |
|
else |
|
{ |
|
// 从原始几何体中减去三角形区域,实现尖角切割 |
|
geometry = FeatureAPI.Difference(geometry, triangle); |
|
} |
|
} |
|
} |
|
} |
|
// 检测处理后的几何体是否仍存在需要处理的尖角 |
|
if (JudgmentSharpAngle(geometry, ref pointList, ref angle)) |
|
{ |
|
if (recursionCount < 10) |
|
{ |
|
// 递归调用时,计数器加1 |
|
geometry = ProcessSharpAngles(geometry, distance, angleThreshold, recursionCount + 1); |
|
} |
|
else |
|
{ |
|
triangle = CreateTriangle(a, b, c); |
|
if (!FeatureAPI.IsInterSect(triangle, geometry)) |
|
{ |
|
geometry = FeatureAPI.Union(geometry, triangle); |
|
} |
|
else |
|
{ |
|
// 从原始几何体中减去三角形区域,实现尖角切割 |
|
geometry = FeatureAPI.Difference(geometry, triangle); |
|
} |
|
} |
|
} |
|
} |
|
// 返回处理后的几何体 |
|
return geometry; |
|
} |
|
private static double GetMinAngle(IGeometry pGeo, ref List<IPoint> pointlist) |
|
{ |
|
double result = -1; |
|
IPolygon4 poly4 = null; |
|
ITopologicalOperator topo = null; |
|
GeometryBag geoBag = null; |
|
IGeometryCollection geoCollection = null; |
|
pointlist = new List<IPoint>(); |
|
try |
|
{ |
|
if (pGeo == null || pGeo.IsEmpty) |
|
return result; |
|
poly4 = pGeo as IPolygon4; |
|
topo = poly4 as ITopologicalOperator; |
|
topo?.Simplify(); |
|
geoBag = poly4.ExteriorRingBag as GeometryBag; |
|
if (geoBag == null) return result; |
|
geoCollection = geoBag as IGeometryCollection; |
|
List<IGeometry> rings = new List<IGeometry>(); |
|
for (int j = 0; j < geoCollection.GeometryCount; j++) |
|
{ |
|
IGeometry geo = geoCollection.get_Geometry(j); |
|
rings.Add(geo); |
|
//内环图形 |
|
IGeometryBag InteriorBag = (pGeo as IPolygon4).get_InteriorRingBag(geo as IRing); |
|
if (InteriorBag != null) |
|
{ |
|
IGeometryCollection InteriorRingGeometryCollection = InteriorBag as IGeometryCollection; |
|
for (int IR = 0; IR < InteriorRingGeometryCollection.GeometryCount; IR++) |
|
{ |
|
rings.Add(InteriorRingGeometryCollection.get_Geometry(IR)); |
|
} |
|
} |
|
} |
|
foreach (IGeometry ring in rings) |
|
{ |
|
if (ring.IsEmpty) continue; |
|
IPointCollection points = ring as IPointCollection; |
|
int num = points.PointCount - 1; |
|
for (int i = 0; i < num; i++) |
|
{ |
|
IPoint p1 = null; |
|
IPoint p2 = points.get_Point(i); |
|
IPoint p3 = null; |
|
if (i == 0) |
|
{ |
|
p1 = points.get_Point(num - 1); |
|
p3 = points.get_Point(i + 1); |
|
} |
|
else if (i == num - 1) |
|
{ |
|
p1 = points.get_Point(i - 1); |
|
p3 = points.get_Point(0); |
|
} |
|
else |
|
{ |
|
p1 = points.get_Point(i - 1); |
|
p3 = points.get_Point(i + 1); |
|
} |
|
if ((p2.X == p1.X && p2.Y == p1.Y) || (p2.X == p3.X && p2.Y == p3.Y) || (p1.X == p3.X && p1.Y == p3.Y)) |
|
continue; |
|
double angle = GetAngle(p2, p1, p3); |
|
if (double.IsNaN(angle)) continue; |
|
if (result == -1) |
|
{ |
|
result = angle; |
|
pointlist.Add(p1); |
|
pointlist.Add(p2); |
|
pointlist.Add(p3); |
|
} |
|
else |
|
{ |
|
if (double.IsNaN(result)) |
|
{ |
|
result = angle; |
|
} |
|
if (result > angle) |
|
{ |
|
result = angle; |
|
if (pointlist.Count > 0) pointlist.Clear(); |
|
pointlist.Add(p1); |
|
pointlist.Add(p2); |
|
pointlist.Add(p3); |
|
} |
|
} |
|
//Marshal.ReleaseComObject(p1); |
|
//Marshal.ReleaseComObject(p2); |
|
//Marshal.ReleaseComObject(p3); |
|
} |
|
//Marshal.ReleaseComObject(ring); |
|
} |
|
} |
|
catch (Exception ex) |
|
{ |
|
throw ex; |
|
} |
|
finally |
|
{ |
|
//if (poly4 != null) |
|
// Marshal.ReleaseComObject(poly4); |
|
//if (topo != null) |
|
// Marshal.ReleaseComObject(topo); |
|
//if (geoBag != null) |
|
// Marshal.ReleaseComObject(geoBag); |
|
//if (geoCollection != null) |
|
// Marshal.ReleaseComObject(geoCollection); |
|
} |
|
return result; |
|
} |
|
private static double GetAngle(IPoint cenPoint, IPoint firstPoint, IPoint secondPoint) |
|
{ |
|
double ma_x = firstPoint.X - cenPoint.X; |
|
double ma_y = firstPoint.Y - cenPoint.Y; |
|
double mb_x = secondPoint.X - cenPoint.X; |
|
double mb_y = secondPoint.Y - cenPoint.Y; |
|
double v1 = (ma_x * mb_x) + (ma_y * mb_y); |
|
double ma_val = Math.Sqrt(ma_x * ma_x + ma_y * ma_y); |
|
double mb_val = Math.Sqrt(mb_x * mb_x + mb_y * mb_y); |
|
if (ma_val * mb_val == 0) |
|
{ |
|
return -1; |
|
} |
|
double cosM = v1 / (ma_val * mb_val); |
|
double angleAMB = Math.Acos(cosM) * 180 / Math.PI; |
|
return angleAMB; |
|
} |
|
private static IGeometry RemoveDuplicatePoints(IGeometry geometry) |
|
{ |
|
IPointCollection srcPoints = geometry as IPointCollection; |
|
IPointCollection dstPoints = new PolygonClass(); |
|
IPoint lastPoint = null; |
|
for (int i = 0; i < srcPoints.PointCount; i++) |
|
{ |
|
IPoint current = srcPoints.get_Point(i); |
|
if (lastPoint == null || !PointsEqual(current, lastPoint)) |
|
{ |
|
dstPoints.AddPoint(current); |
|
lastPoint = current; |
|
} |
|
} |
|
// 处理闭合环特殊情况 |
|
if (dstPoints.PointCount > 1 && |
|
PointsEqual(dstPoints.get_Point(0), dstPoints.get_Point(dstPoints.PointCount - 1))) |
|
{ |
|
dstPoints.RemovePoints(dstPoints.PointCount - 1, 1); |
|
} |
|
return dstPoints as IPolygon; |
|
} |
|
private static bool PointsEqual(IPoint p1, IPoint p2) |
|
{ |
|
const double tolerance = 1e-6; |
|
return Math.Abs(p1.X - p2.X) < tolerance && |
|
Math.Abs(p1.Y - p2.Y) < tolerance; |
|
} |
|
/// <summary> |
|
/// 沿多边形边计算距离终点指定长度的点坐标 |
|
/// </summary> |
|
/// <param name="fromPoint">边的起点坐标</param> |
|
/// <param name="toPoint">边的终点坐标</param> |
|
/// <param name="distanceFromEnd">从终点向起点方向移动的距离(非负数)</param> |
|
/// <returns>计算得到的新坐标点</returns> |
|
/// <exception cref="ArgumentException">当距离参数为负数时抛出</exception> |
|
private static IPoint GetPointAlongEdge(IPoint fromPoint, IPoint toPoint, double distanceFromEnd) |
|
{ |
|
// 参数有效性验证 |
|
if (distanceFromEnd < 0) |
|
throw new ArgumentException("移动距离必须为非负值", nameof(distanceFromEnd)); |
|
const double precisionEpsilon = 1e-9; // 浮点计算精度阈值 |
|
// 计算边向量分量 |
|
double deltaX = toPoint.X - fromPoint.X; |
|
double deltaY = toPoint.Y - fromPoint.Y; |
|
// 计算边长的平方(避免开根号提升性能) |
|
double squaredEdgeLength = deltaX * deltaX + deltaY * deltaY; |
|
/* 处理特殊边界情况: |
|
* 1. 起点终点重合(边长接近0) |
|
* 2. 移动距离接近0 |
|
* 以上情况直接返回终点坐标 |
|
*/ |
|
if (squaredEdgeLength < precisionEpsilon * precisionEpsilon |
|
|| distanceFromEnd < precisionEpsilon) |
|
{ |
|
return ClonePoint(toPoint); |
|
} |
|
// 计算实际边长和标准化比例 |
|
double edgeLength = Math.Sqrt(squaredEdgeLength); |
|
double normalizedRatio = distanceFromEnd / edgeLength; |
|
// 当移动距离超过边长时返回起点 |
|
if (normalizedRatio >= 1.0) |
|
{ |
|
return ClonePoint(fromPoint); |
|
} |
|
/* 坐标计算逻辑: |
|
* 从终点(toPoint)向起点(fromPoint)方向移动指定距离 |
|
* 新坐标 = 终点坐标 - 边向量 * 距离比例 |
|
*/ |
|
return CreateNewPoint( |
|
x: toPoint.X - deltaX * normalizedRatio, |
|
y: toPoint.Y - deltaY * normalizedRatio |
|
); |
|
} |
|
|
|
/// <summary> |
|
/// 创建新点对象(封装对象创建逻辑) |
|
/// </summary> |
|
private static IPoint CreateNewPoint(double x, double y) |
|
=> new PointClass() { X = x, Y = y }; |
|
/// <summary> |
|
/// 复制点对象(避免引用关联) |
|
/// </summary> |
|
private static IPoint ClonePoint(IPoint original) |
|
=> CreateNewPoint(original.X, original.Y); |
|
|
|
private static IPolygon CreateTriangle(IPoint d, IPoint b, IPoint e) |
|
{ |
|
IPolygon triangle = new PolygonClass(); |
|
IPointCollection trianglePoints = triangle as IPointCollection; |
|
trianglePoints.AddPoint(d); |
|
trianglePoints.AddPoint(b); |
|
trianglePoints.AddPoint(e); |
|
trianglePoints.AddPoint(d); // 闭合多边形 |
|
// 确保几何有效 |
|
ITopologicalOperator topo = triangle as ITopologicalOperator; |
|
topo.Simplify(); |
|
return triangle; |
|
} |
|
#region 判断尖锐角与修复尖锐角 |
|
public static bool JudgmentSharpAngle(IGeometry geometry, ref List<IPoint> pointList, ref double angle) |
|
{ |
|
bool isSharpAngle = false; |
|
try |
|
{ |
|
if (geometry == null) return isSharpAngle; |
|
angle = GetMinAngle1(geometry, ref pointList, false); |
|
if (angle < 10) isSharpAngle = true; |
|
} |
|
catch (Exception) { } |
|
return isSharpAngle; |
|
} |
|
#endregion |
|
public static double GetMinAngle1(IGeometry pGeo, ref List<IPoint> pointlist) |
|
{ |
|
const double NoAngleFound = double.MaxValue; |
|
double minAngle = NoAngleFound; |
|
pointlist = new List<IPoint>(); |
|
if (pGeo == null || pGeo.IsEmpty) |
|
return -1; |
|
if (!(pGeo is IPolygon4 poly4)) |
|
return -1; |
|
// 简化几何拓扑 |
|
(poly4 as ITopologicalOperator)?.Simplify(); |
|
// 收集所有环(外环 + 内环) |
|
var rings = new List<IRing>(); |
|
IGeometryCollection exteriorRings = poly4.ExteriorRingBag as IGeometryCollection; |
|
if (exteriorRings != null) |
|
{ |
|
for (int i = 0; i < exteriorRings.GeometryCount; i++) |
|
{ |
|
if (exteriorRings.get_Geometry(i) is IRing ring) |
|
{ |
|
rings.Add(ring); |
|
// 获取当前外环的内环 |
|
IGeometryBag interiorBag = poly4.get_InteriorRingBag(ring); |
|
if (interiorBag != null) |
|
{ |
|
IGeometryCollection interiorRings = interiorBag as IGeometryCollection; |
|
for (int j = 0; j < interiorRings.GeometryCount; j++) |
|
{ |
|
if (interiorRings.get_Geometry(j) is IRing interiorRing) |
|
{ |
|
rings.Add(interiorRing); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
// 存储所有环的点集合(每个环单独存储) |
|
var ringPoints = new List<List<IPoint>>(); |
|
// 提前提取所有环的点坐标 |
|
foreach (IRing ring in rings) |
|
{ |
|
if (ring.IsEmpty) continue; |
|
IPointCollection pColl = ring as IPointCollection; |
|
int totalPoints = pColl.PointCount; |
|
// 检查是否闭合(首尾点相同) |
|
bool isClosed = totalPoints > 0 && PointEqual(pColl.get_Point(0), pColl.get_Point(totalPoints - 1)); |
|
// 提取点坐标(如果是闭合环则忽略最后一个点) |
|
var points = new List<IPoint>(); |
|
int pointsToTake = isClosed ? totalPoints - 1 : totalPoints; |
|
for (int i = 0; i < pointsToTake; i++) |
|
{ |
|
points.Add(pColl.get_Point(i)); |
|
} |
|
ringPoints.Add(points); |
|
} |
|
// 使用线程安全的集合存储结果 |
|
var minAngles = new ConcurrentBag<(double angle, IPoint p1, IPoint p2, IPoint p3)>(); |
|
// 并行处理每个环 |
|
Parallel.ForEach(ringPoints, (points) => |
|
{ |
|
int count = points.Count; |
|
// 单环内并行处理每个点组合 |
|
Parallel.For(0, count, (i) => |
|
{ |
|
int prevIdx = (i - 1 + count) % count; |
|
int nextIdx = (i + 1) % count; |
|
IPoint p1 = points[prevIdx]; |
|
IPoint p2 = points[i]; |
|
IPoint p3 = points[nextIdx]; |
|
if (PointEqual(p1, p2) || PointEqual(p2, p3) || PointEqual(p1, p3)) |
|
return; |
|
double angle = GetAngle(p2, p1, p3); |
|
if (double.IsNaN(angle)) |
|
return; |
|
// 添加到线程安全集合 |
|
minAngles.Add((angle, p1, p2, p3)); |
|
}); |
|
}); |
|
// 在所有结果中找到最小角度 |
|
foreach (var item in minAngles) |
|
{ |
|
if (item.angle < minAngle) |
|
{ |
|
minAngle = item.angle; |
|
pointlist.Clear(); |
|
pointlist.Add(item.p1); |
|
pointlist.Add(item.p2); |
|
pointlist.Add(item.p3); |
|
} |
|
} |
|
return minAngle == NoAngleFound ? -1 : minAngle; |
|
} |
|
|
|
// 辅助方法:判断两点是否重合 |
|
private static bool PointEqual(IPoint a, IPoint b) |
|
=> a.X == b.X && a.Y == b.Y; |
|
|
|
#region MyRegion |
|
public struct PointD |
|
{ |
|
public double X { get; } |
|
public double Y { get; } |
|
public PointD(double x, double y) |
|
{ |
|
X = x; |
|
Y = y; |
|
} |
|
} |
|
// 优化后的最小角度计算方法(针对大型图形) |
|
public static double GetMinAngle1(IGeometry pGeo, ref List<IPoint> pointlist, bool skipSimplify = false) |
|
{ |
|
const double NoAngleFound = double.MaxValue; |
|
double minAngle = NoAngleFound; |
|
pointlist = new List<IPoint>(); |
|
|
|
// 1. 输入校验(提前返回) |
|
if (pGeo == null || pGeo.IsEmpty) |
|
return -1; |
|
if (!(pGeo is IPolygon4 poly4)) |
|
return -1; |
|
|
|
// 2. 拓扑简化(可选跳过,适用于外部已简化的几何) |
|
if (!skipSimplify) |
|
{ |
|
(poly4 as ITopologicalOperator)?.Simplify(); // 仅执行一次简化 |
|
} |
|
|
|
// 3. 提取所有环(外环 + 内环)并转换为值类型点集合(减少 COM 交互) |
|
var allRingsPoints = ExtractRingsAsPointD(poly4); |
|
if (allRingsPoints.Count == 0) |
|
return -1; |
|
|
|
// 4. 并行/串行处理每个环(根据环大小动态选择) |
|
var minAngles = new ConcurrentBag<(double angle, PointD p1, PointD p2, PointD p3)>(); |
|
foreach (var ringPoints in allRingsPoints) |
|
{ |
|
int pointCount = ringPoints.Count; |
|
if (pointCount < 3) continue; // 环至少需要3个点才能构成角 |
|
|
|
// 动态选择处理方式:点数量大时并行,否则串行(避免小任务并行开销) |
|
if (pointCount > 1000) // 阈值可根据实际场景调整 |
|
{ |
|
Parallel.For(0, pointCount, i => ProcessPointInRing(ringPoints, i, minAngles)); |
|
} |
|
else |
|
{ |
|
for (int i = 0; i < pointCount; i++) |
|
{ |
|
ProcessPointInRing(ringPoints, i, minAngles); |
|
} |
|
} |
|
} |
|
|
|
// 5. 查找全局最小角度(并转换回 IPoint) |
|
foreach (var item in minAngles) |
|
{ |
|
if (item.angle < minAngle) |
|
{ |
|
minAngle = item.angle; |
|
pointlist.Clear(); |
|
// 将值类型点转换为 IPoint(仅最后需要时创建 COM 对象) |
|
pointlist.Add(CreateIPoint(item.p1.X, item.p1.Y)); |
|
pointlist.Add(CreateIPoint(item.p2.X, item.p2.Y)); |
|
pointlist.Add(CreateIPoint(item.p3.X, item.p3.Y)); |
|
} |
|
// 优化:若已找到小于10度的角,可提前终止(适用于仅需判断尖锐角的场景) |
|
if (minAngle < 10) break; |
|
} |
|
|
|
return minAngle == NoAngleFound ? -1 : minAngle; |
|
} |
|
|
|
// 辅助方法:提取所有环的点坐标(转换为值类型 PointD) |
|
private static List<List<PointD>> ExtractRingsAsPointD(IPolygon4 poly4) |
|
{ |
|
var allRingsPoints = new List<List<PointD>>(); |
|
|
|
// 提取外环 |
|
IGeometryCollection exteriorRings = poly4.ExteriorRingBag as IGeometryCollection; |
|
if (exteriorRings != null) |
|
{ |
|
for (int i = 0; i < exteriorRings.GeometryCount; i++) |
|
{ |
|
if (exteriorRings.get_Geometry(i) is IRing ring) |
|
{ |
|
var ringPoints = ExtractRingPointsAsPointD(ring); |
|
allRingsPoints.Add(ringPoints); |
|
|
|
// 提取当前外环的内环 |
|
IGeometryBag interiorBag = poly4.get_InteriorRingBag(ring); |
|
if (interiorBag != null) |
|
{ |
|
IGeometryCollection interiorRings = interiorBag as IGeometryCollection; |
|
for (int j = 0; j < interiorRings.GeometryCount; j++) |
|
{ |
|
if (interiorRings.get_Geometry(j) is IRing interiorRing) |
|
{ |
|
var interiorRingPoints = ExtractRingPointsAsPointD(interiorRing); |
|
allRingsPoints.Add(interiorRingPoints); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
return allRingsPoints; |
|
} |
|
|
|
// 辅助方法:提取单个环的点坐标(转换为 PointD) |
|
private static List<PointD> ExtractRingPointsAsPointD(IRing ring) |
|
{ |
|
var points = new List<PointD>(); |
|
if (ring.IsEmpty) return points; |
|
|
|
IPointCollection pColl = ring as IPointCollection; |
|
int totalPoints = pColl.PointCount; |
|
bool isClosed = totalPoints > 0 && PointEqual2(pColl.get_Point(0), pColl.get_Point(totalPoints - 1)); |
|
int pointsToTake = isClosed ? totalPoints - 1 : totalPoints; |
|
|
|
// 仅遍历一次 COM 对象,转换为值类型存储 |
|
for (int i = 0; i < pointsToTake; i++) |
|
{ |
|
IPoint comPoint = pColl.get_Point(i); |
|
points.Add(new PointD(comPoint.X, comPoint.Y)); |
|
} |
|
return points; |
|
} |
|
|
|
// 辅助方法:处理环中的单个点(计算角度) |
|
private static void ProcessPointInRing(List<PointD> ringPoints, int i, ConcurrentBag<(double angle, PointD p1, PointD p2, PointD p3)> minAngles) |
|
{ |
|
int count = ringPoints.Count; |
|
int prevIdx = (i - 1 + count) % count; |
|
int nextIdx = (i + 1) % count; |
|
|
|
PointD p1 = ringPoints[prevIdx]; |
|
PointD p2 = ringPoints[i]; |
|
PointD p3 = ringPoints[nextIdx]; |
|
|
|
// 跳过重合点(提前判断,减少无效计算) |
|
if (PointEqual(p1, p2) || PointEqual(p2, p3) || PointEqual(p1, p3)) |
|
return; |
|
|
|
// 计算角度(预计算向量差值和模长,避免重复计算) |
|
double maX = p1.X - p2.X; |
|
double maY = p1.Y - p2.Y; |
|
double mbX = p3.X - p2.X; |
|
double mbY = p3.Y - p2.Y; |
|
|
|
double dotProduct = maX * mbX + maY * mbY; |
|
double maLength = Math.Sqrt(maX * maX + maY * maY); |
|
double mbLength = Math.Sqrt(mbX * mbX + mbY * mbY); |
|
|
|
if (maLength < 1e-9 || mbLength < 1e-9) // 避免除零错误 |
|
return; |
|
|
|
double cosTheta = dotProduct / (maLength * mbLength); |
|
cosTheta = Math.Max(Math.Min(cosTheta, 1), -1); // 防止浮点误差导致超出 [-1,1] |
|
double angle = Math.Acos(cosTheta) * 180 / Math.PI; |
|
|
|
minAngles.Add((angle, p1, p2, p3)); |
|
} |
|
|
|
// 辅助方法:判断两个 PointD 是否重合(值类型比较更快) |
|
private static bool PointEqual(PointD a, PointD b) |
|
=> Math.Abs(a.X - b.X) < 1e-6 && Math.Abs(a.Y - b.Y) < 1e-6; |
|
|
|
// 辅助方法:将值类型点转换为 IPoint(仅最后需要时创建) |
|
private static IPoint CreateIPoint(double x, double y) |
|
=> new PointClass { X = x, Y = y }; |
|
|
|
// 辅助方法:兼容原 COM 对象的点比较(仅用于初始环提取) |
|
private static bool PointEqual2(IPoint a, IPoint b) |
|
=> Math.Abs(a.X - b.X) < 1e-6 && Math.Abs(a.Y - b.Y) < 1e-6; |
|
#endregion |
|
} |
|
|
|
|
|
// 自定义值类型:存储坐标(替代IPoint引用类型,减少GC) |
|
public struct PointD : IEquatable<PointD> |
|
{ |
|
public double X { get; } |
|
public double Y { get; } |
|
|
|
public PointD(double x, double y) |
|
{ |
|
X = x; |
|
Y = y; |
|
} |
|
|
|
public bool Equals(PointD other) |
|
{ |
|
return X.Equals(other.X) && Y.Equals(other.Y); |
|
} |
|
|
|
public override bool Equals(object obj) |
|
{ |
|
return obj is PointD other && Equals(other); |
|
} |
|
|
|
public override int GetHashCode() |
|
{ |
|
return 0; //HashCode.Combine(X, Y); |
|
} |
|
|
|
public static bool operator ==(PointD left, PointD right) |
|
{ |
|
return left.Equals(right); |
|
} |
|
|
|
public static bool operator !=(PointD left, PointD right) |
|
{ |
|
return !left.Equals(right); |
|
} |
|
} |
|
|
|
public static class GeometryOptimizer |
|
{ |
|
// 线程安全的最小值容器(存储角度及对应三点) |
|
private class ThreadSafeMinAngle |
|
{ |
|
private double _minAngle = double.MaxValue; |
|
private PointD _p1, _p2, _p3; |
|
private readonly object _lock = new object(); |
|
|
|
public void Update(double angle, PointD p1, PointD p2, PointD p3) |
|
{ |
|
lock (_lock) |
|
{ |
|
if (angle < _minAngle) |
|
{ |
|
_minAngle = angle; |
|
_p1 = p1; |
|
_p2 = p2; |
|
_p3 = p3; |
|
} |
|
} |
|
} |
|
|
|
public (double angle, PointD p1, PointD p2, PointD p3) GetResult() |
|
{ |
|
lock (_lock) |
|
{ |
|
return (_minAngle == double.MaxValue ? -1 : _minAngle, _p1, _p2, _p3); |
|
} |
|
} |
|
} |
|
|
|
public static double GetMinAngle1(IGeometry pGeo, ref List<IPoint> pointlist) |
|
{ |
|
// 初始化返回值和常量 |
|
const double NoAngleFound = double.MaxValue; |
|
pointlist = new List<IPoint>(); |
|
|
|
// 输入参数校验 |
|
if (pGeo == null || pGeo.IsEmpty || !(pGeo is IPolygon4 poly4)) |
|
return -1; |
|
|
|
// 简化几何拓扑(修复自相交、冗余顶点等) |
|
(poly4 as ITopologicalOperator)?.Simplify(); |
|
|
|
// 收集所有环的顶点(直接提取,不暂存环对象) |
|
var ringPoints = new List<List<PointD>>(); |
|
CollectRingPoints(poly4, ringPoints); |
|
|
|
// 过滤无效环(顶点数 < 3 的环无法构成角度) |
|
var validRingPoints = ringPoints.Where(points => points.Count >= 3).ToList(); |
|
if (!validRingPoints.Any()) |
|
return -1; |
|
|
|
// 并行计算角度并实时跟踪最小值 |
|
var minAngleContainer = new ThreadSafeMinAngle(); |
|
Parallel.ForEach(validRingPoints, points => |
|
{ |
|
int count = points.Count; |
|
for (int i = 0; i < count; i++) |
|
{ |
|
// 获取当前点及相邻点(循环索引) |
|
int prevIdx = (i - 1 + count) % count; |
|
int nextIdx = (i + 1) % count; |
|
PointD pPrev = points[prevIdx]; |
|
PointD pCurr = points[i]; |
|
PointD pNext = points[nextIdx]; |
|
|
|
// 跳过重复点(避免计算无效角度) |
|
if (pPrev == pCurr || pCurr == pNext || pPrev == pNext) |
|
continue; |
|
|
|
// 计算角度(使用值类型坐标提升效率) |
|
double angle = CalculateAngle(pCurr, pPrev, pNext); |
|
if (!double.IsNaN(angle)) |
|
minAngleContainer.Update(angle, pPrev, pCurr, pNext); |
|
} |
|
}); |
|
|
|
// 处理最终结果 |
|
var result = minAngleContainer.GetResult(); |
|
if (result.angle <= 0) |
|
return -1; |
|
|
|
// 转换值类型坐标为IPoint并返回 |
|
pointlist.Add(CreateIPoint(result.p1)); |
|
pointlist.Add(CreateIPoint(result.p2)); |
|
pointlist.Add(CreateIPoint(result.p3)); |
|
return result.angle; |
|
} |
|
|
|
// 辅助方法:收集多边形所有环的顶点(外环+内环) |
|
private static void CollectRingPoints(IPolygon4 poly4, List<List<PointD>> ringPoints) |
|
{ |
|
// 处理外环 |
|
if (poly4.ExteriorRingBag is IGeometryCollection exteriorRings) |
|
{ |
|
for (int i = 0; i < exteriorRings.GeometryCount; i++) |
|
{ |
|
if (exteriorRings.get_Geometry(i) is IRing exteriorRing) |
|
{ |
|
AddRingPointsToCollection(exteriorRing, ringPoints); |
|
|
|
// 处理当前外环的内环 |
|
if (poly4.get_InteriorRingBag(exteriorRing) is IGeometryCollection interiorRings) |
|
{ |
|
for (int j = 0; j < interiorRings.GeometryCount; j++) |
|
{ |
|
if (interiorRings.get_Geometry(j) is IRing interiorRing) |
|
AddRingPointsToCollection(interiorRing, ringPoints); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// 辅助方法:提取单个环的顶点并添加到集合 |
|
private static void AddRingPointsToCollection(IRing ring, List<List<PointD>> ringPoints) |
|
{ |
|
if (ring.IsEmpty || !(ring is IPointCollection pointColl)) |
|
return; |
|
|
|
// 提取顶点(闭合环忽略最后一个点,避免重复) |
|
int totalPoints = pointColl.PointCount; |
|
bool isClosed = totalPoints > 0 && PointEqual(pointColl.get_Point(0), pointColl.get_Point(totalPoints - 1)); |
|
int pointsToTake = isClosed ? totalPoints - 1 : totalPoints; |
|
|
|
var points = new List<PointD>(); |
|
for (int i = 0; i < pointsToTake; i++) |
|
{ |
|
IPoint p = pointColl.get_Point(i); |
|
points.Add(new PointD(p.X, p.Y)); // 转换为值类型坐标 |
|
} |
|
ringPoints.Add(points); |
|
} |
|
|
|
// 辅助方法:计算三点构成的夹角(pCurr为顶点) |
|
private static double CalculateAngle(PointD pCurr, PointD pPrev, PointD pNext) |
|
{ |
|
// 构建向量:pCurr->pPrev 和 pCurr->pNext |
|
double v1X = pPrev.X - pCurr.X; |
|
double v1Y = pPrev.Y - pCurr.Y; |
|
double v2X = pNext.X - pCurr.X; |
|
double v2Y = pNext.Y - pCurr.Y; |
|
|
|
// 向量模长(避免除以零) |
|
double len1 = Math.Sqrt(v1X * v1X + v1Y * v1Y); |
|
double len2 = Math.Sqrt(v2X * v2X + v2Y * v2Y); |
|
if (len1 < 1e-9 || len2 < 1e-9) |
|
return double.NaN; |
|
|
|
// 向量点积计算夹角(弧度转角度) |
|
double dotProduct = v1X * v2X + v1Y * v2Y; |
|
double cosTheta = Math.Max(-1, Math.Min(1, dotProduct / (len1 * len2))); // 修正精度误差 |
|
return Math.Acos(cosTheta) * (180 / Math.PI); |
|
} |
|
|
|
// 辅助方法:比较两个IPoint是否相等(坐标精度:1e-6) |
|
private static bool PointEqual(IPoint p1, IPoint p2) |
|
{ |
|
return Math.Abs(p1.X - p2.X) < 1e-6 && Math.Abs(p1.Y - p2.Y) < 1e-6; |
|
} |
|
|
|
// 辅助方法:将PointD转换为IPoint(假设IPoint可实例化) |
|
private static IPoint CreateIPoint(PointD point) |
|
{ |
|
var iPoint = new PointClass(); |
|
iPoint.X = point.X; |
|
iPoint.Y = point.Y; |
|
return iPoint; |
|
} |
|
} |
|
} |