【问题标题】:How to calculate center of an ellipse by two points and radius sizes如何通过两个点和半径大小计算椭圆的中心
【发布时间】:2010-09-16 21:26:58
【问题描述】:

在为 Internet Explorer 开发基于其自己的 VML 格式的 SVG 实现时,我遇到了将 SVG 椭圆弧转换为 VML 椭圆弧的问题。

在 VML 中,弧由以下公式给出:椭圆上两点的两个角度和半径的长度, 在 SVG 中,弧由以下形式给出:椭圆上两点的两对坐标和椭圆边界框的大小

所以,问题是:如何将椭圆上两点的角度表示为两对坐标。 一个中间问题可能是:如何通过椭圆曲线上一对点的坐标找到椭圆的中心。

更新:让我们有一个前提条件,即通常放置一个椭圆(其半径平行于线性坐标系轴),因此不应用旋转。

更新:此问题与 svg:ellipse 元素无关,而是与 svg:path 元素中的“a”椭圆弧命令有关 (SVG Paths: The elliptical arc curve commands)

【问题讨论】:

    标签: math geometry svg vml ellipse


    【解决方案1】:

    TypeScript 实现基于 Rikki 的回答。

    默认 DOMMatrix 和 DOMPoint 用于计算(在最新的 Chrome v.80 中测试)而不是外部库。

     ellipseCenter(
        x1: number,
        y1: number,
        rx: number,
        ry: number,
        rotateDeg: number,
        fa: number,
        fs: number,
        x2: number,
        y2: number
      ): DOMPoint {
        const phi = ((rotateDeg % 360) * Math.PI) / 180;
        const m = new DOMMatrix([
          Math.cos(phi),
          -Math.sin(phi),
          Math.sin(phi),
          Math.cos(phi),
          0,
          0,
        ]);
        let v = new DOMPoint((x1 - x2) / 2, (y1 - y2) / 2).matrixTransform(m);
        const x1p = v.x;
        const y1p = v.y;
        rx = Math.abs(rx);
        ry = Math.abs(ry);
        const lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
        if (lambda > 1) {
          rx = Math.sqrt(lambda) * rx;
          ry = Math.sqrt(lambda) * ry;
        }
        const sign = fa === fs ? -1 : 1;
        const div =
          (rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p) /
          (rx * rx * y1p * y1p + ry * ry * x1p * x1p);
    
        const co = sign * Math.sqrt(Math.abs(div));
    
        // inverse matrix b and c
        m.b *= -1;
        m.c *= -1;
        v = new DOMPoint(
          ((rx * y1p) / ry) * co,
          ((-ry * x1p) / rx) * co
        ).matrixTransform(m);
        v.x += (x1 + x2) / 2;
        v.y += (y1 + y2) / 2;
        return v;
      }

    【讨论】:

      【解决方案2】:

      您发布的椭圆曲线圆弧链接包含link to elliptical arc implementation notes

      在那里,您会找到conversion from endpoint to centre parameterisation 的方程式。

      这是我对这些方程的 JavaScript 实现,取自 an interactive demo of elliptical arc paths,使用 Sylvester.js 执行矩阵和向量计算。

      // Calculate the centre of the ellipse
      // Based on http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
      var x1 = 150;  // Starting x-point of the arc
      var y1 = 150;  // Starting y-point of the arc
      var x2 = 400;  // End x-point of the arc
      var y2 = 300;  // End y-point of the arc
      var fA = 1;    // Large arc flag
      var fS = 1;    // Sweep flag
      var rx = 100;  // Horizontal radius of ellipse
      var ry =  50;  // Vertical radius of ellipse
      var phi = 0;   // Angle between co-ord system and ellipse x-axes
      
      var Cx, Cy;
      
      // Step 1: Compute (x1′, y1′)
      var M = $M([
                     [ Math.cos(phi), Math.sin(phi)],
                     [-Math.sin(phi), Math.cos(phi)]
                  ]);
      var V = $V( [ (x1-x2)/2, (y1-y2)/2 ] );
      var P = M.multiply(V);
      
      var x1p = P.e(1);  // x1 prime
      var y1p = P.e(2);  // y1 prime
      
      
      // Ensure radii are large enough
      // Based on http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
      // Step (a): Ensure radii are non-zero
      // Step (b): Ensure radii are positive
      rx = Math.abs(rx);
      ry = Math.abs(ry);
      // Step (c): Ensure radii are large enough
      var lambda = ( (x1p * x1p) / (rx * rx) ) + ( (y1p * y1p) / (ry * ry) );
      if(lambda > 1)
      {
          rx = Math.sqrt(lambda) * rx;
          ry = Math.sqrt(lambda) * ry;
      }
      
      
      // Step 2: Compute (cx′, cy′)
      var sign = (fA == fS)? -1 : 1;
      // Bit of a hack, as presumably rounding errors were making his negative inside the square root!
      if((( (rx*rx*ry*ry) - (rx*rx*y1p*y1p) - (ry*ry*x1p*x1p) ) / ( (rx*rx*y1p*y1p) + (ry*ry*x1p*x1p) )) < 1e-7)
          var co = 0;
      else
          var co = sign * Math.sqrt( ( (rx*rx*ry*ry) - (rx*rx*y1p*y1p) - (ry*ry*x1p*x1p) ) / ( (rx*rx*y1p*y1p) + (ry*ry*x1p*x1p) ) );
      var V = $V( [rx*y1p/ry, -ry*x1p/rx] );
      var Cp = V.multiply(co);
      
      // Step 3: Compute (cx, cy) from (cx′, cy′)
      var M = $M([
                     [ Math.cos(phi), -Math.sin(phi)],
                     [ Math.sin(phi),  Math.cos(phi)]
                  ]);
      var V = $V( [ (x1+x2)/2, (y1+y2)/2 ] );
      var C = M.multiply(Cp).add(V);
      
      Cx = C.e(1);
      Cy = C.e(2);
      

      【讨论】:

        【解决方案3】:

        所以解决方案就在这里:

        椭圆的参数化公式:

        x = x0 + a * cos(t) y = y0 + b * sin(t)

        让我们把已知的两点坐标放到它上面:

        x1 = x0 + a * cos(t1) x2 = x0 + a * cos(t2) y1 = y0 + b * sin(t1) y2 = y0 + b * sin(t2)

        现在我们有了一个包含 4 个变量的方程组:椭圆中心 (x0/y0) 和两个角度 t1、t2

        让我们减去方程以消除中心坐标:

        x1 - x2 = a * (cos(t1) - cos(t2)) y1 - y2 = b * (sin(t1) - sin(t2))

        这可以重写(使用积到和恒等式)为:

        (x1 - x2) / (2 * a) = sin((t1 + t2) / 2) * sin((t1 - t2) / 2) (y2 - y1) / (2 * b) = cos((t1 + t2) / 2) * sin((t1 - t2) / 2)

        让我们替换一些方程式:

        r1: (x1 - x2) / (2 * a) r2: (y2 - y1) / (2 * b) a1: (t1 + t2) / 2 a2: (t1 - t2) / 2

        然后我们得到简单的方程组:

        r1 = sin(a1) * sin(a2) r2 = cos(a1) * sin(a2)

        第一个方程除以第二个得到:

        a1 = arctan(r1/r2)

        将此结果添加到第一个方程得到:

        a2 = arcsin(r2 / cos(arctan(r1/r2)))

        或者,简单(使用三角函数和反三角函数的组合):

        a2 = arcsin(r2 / (1 / sqrt(1 + (r1/r2)^2)))

        甚至更简单:

        a2 = arcsin(sqrt(r1^2 + r2^2))

        现在可以很容易地求解初始的四方程系统,并且可以找到所有角度以及日食中心坐标。

        【讨论】:

        • 明确:t1 = a1+a2, t2 = a1-a2, x0 = x1 - acos(t1), y0 = y1 - bsin(t1)
        • 您能否发布一个从 SVG 弧转换为 VML 弧的示例?例如“A80 80 0 1 0 200 200”产生半径为 80 到 200,200 的顺时针大弧。这怎么能在 VML 中复制呢?谢谢!
        • 另外,y0 = y1 - b*sin(t1) 是否正确,或者应该是 sin(t2)?
        • 小心最后一步,将r2 放在sqrt 中会去掉它的符号,在某些情况下会产生不好的结果!
        • 我实现了这个并检查了推导。这有一个微妙的错误。 r2 应该是 (y1 - y2) / (2 * b)(而不是 (y2 - y1) / (2 * b)),a1 应该是 arctan(-r1/r2)(而不是 arctan(r1/r2))。在我的实现中,原始方程导致椭圆位于 x1,y1 的另一侧,而通过我的更改,椭圆现在位于正确的位置。
        【解决方案4】:

        中间问题相当简单......你没有。您从边界框算出椭圆的中心(即,只要椭圆在框内居中,框的中心就是椭圆的中心)。

        对于您的第一个问题,我将查看椭圆方程的极坐标形式,该形式可在 Wikipedia 上找到。您还需要计算椭圆的偏心率。

        或者您可以从边界框中暴力破解值...确定一个点是否位于椭圆上并与角度匹配,然后遍历边界框中的每个点。

        【讨论】:

        • 我说的是 svg:path 中的椭圆弧,而不是椭圆。至于数学,我已经完成了三篇关于在极坐标和线性坐标系中求解方程的论文,但仍然没有运气。
        • 应该还是一样的过程。椭圆弧只是椭圆的一部分,具有椭圆的 2 个“边界点”。边界矩形应该足以为您提供构建完整椭圆的所有信息。然后,您只需将角度输入方程。
        • 再一次,我没有正确地提出问题。给出的不是边界矩形,而是半径的大小。
        • 好吧,给定的半径可以让您制定一个边界矩形。即 x 半径将使您获得 +x 和 -x 值,对于 y 也是如此。这些组合将为您提供边界矩形的所有点。如果没有给出中心,那么唯一的结论是它是 0,0
        • 这是问题所在,中心不在 0,0 内(它确实可以在任何地方找到),中间步骤可能是找到中心的坐标,以后计算角度会更容易(给定点的坐标和边界矩形的大小)
        【解决方案5】:

        椭圆不能仅由两点定义。甚至一个圆(一个特殊的椭圆)也是由三个点定义的。

        即使有三个点,你也会有无限的椭圆穿过这三个点(想想:旋转)。

        请注意,边界框表示椭圆的中心,并且很可能假定其长轴和短轴平行于 x,y(或 y,x)轴。

        【讨论】:

        • 确实,我忘记添加注释了。现在可以了。
        猜你喜欢
        • 2016-07-26
        • 2017-04-07
        • 2021-04-09
        • 2012-05-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-04
        • 1970-01-01
        相关资源
        最近更新 更多