C#平面几何简单操作(多边形)

对于多边形的操作,网上有很多成熟的插件,可以轻松的进行多边形相关计算,但我貌似没有找到C#可以用的(可能查找方式不对),出于自身的兴趣,简单的写了一下,算法也不够成熟,还有很多缺陷,姑且先记录一下,待完善之后再做补充。

1、多边形实体:

public class PolygonData
{
    /// <summary>
    /// 创建一个多边形实体
    /// </summary>
    /// <param name="points">二维点数组</param>
    public PolygonData(XPoint[] points)
    {
        Vertexs = points;
    }

    /// <summary>
    /// 多边形顶点数组
    /// </summary>
    public XPoint[] Vertexs { get; set; }

    /// <summary>
    /// 多边形轮廓线集合
    /// </summary>
    /// <returns></returns>
    public IEnumerable<XLine> BorderLines()
    {
        List<XLine> borders = new List<XLine>();
        for (int i = 0; i < Vertexs.Length; i++)
        {
            var j = i + 1;
            if (j >= Vertexs.Length)
            {
                j = 0;
            }
            XLine line = new XLine(Vertexs[i], Vertexs[j]);
            borders.Add(line);
        }
        return borders;
    }

    /// <summary>
    /// 获取顺时针定点
    /// </summary>
    /// <returns></returns>
    public XPoint[] GetClockwiseVertexs()
    {
        double d = 0;
        for (int i = 0; i < Vertexs.Length - 1; i++)
        {
            d += -0.5 * (Vertexs[i + 1].Y + Vertexs[i].Y) * (Vertexs[i + 1].X - Vertexs[i].X);
        }
        if (d > 0)
        {
            return GeometryUtility.ReverseArray(Vertexs);
        }
        return Vertexs;
    }

    /// <summary>
    /// 多边形轮廓线集合
    /// </summary>
    /// <returns></returns>
    public IEnumerable<XLine> ClockwiseBorderLines()
    {
        var clockwiseVertexs = GetClockwiseVertexs();
        List<XLine> borders = new List<XLine>();
        for (int i = 0; i < clockwiseVertexs.Length; i++)
        {
            var j = i + 1;
            if (j >= clockwiseVertexs.Length)
            {
                j = 0;
            }
            XLine line = new XLine(clockwiseVertexs[i], clockwiseVertexs[j]);
            borders.Add(line);
        }
        return borders;
    }

    /// <summary>
    /// 是否包含二维点
    /// </summary>
    /// <param name="point"></param>
    /// <returns></returns>
    public bool ContainPoint(XPoint point)
    {
        //点在任意一条边线上直接当做包含
        var borders = BorderLines();
        int digits = 4;
        double tolance = 0.0001;
        foreach (var line in borders)
        {
            var lineStart = line.StartPt;
            var lineEnd = line.EndPt;
            var minX = Math.Min(lineStart.X, lineEnd.X);
            var maxX = Math.Max(lineStart.X, lineEnd.X);
            var minY = Math.Min(lineStart.Y, lineEnd.Y);
            var maxY = Math.Max(lineStart.Y, lineEnd.Y);
            if (Math.Round(point.X - lineStart.X, digits) * Math.Round(lineEnd.Y - lineStart.Y, digits) == Math.Round(lineEnd.X - lineStart.X, digits) * Math.Round(point.Y - lineStart.Y, digits)
                && point.X >= minX - tolance && point.X <= maxX + tolance && point.Y >= minY - tolance && point.Y <= maxY + tolance)
            {
                return true;
            }
        }

        bool result = false;
        int j = Vertexs.Count() - 1;
        for (int i = 0; i < Vertexs.Count(); i++)
        {
            if (Vertexs[i].Y < point.Y && Vertexs[j].Y >= point.Y || Vertexs[j].Y < point.Y && Vertexs[i].Y >= point.Y)
            {
                if (Vertexs[i].X + (point.Y - Vertexs[i].Y) / (Vertexs[j].Y - Vertexs[i].Y) * (Vertexs[j].X - Vertexs[i].X) < point.X)
                {
                    result = !result;
                }
            }
            j = i;
        }
        return result;
    }
}

//这个是多边形计算的时候用到的一个临时中转
public class LineGroup
{
    public double Length { get; set; }

    public XRange LineRange { get; set; }

    public List<XLine> Lines { get; set; }
}

2、多边形与点、线段、圆弧等对象的一些简单操作(不完善,仅供参考):

public class GeometryUtility
{
    /// <summary>
    /// 求两个多边形的交集顶点
    /// </summary>
    /// <param name="first"></param>
    /// <param name="second"></param>
    /// <returns></returns>
    public static XPoint[] IntersectionsVertexs(PolygonData first, PolygonData second)
    {
        var firstBorders = first.ClockwiseBorderLines();
        var secondBorders = second.ClockwiseBorderLines();
        List<XPoint> newFirstVertexs = new List<XPoint>();
        foreach (var fsBd in firstBorders)
        {
            newFirstVertexs.Add(fsBd.StartPt);
            List<XPoint> intersectPts = new List<XPoint>();
            foreach (var scBd in secondBorders)
            {
                var intersectPoint = IntersectWith(fsBd, scBd);
                if (intersectPoint != null)
                {
                    intersectPts.Add(intersectPoint);
                }
            }
            if (intersectPts.Count > 1)
            {
                var tol = fsBd.Length() * 0.001;
                if (Math.Abs(fsBd.StartPt.X - fsBd.EndPt.X) < tol)
                {
                    if (fsBd.StartPt.Y < fsBd.EndPt.Y)
                    {
                        intersectPts = intersectPts.OrderBy(t => t.Y).ToList();
                    }
                    else
                    {
                        intersectPts = intersectPts.OrderByDescending(t => t.Y).ToList();
                    }
                }
                else
                {
                    if (fsBd.StartPt.X < fsBd.EndPt.X)
                    {
                        intersectPts = intersectPts.OrderBy(t => t.X).ToList();
                    }
                    else
                    {
                        intersectPts = intersectPts.OrderByDescending(t => t.X).ToList();
                    }
                }
            }
            newFirstVertexs.AddRange(intersectPts);
            newFirstVertexs.Add(fsBd.EndPt);
        }
        newFirstVertexs = FilterRepeatPoints(newFirstVertexs.ToArray()).ToList();
        newFirstVertexs.Add(newFirstVertexs.First());

        List<XPoint> newPolygonVertexs = new List<XPoint>();
        int lastIndex = -1;
        for (int i = 0; i < newFirstVertexs.Count; i++)
        {
            var fisPt = newFirstVertexs[i];
            if (second.ContainPoint(fisPt))
            {
                newPolygonVertexs.Add(fisPt);
                lastIndex = i;
            }
            else if (lastIndex >= 0 && lastIndex + 1 == i)
            {
                var lastPt = newFirstVertexs[lastIndex];
                bool haveNext = false;
                for (int j = i + 1; j < newFirstVertexs.Count; j++)
                {
                    var nextPt = newFirstVertexs[j];
                    if (second.ContainPoint(nextPt))
                    {
                        haveNext = true;
                        var betweenPts = GetBetweenVertexs(second, lastPt, nextPt);
                        newPolygonVertexs.AddRange(betweenPts);
                        i = j - 1;
                        break;
                    }
                }
                if (!haveNext)
                {
                    var nextPt = newPolygonVertexs.First();
                    if (second.ContainPoint(nextPt))
                    {
                        var betweenPts = GetBetweenVertexs(second, lastPt, nextPt);
                        newPolygonVertexs.AddRange(betweenPts);
                    }
                    break;
                }
            }
        }
        return FilterRepeatPoints(newPolygonVertexs.ToArray());
    }

    /// <summary>
    /// 判断两条线段是否平行
    /// </summary>
    /// <param name="lineA">线段A</param>
    /// <param name="lineB">线段B</param>
    /// <returns>true:平行,反之</returns>
    public static bool IsParallel(XLine lineA, XLine lineB)
    {
        bool flagA = lineA.StartPt.X == lineA.EndPt.X;
        bool flagB = lineB.StartPt.X == lineB.EndPt.X;
        if (flagA && flagB)
        {
            //两条垂直线
            return true;
        }
        else if (flagB || flagB)
        {
            //一条垂直一条不垂直
            return false;
        }
        else
        {
            //利用斜率进行判断
            var slopeA = (lineA.EndPt.Y - lineA.StartPt.Y) / (lineA.EndPt.X - lineA.StartPt.X);
            var slopeB = (lineB.EndPt.Y - lineB.StartPt.Y) / (lineB.EndPt.X - lineB.StartPt.X);
            return slopeA == slopeB;
        }
    }

    /// <summary>
    /// 获取两条线段的交点
    /// </summary>
    /// <param name="lineM">线段M</param>
    /// <param name="lineN">线段N</param>
    /// <returns>相交则返回交点坐标,否则返回null</returns>
    public static XPoint IntersectWith(XLine lineM, XLine lineN, double tol = 0.001)
    {
        if (IsParallel(lineM, lineN))
        {
            return null;
        }
        //已知直线上的两点A(x1,y1),B(x2,y2)
        //则有:A*x1 + B*y1 + C = A*x2 + B*y2 + C
        //变形得:A(x1 - x2) = B(y2 - y1)
        //求得:A = y2 - y1,B = x1 - x2,C = x2*y1 - x1*y2

        //线段M中的各常数(取百万分之一的容差)
        var constA_m = Math.Round(lineM.EndPt.Y - lineM.StartPt.Y, 6);
        var constB_m = Math.Round(lineM.StartPt.X - lineM.EndPt.X, 6);
        var constC_m = Math.Round(lineM.EndPt.X * lineM.StartPt.Y - lineM.StartPt.X * lineM.EndPt.Y, 6);

        //线段N中的各常数(取百万分之一的容差)
        var constA_n = Math.Round(lineN.EndPt.Y - lineN.StartPt.Y, 6);
        var constB_n = Math.Round(lineN.StartPt.X - lineN.EndPt.X, 6);
        var constC_n = Math.Round(lineN.EndPt.X * lineN.StartPt.Y - lineN.StartPt.X * lineN.EndPt.Y, 6);

        double istPtX, istPtY;
        if (constA_m == 0)
        {
            //线段M平行于X轴
            istPtY = -1 * constC_m / constB_m;
            if (constB_n == 0)
            {
                istPtX = -1 * constC_n / constA_n;
            }
            else
            {
                istPtX = -1 * (constB_n * istPtY + constC_n) / constA_n;//使用线段N的直线方程求交点Y值
            }
        }
        else if (constB_m == 0)
        {
            //线段M平行于Y轴
            istPtX = -1 * constC_m / constA_m;
            if (constA_n == 0)
            {
                istPtY = -1 * constC_n / constB_n;
            }
            else
            {
                istPtY = -1 * (constA_m * istPtX + constC_m) / constB_m;//使用线段M的直线方程求交点Y值
            }
        }
        else if (constA_n == 0)
        {
            //线段N平行于X轴
            istPtY = -1 * constC_n / constB_n;
            if (constB_m == 0)
            {
                istPtX = -1 * constC_m / constA_m;
            }
            else
            {
                istPtX = -1 * (constB_m * istPtY + constC_m) / constA_m;//使用线段M的直线方程求交点Y值
            }
        }
        else if (constB_n == 0)
        {
            //线段N平行于Y轴
            istPtX = -1 * constC_n / constA_n;
            if (constA_m == 0)
            {
                istPtY = -1 * constC_m / constB_m;
            }
            else
            {
                istPtY = -1 * (constA_n * istPtX + constC_n) / constB_n;//使用线段M的直线方程求交点Y值
            }
        }
        else
        {
            istPtX = (constB_n * constC_m - constB_m * constC_n) / (constA_n * constB_m - constA_m * constB_n);//求交点X值(两个直线方程带入)
            istPtY = -1 * (constA_m * istPtX + constC_m) / constB_m;//使用线段A的直线方程求交点Y值
        }


        var minX_m = Math.Min(lineM.StartPt.X, lineM.EndPt.X);
        var maxX_m = Math.Max(lineM.StartPt.X, lineM.EndPt.X);
        var minY_m = Math.Min(lineM.StartPt.Y, lineM.EndPt.Y);
        var maxY_m = Math.Max(lineM.StartPt.Y, lineM.EndPt.Y);
        var flagA = istPtX >= minX_m - tol && istPtX <= maxX_m + tol && istPtY >= minY_m - tol && istPtY <= maxY_m + tol;

        var minX_n = Math.Min(lineN.StartPt.X, lineN.EndPt.X);
        var maxX_n = Math.Max(lineN.StartPt.X, lineN.EndPt.X);
        var minY_n = Math.Min(lineN.StartPt.Y, lineN.EndPt.Y);
        var maxY_n = Math.Max(lineN.StartPt.Y, lineN.EndPt.Y);
        var flagB = istPtX >= minX_n - tol && istPtX <= maxX_n + tol && istPtY >= minY_n - tol && istPtY <= maxY_n + tol;

        //判断交点是否在两条线段上
        if (flagA && flagB)
        {
            return new XPoint(istPtX, istPtY);
        }
        return null;
    }

    /// <summary>
    /// 获取多边形边线上两点中间的所有顶点
    /// </summary>
    /// <param name="polygon">多边形</param>
    /// <param name="startPt">开始点</param>
    /// <param name="endPt">结束点</param>
    /// <returns></returns>
    public static List<XPoint> GetBetweenVertexs(PolygonData polygon, XPoint startPt, XPoint endPt)
    {
        var borders = polygon.ClockwiseBorderLines().ToList();
        int idxStart = -1, idxEnd = -1;
        for (int i = 0; i < borders.Count; i++)
        {
            var curLine = borders[i];
            if (PointOnLine(startPt, curLine))
            {
                idxStart = i;
            }
            if (PointOnLine(endPt, curLine))
            {
                idxEnd = i;
            }
            if (idxStart >= 0 && idxEnd >= 0)
            {
                break;
            }
        }

        List<XPoint> resultPts = new List<XPoint>();
        if (idxStart >= 0 && idxEnd >= 0)
        {
            if (idxStart != idxEnd)
            {
                if (idxStart < idxEnd)
                {
                    for (int k = idxStart; k <= idxEnd; k++)
                    {
                        var curLine = borders[k];
                        if (k != idxStart)
                        {
                            resultPts.Add(curLine.StartPt);
                        }
                        if (k != idxEnd)
                        {
                            resultPts.Add(curLine.EndPt);
                        }
                    }
                }
                else
                {
                    for (int k = idxStart; k < borders.Count; k++)
                    {
                        var curLine = borders[k];
                        if (k != idxStart)
                        {
                            resultPts.Add(curLine.StartPt);
                        }
                        resultPts.Add(curLine.EndPt);
                    }
                    for (int k = 0; k <= idxEnd; k++)
                    {
                        var curLine = borders[k];
                        resultPts.Add(curLine.StartPt);
                        if (k != idxEnd)
                        {
                            resultPts.Add(curLine.EndPt);
                        }
                    }
                }
            }
            else
            {
                var borderLine = borders[idxStart];
                var tol = borderLine.Length() * 0.001;
                var flag = false;
                if (Math.Abs(borderLine.StartPt.X - borderLine.EndPt.X) < tol)
                {
                    flag = (borderLine.StartPt.Y < borderLine.EndPt.Y && startPt.Y > endPt.Y) || (borderLine.StartPt.Y > borderLine.EndPt.Y && startPt.Y < endPt.Y);
                }
                else
                {
                    flag = (borderLine.StartPt.X < borderLine.EndPt.X && startPt.X > endPt.X) || (borderLine.StartPt.X > borderLine.EndPt.X && startPt.X < endPt.X);
                }
                if (flag)
                {
                    for (int k = idxStart; k < borders.Count; k++)
                    {
                        var curLine = borders[k];
                        if (k != idxStart)
                        {
                            resultPts.Add(curLine.StartPt);
                        }
                        resultPts.Add(curLine.EndPt);
                    }
                    for (int k = 0; k <= idxEnd; k++)
                    {
                        var curLine = borders[k];
                        resultPts.Add(curLine.StartPt);
                        if (k != idxEnd)
                        {
                            resultPts.Add(curLine.EndPt);
                        }
                    }
                }
            }
        }

        return resultPts;
    }

    /// <summary>
    /// 过滤重复点
    /// </summary>
    /// <param name="points">点集</param>
    /// <param name="tol">容差</param>
    /// <returns></returns>
    public static XPoint[] FilterRepeatPoints(XPoint[] points, double tol = 0.001)
    {
        List<XPoint> lstResult = new List<XPoint>();
        foreach (var pt in points)
        {
            if (!lstResult.Any(t => XPoint.Distance(t, pt) < tol))
            {
                lstResult.Add(pt);
            }
        }
        return lstResult.ToArray();
    }

    /// <summary>
    /// 将二维点数组逆序
    /// </summary>
    /// <param name="points"></param>
    /// <returns></returns>
    public static XPoint[] ReverseArray(XPoint[] points)
    {
        List<XPoint> result = new List<XPoint>();
        for (int i = points.Length - 1; i >= 0; i--)
        {
            result.Add(points[i]);
        }
        return result.ToArray();
    }

    /// <summary>
    /// 获取线段与多边形的交点
    /// </summary>
    /// <param name="polygon">多边形</param>
    /// <param name="startPt">线段起点</param>
    /// <param name="endPt">线段终点</param>
    /// <returns></returns>
    public static XPoint[] IntersectionsPoints(PolygonData polygon, XPoint startPt, XPoint endPt)
    {
        var borders = polygon.BorderLines();
        XLine line = new XLine(startPt, endPt);
        List<XPoint> insPoints = new List<XPoint>();
        foreach (var item in borders)
        {
            var intersectPoint = IntersectWith(item, line);
            if (intersectPoint != null)
            {
                insPoints.Add(intersectPoint);
            }
        }
        return insPoints.ToArray();
    }

    /// <summary>
    /// 判断点是否在线段上
    /// </summary>
    /// <param name="point">要判断的点</param>
    /// <param name="line">要判断的线段</param>
    /// <param name="digits">容差小数位数</param>
    /// <returns>true:在线段上,false:不在线段上</returns>
    public static bool PointOnLine(XPoint point, XLine line, int digits = 4)
    {
        var lineStart = line.StartPt;
        var lineEnd = line.EndPt;
        var tolance = Math.Pow(0.1, digits);
        var minX = Math.Min(lineStart.X, lineEnd.X);
        var maxX = Math.Max(lineStart.X, lineEnd.X);
        var minY = Math.Min(lineStart.Y, lineEnd.Y);
        var maxY = Math.Max(lineStart.Y, lineEnd.Y);
        return Math.Round(point.X - lineStart.X, 4) * Math.Round(lineEnd.Y - lineStart.Y, 4) == Math.Round(lineEnd.X - lineStart.X, 4) * Math.Round(point.Y - lineStart.Y, 4)
                && point.X >= minX - tolance && point.X <= maxX + tolance && point.Y >= minY - tolance && point.Y <= maxY + tolance;
    }

    /// <summary>
    /// 求线段与圆的交点
    /// 参考资料:https://blog.csdn.net/MiMi_Liang/article/details/105995864
    /// </summary>
    /// <param name="lineStart">线段起点</param>
    /// <param name="lineEnd">线段终点</param>
    /// <param name="circleCenter">圆心点</param>
    /// <param name="radius">圆的半径</param>
    /// <returns></returns>
    public static List<XPoint> LineInterCircle(XPoint lineStart, XPoint lineEnd, XPoint circleCenter, double radius)
    {
        double tolerance = 0.000001;
        List<XPoint> lstResult = new List<XPoint>();
        //线段长度
        double lineLen = Math.Sqrt(Math.Pow(lineEnd.X - lineStart.X, 2) + Math.Pow(lineEnd.Y - lineStart.Y, 2));
        //线段的单位向量(简称:向量a)
        var lineUnitVector = new XPoint((lineEnd.X - lineStart.X) / lineLen, (lineEnd.Y - lineStart.Y) / lineLen);
        //圆心与线段起点的向量(简称:向量b)
        var circleWithLineStart = new XPoint(circleCenter.X - lineStart.X, circleCenter.Y - lineStart.Y);

        //向量投影长度(向量b投影到向量a)
        var vectorProjectionLen = circleWithLineStart.X * lineUnitVector.X + circleWithLineStart.Y * lineUnitVector.Y;
        //向量b的长度
        var cirLStrLen = Math.Sqrt(circleWithLineStart.X * circleWithLineStart.X + circleWithLineStart.Y * circleWithLineStart.Y);
        if (Math.Pow(radius, 2) - (Math.Pow(cirLStrLen, 2) - Math.Pow(vectorProjectionLen, 2)) >= 0)
        {
            //当点到直线的距离小于等于圆的半径,才会有交点
            var interLineHalfLen = Math.Sqrt(Math.Pow(radius, 2) - (Math.Pow(cirLStrLen, 2) - Math.Pow(vectorProjectionLen, 2)));
            if (interLineHalfLen == 0)
            {
                if (vectorProjectionLen > tolerance * -1 && vectorProjectionLen < lineLen + tolerance)
                {
                    lstResult.Add(new XPoint(lineStart.X + vectorProjectionLen * lineUnitVector.X, lineStart.Y + vectorProjectionLen * lineUnitVector.Y));
                }
            }
            else
            {
                var startToFirstLen = vectorProjectionLen - interLineHalfLen;
                if (startToFirstLen > tolerance * -1 && startToFirstLen < lineLen + tolerance)
                {
                    lstResult.Add(new XPoint(lineStart.X + startToFirstLen * lineUnitVector.X, lineStart.Y + startToFirstLen * lineUnitVector.Y));
                }
                var startToSecondLen = vectorProjectionLen + interLineHalfLen;
                if (startToSecondLen > tolerance * -1 && startToSecondLen < lineLen + tolerance)
                {
                    lstResult.Add(new XPoint(lineStart.X + startToSecondLen * lineUnitVector.X, lineStart.Y + startToSecondLen * lineUnitVector.Y));
                }
            }
        }
        return lstResult;
    }

    /// <summary>
    /// 获取在圆弧上的点
    /// </summary>
    /// <param name="source">点集</param>
    /// <param name="circleCenter">圆心</param>
    /// <param name="radius">半径</param>
    /// <param name="arcStart">圆弧起点</param>
    /// <param name="arcEnd">圆弧终点</param>
    /// <param name="centerAngle">圆心角</param>
    /// <returns></returns>
    public static List<XPoint> FilterPointOnCircle(List<XPoint> source, XPoint circleCenter, double radius, XPoint arcStart, XPoint arcEnd, double centerAngle)
    {
        var tol = 0.001;
        var startRadian = GetRadian(circleCenter, radius, arcStart);
        var endRadian = GetRadian(circleCenter, radius, arcEnd);
        var diffRadian = Math.Abs(endRadian - startRadian);
        var minRadian = Math.Min(startRadian, endRadian);
        var maxRadian = Math.Max(startRadian, endRadian);

        List<XPoint> result = new List<XPoint>();
        foreach (var point in source)
        {
            var pointRadian = GetRadian(circleCenter, radius, point);
            if (Math.Abs(diffRadian - centerAngle) < tol)
            {
                if (pointRadian >= minRadian && pointRadian <= maxRadian)
                {
                    result.Add(point);
                }
            }
            else
            {
                if (pointRadian <= minRadian || pointRadian >= maxRadian)
                {
                    result.Add(point);
                }
            }
        }
        return result;
    }

    /// <summary>
    /// 根据圆弧排序圆上的点
    /// </summary>
    /// <param name="source">点集</param>
    /// <param name="circleCenter">圆弧所在圆的圆心</param>
    /// <param name="radius">圆弧所在圆的半径</param>
    /// <param name="arcStart">圆弧起点</param>
    /// <param name="arcEnd">圆弧终点</param>
    /// <param name="centerAngle">圆心角(弧度)</param>
    /// <returns></returns>
    public static List<XPoint> OrderPointOnCircle(List<XPoint> source, XPoint circleCenter, double radius, XPoint arcStart, XPoint arcEnd, double centerAngle)
    {
        var tol = 0.001;
        var startRadian = GetRadian(circleCenter, radius, arcStart);
        var endRadian = GetRadian(circleCenter, radius, arcEnd);
        var diffRadian = Math.Abs(endRadian - startRadian);
        var minRadian = Math.Min(startRadian, endRadian);
        var maxRadian = Math.Max(startRadian, endRadian);

        source = FilterRepeatPoints(source.ToArray()).ToList();
        List<Tuple<XPoint, double>> tuples = new List<Tuple<XPoint, double>>();
        foreach (var point in source)
        {
            var pointRadian = GetRadian(circleCenter, radius, point);
            tuples.Add(new Tuple<XPoint, double>(point, pointRadian));
        }
        tuples = tuples.OrderBy(t => t.Item2).ToList();
        List<XPoint> result = new List<XPoint>();
        if (Math.Abs(diffRadian - centerAngle) < tol)
        {
            result.AddRange(tuples.Select(t => t.Item1).ToList());
        }
        else
        {
            var maxRadianList = tuples.Where(t => t.Item2 >= maxRadian).Select(d => d.Item1).ToList();
            result.AddRange(maxRadianList);
            var minRadianList = tuples.Where(t => t.Item2 <= minRadian).Select(d => d.Item1).ToList();
            result.AddRange(minRadianList);
        }
        return result;
    }

    /// <summary>
    /// 获取圆上任意一点的弧度(0~2π)
    /// </summary>
    /// <param name="circleCenter">圆心坐标</param>
    /// <param name="radius">半径</param>
    /// <param name="point">任意点</param>
    /// <returns></returns>
    public static double GetRadian(XPoint circleCenter, double radius, XPoint point)
    {
        double radian;
        //把起点视为原点,判断端点属于第几象限
        var cosAngle = (Math.Pow(radius, 2) + Math.Pow(point.X - circleCenter.X, 2) - Math.Pow(point.Y - circleCenter.Y, 2)) / (2 * radius * Math.Abs(point.X - circleCenter.X));
        if (point.X > circleCenter.X)
        {
            if (point.Y > circleCenter.Y)
            {
                //第一象限
                radian = Math.Acos(cosAngle);
            }
            else if (point.Y < circleCenter.Y)
            {
                //第四象限
                radian = 2 * Math.PI - Math.Acos(cosAngle);
            }
            else
            {
                //X正半轴
                radian = 0;
            }
        }
        else if (point.X < circleCenter.X)
        {
            if (point.Y > circleCenter.Y)
            {
                //第二象限
                radian = Math.PI - Math.Acos(cosAngle);
            }
            else if (point.Y < circleCenter.Y)
            {
                //第三象限
                radian = Math.PI + Math.Acos(cosAngle);
            }
            else
            {
                //X负半轴
                radian = Math.PI;
            }
        }
        else
        {
            if (point.Y > circleCenter.Y)
            {
                //Y正半轴
                radian = Math.PI / 2;
            }
            else if (point.Y < circleCenter.Y)
            {
                //Y负半轴
                radian = Math.PI + Math.PI / 2;
            }
            else
            {
                //同一点
                radian = double.NaN;
            }
        }
        return radian;
    }

    /// <summary>
    /// 获取圆弧的中点坐标
    /// </summary>
    /// <param name="circleCenter">圆心</param>
    /// <param name="radius">半径</param>
    /// <param name="arcStart">起点</param>
    /// <param name="arcEnd">终点</param>
    /// <param name="clockwise">是否顺时针</param>
    /// <returns></returns>
    public static XPoint GetArcMiddlePoint(XPoint circleCenter, double radius, XPoint arcStart, XPoint arcEnd, bool clockwise)
    {
        var tol = 0.001;
        Autodesk.AutoCAD.Geometry.Vector2d vectorOA = new Autodesk.AutoCAD.Geometry.Vector2d(arcStart.X - circleCenter.X, arcStart.Y - circleCenter.Y);
        Autodesk.AutoCAD.Geometry.Vector2d vectorOB = new Autodesk.AutoCAD.Geometry.Vector2d(arcEnd.X - circleCenter.X, arcEnd.Y - circleCenter.Y);
        double centerAngle, istRadian;
        if (clockwise)
        {
            if (vectorOB.Angle > vectorOA.Angle)
            {
                centerAngle = vectorOB.Angle - vectorOA.Angle;
                istRadian = (vectorOA.Angle + vectorOB.Angle) / 2;
            }
            else
            {
                centerAngle = 2 * Math.PI - vectorOA.Angle + vectorOB.Angle;
                istRadian = (vectorOA.Angle + vectorOB.Angle) / 2 + Math.PI;
            }
        }
        else
        {
            if (vectorOA.Angle > vectorOB.Angle)
            {
                centerAngle = vectorOA.Angle - vectorOB.Angle;
                istRadian = (vectorOA.Angle + vectorOB.Angle) / 2;
            }
            else
            {
                centerAngle = 2 * Math.PI - vectorOB.Angle + vectorOA.Angle;
                istRadian = (vectorOA.Angle + vectorOB.Angle) / 2 + Math.PI;
            }
        }

        if (Math.Abs(Math.PI - centerAngle) < tol)
        {
            //半圆使用独立算法
            return GetSemicircleMiddlePoint(circleCenter, radius, arcStart, centerAngle);
        }

        //连接圆弧起点和终点得到线段L,此时圆心到线段L的垂点即为线段L的中点,则有:
        XPoint verticalPt = new XPoint((arcStart.X + arcEnd.X) / 2, (arcStart.Y + arcEnd.Y) / 2);
        //垂线与圆弧的交点到垂点的距离为
        double verticalToCircle;
        if (centerAngle < Math.PI)
        {
            verticalToCircle = radius - Math.Sqrt(Math.Pow(verticalPt.X - circleCenter.X, 2) + Math.Pow(verticalPt.Y - circleCenter.Y, 2));
        }
        else
        {
            verticalToCircle = radius + Math.Sqrt(Math.Pow(verticalPt.X - circleCenter.X, 2) + Math.Pow(verticalPt.Y - circleCenter.Y, 2));
        }

        //已知直线上的两点A(x1,y1),B(x2,y2)
        //则有:A*x1 + B*y1 + C = A*x2 + B*y2 + C
        //变形得:A(x1 - x2) = B(y2 - y1)
        //求得:A = y2 - y1,B = x1 - x2,C = x2*y1 - x1*y2

        //圆心跟垂点所在直线方程中的各常数(取百万分之一的容差)
        var constA = Math.Round(verticalPt.Y - circleCenter.Y, 6);
        var constB = Math.Round(circleCenter.X - verticalPt.X, 6);
        var constC = Math.Round(verticalPt.X * circleCenter.Y - circleCenter.X * verticalPt.Y, 6);

        //设圆弧中点P(x,y),则有:
        //方程一:constA * x + constB * y + constC = 0
        //方程二:Math.Pow(x - circleCenter.X, 2) + Math.Pow(y - circleCenter.Y, 2) = Math.Pow(radius, 2)
        //方程三:Math.Pow(x - verticalPt.X, 2) + Math.Pow(y - verticalPt.Y, 2) = Math.Pow(verticalToCircle, 2)
        //解方程二和三可得方程四:2 * x * (verticalPt.X - circleCenter.X) + 2 * y * (verticalPt.Y - circleCenter.Y) + circleCenter.X * circleCenter.X - verticalPt.X * verticalPt.X + circleCenter.Y * circleCenter.Y - verticalPt.Y * verticalPt.Y =  Math.Pow(radius, 2) - Math.Pow(verticalToCircle, 2)
        var constI = 2 * (verticalPt.X - circleCenter.X);
        var constJ = 2 * (verticalPt.Y - circleCenter.Y);
        var constK = circleCenter.X * circleCenter.X - verticalPt.X * verticalPt.X + circleCenter.Y * circleCenter.Y - verticalPt.Y * verticalPt.Y - radius * radius + verticalToCircle * verticalToCircle;

        double istPtX, istPtY;
        if (constA == 0)
        {
            //线段M平行于X轴
            istPtY = -1 * constC / constB;
            if (constJ == 0)
            {
                istPtX = -1 * constK / constI;
            }
            else
            {
                istPtX = -1 * (constJ * istPtY + constK) / constI;//使用方程四求交点Y值
            }
        }
        else if (constB == 0)
        {
            //线段M平行于Y轴
            istPtX = -1 * constC / constA;
            if (constI == 0)
            {
                istPtY = -1 * constK / constJ;
            }
            else
            {
                istPtY = -1 * (constA * istPtX + constC) / constB;//使用线段M的直线方程求交点Y值
            }
        }
        else if (constI == 0)
        {
            //线段N平行于X轴
            istPtY = -1 * constK / constJ;
            if (constB == 0)
            {
                istPtX = -1 * constC / constA;
            }
            else
            {
                istPtX = -1 * (constB * istPtY + constC) / constA;//使用线段M的直线方程求交点Y值
            }
        }
        else if (constJ == 0)
        {
            //线段N平行于Y轴
            istPtX = -1 * constK / constI;
            if (constA == 0)
            {
                istPtY = -1 * constC / constB;
            }
            else
            {
                istPtY = -1 * (constI * istPtX + constK) / constJ;//使用线段M的直线方程求交点Y值
            }
        }
        else
        {
            istPtX = (constJ * constC - constB * constK) / (constI * constB - constA * constJ);//求交点X值(两个直线方程带入)
            istPtY = -1 * (constA * istPtX + constC) / constB;//使用线段A的直线方程求交点Y值
        }
        return new XPoint(istPtX, istPtY);
    }

    /// <summary>
    /// 半圆中点计算
    /// </summary>
    /// <param name="circleCenter">圆心坐标</param>
    /// <param name="radius">半径</param>
    /// <param name="arcStart">起点坐标</param>
    /// <param name="istRadian">中点弧度</param>
    /// <returns></returns>
    public static XPoint GetSemicircleMiddlePoint(XPoint circleCenter, double radius, XPoint arcStart, double istRadian)
    {
        var tol = 0.001;
        //设中点坐标为(istPtX, istPtY)
        double istPtX, istPtY;
        //通过半圆中点与半圆起终直线与半径的关系,可得出:
        //方程一:Math.Pow(istPtX - arcStart.X, 2) + Math.Pow(istPtY - arcStart.Y, 2) = Math.Pow(radius, 2) + Math.Pow(radius, 2);
        //方程二:Math.Pow(istPtX - circleCenter.X, 2) + Math.Pow(istPtY - circleCenter.Y, 2) = Math.Pow(radius, 2);
        //方程一减方程二得方程三:istPtX * (2 * circleCenter.X - 2 * arcStart.X) + istPtY * (2 * circleCenter.Y - 2 * arcStart.Y) + Math.Pow(arcStart.X, 2) - Math.Pow(circleCenter.X, 2) + Math.Pow(arcStart.Y, 2) - Math.Pow(circleCenter.Y, 2) = Math.Pow(radius, 2);

        //角度
        var istAngle = istRadian / Math.PI * 180;
        if (istAngle != 90 && istAngle != 270)
        {
            //圆心与中点所过直线的斜率为
            var k = Math.Tan(istAngle);
            //使用圆心坐标求出斜截式方程(y = kx + b)中的常量b
            var constB = circleCenter.Y - k * circleCenter.X;
            //则有方程四:istPtY = k * istPtX + constB;
            //将方程四带入方程三后,可得:
            istPtX = (Math.Pow(radius, 2) - Math.Pow(arcStart.X, 2) - Math.Pow(arcStart.Y, 2) + Math.Pow(circleCenter.X, 2) + Math.Pow(circleCenter.Y, 2) - 2 * constB * (circleCenter.Y - arcStart.Y)) / (2 * (circleCenter.X - arcStart.X + k * circleCenter.Y - k * arcStart.Y));
            //带入方程四得:
            istPtY = k * istPtX + constB;
        }
        else
        {
            istPtX = circleCenter.X;
            //则有:
            istPtY = circleCenter.Y + radius;
            Autodesk.AutoCAD.Geometry.Vector2d vectorOP = new Autodesk.AutoCAD.Geometry.Vector2d(istPtX - circleCenter.X, istPtY - circleCenter.Y);
            if (Math.Abs(vectorOP.Angle - istRadian) > tol)
            {
                istPtY = circleCenter.Y - radius;
            }
        }
        return new XPoint(istPtX, istPtY);
    }

    /// <summary>
    /// 获取多边形与圆弧的交点
    /// </summary>
    /// <param name="polygon">多边形</param>
    /// <param name="circleCenter">圆弧所在圆的圆心</param>
    /// <param name="radius">圆弧所在圆的半径</param>
    /// <param name="arcStart">圆弧起点</param>
    /// <param name="arcEnd">圆弧终点</param>
    /// <param name="centerAngle">圆心角(弧度)</param>
    /// <returns></returns>
    public static List<XPoint> IntersectionsPoints(PolygonData polygon, XPoint circleCenter, double radius, XPoint arcStart, XPoint arcEnd, double centerAngle)
    {
        var borders = polygon.BorderLines();
        List<XPoint> insPoints = new List<XPoint>();
        foreach (var item in borders)
        {
            var intersectPoints = LineInterCircle(item.StartPt, item.EndPt, circleCenter, radius);
            if (intersectPoints != null && intersectPoints.Any())
            {
                var filterResult = FilterPointOnCircle(intersectPoints, circleCenter, radius, arcStart, arcEnd, centerAngle);
                insPoints.AddRange(filterResult);
            }
        }
        return insPoints;
    }

    /// <summary>
    /// 提取矩形
    /// </summary>
    /// <param name="horLines">横线集合</param>
    /// <param name="verLines">竖线集合</param>
    /// <returns></returns>
    public static Dictionary<XExtents, List<XLine>> ExtractRectangle(List<XLine> horLines, List<XLine> verLines, double ratioTolerance)
    {
        //先将可以合并的线段进行合并排序
        //var sourceLines = MergeContainLines(_ratioTolerance, horLines.ToArray(), verLines.ToArray(), _ratioTolerance);
        //求横线的对边
        List<int> lstIndex = new List<int>();//记录已经匹配过了的线段索引
        List<LineGroup> horLineGroup = new List<LineGroup>();
        for (int i = 0; i < horLines.Count; i++)
        {
            if (lstIndex.Contains(i)) continue;//已经匹配过的线段不进行二次匹配
            var hLine = horLines[i];
            var tol = hLine.Length() * ratioTolerance;
            var matchLines = horLines.Where(t => IsHorRect(t, hLine, tol) && t != hLine).ToList();
            if (matchLines.Any())
            {
                List<XLine> group = new List<XLine>() { hLine };
                if (matchLines.Count > 100)
                {
                    //如果数量大于100,一般不会是目录表格
                    //保险起见,取距离最远的一根
                    var distantLine = matchLines.OrderByDescending(t => Math.Abs(t.MidPt.Y - hLine.MidPt.Y)).FirstOrDefault();
                    group.Add(distantLine);
                }
                else
                {
                    group.AddRange(matchLines);
                }
                LineGroup lineGroup = new LineGroup();
                lineGroup.Length = hLine.Length();
                lineGroup.LineRange = new XRange(hLine.StartPt.X, hLine.EndPt.X);
                lineGroup.Lines = group;
                horLineGroup.Add(lineGroup);
                foreach (var line in group)
                {
                    lstIndex.Add(horLines.ToList().IndexOf(line));
                }
            }
        }
        lstIndex = new List<int>();
        List<LineGroup> vorLineGroup = new List<LineGroup>();
        for (int i = 0; i < verLines.Count; i++)
        {
            if (lstIndex.Contains(i)) continue;//已经匹配过的线段不进行二次匹配
            var vLine = verLines[i];
            var tol = vLine.Length() * ratioTolerance;
            var matchLines = verLines.Where(t => IsVerRect(t, vLine, tol) && t != vLine).ToList();
            if (matchLines.Any())
            {
                List<XLine> group = new List<XLine>() { vLine };
                if (matchLines.Count > 100)
                {
                    //如果数量大于100,一般不会是目录表格
                    //保险起见,取距离最远的一根
                    var distantLine = matchLines.OrderByDescending(t => Math.Abs(t.MidPt.Y - vLine.MidPt.Y)).FirstOrDefault();
                    group.Add(distantLine);
                }
                else
                {
                    group.AddRange(matchLines);
                }
                LineGroup lineGroup = new LineGroup();
                lineGroup.Length = vLine.Length();
                lineGroup.LineRange = new XRange(vLine.StartPt.Y, vLine.EndPt.Y);
                lineGroup.Lines = group;
                vorLineGroup.Add(lineGroup);
                foreach (var line in group)
                {
                    lstIndex.Add(verLines.ToList().IndexOf(line));
                }
            }
        }

        //通过筛选出来的横线查找匹配的竖线
        Dictionary<XExtents, List<XLine>> result = new Dictionary<XExtents, List<XLine>>();
        foreach (var group in horLineGroup)
        {
            for (int m = 0; m < group.Lines.Count; m++)
            {
                var startLine = group.Lines[m];
                var tol = startLine.Length() * ratioTolerance;
                for (int n = m + 1; n < group.Lines.Count; n++)
                {
                    var endLine = group.Lines[n];
                    var spacing = endLine.MidPt.Y - startLine.MidPt.Y;
                    XRange yRange = new XRange(startLine.MidPt.Y, endLine.MidPt.Y);
                    //var matchLines = sourceLines.Item2.Where(t => Math.Abs(t.Length() - spacing) < tol && Math.Abs(t.StartPt.Y - startLine.MidPt.Y) < tol && Math.Abs(t.EndPt.Y - endLine.MidPt.Y) < tol).ToList();
                    var matchGroups = vorLineGroup.Where(g => Math.Abs(g.Length - spacing) < tol && yRange.In(g.LineRange.Min, tol) && yRange.In(g.LineRange.Max, tol)).ToList();
                    if (matchGroups.Any())
                    {
                        foreach (var vGroup in matchGroups)
                        {
                            //继续进行更精确的判断
                            List<XLine> lstVerLines = new List<XLine>();
                            foreach (var vLine in vGroup.Lines)
                            {
                                if (XLine.LineJoint(startLine, vLine, tol) && XLine.LineJoint(endLine, vLine, tol))
                                {
                                    lstVerLines.Add(vLine);
                                }
                            }
                            //如果有两条及以上满足条件的竖线,则构成矩形
                            if (lstVerLines.Count >= 2)
                            {
                                XExtents ext = new XExtents();
                                ext.AddPoint(startLine.StartPt);
                                ext.AddPoint(endLine.EndPt);
                                List<XLine> lines = new List<XLine>() { startLine, endLine };
                                foreach (var vl in lstVerLines)
                                {
                                    ext.AddPoint(vl.StartPt);
                                    ext.AddPoint(vl.EndPt);
                                    lines.Add(vl);
                                }
                                if (!result.ContainsKey(ext))
                                {
                                    result.Add(ext, lines);
                                }
                            }
                        }
                    }
                }
            }
        }
        return result;
    }

    /// <summary>
    /// 判断两条横线是否构成矩形对边
    /// </summary>
    /// <param name="lineA"></param>
    /// <param name="lineB"></param>
    /// <param name="tol"></param>
    /// <returns></returns>
    private static bool IsHorRect(XLine lineA, XLine lineB, double tol)
    {
        double alen = lineA.Length();
        double blen = lineB.Length();
        if (Math.Abs(alen - blen) < tol && alen >= tol) //等长且非一个点
        {
            if (Math.Abs(lineA.MidPt.Y - lineB.MidPt.Y) < tol)
            {
                return false;
            }
            var flagA = Math.Abs(lineA.StartPt.X - lineB.StartPt.X) < tol;
            var flagB = Math.Abs(lineA.EndPt.X - lineB.EndPt.X) < tol;
            return flagA && flagB;
        }
        return false;
    }

    /// <summary>
    /// 判断两条竖线是否构成矩形对边
    /// </summary>
    /// <param name="lineA"></param>
    /// <param name="lineB"></param>
    /// <param name="tol"></param>
    /// <returns></returns>
    private static bool IsVerRect(XLine lineA, XLine lineB, double tol)
    {
        double alen = lineA.Length();
        double blen = lineB.Length();
        if (Math.Abs(alen - blen) < tol && alen >= tol) //等长且非一个点
        {
            if (Math.Abs(lineA.MidPt.X - lineB.MidPt.X) < tol)
            {
                return false;
            }
            var flagA = Math.Abs(lineA.StartPt.Y - lineB.StartPt.Y) < tol;
            var flagB = Math.Abs(lineA.EndPt.Y - lineB.EndPt.Y) < tol;
            return flagA && flagB;
        }
        return false;
    }
}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值