I have found algorithm to calculate shortest distance between two lines in 3D and rewrite it in Python.But also I would like to enchanced it that it could return not only distance,but also the postion of closest points.
def line2line(-0.073455669 4.9843092 0.26107353 0.0 0.0 -1.0 -3.85838175 12.1999998 -4.50372314 0.405142069 -0.76723671 0.497199893):
epsilon = 0.00000001
L1P0 = np.array([xbeam,ybeam,zbeam]) #position of P0 on first line
L2P0= np.array([xout,yout,zout]) #position of P0 on first line
L1P1 = np.array([xbeam + ubeam ,ybeam + vbeam ,zbeam + wbeam]) #ubeam,vbeam and wbeam are direction cosines
L2P1 = np.array([xout + cx,yout + cy,zout + cz]) #cx,cy,cz are direction cosines
u = L1P1 - L1P0
v = L2P1 - L2P0
w = L1P0 - L2P0
a = np.dot(u,u)
b = np.dot(u,v)
c = np.dot(v,v)
d = np.dot(u,w)
e = np.dot(v,w)
D = a*c - b*b
if D < epsilon:
sc = 0.0
tc = d/b if b>c else e/c
else:
sc = (b*e - c*d) / D
tc = (a*e - b*d) / D
dP = w + (sc * u) - (tc * v)
return np.linalg.norm(dP)
It returns around 0.049 which is correct but when I tried to print
w + (sc * u) or
(tc * v) what I was thinking it was position,it printed me:
0. , 0. , -19.82274615
3.8142822 , -7.22328672, 4.68097699
It's not correct.Coordinates I'm looking for for one of these points are:
-0.073455669 4.9843092 0.26107353
Algorithm I have found here
dP is difference vector. But you need absolute coordinates of the closest points
I cannot open linked page, but seems that sc is parameter for the first line parametric equation and tc is parameter for the second line. In this case
ClosestPointAtFirst = L1P0 + sc * u
ClosestPointAtSecond = L2P0 + tc * v
I think I have the approach.
The algorithm does find the unit vector normal to both lines. You also know the distance. That gives you the vector of the line segment connecting the two points of interest.
Apply that vector as a linear transformation to u. This gives you a new line, u-prime, which is shifted in the direction of the normal vector, by a distance you've computed. u-prime will intersect v at one of the two points. Find that intersection (which is the point on v of closest approach), subtract the connecting vector, and that gives your other point (on the original u).
If it helps to visualize, not that u-prime and v define a plane that is perpendicular to the normal vector.
Related
I have written code that calculates the angle between two vectors. However the way in which is does this is to start with two vectors, rotate each according to some euler angles calculated in a separate program, then calculate the angle between the vectors.
Up until now I have been working with a use case that means both starting vectors are (0,0,1) that makes life super easy. I could just take one set of euler angles away from the other and then calculate the angle between 0,0,1 and the vector that had been rotated by the difference. It meant I could plot nice distribution plots and vector diagrams because everything was normalised to 0,0,1. (I have 1000s of these vectors for the record).
No I am trying to write in a function that would allow for a use case where the two starting vectors are not on 0,0,1. I figured the easiest way to do this would be to calculate direction of the vector relative to 0,0,1 and after calculating the position of the vector just rotate by the precalculated offsets. (this might be a stupid way to do it, if it is please tell me).
MY current code works for a case where a vector is 0,1,0 but then breaks down if i start entering random numbers.
import numpy as np
import math
def RotationMatrix(axis, rotang):
"""
This uses Euler-Rodrigues formula.
"""
#Input taken in degrees, here we change it to radians
theta = rotang * 0.0174532925
axis = np.asarray(axis)
#Ensure axis is a unit vector
axis = axis/math.sqrt(np.dot(axis, axis))
#calclating a, b, c and d according to euler-rodrigues forumla requirments
a = math.cos(theta/2)
b, c, d = axis*math.sin(theta/2)
a2, b2, c2, d2 = a*a, b*b, c*c, d*d
bc, ad, ac, ab, bd, cd = b*c, a*d, a*c, a*b, b*d, c*d
#Return the rotation matrix
return np.array([[a2+b2-c2-d2, 2*(bc-ad), 2*(bd+ac)],
[2*(bc+ad), a2+c2-b2-d2, 2*(cd-ab)],
[2*(bd-ac), 2*(cd+ab), a2+d2-b2-c2]])
def ApplyRotationMatrix(vector, rotationmatrix):
"""
This function take the output from the RotationMatrix function and
uses that to apply the rotation to an input vector
"""
a1 = (vector[0] * rotationmatrix[0, 0]) + (vector[1] * rotationmatrix[0, 1]) + (vector[2] * rotationmatrix[0, 2])
b1 = (vector[0] * rotationmatrix[1, 0]) + (vector[1] * rotationmatrix[1, 1]) + (vector[2] * rotationmatrix[1, 2])
c1 = (vector[0] * rotationmatrix[2, 0]) + (vector[1] * rotationmatrix[2, 1]) + (vector[2] * rotationmatrix[2, 2])
return np.array((a1, b1, c1)
'''
Functions for Calculating the angles of 3D vectors relative to one another
'''
def CalculateAngleBetweenVector(vector, vector2):
"""
Does what it says on the tin, outputs an angle in degrees between two input vectors.
"""
dp = np.dot(vector, vector2)
maga = math.sqrt((vector[0] ** 2) + (vector[1] ** 2) + (vector[2] ** 2))
magb = math.sqrt((vector2[0] ** 2) + (vector2[1] ** 2) + (vector2[2] ** 2))
magc = maga * magb
dpmag = dp / magc
#These if statements deal with rounding errors of floating point operations
if dpmag > 1:
error = dpmag - 1
print('error = {}, do not worry if this number is very small'.format(error))
dpmag = 1
elif dpmag < -1:
error = 1 + dpmag
print('error = {}, do not worry if this number is very small'.format(error))
dpmag = -1
angleindeg = ((math.acos(dpmag)) * 180) / math.pi
return angleindeg
def CalculateAngleAroundZ(Vector):
X,Y,Z = Vector[0], Vector[1], Vector[2]
AngleAroundZ = math.atan2(Y, X)
AngleAroundZdeg = (AngleAroundZ*180)/math.pi
return AngleAroundZdeg
def CalculateAngleAroundX(Vector):
X,Y,Z = Vector[0], Vector[1], Vector[2]
AngleAroundZ = math.atan2(Y, Z)
AngleAroundZdeg = (AngleAroundZ*180)/math.pi
return AngleAroundZdeg
def CalculateAngleAroundY(Vector):
X,Y,Z = Vector[0], Vector[1], Vector[2]
AngleAroundZ = math.atan2(X, Z)
AngleAroundZdeg = (AngleAroundZ*180)/math.pi
return AngleAroundZdeg
V1 = (0,0,1)
V2 = (3,5,4)
Xoffset = (CalculateAngleAroundX(V2))
Yoffset = (CalculateAngleAroundY(V2))
Zoffset = (CalculateAngleAroundZ(V2))
XRM = RotationMatrix((1,0,0), (Xoffset * 1))
YRM = RotationMatrix((0,1,0), (Yoffset * 1))
ZRM = RotationMatrix((0,0,1), (Zoffset * 1))
V2 = V2 / np.linalg.norm(V2)
V2X = ApplyRotationMatrix(V2, XRM)
V2XY = ApplyRotationMatrix(V2X, YRM)
V2XYZ = ApplyRotationMatrix(V2XY, ZRM)
print(V2XYZ)
print(CalculateAngleBetweenVector(V1, V2XYZ))
Any advice to fix this problem will be much appreciated.
I'm not sure to fully understand what you need but if it is to compute the angle between two vectors in space you can use the formula:
where a.b is the scalar product and theta is the angle between vectors.
thus your function CalculateAngleBetweenVector becomes:
def CalculateAngleBetweenVector(vector, vector2):
return math.acos(np.dot(vector,vector2)/(np.linalg.norm(vector)* np.linalg.norm(vector2))) * 180 /math.pi
You can also simplify your ApplyRotationMatrix function:
def ApplyRotationMatrix(vector, rotationmatrix):
"""
This function take the output from the RotationMatrix function and
uses that to apply the rotation to an input vector
"""
return rotationmatrix # vector
the # symbol is the matrix product
Hope this will help you. Feel free to precise your request if this is not helpfull.
Im an idiot I just needed to do the cross product and the dot product and rotate by the dot product *-1 around the cross product.
I am trying to find a point 'p2' on a curve, and it is 'd' away from point 'p1'.
The curve is quadratic formula ax^2 + bx + c = y
point p1 is on the curve, let us say (p1x, p1y)
point p2 is on the curve, but we only know its distance 'along the curve' from p1, which is 'd'. A distance on a curve can be calculated by integrating'(1+(2*a*x+b)^2)^(1/2) dx'. Here, integrating'(1+(2*a*x+b)^2)^(1/2) dx' from p1x to p2x is expectd to have a given number k. p2x is unknown.
I have been using a loop to find the point.
from scipy import integrate
def integral(a, b, c, p1x, distance_between_p1_and_p2):
x = lambda x:(1+(2*a*x+b)**2)**(1/2)
best_i=0
p2x=0
for points_on_curve in range(int(p1x*1000),int((p1x+0.15)*1000),1):
i,j = integrate.quad(x,p1x,points_on_curve/1000)
if abs(i-distance_between_p1_and_p2)<abs(best_i-distance_between_p1_and_p2):
best_i=i
p2x=points_on_curve/1000
return p1x+p2x
The problem here is it takes so long becuase it begins from p1x and slightly increase the value, calculate the length from p1 to potential p2 and see if it is closer to the target distance_between_p1_and_p2 than the previous one.
Would it be there a better way of programming it?
I have been working on it and i found two solutions to this.
First, I used sympy.geometry.curve
from sympy.geometry.curve import Curve
x = sp. Symbol('x')
a = sp. Symbol('a')
b = sp. Symbol('b')
c = sp. Symbol('c')
start = sp. Symbol('start')
end = sp. Symbol('end')
print('length')
print(Curve((a*x**2+b*x+c, x), (x, start, end)).length)
I get this as an output.
(end + b/(2*a))*sqrt(4*a**2*(end + b/(2*a))**2 + 1)/2 - (start + b/(2*a))*sqrt(4*a**2*(start + b/(2*a))**2 + 1)/2 + asinh(2*a*(end + b/(2*a)))/(4*a) - asinh(2*a*(start + b/(2*a)))/(4*a)
Here, I can use the equation.
from sympy import solve, sqrt, asinh, nsolve
end = sp.S('end')
a = -1
b = 0
c = 4
w3 = 1
length = 2
eq = sp.Eq((end + b/(2*a))*sqrt(4*a**2*(end + b/(2*a))**2 + 1)/2 - (w3 + b/(2*a))*sqrt(4*a**2*(w3 + b/(2*a))**2 + 1)/2 + asinh(2*a*(end + b/(2*a)))/(4*a) - asinh(2*a*(w3 + b/(2*a)))/(4*a),length)
I found two ways to solve the equation.
Use nsolve. This gives only one answer even if I have two. For example, if there are two answers (a+sqrt(b), a-sqrt(b)), I guess this gives only one answer closer to expected_value_to_start_search_answer.
print(sp.nsolve(eq, expected_value_to_start_search_answer))
Use solve. This gives all possible answers, but it is slower than the first option.
sol = solve(eq,end)
print(sol)
Your target points x, y sit on the parabolic curve as well as on a circle around p1, in other words, all fulfill the equations
a x^2 + b x + c = y
(x - p1x)^2 + (y - p1y)^2 = r^2
You can simply eliminate y by inserting the lhs from the first equation into the second, and solve the resulting quadratic equation for x.
I have a defined plane (one which happens to be orthogonal to the vector defined by two xyz points in 3D space). I can project any xyz point onto the plane and represent that projection uv coordinate space. I would like to take an arbitrary point in uv coordinate space and find out what its coordinates are in xyz space.
a = x2 - x1
b = y2 - y1
c = z2 - z1
d = -1*(a*x1 + b*y1 + c*z1)
magnitude = (a**2 + b**2 + c**2)**.5
u_magnitude = (b**2 + a**2)**.5
normal = [a/magnitude, b/magnitude, c/magnitude]
u = [b/u_magnitude, -a/u_magnitude, 0]
v = np.cross(normal, u)
p_u = np.dot(u,[x1,y1,z1])
p_v = np.dot(v,[x1,y1,z1])
This code I believe accurately produces the plane I want and will assign the x1,y1,z1 point in uv coordinates to p_u,p_v. My sense is that I have everything I need to do the reverse operation, but I don't know how. If I have a point u0,v0 how can I find x0,y0,z0 that describes its location in 3D space?
From the definition in the text (not reading the code), the problem is not well defined - as there is an infinite number of planes orthogonal to a given vector (think of all the options as planes at different "offsets" along the line from the first point to the second). What you need is first to pick some point through which the plane has to go.
Secondly, when we convert a (U, V) pair to 3D point, I assume you mean a 3D point on the plane.
Trying to be more concrete though, here is your code, with documentation on how I understand it, and how to do the reverse:
# ### The original computation of the plane equation ###
# Given points p1 and p2, the vector through them is W = (p2 - p1)
# We want the plane equation Ax + By + Cz + d = 0, and to make
# the plane prepandicular to the vector, we set (A, B, C) = W
p1 = np.array([x1, y1, z1])
p2 = np.array([x2, y2, z2])
A, B, C = W = p2 - p1
# Now we can solve D in the plane equation. This solution assumes that
# the plane goes through p1.
D = -1 * np.dot(W, p1)
# ### Normalizing W ###
magnitude = np.linalg.norm(W)
normal = W / magnitude
# Now that we have the plane, we want to define
# three things:
# 1. The reference point in the plane (the "origin"). Given the
# above computation of D, that is p1.
# 2. The vectors U and V that are prepandicular to W
# (and therefore spanning the plane)
# We take a vector U that we know that is perpendicular to
# W, but we also need to make sure it's not zero.
if A != 0:
u_not_normalized = np.array([B, -A, 0])
else:
# If A is 0, then either B or C have to be nonzero
u_not_normalized = np.array([0, B, -C])
u_magnitude = np.linalg.norm(u_not_normalized)
# ### Normalizing W ###
U = u_not_normalized / u_magnitude
V = np.cross(normal, U)
# Now, for a point p3 = (x3, y3, z3) it's (u, v) coordinates would be
# computed relative to our reference point (p1)
p3 = np.array([x3, y3, z3])
p3_u = np.dot(U, p3 - p1)
p3_v = np.dot(V, p3 - p1)
# And to convert the point back to 3D, we just use the same reference point
# and multiply U and V by the coordinates
p3_again = p1 + p3_u * U + p3_v * V
i plot an angle in python
here is the code
x = [0,0.5,1]
y = [0,0.5,0]
plt.scatter(x,y)
plt.plot(x,y)
plt.show()
is there a way to examine if the angle is a right angle programmatically?
The easiest way is to test if the dot product of the vectors is 0.
In your case, you simply compute:
v1 = ( (x[1]-x[0]), (y[1]-y[0]) ) <- (0.5, 0.5)
v2 = ( (x[2]-x[1]), (y[2]-y[1]) ) <- (0.5, -0.5)
dot_product = v1[0]*v2[0] + v1[1]*v2[1] <- 0.5² - 0.5² = 0
The other answers do not really care about possible inaccuracies and truncation errors, nor efficiency.
Rather than an exact comparison to 90° (or 0° in case of dot product), it is wiser to check for a small angle difference to 90° (resp. 0°).
Also wise to avoid divisions, square roots and trigonometric functions. The cross-product method is among the most attractive.
Compute the cross-product of the sides of the angle and their squared lengths, and compare
with a precomputed tolerance:
(ABx . BCy - ABy . BCx)² ≥ α.(ABx² + ABy²).(BCx² + BCy²)
with α = cos²δ where δ is the angle tolerance.
You can try to calculate the angle, but an easier way could be to check whether the Pythagorean Theorem applies. For that you'll need to calculate the size of the three edges and then check whether A^2 + B^2 ~= C^2
Yes, there is.
x = [0,0.5,1]
y = [0,0.5,0]
points = [np.array(point) for point in zip(x,y)]
a, b, c = points
ba = a - b
bc = c - b
cosine_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))
angle_rad = np.arccos(cosine_angle)
angle_deg = np.rad2deg(angle_rad)
print(angle_deg) # 90.0
You can compute the angle between the two vectors as following: first, get the two vectors v1 and v2 and then use np.arccos() which returns the angle in radians. Convert it to degrees to check if it is 90 degrees. The formulae for computing angle between two vectors can be found on this Wiki link
import numpy as np
x = np.array([0,0.5,1])
y = np.array([0,0.5,0])
vecs = np.vstack((x, y))
v1 = vecs[:, 1] - vecs[:, 0]
v2 = vecs[:, 2] - vecs[:, 1]
angle_rad = np.arccos(np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2)))
angle_deg = np.rad2deg(angle_rad)
# 90.0
I have code to expand the polygon, it works by multiplying the xs and ys by a factor then re centering the resultant polyon at the center of the original.
I also have code to find the value for the expansion factor, given a point that the polygon needs to reach:
import numpy as np
import itertools as IT
import copy
from shapely.geometry import LineString, Point
def getPolyCenter(points):
"""
http://stackoverflow.com/a/14115494/190597 (mgamba)
"""
area = area_of_polygon(*zip(*points))
result_x = 0
result_y = 0
N = len(points)
points = IT.cycle(points)
x1, y1 = next(points)
for i in range(N):
x0, y0 = x1, y1
x1, y1 = next(points)
cross = (x0 * y1) - (x1 * y0)
result_x += (x0 + x1) * cross
result_y += (y0 + y1) * cross
result_x /= (area * 6.0)
result_y /= (area * 6.0)
return (result_x, result_y)
def expandPoly(points, factor):
points = np.array(points, dtype=np.float64)
expandedPoly = points*factor
expandedPoly -= getPolyCenter(expandedPoly)
expandedPoly += getPolyCenter(points)
return np.array(expandedPoly, dtype=np.int64)
def distanceLine2Point(points, point):
points = np.array(points, dtype=np.float64)
point = np.array(point, dtype=np.float64)
points = LineString(points)
point = Point(point)
return points.distance(point)
def distancePolygon2Point(points, point):
distances = []
for i in range(len(points)):
if i==len(points)-1:
j = 0
else:
j = i+1
line = [points[i], points[j]]
distances.append(distanceLine2Point(line, point))
minDistance = np.min(distances)
#index = np.where(distances==minDistance)[0][0]
return minDistance
"""
Returns the distance from a point to the nearest line of the polygon,
AND the distance from where the normal to the line (to reach the point)
intersets the line to the center of the polygon.
"""
def distancePolygon2PointAndCenter(points, point):
distances = []
for i in range(len(points)):
if i==len(points)-1:
j = 0
else:
j = i+1
line = [points[i], points[j]]
distances.append(distanceLine2Point(line, point))
minDistance = np.min(distances)
i = np.where(distances==minDistance)[0][0]
if i==len(points)-1:
j = 0
else:
j = i+1
line = copy.deepcopy([points[i], points[j]])
centerDistance = distanceLine2Point(line, getPolyCenter(points))
return minDistance, centerDistance
minDistance, centerDistance = distancePolygon2PointAndCenter(points, point)
expandedPoly = expandPoly(points, 1+minDistance/centerDistance)
This code only works when the point is directly opposing one of the polygons lines.
Modify your method distancePolygon2PointAndCenter to instead of
Returns the distance from a point to the nearest line of the polygon
To return the distance from a point to the segment intersected by a ray from the center to the point. This is the line that will intersect the point once the polygon is fully expanded. To get this segment, take both endpoints of each segment of your polygon, and plug them into the equation for the line parallel & intersecting the ray mentioned earlier. That is y = ((centerY-pointY)/(centerX-pointX)) * (x - centerX) + centerY. You want to want to find endpoints where either one of them intersect the line, or the two are on opposite sides of the line.
Then, the only thing left to do is make sure that we pick the segment intersecting the right "side" of the line. To do this, there are a few options. The fail-safe method would be to use the formula cos(theta) = sqrt((centerX**2 + centerY**2)*(pointX**2 + pointY**2)) / (centerX * pointX + centerY * pointY) however, you could use methods such as comparing x and y values, taking the arctan2(), and such to figure out which segment is on the correct "side" of center. You'll just have lots of edge cases to cover. After all this is said and done, your two (unless its not convex, in which case take the segment farthest from you center) endpoints makeup the segment to expand off of.
Determine what is "polygon center" as central point C of expanding. Perhaps it is centroid (or some point with another properties?).
Make a segment from your point P to C. Find intersection point I between PC and polygon edges. If polygon is concave and there are some intersection points, choose the closest one to P.
Calculate coefficient of expanding:
E = Length(PC) / Length(CI)
Calculate new vertex coordinates. For i-th vertex of polygon:
V'[i].X = C.X + (V[i].X - C.X) * E
V'[i].Y = C.Y + (V[i].Y - C.Y) * E
Decide which point you want to reach, then calculate how much % your polygon needs to expand to reach that point and use the shapely.affinity.scale function. For example, in my case I just needed to make the polygon 5% bigger:
region = shapely.affinity.scale(myPolygon,
xfact=1.05, yfact=1.05 )