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(

我正在研究地理信息,最近我需要画一个椭圆。为了与OGC约定兼容,我不能使用椭圆;取而代之的是,我使用一个多边形来近似椭圆,通过取一个包含在椭圆中的多边形,并使用任意多个点

我用来为给定数量的点N生成椭圆的过程如下(使用C#和一个虚构的多边形类):

多边形创建椭圆多边形(坐标中心、双半径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