C# 用多边形逼近椭圆
我正在研究地理信息,最近我需要画一个椭圆。为了与OGC约定兼容,我不能使用椭圆;取而代之的是,我使用一个多边形来近似椭圆,通过取一个包含在椭圆中的多边形,并使用任意多个点 我用来为给定数量的点N生成椭圆的过程如下(使用C#和一个虚构的多边形类):C# 用多边形逼近椭圆,c#,geometry,polygon,computational-geometry,ellipse,C#,Geometry,Polygon,Computational Geometry,Ellipse,我正在研究地理信息,最近我需要画一个椭圆。为了与OGC约定兼容,我不能使用椭圆;取而代之的是,我使用一个多边形来近似椭圆,通过取一个包含在椭圆中的多边形,并使用任意多个点 我用来为给定数量的点N生成椭圆的过程如下(使用C#和一个虚构的多边形类): 多边形创建椭圆多边形(坐标中心、双半径X、双半径Y、整数点) { 多边形结果=新多边形(); 对于(int i=0;i我建议您切换到极坐标: 极坐标中的椭圆为: x(t) = XRadius * cos(t) y(t) = YRadius * sin(
多边形创建椭圆多边形(坐标中心、双半径X、双半径Y、整数点)
{
多边形结果=新多边形();
对于(int i=0;i我建议您切换到极坐标:
极坐标中的椭圆为:
x(t) = XRadius * cos(t)
y(t) = YRadius * sin(t)
对于0伊拉迪乌斯(或伊拉迪乌斯>>伊拉迪乌斯)
不用numberOfPoints,你可以使用一组角度,这些角度显然不完全相同。
也就是说,用36个点等分,你可以得到每个扇区的角度=2*pi*n/36弧度。
当你在一个“邻居”中得到n=0(或36)或n=18时在这两个值中,“近似”方法效果不佳,因为椭圆扇形与用于近似的三角形明显不同。您可以减小此点周围的扇形大小,从而提高精度。而不仅仅是增加点数,这也会增加其他不需要的区域中的线段。序列角度的大小应类似于(以度为单位):
第一个5度序列用于t=0,第二个用于t=pi,最后一个约为2*pi。实现闭合轮廓(如椭圆)自适应离散化的一种方法是反向运行:
1. Start with a coarse description of the contour C, in this case 4
points located at the left, right, top and bottom of the ellipse.
2. Push the initial 4 edges onto a queue Q.
while (N < Nmax && Q not empty)
3. Pop an edge [pi,pj] <- Q, where pi,pj are the endpoints.
4. Project a midpoint pk onto the contour C. (I expect that
simply bisecting the theta endpoint values will suffice
for an ellipse).
5. Calculate distance D between point pk and edge [pi,pj].
if (D > TOL)
6. Replace edge [pi,pj] with sub-edges [pi,pk], [pk,pj].
7. Push new edges onto Q.
8. N = N+1
endif
endwhile
1.从轮廓C的粗略描述开始,在本例中为4
位于椭圆左侧、右侧、顶部和底部的点。
2.将初始4条边推到队列Q上。
while(N
该算法迭代地细化轮廓C
的初始离散化,在高曲率区域聚集点。当满足(i)
用户定义的容错TOL
或(ii)
使用最大允许点数Nmax
时,该算法终止
我确信有可能找到一种特别针对椭圆优化的替代方法,但我认为这种方法的通用性非常方便
finding a polygon in which the angle between every two lines is
always the same
是的,这是可能的。我们想找到(第一个)椭圆象限的点,这些点的切线角形成等距(相同的角度差)序列。在点上找到切线并不难
x=a*Cos(fi)
y=b*Sin(Fi)
derivatives
dx=-a*Sin(Fi), dy=b*Cos(Fi)
y'=dy/dx=-b/a*Cos(Fi)/Sin(Fi)=-b/a*Ctg(Fi)
导数y'描述切线,此切线具有角度系数
k=b/a*Cotangent(Fi)=Tg(Theta)
Fi = ArcCotangent(a/b*Tg(Theta)) = Pi/2-ArcTan(a/b*Tg(Theta))
由于
其中Fi从0到Pi/2变化,θ-从Pi/2到0。
因此,在每个象限中查找N+1个点(包括极值点)的代码可能如下所示(这是生成附图的Delphi代码)
完美角度多边形的草图:
这是我使用的一个迭代算法
我没有寻找理论上最优的解决方案,但对我来说效果很好
请注意,此算法的输入是多边形质数相对于椭圆的最大误差,而不是您希望的点数
public static class EllipsePolygonCreator
{
#region Public static methods
public static IEnumerable<Coordinate> CreateEllipsePoints(
double maxAngleErrorRadians,
double width,
double height)
{
IEnumerable<double> thetas = CreateEllipseThetas(maxAngleErrorRadians, width, height);
return thetas.Select(theta => GetPointOnEllipse(theta, width, height));
}
#endregion
#region Private methods
private static IEnumerable<double> CreateEllipseThetas(
double maxAngleErrorRadians,
double width,
double height)
{
double firstQuarterStart = 0;
double firstQuarterEnd = Math.PI / 2;
double startPrimeAngle = Math.PI / 2;
double endPrimeAngle = 0;
double[] thetasFirstQuarter = RecursiveCreateEllipsePoints(
firstQuarterStart,
firstQuarterEnd,
maxAngleErrorRadians,
width / height,
startPrimeAngle,
endPrimeAngle).ToArray();
double[] thetasSecondQuarter = new double[thetasFirstQuarter.Length];
for (int i = 0; i < thetasFirstQuarter.Length; ++i)
{
thetasSecondQuarter[i] = Math.PI - thetasFirstQuarter[thetasFirstQuarter.Length - i - 1];
}
IEnumerable<double> thetasFirstHalf = thetasFirstQuarter.Concat(thetasSecondQuarter);
IEnumerable<double> thetasSecondHalf = thetasFirstHalf.Select(theta => theta + Math.PI);
IEnumerable<double> thetas = thetasFirstHalf.Concat(thetasSecondHalf);
return thetas;
}
private static IEnumerable<double> RecursiveCreateEllipsePoints(
double startTheta,
double endTheta,
double maxAngleError,
double widthHeightRatio,
double startPrimeAngle,
double endPrimeAngle)
{
double yDelta = Math.Sin(endTheta) - Math.Sin(startTheta);
double xDelta = Math.Cos(startTheta) - Math.Cos(endTheta);
double averageAngle = Math.Atan2(yDelta, xDelta * widthHeightRatio);
if (Math.Abs(averageAngle - startPrimeAngle) < maxAngleError &&
Math.Abs(averageAngle - endPrimeAngle) < maxAngleError)
{
return new double[] { endTheta };
}
double middleTheta = (startTheta + endTheta) / 2;
double middlePrimeAngle = GetPrimeAngle(middleTheta, widthHeightRatio);
IEnumerable<double> firstPoints = RecursiveCreateEllipsePoints(
startTheta,
middleTheta,
maxAngleError,
widthHeightRatio,
startPrimeAngle,
middlePrimeAngle);
IEnumerable<double> lastPoints = RecursiveCreateEllipsePoints(
middleTheta,
endTheta,
maxAngleError,
widthHeightRatio,
middlePrimeAngle,
endPrimeAngle);
return firstPoints.Concat(lastPoints);
}
private static double GetPrimeAngle(double theta, double widthHeightRatio)
{
return Math.Atan(1 / (Math.Tan(theta) * widthHeightRatio)); // Prime of an ellipse
}
private static Coordinate GetPointOnEllipse(double theta, double width, double height)
{
double x = width * Math.Cos(theta);
double y = height * Math.Sin(theta);
return new Coordinate(x, y);
}
#endregion
}
公共静态类EllipsePolygonCreator
{
#区域公共静态方法
公共静态IEnumerable CreateEllipsePoints(
双最大弧度,
双倍宽度,
双高)
{
IEnumerableθ=CreateEllipsetetetas(最大角度误差弧度、宽度、高度);
返回thetas.Select(theta=>GetPointOnEllipse(theta,width,height));
}
#端区
#区域私有方法
私有静态IEnumerable CreateEllipsetetetas(
双最大弧度,
双倍宽度,
双高)
{
双四分之一起点=0;
双首四分之一=Math.PI/2;
双启动定时角=Math.PI/2;
双端夹角=0;
double[]Thetas FirstQuarter=递归CreateEllipsePoints(
第一季度开始,
第一季度,
最大弧度,
宽度/高度,
StartTimeAngle,
endPrimeAngle)。ToArray();
double[]thetasSecondQuarter=新的double[thetasFirstQuarter.Length];
对于(int i=0;itheta+Math.PI);
IEnumerable thetas=thetas firsthalf.Concat(thetasSecondHalf);
返回θ;
}
私有静态IEnumerable递归CreateEllipsePoints(
双星,
双端θ,
双最大角度误差,
双宽高比,
双启动时间角,
双端(角)
{
double yDelta=Math.Sin(endTheta)-Math.Sin(startTheta);
double-xDelta=Math.Cos(startTheta)-Math.Cos(endTheta);
双平均角=Math.Atan2(yDelta,xDelta*宽高比);
if(Math.Abs(averageAngle-StartTimeAngle)k=b/a*Cotangent(Fi)=Tg(Theta)
Fi = ArcCotangent(a/b*Tg(Theta)) = Pi/2-ArcTan(a/b*Tg(Theta))
for i := 0 to N - 1 do begin
Theta := Pi/2 * i / N;
Fi := Pi/2 - ArcTan(Tan(Theta) * a/b);
x := CenterX + Round(a * Cos(Fi));
y := CenterY + Round(b * Sin(Fi));
end;
// I've removed Nth point calculation, that involves indefinite Tan(Pi/2)
// It would better to assign known value 0 to Fi in this point
public static class EllipsePolygonCreator
{
#region Public static methods
public static IEnumerable<Coordinate> CreateEllipsePoints(
double maxAngleErrorRadians,
double width,
double height)
{
IEnumerable<double> thetas = CreateEllipseThetas(maxAngleErrorRadians, width, height);
return thetas.Select(theta => GetPointOnEllipse(theta, width, height));
}
#endregion
#region Private methods
private static IEnumerable<double> CreateEllipseThetas(
double maxAngleErrorRadians,
double width,
double height)
{
double firstQuarterStart = 0;
double firstQuarterEnd = Math.PI / 2;
double startPrimeAngle = Math.PI / 2;
double endPrimeAngle = 0;
double[] thetasFirstQuarter = RecursiveCreateEllipsePoints(
firstQuarterStart,
firstQuarterEnd,
maxAngleErrorRadians,
width / height,
startPrimeAngle,
endPrimeAngle).ToArray();
double[] thetasSecondQuarter = new double[thetasFirstQuarter.Length];
for (int i = 0; i < thetasFirstQuarter.Length; ++i)
{
thetasSecondQuarter[i] = Math.PI - thetasFirstQuarter[thetasFirstQuarter.Length - i - 1];
}
IEnumerable<double> thetasFirstHalf = thetasFirstQuarter.Concat(thetasSecondQuarter);
IEnumerable<double> thetasSecondHalf = thetasFirstHalf.Select(theta => theta + Math.PI);
IEnumerable<double> thetas = thetasFirstHalf.Concat(thetasSecondHalf);
return thetas;
}
private static IEnumerable<double> RecursiveCreateEllipsePoints(
double startTheta,
double endTheta,
double maxAngleError,
double widthHeightRatio,
double startPrimeAngle,
double endPrimeAngle)
{
double yDelta = Math.Sin(endTheta) - Math.Sin(startTheta);
double xDelta = Math.Cos(startTheta) - Math.Cos(endTheta);
double averageAngle = Math.Atan2(yDelta, xDelta * widthHeightRatio);
if (Math.Abs(averageAngle - startPrimeAngle) < maxAngleError &&
Math.Abs(averageAngle - endPrimeAngle) < maxAngleError)
{
return new double[] { endTheta };
}
double middleTheta = (startTheta + endTheta) / 2;
double middlePrimeAngle = GetPrimeAngle(middleTheta, widthHeightRatio);
IEnumerable<double> firstPoints = RecursiveCreateEllipsePoints(
startTheta,
middleTheta,
maxAngleError,
widthHeightRatio,
startPrimeAngle,
middlePrimeAngle);
IEnumerable<double> lastPoints = RecursiveCreateEllipsePoints(
middleTheta,
endTheta,
maxAngleError,
widthHeightRatio,
middlePrimeAngle,
endPrimeAngle);
return firstPoints.Concat(lastPoints);
}
private static double GetPrimeAngle(double theta, double widthHeightRatio)
{
return Math.Atan(1 / (Math.Tan(theta) * widthHeightRatio)); // Prime of an ellipse
}
private static Coordinate GetPointOnEllipse(double theta, double width, double height)
{
double x = width * Math.Cos(theta);
double y = height * Math.Sin(theta);
return new Coordinate(x, y);
}
#endregion
}
newPoint.x = radiusX*cos(currentEllipseAngle) + center.x
newPoint.y = radiusY*sin(currentEllipseAngle) + center.y