Search code examples
pythonmatplotlib

How can I get a certain number of evenly spaced points along the octagon perimeter?


I want to get the coordinates of a number of points that together form an octagon. For a circle this is done easily as follows:

import numpy as np

n = 100
x = np.cos(np.linspace(0, 2 * np.pi, n))
y = np.sin(np.linspace(0, 2 * np.pi, n))

coordinates = list(zip(x, y))

By changing n I can increase/decrease the "angularity". Now I want to do the same for an octagon. I know that an octagon has 8 sides and the angle between each side 45 degrees. Let's assume that the perimeter of the octagon is 30.72m. Each side has therefore a length of 3.79m.

perimeter = 30.72
n_sides = 8
angle = 45

How can I get n coordinates that represent this octagon?

Edit: With the help of the answers of @lastchance and @mozway I am able to generate an octagon. My goal is to get evenly-spaced n coordinates from the perimeter of this octagon. If n = 8 these coordinates correspond to the corners of the octagon, but I'm interested in cases where n > 8


Solution

  • Let's use some math.

    polygon in circle

    Each triangle in the polygon is isosceles. Assuming r the radius of the containing circle and a each side of the polygon we have:

    n_sides = 8
    perimeter = n_sides * a
    a/2 = sin(pi/n_sides) / r # isosceles = 2 equal right triangles
    perimeter = n_sides * 2 * sin(pi/n_sides) / r
    r =  perimeter/(2 * n_sides * sin(pi/n_sides))
    

    Thus, the coordinates are:

    perimeter = 30.72
    n_sides = 8
    
    r = perimeter/(2 * n_sides * np.sin(np.pi/n_sides))
    x = r * np.cos(np.linspace(0, 2 * np.pi, n_sides, endpoint=False))
    y = r * np.sin(np.linspace(0, 2 * np.pi, n_sides, endpoint=False))
    

    Note the endpoint=False in linspace.

    As a graph:

    ax = plt.subplot(aspect=1)
    ax.plot(x, y, marker='o')
    

    Output:

    computing a polygon coordinates from a number of sides and perimeter

    If you want one extra point to "close" the polygon (the last point being the same as the first point):

    x = r * np.cos(np.linspace(0, 2 * np.pi, n_sides+1))
    y = r * np.sin(np.linspace(0, 2 * np.pi, n_sides+1))
    

    computing a polygon coordinates from a number of sides and perimeter

    Now, let's use shapely to check that the calculation is correct:

    from shapely.geometry import Polygon
    
    Polygon(zip(x, y)).length # 30.72
    

    Interpolating n points on the octagon/polygon

    Now that we have a polygon, we can interpolate n points along its perimeter.

    Let's generate a "closed" polygon (i.e. n_sides+1 point in which the last one is equal to the first), create a LineString and interpolate n values along it:

    from shapely.geometry import LineString
    
    perimeter = 30.72
    n_sides = 8
    n = 12
    
    # compute the points of the polygon
    r = perimeter/(2 * n_sides * np.sin(np.pi/n_sides))
    x = r * np.cos(np.linspace(0, 2 * np.pi, n_sides+1))
    y = r * np.sin(np.linspace(0, 2 * np.pi, n_sides+1))
    
    # create a line string
    # interpolate n points on the perimeter
    line = LineString(zip(x, y))
    
    coords = np.concatenate(list(line.interpolate(x).coords
                                 for x in np.linspace(0, line.length, n,
                                                      endpoint=False)))
    
    # plot
    ax = plt.subplot(aspect=1)
    ax.plot(x, y)
    ax.plot(*coords.T, ls='', marker='o', label='n = 8')
    

    Alternatively, performing the interpolation with numpy.interp:

    perimeter = 30.72
    n_sides = 8
    n = 12
    
    # compute the points of the polygon
    r = perimeter/(2 * n_sides * np.sin(np.pi/n_sides))
    x = r * np.cos(np.linspace(0, 2 * np.pi, n_sides+1))
    y = r * np.sin(np.linspace(0, 2 * np.pi, n_sides+1))
    
    # interpolate the n points along the perimeter
    ref = np.linspace(0, 1, n_sides+1)*perimeter # distance to origin: ref
    dist = np.linspace(0, 1, n+1)*perimeter      # distance to origin: n points
    new_x = np.interp(dist, ref, x)   # interpolated x
    new_y = np.interp(dist, ref, y)   # interpolated y
    
    ax = plt.subplot(aspect=1)
    ax.plot(x, y)
    ax.plot(new_x, new_y, ls='', marker='o')
    

    Output:

    enter image description here