I'm trying to draw an ellipse between two points. So far, I have it mostly working:
The issue comes with setting the ellipse height (ellipse_h below).
x = center_x + radius*np.cos(theta+deg)
y = center_y - ellipse_h * radius*np.sin(theta+deg)
In this example, it's set to -0.5:
Can anyone please help me rotate the ellipse height with the ellipse? Thank you!
import numpy as np
import matplotlib.pyplot as plt
def distance(x1, y1, x2, y2):
return np.sqrt(np.power(x2 - x1, 2) + np.power(y2 - y1, 2) * 1.0)
def midpoint(x1, y1, x2, y2):
return [(x1 + x2) / 2,(y1 + y2) / 2]
def angle(x1, y1, x2, y2):
#radians
return np.arctan2(y2 - y1, x2 - x1)
x1 = 100
y1 = 150
x2 = 200
y2 = 190
ellipse_h = -1
x_coords = []
y_coords = []
mid = midpoint(x1, y1, x2, y2)
center_x = mid[0]
center_y = mid[1]
ellipse_resolution = 40
step = 2*np.pi/ellipse_resolution
radius = distance(x1, y1, x2, y2) * 0.5
deg = angle(x1, y1, x2, y2)
cos = np.cos(deg * np.pi /180)
sin = np.sin(deg * np.pi /180)
for theta in np.arange(0, np.pi+step, step):
x = center_x + radius*np.cos(theta+deg)
y = center_y - ellipse_h * radius*np.sin(theta+deg)
x_coords.append(x)
y_coords.append(y)
plt.xlabel("X")
plt.ylabel("Y")
plt.title("Arc between 2 Points")
plt.plot(x_coords,y_coords)
plt.scatter([x1,x2],[y1,y2])
plt.axis('equal')
plt.show()
A simple solution is to describe the ellipse by its standard parametric equation, as you effectively did. However, under the assumption that it is centered on the origin of the coordinate system, it becomes straightforward to then apply a rotation to its points using a 2d rotation matrix and finally apply a translation to position it on its true center. This gives the following:
import numpy as np
import matplotlib.pyplot as plt
# extreme points along the major axis
x1, y1 = 100, 150
x2, y2 = 200, 190
# along minor axis
height = 15
# number of points
n = 100
# center
x_c, y_c = (x1 + x2)/2, (y1 + y2)/2
# width (major axis) and height (minor) of the ellipse halved
a, b = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)/2, height/2
# rotation angle
angle = np.arctan2(y2 - y1, x2 - x1)
# standard parametric equation of an ellipse
t = np.linspace(0, 2*np.pi, n)
ellipse = np.array([a*np.cos(t), b*np.sin(t)])
# 2d rotation matrix
R = np.array([[np.cos(angle), -np.sin(angle)],
[np.sin(angle), np.cos(angle)]])
# apply the rotation to the ellipse
ellipse_rot = R # ellipse
plt.plot(x_c + ellipse_rot[0], y_c + ellipse_rot[1], 'r' )
plt.scatter([x1, x2], [y1, y2], color='k')
plt.axis('equal')
plt.show()
See the output for different heights:
Following your comment, for the limiting case of the circle, you need to specify height = np.sqrt((x2 - x1)**2 + (y2 - y1)**2), so that a = b.
Hope this helps !
Related
I'm using a python caller in fme to create polygons from points with aixm 4.5 data
Somes of the polygons contains arcs, and theirs direction clockwise (CWA) or anti-clock wise (CCA) matters, I don't know how to handle this.
here's the code I have so far:
import fme
import fmeobjects
from math import hypot
def replaceWithArc(feature):
coords = feature.getAllCoordinates()
x0, y0 = coords[0][0], coords[0][1] # coordinates of start of arc
xc, yc = coords[1][0], coords[1][1] # coordinates of cetner of arc
x2, y2 = coords[2][0], coords[2][1] # coordinates of end of arc
vx0, vy0 = (x0 - xc), (y0 - yc) # vector: center -> start
vx2, vy2 = (x2 - xc), (y2 - yc) # vector: center -> end
vx1, vy1 = (vx0 + vx2), (vy0 + vy2) # vector: center -> middle
len = hypot(vx1, vy1) # length of the vector
radius = (hypot(vx0, vy0) + hypot(vx2, vy2)) * 0.5
x1, y1 = xc + vx1 / len * radius, yc + vy1 / len * radius # coordinates of middle point on arc
threePoints = (
fmeobjects.FMEPoint(x0, y0),
fmeobjects.FMEPoint(x1, y1),
fmeobjects.FMEPoint(x2, y2)
)
feature.setGeometry(fmeobjects.FMEArc(threePoints))
This looks to me like there is something wrong with the three points.
Could you please paste the values?
From the image above it looks slightly asymmetric, but I could be wrong.
Another thing that you could try is to use a different function to initialize FMEArc, e.g.
init(twoPoints, bulge)
init(centerPoint, rotation, primaryRadius, secondaryRadius, startAngle, sweepAngle, startPoint, endPoint)
As above: I'm trying to draw a bounding box. Given the x and y coordinates of the two opposite points that form a box, I'm trying to implement a function that multiplies the diagonal of that box by an arbitrary multiplier, then compute the new x and y coordinates. x and y can be positive or negative floats.
def return_box(x1, x2, y1, y2, multiplier = n):
do_magic()
return new_x, new_y
Assuming you want to keep (x1, y1) in the same place, scale the diagonal and return the new (x2, y2):
def return_box(x1, x2, y1, y2, multiplier = n):
return (
x1 + multiplier * (x2 - x1),
y1 + multiplier * (y2 - y1)
)
If you instead want to keep the center of the rectangle in the same place and return the two new corners:
def return_box(x1, x2, y1, y2, multiplier = n):
xmid = (x1 + x2) / 2
ymid = (y1 + y2) / 2
return (
xmid + multiplier * (x1 - xmid),
ymid + multiplier * (y1 - ymid),
), (
xmid + multiplier * (x2 - xmid),
ymid + multiplier * (y2 - ymid)
)
I would like to find the best-fit axis of points that are on a cylindrical surface, using python.
Seems that scipy.linalg.svd is the function to look for.
So to test out, I decide to generate some points, function makeCylinder, from this thread How to generate regular points on cylindrical surface, and estimate the axis.
This is the code:
def rotMatrixAxisAngle(axis, theta, theta2deg=False):
# Load
from math import radians, cos, sin
from numpy import array
# Convert to radians
if theta2deg:
theta = radians(theta)
#
a = cos(theta/2.0)
b, c, d = -array(axis)*sin(theta/2.0)
# Rotation matrix
R = array([ [a*a+b*b-c*c-d*d, 2.0*(b*c-a*d), 2.0*(b*d+a*c)],
[2.0*(b*c+a*d), a*a+c*c-b*b-d*d, 2.0*(c*d-a*b)],
[2.0*(b*d-a*c), 2.0*(c*d+a*b), a*a+d*d-b*b-c*c] ])
return R
def makeCylinder(radius, length, nlength, alpha, nalpha, center, orientation):
# Load
from numpy import array, allclose, linspace, tile, vstack
from numpy import pi, cos, sin, arccos, cross
from numpy.linalg import norm
# Create the length array
I = linspace(0, length, nlength)
# Create alpha array avoid duplication of endpoints
if int(alpha) == 360:
A = linspace(0, alpha, num=nalpha, endpoint=False)/180.0*pi
else:
A = linspace(0, alpha, num=nalpha)/180.0*pi
# Calculate X and Y
X = radius * cos(A)
Y = radius * sin(A)
# Tile/repeat indices so all unique pairs are present
pz = tile(I, nalpha)
px = X.repeat(nlength)
py = Y.repeat(nlength)
# Points
points = vstack(( pz, px, py )).T
## Shift to center
points += array(center) - points.mean(axis=0)
# Orient tube to new vector
ovec = orientation / norm(orientation)
cylvec = array([1,0,0])
if allclose(cylvec, ovec):
return points
# Get orthogonal axis and rotation
oaxis = cross(ovec, cylvec)
rot = arccos(ovec.dot(cylvec))
R = rotMatrixAxisAngle(oaxis, rot)
return points.dot(R)
from numpy.linalg import norm
from numpy.random import rand
from scipy.linalg import svd
for i in xrange(100):
orientation = rand(3)
orientation[0] = 0
orientation /= norm(orientation)
# Generate sample points
points = makeCylinder(radius = 3.0,
length = 20.0, nlength = 20,
alpha = 360, nalpha = 30,
center = [0,0,0],
orientation = orientation)
# Least Square
uu, dd, vv = svd(points - points.mean(axis=0))
asse = vv[0]
assert abs( abs(orientation.dot(asse)) - 1) <= 1e-4, orientation.dot(asse)
As you can see, I generate multiple cylinder whose axis is random (rand(3)).
The funny thing is that svd returns an axis that is absolutely perfect if the first component of orientation is zero (orientation[0] = 0).
If I comment this line the estimated axis is way off.
Update 1:
Even using leastsq on a cylinder equation returns the same behavior:
def bestLSQ1(points):
from numpy import array, sqrt
from scipy.optimize import leastsq
# Expand
points = array(points)
x = points[:,0]
y = points[:,1]
z = points[:,2]
# Calculate the distance of each points from the center (xc, yc, zc)
# http://geometry.puzzles.narkive.com/2HaVJ3XF/geometry-equation-of-an-arbitrary-orientated-cylinder
def calc_R(xc, yc, zc, u1, u2, u3):
return sqrt( (x-xc)**2 + (y-yc)**2 + (z-zc)**2 - ( (x-xc)*u1 + (y-yc)*u2 + (z-zc)*u3 )**2 )
# Calculate the algebraic distance between the data points and the mean circle centered at c=(xc, yc, zc)
def dist(c):
Ri = calc_R(*c)
return Ri - Ri.mean()
# Axes - Minimize residu
xM, yM, zM = points.mean(axis=0)
# Calculate the center
center, ier = leastsq(dist, (xM, yM, zM, 0, 0, 1))
xc, yc, zc, u1, u2, u3 = center
asse = u1, u2, u3
return asse
Despite your interesting approach using svd, you could also do a more intuitive approach with scipy.optimize.leastsq.
This would need a function to calculate distance between the axis and your cloud of points in order to find the best-fitting axis.
The code could be something like shown below (distance_axis_points adapted from alg3dpy):
from numpy.linalg import norm
from numpy.random import rand
from scipy.optimize import leastsq
for i in range(100):
orientation = rand(3)
orientation[0] = 0
orientation /= norm(orientation)
# Generate sample points
points = makeCylinder(radius = 3.0,
length = 20.0, nlength = 20,
alpha = 360, nalpha = 30,
center = [0,0,0],
orientation = orientation)
def dist_axis_points(axis, points):
axis_pt0 = points.mean(axis=0)
axis = np.asarray(axis)
x1 = axis_pt0[0]
y1 = axis_pt0[1]
z1 = axis_pt0[2]
x2 = axis[0]
y2 = axis[1]
z2 = axis[2]
x3 = points[:, 0]
y3 = points[:, 1]
z3 = points[:, 2]
den = ((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)
t = ((x1**2 + x2 * x3 - x1 * x3 - x1 * x2 +
y1**2 + y2 * y3 - y1 * y3 - y1 * y2 +
z1**2 + z2 * z3 - z1 * z3 - z1 * z2)/den)
projected_pt = t[:, None]*(axis[None, :] - axis_pt0[None, :]) + axis_pt0[None, :]
return np.sqrt(((points - projected_pt)**2).sum(axis=-1))
popt, pconv = leastsq(dist_axis_points, x0=[1, 1, 1], args=(points,))
popt /= norm(popt)
assert abs(abs(orientation.dot(popt)) - 1) <= 1e-4, orientation.dot(popt)
I am rusty in my math, not sure how to calculate the distrance from the highest point H to the intersection between the 2 lowest points in the middle for the N point.
import matplotlib.pyplot as plt
from scipy import interpolate
import numpy as np
y= [10.5,10,12,13,10,11,16,10,9,13,10]
x= np.linspace(1, len(y), len(y), endpoint=True)
dist = np.linalg.norm(y-x)
print dst
fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(x, y, color='red')
import heapq
import operator
import math
y = [10.5,10,12,13,10,11,16,10,9,13,10]
x= np.linspace(1, len(y), len(y), endpoint=True)
y1,y2 = heapq.nsmallest(2, enumerate(y), key=operator.itemgetter(1))
x1,y1 = y1
x1 = x[x1]
x2,y2 = y2
x2 = x[x2]
m = (y2-y1)/(x2-x1)
print("Equation of line: y = {}(x-{}) + {}".format(m, x1, y1))
apexPoint = (5,4) # or wherever the apex point is
X,Y = apexPoint
M = 1/m
print("Equation of perpendicular line: y = {}(x-{}) + {}".format(M, X, Y))
intersect_x = ((M*X)+Y-(m*x1)-y1)/(M-m)
intersect_y = m*(intersect_x - x1) + y1
dist = math.sqrt((X - intersect_x)**2 + (Y - intersect_y)**2) # this is your answer
This doesn't answer your question directly but perhaps you would want to checkout the awesome python module shapely.
You can create geometric objects such as LineStrings, Points using the module.
And a simple call to:
object.project(other[, normalized=False])
Returns the distance along this geometric object to a point nearest
the other object.
will give you your answer.
Here's its documentation:
Shapely Documentation
I looked at your chart. The line is not perpendicular. It is a vertical line from a point to a line segment.
Assuming your apex point is (x0,y0) and your base points are (x1,y1) and (x2,y2):
The equation of the line joining 2 points (x1,y1) and (x2,y2) is:
y = (y2-y1)/(x2-x1) * x + (y2 * (x2-x1) - x1 * (y2-y1)) / (x2-x1)
Get the y intercept on the the line:
ymid = (y2-y1)/(x2-x1) * x0 + (y2 * (x2-x1) - x1 * (y2-y1)) / (x2-x1)
Your distance is:
y0 - ymid
See http://pythonfiddle.com/SO-33162756/
I need to make an offset parallel enclosure of an airfoil profile curve, but I cant figure out how to make all the points be equidistant to the points on the primary profile curve at desired distance.
this is my example airfoil profile
this is my best and not good approach
EDIT #Patrick Solution for distance 0.2
You'll have to special-case slopes of infinity/zero, but the basic approach is to use interpolation to calculate the slope at a point, and then find the perpendicular slope, and then calculate the point at that distance.
I have modified the example from here to add a second graph. It works with the data file you provided, but you might need to change the sign calculation for a different envelope.
EDIT As per your comments about wanting the envelope to be continuous, I have added a cheesy semicircle at the end that gets really close to doing this for you. Essentially, when creating the envelope, the rounder and more convex you can make it, the better it will work. Also, you need to overlap the beginning and the end, or you'll have a gap.
Also, it could almost certainly be made more efficient -- I am not a numpy expert by any means, so this is just pure Python.
def offset(coordinates, distance):
coordinates = iter(coordinates)
x1, y1 = coordinates.next()
z = distance
points = []
for x2, y2 in coordinates:
# tangential slope approximation
try:
slope = (y2 - y1) / (x2 - x1)
# perpendicular slope
pslope = -1/slope # (might be 1/slope depending on direction of travel)
except ZeroDivisionError:
continue
mid_x = (x1 + x2) / 2
mid_y = (y1 + y2) / 2
sign = ((pslope > 0) == (x1 > x2)) * 2 - 1
# if z is the distance to your parallel curve,
# then your delta-x and delta-y calculations are:
# z**2 = x**2 + y**2
# y = pslope * x
# z**2 = x**2 + (pslope * x)**2
# z**2 = x**2 + pslope**2 * x**2
# z**2 = (1 + pslope**2) * x**2
# z**2 / (1 + pslope**2) = x**2
# z / (1 + pslope**2)**0.5 = x
delta_x = sign * z / ((1 + pslope**2)**0.5)
delta_y = pslope * delta_x
points.append((mid_x + delta_x, mid_y + delta_y))
x1, y1 = x2, y2
return points
def add_semicircle(x_origin, y_origin, radius, num_x = 50):
points = []
for index in range(num_x):
x = radius * index / num_x
y = (radius ** 2 - x ** 2) ** 0.5
points.append((x, -y))
points += [(x, -y) for x, y in reversed(points)]
return [(x + x_origin, y + y_origin) for x, y in points]
def round_data(data):
# Add infinitesimal rounding of the envelope
assert data[-1] == data[0]
x0, y0 = data[0]
x1, y1 = data[1]
xe, ye = data[-2]
x = x0 - (x0 - x1) * .01
y = y0 - (y0 - y1) * .01
yn = (x - xe) / (x0 - xe) * (y0 - ye) + ye
data[0] = x, y
data[-1] = x, yn
data.extend(add_semicircle(x, (y + yn) / 2, abs((y - yn) / 2)))
del data[-18:]
from pylab import *
with open('ah79100c.dat', 'rb') as f:
f.next()
data = [[float(x) for x in line.split()] for line in f if line.strip()]
t = [x[0] for x in data]
s = [x[1] for x in data]
round_data(data)
parallel = offset(data, 0.1)
t2 = [x[0] for x in parallel]
s2 = [x[1] for x in parallel]
plot(t, s, 'g', t2, s2, 'b', lw=1)
title('Wing with envelope')
grid(True)
axes().set_aspect('equal', 'datalim')
savefig("test.png")
show()
If you are willing (and able) to install a third-party tool, I'd highly recommend the Shapely module. Here's a small sample that offsets both inward and outward:
from StringIO import StringIO
import matplotlib.pyplot as plt
import numpy as np
import requests
import shapely.geometry as shp
# Read the points
AFURL = 'http://m-selig.ae.illinois.edu/ads/coord_seligFmt/ah79100c.dat'
afpts = np.loadtxt(StringIO(requests.get(AFURL).content), skiprows=1)
# Create a Polygon from the nx2 array in `afpts`
afpoly = shp.Polygon(afpts)
# Create offset airfoils, both inward and outward
poffafpoly = afpoly.buffer(0.03) # Outward offset
noffafpoly = afpoly.buffer(-0.03) # Inward offset
# Turn polygon points into numpy arrays for plotting
afpolypts = np.array(afpoly.exterior)
poffafpolypts = np.array(poffafpoly.exterior)
noffafpolypts = np.array(noffafpoly.exterior)
# Plot points
plt.plot(*afpolypts.T, color='black')
plt.plot(*poffafpolypts.T, color='red')
plt.plot(*noffafpolypts.T, color='green')
plt.axis('equal')
plt.show()
And here's the output; notice how the 'bowties' (self-intersections) on the inward offset are automatically removed: