Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I generate a set of points evenly distributed along the perimeter of an ellipse?

If I want to generate a bunch of points distributed uniformly around a circle, I can do this (python):

r = 5  #radius
n = 20 #points to generate
circlePoints = [
    (r * math.cos(theta), r * math.sin(theta))
    for theta in (math.pi*2 * i/n for i in range(n))
]

However, the same logic doesn't generate uniform points on an ellipse: points on the "ends" are more closely spaced than points on the "sides".

r1 = 5
r2 = 10
n = 20 #points to generate
ellipsePoints = [
    (r1 * math.cos(theta), r2 * math.sin(theta))
    for theta in (math.pi*2 * i/n for i in range(n))
]

Is there an easy way to generate equally spaced points around an ellipse?

like image 757
Eric Avatar asked Aug 07 '11 10:08

Eric


3 Answers

This is an old thread, but since I am seeking the same task of creating evenly spaced points along and ellipse and was not able to find an implementation, I offer this Java code that implements the pseudo code of Howard:

 package com.math;

  public class CalculatePoints {

  public static void main(String[] args) {
    // TODO Auto-generated method stub

    /*
     * 
        dp(t) = sqrt( (r1*sin(t))^2 + (r2*cos(t))^2)
        circ = sum(dp(t), t=0..2*Pi step 0.0001)

        n = 20

        nextPoint = 0
        run = 0.0
        for t=0..2*Pi step 0.0001
            if n*run/circ >= nextPoint then
                set point (r1*cos(t), r2*sin(t))
                nextPoint = nextPoint + 1
            next
            run = run + dp(t)
        next
     */


    double r1 = 20.0;
    double r2 = 10.0;

    double theta = 0.0;
    double twoPi = Math.PI*2.0;
    double deltaTheta = 0.0001;
    double numIntegrals = Math.round(twoPi/deltaTheta);
    double circ=0.0;
    double dpt=0.0;

    /* integrate over the elipse to get the circumference */
    for( int i=0; i < numIntegrals; i++ ) {
        theta += i*deltaTheta;
        dpt = computeDpt( r1, r2, theta);
        circ += dpt;
    }
    System.out.println( "circumference = " + circ );

    int n=20;
    int nextPoint = 0;
    double run = 0.0;
    theta = 0.0;

    for( int i=0; i < numIntegrals; i++ ) {
        theta += deltaTheta;
        double subIntegral = n*run/circ;
        if( (int) subIntegral >= nextPoint ) {
            double x = r1 * Math.cos(theta);
            double y = r2 * Math.sin(theta);
            System.out.println( "x=" + Math.round(x) + ", y=" + Math.round(y));
            nextPoint++;
        }
        run += computeDpt(r1, r2, theta);
    }
}

static double computeDpt( double r1, double r2, double theta ) {
    double dp=0.0;

    double dpt_sin = Math.pow(r1*Math.sin(theta), 2.0);
    double dpt_cos = Math.pow( r2*Math.cos(theta), 2.0);
    dp = Math.sqrt(dpt_sin + dpt_cos);

    return dp;
}

}
like image 190
Dave Avatar answered Sep 18 '22 22:09

Dave


(UPDATED: to reflect new packaging).

An efficient solution of this problem for Python can be found in the numeric branch FlyingCircus-Numeric of FlyingCircus Python package.

Disclaimer: I am the main author of them.

Briefly, the (simplified) code looks (where a is the minor axis, and b is the major axis):

import numpy as np
import scipy as sp
import scipy.optimize

def angles_in_ellipse(
        num,
        a,
        b):
    assert(num > 0)
    assert(a < b)
    angles = 2 * np.pi * np.arange(num) / num
    if a != b:
        e2 = (1.0 - a ** 2.0 / b ** 2.0)
        tot_size = sp.special.ellipeinc(2.0 * np.pi, e2)
        arc_size = tot_size / num
        arcs = np.arange(num) * arc_size
        res = sp.optimize.root(
            lambda x: (sp.special.ellipeinc(x, e2) - arcs), angles)
        angles = res.x 
    return angles

It makes use of scipy.special.ellipeinc() which provides the numerical integral along the perimeter of the ellipse, and scipy.optimize.root() for solving the equal-arcs length equation for the angles.

To test that it is actually working:

a = 10
b = 20
n = 16

phi = angles_in_ellipse(n, a, b)
print(np.round(np.rad2deg(phi), 2))
# [  0.    17.55  36.47  59.13  90.   120.87 143.53 162.45 180.   197.55
#  216.47 239.13 270.   300.87 323.53 342.45]

e = (1.0 - a ** 2.0 / b ** 2.0) ** 0.5
arcs = sp.special.ellipeinc(phi, e)
print(np.round(np.diff(arcs), 4))
# [0.3022 0.2982 0.2855 0.2455 0.2455 0.2855 0.2982 0.3022 0.3022 0.2982
#  0.2855 0.2455 0.2455 0.2855 0.2982]

# plotting
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca()
ax.axes.set_aspect('equal')
ax.scatter(b * np.sin(phi), a * np.cos(phi))
plt.show()

plot

like image 44
norok2 Avatar answered Sep 20 '22 22:09

norok2


You have to calculate the perimeter, then divide it into equal length arcs. The length of an arc of an ellipse is an elliptic integral and cannot be written in closed form so you need numerical computation.

The article on ellipses on wolfram gives you the formula needed to do this, but this is going to be ugly.

like image 42
Karoly Horvath Avatar answered Sep 18 '22 22:09

Karoly Horvath