如何通过两点和半径大小计算椭圆的中心

发布于 2024-07-07 00:12:47 字数 486 浏览 16 评论 0原文

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

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

因此,问题是:如何将椭圆上两点的角度表达为它们的两对坐标。 中间的问题可能是:如何通过椭圆曲线上一对点的坐标找到椭圆的中心。

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

更新:这个问题与 svg:ellipse 元素无关,而是与 svg:path 元素中的“a”椭圆弧命令相关(SVG 路径:椭圆弧曲线命令)

While working on SVG implementation for Internet Explorer to be based on its own VML format I came to a problem of translation of an SVG elliptical arc to an VML elliptical arc.

In VML an arc is given by: two angles for two points on ellipse and lengths of radiuses,
In SVG an arc is given by: two pairs of coordinates for two points on ellipse and sizes of ellipse boundary box

So, the question is: How to express angles of two points on ellipse to two pairs of their coordinates.
An intermediate question could be: How to find the center of an ellipse by coordinates of a pair of points on its curve.

Update: Let's have a precondition saying that an ellipse is normally placed (its radiuses are parallel to linear coordinate system axis), thus no rotation is applied.

Update: This question is not related to svg:ellipse element, rather to "a" elliptical arc command in svg:path element (SVG Paths: The elliptical arc curve commands)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

缺⑴份安定 2024-07-14 00:12:53

用代码回答部分问题

如何通过椭圆曲线上一对点的坐标找到椭圆的中心。

这是一个 TypeScript 函数,它基于上面 Sergey Illinsky 所接受的优秀答案(恕我直言,它在中途结束了)。 它计算具有给定半径的椭圆的中心,前提是提供的点 ab 必须位于椭圆的圆周上。 由于此问题(几乎)总是有两个解决方案,因此代码选择将椭圆放置在两点“上方”的解决方案:(

请注意,椭圆必须具有平行于水平/垂直的长轴和短轴)

/**
 * We're in 2D, so that's what our vertors look like
 */
export type Point = [number, number];

/**
 * Calculates the vector that connects the two points
 */
function deltaXY (from: Point, to: Point): Point {
    return [to[0]-from[0], to[1]-from[1]];
}

/**
 * Calculates the sum of an arbitrary amount of vertors
 */
function vecAdd (...vectors: Point[]): Point {
    return vectors.reduce((acc, curr) => [acc[0]+curr[0], acc[1]+curr[1]], [0, 0]);
}

/**
 * Given two points a and b, as well as ellipsis radii rX and rY, this 
 * function calculates the center-point of the ellipse, so that it
 * is "above" the two points and has them on the circumference
 */
function topLeftOfPointsCenter (a: Point, b: Point, rX: number, rY: number): Point {
    const delta = deltaXY(a, b);
    
    // Sergey's work leads up to a simple system of liner equations. 
    // Here, we calculate its general solution for the first of the two angles (t1)
    const A = Math.asin(Math.sqrt((delta[0]/(2*rX))**2+(delta[1]/(2*rY))**2));
    const B = Math.atan(-delta[0]/delta[1] * rY/rX);
    const alpha = A + B;
    
    // This may be the new center, but we don't know to which of the two
    // solutions it belongs, yet
    let newCenter = vecAdd(a, [
        rX * Math.cos(alpha),
        rY * Math.sin(alpha)
    ]);

    // Figure out if it is the correct solution, and adjusting if not
    const mean = vecAdd(a, [delta[0] * 0.5, delta[1] * 0.5]);
    const factor = mean[1] > newCenter[1] ? 1 : -1;
    const offMean = deltaXY(mean, newCenter);
    newCenter = vecAdd(mean, [offMean[0] * factor, offMean[1] * factor]);

    return newCenter;
}

此函数执行以下操作: 检查解决方案是否可行,这意味着提供的半径是否足够大以连接两点!

Answering part of the question with code

How to find the center of an ellipse by coordinates of a pair of points on its curve.

This is a TypeScript function which is based on the excellent accepted answer by Sergey Illinsky above (which ends somewhat halfway through, IMHO). It calculates the center of an ellipse with given radii, given the condition that both provided points a and b must lie on the circumference of the ellipse. Since there are (almost) always two solutions to this problem, the code choses the solution that places the ellipse "above" the two points:

(Note that the ellipse must have major and minor axis parallel to the horizontal/vertical)

/**
 * We're in 2D, so that's what our vertors look like
 */
export type Point = [number, number];

/**
 * Calculates the vector that connects the two points
 */
function deltaXY (from: Point, to: Point): Point {
    return [to[0]-from[0], to[1]-from[1]];
}

/**
 * Calculates the sum of an arbitrary amount of vertors
 */
function vecAdd (...vectors: Point[]): Point {
    return vectors.reduce((acc, curr) => [acc[0]+curr[0], acc[1]+curr[1]], [0, 0]);
}

/**
 * Given two points a and b, as well as ellipsis radii rX and rY, this 
 * function calculates the center-point of the ellipse, so that it
 * is "above" the two points and has them on the circumference
 */
function topLeftOfPointsCenter (a: Point, b: Point, rX: number, rY: number): Point {
    const delta = deltaXY(a, b);
    
    // Sergey's work leads up to a simple system of liner equations. 
    // Here, we calculate its general solution for the first of the two angles (t1)
    const A = Math.asin(Math.sqrt((delta[0]/(2*rX))**2+(delta[1]/(2*rY))**2));
    const B = Math.atan(-delta[0]/delta[1] * rY/rX);
    const alpha = A + B;
    
    // This may be the new center, but we don't know to which of the two
    // solutions it belongs, yet
    let newCenter = vecAdd(a, [
        rX * Math.cos(alpha),
        rY * Math.sin(alpha)
    ]);

    // Figure out if it is the correct solution, and adjusting if not
    const mean = vecAdd(a, [delta[0] * 0.5, delta[1] * 0.5]);
    const factor = mean[1] > newCenter[1] ? 1 : -1;
    const offMean = deltaXY(mean, newCenter);
    newCenter = vecAdd(mean, [offMean[0] * factor, offMean[1] * factor]);

    return newCenter;
}

This function does not check if a solution is possible, meaning whether the radii provided are large enough to even connect the two points!

天暗了我发光 2024-07-14 00:12:52

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;
  }

TypeScript implementation based on the answer from Rikki.

Default DOMMatrix and DOMPoint are used for the calculations (Tested in the latest Chrome v.80) instead of the external library.

 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;
  }

記柔刀 2024-07-14 00:12:51

椭圆不能仅由两点定义。 即使是圆(特殊情况下的椭圆)也是由三个点定义的。

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

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

An ellipse cannot be defined by only two points. Even a circle (a special cased ellipse) is defined by three points.

Even with three points, you would have infinite ellipses passing through these three points (think: rotation).

Note that a bounding box suggests a center for the ellipse, and most probably assumes that its major and minor axes are parallel to the x,y (or y,x) axes.

屋顶上的小猫咪 2024-07-14 00:12:51

中间的问题相当简单......你不知道。 从边界框算出椭圆的中心(即,只要椭圆位于框的中心,框的中心就是椭圆的中心)。

对于你的第一个问题,我会看看椭圆方程的极坐标形式,该方程可以在 Wikipedia< /a>. 您还需要计算出椭圆的偏心率。

或者您可以暴力破解边界框中的值...计算出一个点是否位于椭圆上并与角度匹配,然后迭代边界框中的每个点。

The intermediate question is fairly easy... you don't. You work out the centre of an ellipse from the bounding box (namely, the centre of the box is the centre of the ellipse, as long as the ellipse is centred in the box).

For your first question, I'd look at the polar form of the ellipse equation, which is available on Wikipedia. You would need to work out the eccentricity of the ellipse as well.

Or you could brute force the values from the bounding box... work out if a point lies on the ellipse and matches the angle, and iterate through every point in the bounding box.

旧瑾黎汐 2024-07-14 00:12:50

您发布的椭圆曲线圆弧链接包含椭圆弧实施说明的链接

在那里,您将找到从端点参数化到中心参数化的转换的方程式。

这是我对这些方程的 JavaScript 实现,取自交互式椭圆弧路径演示,使用 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);

The elliptical curve arc link you posted includes a link to elliptical arc implementation notes.

In there, you will find the equations for conversion from endpoint to centre parameterisation.

Here is my JavaScript implementation of those equations, taken from an interactive demo of elliptical arc paths, using Sylvester.js to perform the matrix and vector calculations.

// 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);
闻呓 2024-07-14 00:12:49

所以解决方案如下:

椭圆的参数化公式:

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))

现在可以轻松求解初始四方程组,并且可以找到所有角度以及日食中心坐标。

So the solution is here:

The parametrized formula of an ellipse:

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

Let's put known coordinates of two points to it:

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

Now we have a system of equations with 4 variables: center of ellipse (x0/y0) and two angles t1, t2

Let's subtract equations in order to get rid of center coordinates:

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

This can be rewritten (with product-to-sum identities formulas) as:

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

Let's replace some of the equations:

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

Then we get simple equations system:

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

Dividing first equation by second produces:

a1 = arctan(r1/r2)

Adding this result to the first equation gives:

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

Or, simple (using compositions of trig and inverse trig functions):

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

or even more simple:

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

Now the initial four-equations system can be resolved with easy and all angles as well as eclipse center coordinates can be found.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文