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.
		
		
		
		
		
			
		
			
				
					
					
						
							748 lines
						
					
					
						
							38 KiB
						
					
					
				
			
		
		
	
	
							748 lines
						
					
					
						
							38 KiB
						
					
					
				using System; | 
						|
using System.Collections.Generic; | 
						|
using System.Runtime.InteropServices; | 
						|
using ESRI.ArcGIS.Geometry; | 
						|
using GeoAPI.Operation.Buffer; | 
						|
using KGIS.Framework.AE; | 
						|
using NetTopologySuite.IO; | 
						|
using NetTopologySuite.Operation.Buffer; | 
						|
 | 
						|
namespace Kingo.PluginServiceInterface.Helper | 
						|
{ | 
						|
    /// <summary> | 
						|
    /// 狭长图形检测工具类(静态类) | 
						|
    /// 功能:检测多边形中是否存在狭长区域(如长条状、细带状图斑) | 
						|
    /// 核心算法思路:通过分析多边形环的顶点分布,识别局部密集点对,构建子多边形并通过紧凑度指标判断是否为狭长区域 | 
						|
    /// </summary> | 
						|
    public static class PolygonNarrowAreaChecker | 
						|
    { | 
						|
        /// <summary> | 
						|
        /// 检测多边形中的局部狭长区域 | 
						|
        /// </summary> | 
						|
        /// <param name="polygon">待检测的输入多边形(支持带内环的复杂多边形)</param> | 
						|
        /// <param name="distanceThreshold">距离阈值(单位:与坐标系一致,默认1):用于判断两个顶点是否"过近",作为狭长区域的初步筛选条件</param> | 
						|
        /// <returns>检测到的狭长区域多边形列表(每个元素为一个狭长子多边形)</returns> | 
						|
        public static List<IPolygon> CheckLocalNarrowAreas(IPolygon polygon, double distanceThreshold = 1) | 
						|
        { | 
						|
            List<IPolygon> narrowAreas = new List<IPolygon>(); // 存储检测到的狭长区域 | 
						|
            //var xxx = GeometryConvertHelper.ConvertIGeoemtryToWKT(polygon); | 
						|
            string sNarrow = ISNarrow(polygon); | 
						|
            if (!string.IsNullOrEmpty(sNarrow)) | 
						|
            { | 
						|
                #region 删除图形中的重复点(首位点除外) | 
						|
                IGeometryCollection geomColl = polygon as IGeometryCollection; | 
						|
                // 遍历所有子环(外环和内环) | 
						|
                for (int i = 0; i < geomColl.GeometryCount; i++) | 
						|
                { | 
						|
                    IGeometry subGeom = geomColl.get_Geometry(i); | 
						|
                    IRing ring = subGeom as IRing; // 环是闭合的线(IRing 继承自 IPolyline) | 
						|
                    if (ring == null) continue; | 
						|
                    // 处理当前环的重复点 | 
						|
                    RemoveDuplicatePointsInRing(ring); | 
						|
                } | 
						|
                #endregion | 
						|
                IGeometryCollection geometryCollection = polygon as IGeometryCollection;// 将多边形转换为几何集合(用于遍历内部环) | 
						|
                if (geometryCollection == null) return narrowAreas; // 非几何集合类型直接返回空列表 | 
						|
                for (int ringIndex = 0; ringIndex < geometryCollection.GeometryCount; ringIndex++)// 遍历多边形的所有环(外环+内环) | 
						|
                { | 
						|
                    IRing ring = geometryCollection.get_Geometry(ringIndex) as IRing; // 获取当前环 | 
						|
                    if (ring == null) continue; // 非环类型跳过 | 
						|
                    IPointCollection points = ring as IPointCollection;// 将环转换为点集合(用于获取顶点坐标) | 
						|
                    int pointCount = points.PointCount; // 环的总顶点数 | 
						|
                    bool isClosed = ring.IsClosed; // 判断环是否闭合(多边形环通常需闭合) | 
						|
                    int effectivePointCount = isClosed ? pointCount - 1 : pointCount; // 有效顶点数(闭合环需排除最后一个重复点) | 
						|
                                                                                      // 【步骤1】预处理环的顶点坐标:转换为结构体数组(提升计算性能)和IPoint对象数组(用于几何构建) | 
						|
                    var (ringPoints, ringPointsIPoint) = PreprocessRingPoints(points, effectivePointCount); if (ringPoints == null) continue; // 预处理失败时跳过当前环 | 
						|
                    HashSet<(double X, double Y)> usedPoints = new HashSet<(double X, double Y)>(); | 
						|
                    // 预计算所有线段的AABB(索引k对应线段k→k+1) | 
						|
                    var segmentAABBs = new (double MinX, double MaxX, double MinY, double MaxY)[effectivePointCount - 1]; | 
						|
                    for (int idx = 0; idx < effectivePointCount - 1; idx++) | 
						|
                    { | 
						|
                        IPoint pStart = ringPointsIPoint[idx]; | 
						|
                        IPoint pEnd = ringPointsIPoint[idx + 1]; | 
						|
                        segmentAABBs[idx] = ( | 
						|
                            MinX: Math.Min(pStart.X, pEnd.X), | 
						|
                            MaxX: Math.Max(pStart.X, pEnd.X), | 
						|
                            MinY: Math.Min(pStart.Y, pEnd.Y), | 
						|
                            MaxY: Math.Max(pStart.Y, pEnd.Y) | 
						|
                        ); | 
						|
                    } | 
						|
                    // 遍历所有可能的线段对(需优化遍历范围,避免O(n²)复杂度) | 
						|
                    for (int k = 0; k < effectivePointCount - 1; k++) | 
						|
                    { | 
						|
                        IPoint p1 = ringPointsIPoint[k]; | 
						|
                        IPoint p2 = ringPointsIPoint[k + 1]; | 
						|
                        // 检查p1/p2是否已被使用(通过坐标判断) | 
						|
                        if (usedPoints.Contains((p1.X, p1.Y)) || usedPoints.Contains((p2.X, p2.Y))) continue; | 
						|
                        // 获取当前线段k的AABB | 
						|
                        var aabbK = segmentAABBs[k]; | 
						|
                        for (int m = k + 2; m < effectivePointCount - 1; m++) // 跳过相邻线段 | 
						|
                        { | 
						|
                            IPoint p3 = ringPointsIPoint[m]; | 
						|
                            IPoint p4 = ringPointsIPoint[m + 1]; | 
						|
                            // 检查p3/p4是否已被使用 | 
						|
                            if (usedPoints.Contains((p3.X, p3.Y)) || usedPoints.Contains((p4.X, p4.Y))) continue; | 
						|
                            // 获取对比线段m的AABB | 
						|
                            var aabbM = segmentAABBs[m]; | 
						|
                            // AABB预筛选:判断两线段的包围盒是否可能相交 | 
						|
                            // 条件:x轴投影重叠且y轴投影重叠 | 
						|
                            double expandDistance = 1;//相当于外扩 | 
						|
                            bool xOverlap = (aabbK.MaxX + expandDistance) >= (aabbM.MinX - expandDistance) && | 
						|
                    (aabbK.MinX - expandDistance) <= (aabbM.MaxX + expandDistance); | 
						|
                            bool yOverlap = (aabbK.MaxY + expandDistance) >= (aabbM.MinY - expandDistance) && | 
						|
                    (aabbK.MinY - expandDistance) <= (aabbM.MaxY + expandDistance); | 
						|
                            if (!xOverlap || !yOverlap) continue; // AABB不重叠,直接跳过后续复杂计算 | 
						|
                                                                  // 判断线段是否共点(如p1=p3, p2=p4等) | 
						|
                            if (ArePointsEqual(p1, p3) || ArePointsEqual(p1, p4) || ArePointsEqual(p2, p3) || ArePointsEqual(p2, p4)) continue; | 
						|
                            // 计算线段(p1,p2)和(p3,p4)的距离(仅保留AABB重叠的情况) | 
						|
                            Vector2D vecAB = CalculateVector(p1, p2); | 
						|
                            Vector2D vecCD = CalculateVector(p3, p4); | 
						|
                            bool isSameDirection = AreVectorsSameDirection(vecAB, vecCD, 0.01); | 
						|
                            if (isSameDirection) continue; | 
						|
                            var vangle = CalculateAngle(vecAB, vecCD); | 
						|
                            if (vangle < 25 || vangle > 155) continue; | 
						|
                            double distance = CalculateSegmentDistance(p1, p2, p3, p4); | 
						|
//                            var xxx1 = $@" | 
						|
//{GeometryConvertHelper.ConvertIGeoemtryToWKT(p1)} | 
						|
//{GeometryConvertHelper.ConvertIGeoemtryToWKT(p2)} | 
						|
//{GeometryConvertHelper.ConvertIGeoemtryToWKT(p3)} | 
						|
//{GeometryConvertHelper.ConvertIGeoemtryToWKT(p4)}"; | 
						|
                            if (distance < 0.1) | 
						|
                            { | 
						|
                                IPoint iIntersection = GetExtendedLineIntersection(p1, p2, p3, p4); //两条线段的交点 | 
						|
                                if (iIntersection != null) | 
						|
                                { | 
						|
                                    double p1distance = PointToSegmentDistance(p1, p3, p4); | 
						|
                                    double p2distance = PointToSegmentDistance(p2, p3, p4); | 
						|
                                    double p3distance = PointToSegmentDistance(p3, p1, p2); | 
						|
                                    double p4distance = PointToSegmentDistance(p4, p1, p2); | 
						|
                                    int pindex_k = p1distance > p2distance ? k + 1 : k; | 
						|
                                    int pindex_m = p3distance > p4distance ? m + 1 : m; | 
						|
                                    double iIntersection12 = PointToSegmentDistance(iIntersection, p1, p2); | 
						|
                                    double iIntersection34 = PointToSegmentDistance(iIntersection, p3, p4); | 
						|
                                    IPoint point = null; | 
						|
                                    if (iIntersection34 < iIntersection12) | 
						|
                                    { | 
						|
                                        point = p1distance > p2distance ? p2 : p1; | 
						|
                                    } | 
						|
                                    else | 
						|
                                    { | 
						|
                                        point = p3distance > p4distance ? p4 : p3; | 
						|
                                    } | 
						|
                                    try | 
						|
                                    { | 
						|
                                        IPolyline polyline1 = new PolylineClass(); | 
						|
                                        polyline1.FromPoint = iIntersection; | 
						|
                                        polyline1.ToPoint = point; | 
						|
                                        polyline1.SpatialReference = polygon.SpatialReference; | 
						|
                                        if (polyline1.Length < 0.1) | 
						|
                                        { | 
						|
                                            polyline1 = getExtendLine(polyline1, 3, 0.01); | 
						|
                                        } | 
						|
                                        var CutGeometryresult = FeatureAPI.CutGeometry(polygon, polyline1); | 
						|
                                        Marshal.ReleaseComObject(polyline1); | 
						|
                                        if (CutGeometryresult != null && CutGeometryresult.Count == 2) | 
						|
                                        { | 
						|
                                            foreach (var item in CutGeometryresult) | 
						|
                                            { | 
						|
                                                sNarrow = ISNarrow(item); | 
						|
                                                if (sNarrow == "POLYGON EMPTY") | 
						|
                                                { | 
						|
                                                    List<IPoint> pointList = null; | 
						|
                                                    var angle = SharpAngle.GetMinAngle1(item, ref pointList, false); | 
						|
                                                    if (angle < 10) continue; | 
						|
                                                    if (isThreeSides(item)) continue; | 
						|
                                                    narrowAreas.Add(item as IPolygon); | 
						|
                                                    usedPoints.Add((p1.X, p1.Y)); | 
						|
                                                    usedPoints.Add((p2.X, p2.Y)); | 
						|
                                                    usedPoints.Add((p3.X, p3.Y)); | 
						|
                                                    usedPoints.Add((p4.X, p4.Y)); | 
						|
                                                } | 
						|
                                                else if (!string.IsNullOrEmpty(sNarrow)) | 
						|
                                                { | 
						|
                                                    narrowAreas = CheckLocalNarrowAreas(item as IPolygon); | 
						|
                                                } | 
						|
                                            } | 
						|
                                        } | 
						|
                                        else if (CutGeometryresult != null && CutGeometryresult.Count == 1) | 
						|
                                        { | 
						|
                                            continue; | 
						|
                                            //IGeometry geo = CutGeometryresult[0]; | 
						|
                                            //if (geo != null && !geo.IsEmpty) | 
						|
                                            //{ | 
						|
                                            //    sNarrow = ISNarrow(geo); | 
						|
                                            //    if (!string.IsNullOrEmpty(sNarrow)) | 
						|
                                            //    { | 
						|
                                            //        List<IPoint> pointList = null; | 
						|
                                            //        var angle = SharpAngle.GetMinAngle1(geo, ref pointList, false); | 
						|
                                            //        if (angle < 10) continue; | 
						|
                                            //        narrowAreas.Add(geo as IPolygon); | 
						|
                                            //        usedPoints.Add((p1.X, p1.Y)); | 
						|
                                            //        usedPoints.Add((p2.X, p2.Y)); | 
						|
                                            //        usedPoints.Add((p3.X, p3.Y)); | 
						|
                                            //        usedPoints.Add((p4.X, p4.Y)); | 
						|
                                            //        continue; | 
						|
                                            //    } | 
						|
                                            //} | 
						|
                                            //else continue; | 
						|
                                        } | 
						|
                                    } | 
						|
                                    catch (Exception ex) | 
						|
                                    { | 
						|
                                        continue; | 
						|
                                    } | 
						|
                                } | 
						|
                            } | 
						|
                        } | 
						|
                    } | 
						|
                } | 
						|
                return narrowAreas; | 
						|
            } | 
						|
            return new List<IPolygon>(); | 
						|
        } | 
						|
 | 
						|
        private static IPoint GetExtendedLineIntersection(IPoint start1, IPoint end1, IPoint start2, IPoint end2) | 
						|
        { | 
						|
            // 创建原始线段 | 
						|
            IPolyline polyline1 = new PolylineClass(); | 
						|
            polyline1.FromPoint = start1; | 
						|
            polyline1.ToPoint = end1; | 
						|
            // 延长线段(假设 getExtendLine 可能返回包含中间点的多段线) | 
						|
            IPolyline extendedPolyline1 = getExtendLine(polyline1, 3, 1); | 
						|
            // 提取延长后的起点和终点,重建为仅含两点的线段 | 
						|
            IPolyline simplifiedLine1 = new PolylineClass(); | 
						|
            simplifiedLine1.FromPoint = extendedPolyline1.FromPoint; // 延长后的起点 | 
						|
            simplifiedLine1.ToPoint = extendedPolyline1.ToPoint;     // 延长后的终点 | 
						|
            // 对第二条线段执行相同操作 | 
						|
            IPolyline polyline2 = new PolylineClass(); | 
						|
            polyline2.FromPoint = start2; | 
						|
            polyline2.ToPoint = end2; | 
						|
            IPolyline extendedPolyline2 = getExtendLine(polyline2, 3, 1); | 
						|
            IPolyline simplifiedLine2 = new PolylineClass(); | 
						|
            simplifiedLine2.FromPoint = extendedPolyline2.FromPoint; | 
						|
            simplifiedLine2.ToPoint = extendedPolyline2.ToPoint; | 
						|
            // 计算简化后线段的交点 | 
						|
            ITopologicalOperator topoOp = simplifiedLine1 as ITopologicalOperator; | 
						|
            IGeometry intersection = topoOp.Intersect(simplifiedLine2, esriGeometryDimension.esriGeometry0Dimension); | 
						|
            // 处理交点结果(与原逻辑一致) | 
						|
            if (intersection != null && intersection.GeometryType == esriGeometryType.esriGeometryPoint) | 
						|
            { | 
						|
                return intersection as IPoint; | 
						|
            } | 
						|
            else if (intersection.GeometryType == esriGeometryType.esriGeometryMultipoint) | 
						|
            { | 
						|
                IPointCollection points = intersection as IPointCollection; | 
						|
                if (points.PointCount > 0) | 
						|
                    return points.get_Point(0); | 
						|
            } | 
						|
            return null; | 
						|
        } | 
						|
        /// <summary> | 
						|
        /// 延长线段 | 
						|
        /// </summary> | 
						|
        /// <param name="passLine">传入去的线</param> | 
						|
        /// <param name="mode">模式,1为从FromPoint处延长,2为从ToPint处延长,3为两端延长</param> | 
						|
        /// <param name="dis">延长的距离</param> | 
						|
        /// <returns></returns> | 
						|
        /// 创建人:懒羊羊 | 
						|
        public static IPolyline getExtendLine(IPolyline passLine, int mode, double dis) | 
						|
        { | 
						|
            IPointCollection pPointCol = passLine as IPointCollection; | 
						|
            switch (mode) | 
						|
            { | 
						|
                case 1: | 
						|
                    IPoint fromPoint = new PointClass(); | 
						|
                    passLine.QueryPoint(esriSegmentExtension.esriExtendAtFrom, -1 * dis, false, fromPoint); | 
						|
                    pPointCol.InsertPoints(0, 1, ref fromPoint); | 
						|
                    break; | 
						|
                case 2: | 
						|
                    IPoint endPoint = new PointClass(); | 
						|
                    object missing = Type.Missing; | 
						|
                    passLine.QueryPoint(esriSegmentExtension.esriExtendAtTo, dis + passLine.Length, false, endPoint); | 
						|
                    pPointCol.AddPoint(endPoint, ref missing, ref missing); | 
						|
                    break; | 
						|
                case 3: | 
						|
                    IPoint fPoint = new PointClass(); | 
						|
                    IPoint ePoint = new PointClass(); | 
						|
                    object missing2 = Type.Missing; | 
						|
                    passLine.QueryPoint(esriSegmentExtension.esriExtendAtFrom, -1 * dis, false, fPoint); | 
						|
                    pPointCol.InsertPoints(0, 1, ref fPoint); | 
						|
                    passLine.QueryPoint(esriSegmentExtension.esriExtendAtTo, dis + passLine.Length, false, ePoint); | 
						|
                    pPointCol.AddPoint(ePoint, ref missing2, ref missing2); | 
						|
                    break; | 
						|
                default: | 
						|
                    return pPointCol as IPolyline; | 
						|
            } | 
						|
            return pPointCol as IPolyline; | 
						|
        } | 
						|
        /// <summary> | 
						|
        /// 判断图形是否为三角形 | 
						|
        /// </summary> | 
						|
        /// <param name="geometry"></param> | 
						|
        /// <returns></returns> | 
						|
        private static bool isThreeSides(IGeometry geometry) | 
						|
        { | 
						|
            bool isThreeSides = false; | 
						|
            try | 
						|
            { | 
						|
                if (geometry is IPolygon polygon1) | 
						|
                { | 
						|
                    IPointCollection pointCollection = polygon1 as IPointCollection; | 
						|
                    int vertexCount = pointCollection.PointCount; | 
						|
                    // 排除闭合多边形中重复的首尾顶点 | 
						|
                    IPoint startingpoint = pointCollection.get_Point(0);//起点 | 
						|
                    IPoint terminalpoint = pointCollection.get_Point(vertexCount - 1);//终点 | 
						|
                    if (polygon1.IsClosed && vertexCount > 1 && startingpoint.X == terminalpoint.X && startingpoint.Y == terminalpoint.Y) | 
						|
                    { | 
						|
                        vertexCount--; | 
						|
                    } | 
						|
                    isThreeSides = vertexCount == 3; | 
						|
                } | 
						|
            } | 
						|
            catch (Exception ex) | 
						|
            { | 
						|
 | 
						|
            } | 
						|
            return isThreeSides; | 
						|
        } | 
						|
        /// <summary> | 
						|
        /// 判断两个点是否共点(考虑浮点精度误差) | 
						|
        /// </summary> | 
						|
        /// <param name="p1">点1</param> | 
						|
        /// <param name="p2">点2</param> | 
						|
        /// <param name="tolerance">容差(默认1e-6,与坐标系单位一致)</param> | 
						|
        /// <returns>true:共点;false:不共点</returns> | 
						|
        private static bool ArePointsEqual(IPoint p1, IPoint p2, double tolerance = 1e-6) | 
						|
        { | 
						|
            return Math.Abs(p1.X - p2.X) < tolerance && Math.Abs(p1.Y - p2.Y) < tolerance; | 
						|
        } | 
						|
        /// <summary> | 
						|
        /// 计算两条线段的最短距离(支持不相交、相交等场景) | 
						|
        /// </summary> | 
						|
        /// <param name="a1">线段1的起点</param> | 
						|
        /// <param name="a2">线段1的终点</param> | 
						|
        /// <param name="b1">线段2的起点</param> | 
						|
        /// <param name="b2">线段2的终点</param> | 
						|
        /// <returns>两条线段的最短距离</returns> | 
						|
        private static double CalculateSegmentDistance(IPoint a1, IPoint a2, IPoint b1, IPoint b2) | 
						|
        { | 
						|
            // 向量定义:u为a线段方向,v为b线段方向,w为a1到b1的向量 | 
						|
            double ux = a2.X - a1.X; | 
						|
            double uy = a2.Y - a1.Y; | 
						|
            double vx = b2.X - b1.X; | 
						|
            double vy = b2.Y - b1.Y; | 
						|
            double wx = a1.X - b1.X; | 
						|
            double wy = a1.Y - b1.Y; | 
						|
            double a = ux * ux + uy * uy; // u·u(a线段长度平方) | 
						|
            double b = ux * vx + uy * vy; // u·v(方向向量点积) | 
						|
            double c = vx * vx + vy * vy; // v·v(b线段长度平方) | 
						|
            double d = ux * wx + uy * wy; // u·w | 
						|
            double e = vx * wx + vy * wy; // v·w | 
						|
            double denominator = a * c - b * b; // 分母:(u·u)(v·v)-(u·v)² | 
						|
            double t, s; | 
						|
            // 情况1:线段平行或共线(分母为0) | 
						|
            if (Math.Abs(denominator) < 1e-9) | 
						|
            { | 
						|
                t = 0; // a线段起点投影到b线段 | 
						|
                s = (b * t + e) / c; // 计算s(若c=0则b线段为点) | 
						|
            } | 
						|
            // 情况2:线段不平行,计算参数t和s | 
						|
            else | 
						|
            { | 
						|
                t = (b * e - c * d) / denominator; // 投影参数t(a线段上) | 
						|
                s = (a * e - b * d) / denominator; // 投影参数s(b线段上) | 
						|
            } | 
						|
            // 限制t和s在[0,1]范围内(超出则取端点) | 
						|
            t = Math.Max(0, Math.Min(1, t)); | 
						|
            s = Math.Max(0, Math.Min(1, s)); | 
						|
            // 计算投影点距离 | 
						|
            double dx = (wx + ux * t - vx * s); | 
						|
            double dy = (wy + uy * t - vy * s); | 
						|
            return Math.Sqrt(dx * dx + dy * dy); | 
						|
        } | 
						|
        private static double PointToSegmentDistance(IPoint p, IPoint segStart, IPoint segEnd) | 
						|
        { | 
						|
            double segX = segEnd.X - segStart.X; | 
						|
            double segY = segEnd.Y - segStart.Y; | 
						|
            if (segX == 0 && segY == 0) // 线段为点 | 
						|
                return Math.Sqrt(Math.Pow(p.X - segStart.X, 2) + Math.Pow(p.Y - segStart.Y, 2)); | 
						|
            double t = ((p.X - segStart.X) * segX + (p.Y - segStart.Y) * segY) / (segX * segX + segY * segY); | 
						|
            t = Math.Max(0, Math.Min(1, t)); // 投影参数限制在[0,1] | 
						|
            double projX = segStart.X + t * segX; | 
						|
            double projY = segStart.Y + t * segY; | 
						|
            return Math.Sqrt(Math.Pow(p.X - projX, 2) + Math.Pow(p.Y - projY, 2)); | 
						|
        } | 
						|
        /// <summary> | 
						|
        /// 预处理环的顶点坐标(结构体数组和IPoint数组) | 
						|
        /// </summary> | 
						|
        /// <param name="points">环的顶点集合(IPoint类型)</param> | 
						|
        /// <param name="effectivePointCount">有效顶点数(闭合环需排除最后一个重复点)</param> | 
						|
        /// <returns>元组:(顶点坐标结构体数组, IPoint对象数组);若异常则返回(null, null)</returns> | 
						|
        private static ((double X, double Y)[], IPoint[]) PreprocessRingPoints(IPointCollection points, int effectivePointCount) | 
						|
        { | 
						|
            try | 
						|
            { | 
						|
                var ringPoints = new (double X, double Y)[effectivePointCount]; // 存储顶点坐标(结构体,轻量级) | 
						|
                var ringPointsIPoint = new IPoint[effectivePointCount]; // 存储IPoint对象(用于几何操作) | 
						|
                // 遍历有效顶点,填充两个数组 | 
						|
                for (int k = 0; k < effectivePointCount; k++) | 
						|
                { | 
						|
                    IPoint p = points.get_Point(k); // 获取第k个顶点 | 
						|
                    ringPoints[k] = (p.X, p.Y); // 转换为结构体 | 
						|
                    ringPointsIPoint[k] = p; // 保留IPoint对象 | 
						|
                } | 
						|
                return (ringPoints, ringPointsIPoint); | 
						|
            } | 
						|
            catch (Exception ex) | 
						|
            { | 
						|
                // 异常处理:顶点读取失败时记录日志(实际开发中建议添加日志记录) | 
						|
                return (null, null); | 
						|
            } | 
						|
        } | 
						|
        /// <summary> | 
						|
        /// 判断两个向量是否同向 | 
						|
        /// </summary> | 
						|
        /// <param name="vec1">向量1(如AB)</param> | 
						|
        /// <param name="vec2">向量2(如CD)</param> | 
						|
        /// <param name="tolerance">浮点数精度容差(默认1e-6)</param> | 
						|
        /// <returns>是否同向</returns> | 
						|
        public static bool AreVectorsSameDirection(Vector2D vec1, Vector2D vec2, double tolerance = 1e-6) | 
						|
        { | 
						|
            // 1. 判断共线性:叉积的绝对值小于容差(考虑浮点数精度) | 
						|
            double crossProduct = vec1.X * vec2.Y - vec2.X * vec1.Y; | 
						|
            if (Math.Abs(crossProduct) > tolerance) | 
						|
                return false; // 不共线,直接返回false | 
						|
            // 2. 判断同向性:点积大于0(且向量模长不为0,避免零向量干扰) | 
						|
            double dotProduct = vec1.X * vec2.X + vec1.Y * vec2.Y; | 
						|
            double length1 = CalculateVectorLength(vec1); | 
						|
            double length2 = CalculateVectorLength(vec2); | 
						|
            // 模长为0的向量无方向(实际线段向量不会为零,此处为安全校验) | 
						|
            if (length1 < tolerance || length2 < tolerance) | 
						|
                return false; | 
						|
            return dotProduct > tolerance; // 点积为正,方向相同 | 
						|
        } | 
						|
        /// <summary> | 
						|
        /// 计算向量模长 | 
						|
        /// </summary> | 
						|
        public static double CalculateVectorLength(Vector2D vector) | 
						|
        { | 
						|
            return Math.Sqrt(Math.Pow(vector.X, 2) + Math.Pow(vector.Y, 2)); | 
						|
        } | 
						|
        /// <summary> | 
						|
        /// 计算线段向量 | 
						|
        /// </summary> | 
						|
        /// <param name="startPoint">线段起点</param> | 
						|
        /// <param name="endPoint">线段终点</param> | 
						|
        /// <returns>向量</returns> | 
						|
        public static Vector2D CalculateVector(IPoint startPoint, IPoint endPoint) | 
						|
        { | 
						|
            if (startPoint == null || endPoint == null) | 
						|
                throw new ArgumentNullException("点对象不能为空"); | 
						|
 | 
						|
            return new Vector2D( | 
						|
                endPoint.X - startPoint.X, | 
						|
                endPoint.Y - startPoint.Y | 
						|
            ); | 
						|
        } | 
						|
        /// <summary> | 
						|
        /// 计算两个向量的夹角(角度制,0°~180°) | 
						|
        /// </summary> | 
						|
        /// <param name="vec1">向量1</param> | 
						|
        /// <param name="vec2">向量2</param> | 
						|
        /// <param name="tolerance">精度容差(默认1e-6)</param> | 
						|
        /// <returns>夹角角度(°)</returns> | 
						|
        public static double CalculateAngle(Vector2D vec1, Vector2D vec2, double tolerance = 1e-6) | 
						|
        { | 
						|
            // 计算模长 | 
						|
            double len1 = CalculateVectorLength(vec1); | 
						|
            double len2 = CalculateVectorLength(vec2); | 
						|
            // 处理零向量(避免除零异常) | 
						|
            if (len1 < tolerance || len2 < tolerance) | 
						|
                throw new ArgumentException("向量模长不能为零"); | 
						|
            // 计算点积 | 
						|
            double dotProduct = vec1.X * vec2.X + vec1.Y * vec2.Y; | 
						|
            // 计算cosθ(点积/(模长乘积)) | 
						|
            double cosTheta = dotProduct / (len1 * len2); | 
						|
            // 修正数值精度误差(cosθ范围需在[-1, 1]内) | 
						|
            cosTheta = Math.Max(Math.Min(cosTheta, 1.0), -1.0); | 
						|
            // 计算弧度并转换为角度 | 
						|
            double radians = Math.Acos(cosTheta); | 
						|
            return radians * (180 / Math.PI); // 弧度转角度 | 
						|
        } | 
						|
 | 
						|
        // 定义向量结构体 | 
						|
        public struct Vector2D | 
						|
        { | 
						|
            public double X; | 
						|
            public double Y; | 
						|
 | 
						|
            public Vector2D(double x, double y) | 
						|
            { | 
						|
                X = x; | 
						|
                Y = y; | 
						|
            } | 
						|
        } | 
						|
 | 
						|
        #region 节点去重 | 
						|
        private static void RemoveDuplicatePointsInRing(IRing ring) | 
						|
        { | 
						|
            IPointCollection pointColl = ring as IPointCollection; | 
						|
            if (pointColl == null || pointColl.PointCount <= 1) return; | 
						|
            // 步骤1:提取所有点到临时列表 | 
						|
            List<IPoint> allPoints = new List<IPoint>(); | 
						|
            for (int i = 0; i < pointColl.PointCount; i++) | 
						|
            { | 
						|
                allPoints.Add(pointColl.get_Point(i)); | 
						|
            } | 
						|
            // 步骤2:全局去重(支持X/Y/Z/M全维度比较) | 
						|
            HashSet<string> seenCoordinates = new HashSet<string>(); | 
						|
            List<IPoint> uniquePoints = new List<IPoint>(); | 
						|
            foreach (IPoint point in allPoints) | 
						|
            { | 
						|
                // 生成坐标唯一键(根据需求调整是否包含Z/M) | 
						|
                string coordKey = GetCoordinateKey(point); | 
						|
                if (!seenCoordinates.Contains(coordKey)) | 
						|
                { | 
						|
                    seenCoordinates.Add(coordKey); | 
						|
                    uniquePoints.Add(point); | 
						|
                } | 
						|
            } | 
						|
            // 步骤3:重建点集合(先清空再添加去重点) | 
						|
            pointColl.RemovePoints(0, pointColl.PointCount); // 清空原始点 | 
						|
            foreach (IPoint uniquePoint in uniquePoints) | 
						|
            { | 
						|
                pointColl.AddPoint(uniquePoint); | 
						|
            } | 
						|
            // 步骤4:确保环闭合(最后一点与第一点一致) | 
						|
            ring.Close(); | 
						|
        } | 
						|
 | 
						|
        // 生成坐标唯一键(可根据需求调整比较维度) | 
						|
        private static string GetCoordinateKey(IPoint point) | 
						|
        { | 
						|
            // 仅比较X/Y(忽略Z/M) | 
						|
            double x = point.X; | 
						|
            double y = point.Y; | 
						|
            return $"{x:F6},{y:F6}"; // 保留6位小数避免精度问题 | 
						|
        } | 
						|
        #endregion | 
						|
 | 
						|
        /// <summary> | 
						|
        /// 判断图形是否存在狭长 | 
						|
        /// </summary> | 
						|
        /// <param name="geometry"></param> | 
						|
        /// <returns></returns> | 
						|
        private static string ISNarrow(IGeometry geometry) | 
						|
        { | 
						|
            try | 
						|
            { | 
						|
                var strgeometry = GeometryConvertHelper.ConvertIGeoemtryToWKT(geometry); | 
						|
                NetTopologySuite.Geometries.Geometry ntsGeometry = (NetTopologySuite.Geometries.Geometry)new WKBReader().Read(GeometryConvertHelper.ConvertGeometryToWKB(geometry)); | 
						|
                // 内缩参数(侵蚀) | 
						|
                var innerBufferParams = new BufferParameters | 
						|
                { | 
						|
                    QuadrantSegments = 30,          // 内缩平滑度 | 
						|
                    JoinStyle = JoinStyle.Bevel,    // 斜角连接避免尖锐残留 | 
						|
                    EndCapStyle = EndCapStyle.Square  // 平端帽处理开放线段 | 
						|
                }; | 
						|
                var ntsGeometry1 = ntsGeometry.Buffer(-0.05, innerBufferParams);  // 内缩0.1 | 
						|
                strgeometry = ntsGeometry1.AsText(); | 
						|
                if (strgeometry == "POLYGON EMPTY") | 
						|
                { | 
						|
                    return strgeometry; | 
						|
                } | 
						|
                // 外扩参数(膨胀) | 
						|
                var outerBufferParams = new BufferParameters | 
						|
                { | 
						|
                    QuadrantSegments = 200,  // 高平滑度外扩 | 
						|
                    JoinStyle = JoinStyle.Mitre, | 
						|
                    MitreLimit = 2  // 限制尖锐拐角延伸长度 | 
						|
                }; | 
						|
                var ntsGeometry2 = ntsGeometry1.Buffer(0.05, outerBufferParams);  // 外扩0.1 | 
						|
                strgeometry = ntsGeometry2.AsText(); | 
						|
                double length1 = ntsGeometry.Length; | 
						|
                int pointCount1 = ntsGeometry.NumPoints; | 
						|
                double length2 = ntsGeometry2.Length; | 
						|
                int pointCount2 = ntsGeometry2.NumPoints; | 
						|
                double delta_length = length1 - length2; | 
						|
                double avg_halfangle = 180 * (pointCount1 - 1 - 2) / (pointCount1 - 1) / 2; | 
						|
                double conner_normal_length = 2 * 0.05 / Math.Tan(avg_halfangle * (Math.PI / 180)); | 
						|
                if (delta_length > 8 * conner_normal_length * (pointCount1 - 1)) | 
						|
                { | 
						|
                    return strgeometry; | 
						|
                } | 
						|
            } | 
						|
            catch (Exception ex) | 
						|
            { | 
						|
            } | 
						|
            return string.Empty; | 
						|
        } | 
						|
 | 
						|
    } | 
						|
 | 
						|
    public class EarClipper | 
						|
    { | 
						|
        // 三角剖分结果(每个三角形为3个点的集合) | 
						|
        public List<List<IPoint>> Triangles { get; private set; } | 
						|
 | 
						|
        public EarClipper() | 
						|
        { | 
						|
            Triangles = new List<List<IPoint>>(); | 
						|
        } | 
						|
        // 多边形整体方向(顺时针/逆时针) | 
						|
        private bool _isCounterClockwise; | 
						|
        /// <summary> | 
						|
        /// 对多边形执行耳切法三角剖分 | 
						|
        /// </summary> | 
						|
        /// <param name="polygon">输入多边形(需为简单多边形,无自交)</param> | 
						|
        /// <returns>三角剖分结果</returns> | 
						|
        public bool Clip(IPolygon polygon) | 
						|
        { | 
						|
            if (polygon == null || polygon.IsEmpty) | 
						|
                return false; | 
						|
            // 提取多边形外环的顶点(简单多边形默认处理外环) | 
						|
            IPointCollection pointCollection = polygon as IPointCollection; | 
						|
            if (pointCollection == null || pointCollection.PointCount < 3) | 
						|
                return false; | 
						|
            // 转换为顶点列表(排除最后一个闭合点) | 
						|
            List<IPoint> vertices = new List<IPoint>(); | 
						|
            for (int i = 0; i < pointCollection.PointCount - 1; i++) | 
						|
            { | 
						|
                IPoint p = pointCollection.get_Point(i); | 
						|
                vertices.Add(p); | 
						|
            } | 
						|
            // 关键:计算多边形整体方向(顺时针/逆时针) | 
						|
            _isCounterClockwise = IsPolygonCounterClockwise(vertices); | 
						|
            // 执行耳切法 | 
						|
            Triangles.Clear(); | 
						|
            ClipEars(vertices); | 
						|
            return Triangles.Count > 0; | 
						|
        } | 
						|
 | 
						|
        /// <summary> | 
						|
        /// 迭代切割耳 | 
						|
        /// </summary> | 
						|
        private void ClipEars(List<IPoint> vertices) | 
						|
        { | 
						|
            if (vertices.Count < 3) return; | 
						|
            int index = 0; | 
						|
            while (vertices.Count > 3) | 
						|
            { | 
						|
                // 循环取顶点(处理索引越界) | 
						|
                int prevIndex = (index - 1 + vertices.Count) % vertices.Count; | 
						|
                int currIndex = index % vertices.Count; | 
						|
                int nextIndex = (index + 1) % vertices.Count; | 
						|
                IPoint prev = vertices[prevIndex]; | 
						|
                IPoint curr = vertices[currIndex]; | 
						|
                IPoint next = vertices[nextIndex]; | 
						|
                // 检查当前三点是否构成"耳" | 
						|
                if (IsConvex(prev, curr, next) && IsEar(prev, curr, next, vertices)) | 
						|
                { | 
						|
                    // 记录三角形 | 
						|
                    Triangles.Add(new List<IPoint> { prev, curr, next }); | 
						|
                    // 移除当前顶点(切割耳) | 
						|
                    vertices.RemoveAt(currIndex); | 
						|
                    // 重置索引(从下一个顶点开始检查) | 
						|
                    index = 0; | 
						|
                } | 
						|
                else | 
						|
                { | 
						|
                    index++; | 
						|
                    // 防止死循环(理论上简单多边形必然有耳) | 
						|
                    if (index >= vertices.Count * 2) | 
						|
                        break; | 
						|
                } | 
						|
            } | 
						|
            // 添加最后一个三角形 | 
						|
            if (vertices.Count == 3) | 
						|
            { | 
						|
                Triangles.Add(new List<IPoint> { vertices[0], vertices[1], vertices[2] }); | 
						|
            } | 
						|
        } | 
						|
 | 
						|
        /// <summary> | 
						|
        /// 判断顶点curr是否为凸点(通过叉积) | 
						|
        /// </summary> | 
						|
        private bool IsConvex(IPoint prev, IPoint curr, IPoint next) | 
						|
        { | 
						|
            // 计算向量:prev->curr 和 curr->next | 
						|
            double v1x = curr.X - prev.X; | 
						|
            double v1y = curr.Y - prev.Y; | 
						|
            double v2x = next.X - curr.X; | 
						|
            double v2y = next.Y - curr.Y; | 
						|
            // 叉积(z分量):v1 × v2 = v1x*v2y - v1y*v2x | 
						|
            double cross = v1x * v2y - v1y * v2x; | 
						|
            // 叉积≥0:逆时针多边形的凸点(根据多边形方向调整符号) | 
						|
            // 关键:根据多边形整体方向判断凸点 | 
						|
            if (_isCounterClockwise) | 
						|
            { | 
						|
                // 逆时针多边形:叉积 >= 0 为凸点 | 
						|
                return cross >= 0; | 
						|
            } | 
						|
            else | 
						|
            { | 
						|
                // 顺时针多边形:叉积 <= 0 为凸点(与逆时针相反) | 
						|
                return cross <= 0; | 
						|
            } | 
						|
        } | 
						|
        /// <summary> | 
						|
        /// 判断多边形顶点是顺时针还是逆时针排列 | 
						|
        /// </summary> | 
						|
        private bool IsPolygonCounterClockwise(List<IPoint> vertices) | 
						|
        { | 
						|
            double sum = 0; | 
						|
            int n = vertices.Count; | 
						|
            for (int i = 0; i < n; i++) | 
						|
            { | 
						|
                IPoint p = vertices[i]; | 
						|
                IPoint q = vertices[(i + 1) % n]; // 下一个顶点(首尾相连) | 
						|
                sum += (q.X - p.X) * (q.Y + p.Y); | 
						|
            } | 
						|
            // sum > 0 表示逆时针,sum < 0 表示顺时针 | 
						|
            return sum > 0; | 
						|
        } | 
						|
 | 
						|
        /// <summary> | 
						|
        /// 判断三点组成的三角形是否为"耳"(内部无其他顶点) | 
						|
        /// </summary> | 
						|
        private bool IsEar(IPoint a, IPoint b, IPoint c, List<IPoint> vertices) | 
						|
        { | 
						|
            // 检查所有其他顶点是否在三角形abc内部 | 
						|
            foreach (IPoint p in vertices) | 
						|
            { | 
						|
                // 跳过a、b、c本身 | 
						|
                if (p.Equals(a) || p.Equals(b) || p.Equals(c)) | 
						|
                    continue; | 
						|
                // 判断点p是否在三角形abc内部 | 
						|
                if (IsPointInTriangle(p, a, b, c)) | 
						|
                    return false; | 
						|
            } | 
						|
            return true; | 
						|
        } | 
						|
 | 
						|
        /// <summary> | 
						|
        /// 判断点p是否在三角形abc内部( barycentric坐标法) | 
						|
        /// </summary> | 
						|
        private bool IsPointInTriangle(IPoint p, IPoint a, IPoint b, IPoint c) | 
						|
        { | 
						|
            double v0x = c.X - a.X; | 
						|
            double v0y = c.Y - a.Y; | 
						|
            double v1x = b.X - a.X; | 
						|
            double v1y = b.Y - a.Y; | 
						|
            double v2x = p.X - a.X; | 
						|
            double v2y = p.Y - a.Y; | 
						|
            // 计算点积 | 
						|
            double dot00 = v0x * v0x + v0y * v0y; | 
						|
            double dot01 = v0x * v1x + v0y * v1y; | 
						|
            double dot02 = v0x * v2x + v0y * v2y; | 
						|
            double dot11 = v1x * v1x + v1y * v1y; | 
						|
            double dot12 = v1x * v2x + v1y * v2y; | 
						|
            // 计算 barycentric坐标 | 
						|
            double invDenom = 1.0 / (dot00 * dot11 - dot01 * dot01); | 
						|
            double u = (dot11 * dot02 - dot01 * dot12) * invDenom; | 
						|
            double v = (dot00 * dot12 - dot01 * dot02) * invDenom; | 
						|
            // 点在三角形内(包括边界) | 
						|
            return (u >= 0) && (v >= 0) && (u + v <= 1); | 
						|
        } | 
						|
    } | 
						|
} |