对于多边形的操作,网上有很多成熟的插件,可以轻松的进行多边形相关计算,但我貌似没有找到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;
}
}