I'd like to place 4 points around a point on a sphere (cartesian coordinates: x y z), it doesn't matter how far these 4 points are from the center point (straight line distance or spherical distance) but I'd like these 4 points to be the same distance D from the center point (ideally the 5 points should have a + or x shape, so one north, one south, one east and one south).
I could do it by changing one variable (x, y or z) then keeping another the same and calculating the last variable based on the formula x * x + y * y + z * z = radius * radius but that didn't give good results. I could also maybe use the pythagorean theorem to get the distance between each of the 4 points and the center but I think there is a better formula that I don't know (and couldn't find by doing my research).
Thank you.
Some math
AFAIU your problem is that you have a sphere and a point on the sphere and you want to add 4 more points on the same sphere that would form a kind of a cross on the surface of the sphere around the target point.
I think it is easier to think about this problem in terms of vectors. You have a vector from the center of the sphere to your target point V of size R. All the point lying on the distance d from the target point form another sphere. The crossing of two sphere is a circle. Obviously this circle lies in a plane that is orthogonal to V. Solving a simple system of equations you can find that the distance from the target point to that plane is d^2/(2*R). So the vector from the center of the original sphere to the center of the circle:
Vc = V * (1 - d^2/(2*R^2))
and the radius of that circle is
Rc = sqrt(d^2 - (d^2/(2*R))**2)
So now to select 4 points, you need to select two orthogonal unit vectors lying in that plane D1 and D2. Then 4 points would be Vc + Rc*D1, Vc - Rc*D1, Vc + Rc*D2, and Vc - Rc*D2. To do this you may first select D1 fixing z =0 and switch x and y in Vc
D1 = (Vy/sqrt(Vx^2+Vy^2), -Vx/sqrt(Vx^2+Vy^2), 0)
and then find D2 as a result of cross-product of V and D1. This will work unless unless Vx = Vy = 0 (i.e. V goes along the z-axis) but in that case you can select
D1 = (1,0,0)
D2 = (0,1,0)
Some code
And here is some Python code that implements that math:
def cross_product(v1, v2):
return (v1[1] * v2[2] - v1[2] * v2[1],
v1[2] * v2[0] - v1[0] * v2[2],
v1[0] * v2[1] - v1[1] * v2[0])
def find_marks(sphereCenter, target, d):
lsc = list(sphereCenter)
lt0 = list(target)
lt1 = map(lambda c1, c0: (c1 - c0), lt0, lsc) # shift everything as if sphereCenter is (0,0,0)
rs2 = sum(map(lambda x: x ** 2, lt1)) # spehere radius**2
rs = rs2 ** 0.5
dv = d ** 2 / 2.0 / rs
dvf = d ** 2 / 2.0 / rs2
lcc = map(lambda c: c * (1 - dvf), lt1) # center of the circle in the orthogonal plane
rc = (d ** 2 - dv ** 2) ** 0.5 # orthogonal circle radius
relEps = 0.0001
absEps = relEps * rs
dir1 = (lt1[1], -lt1[0], 0) # select any direction orthogonal to the original vector
dl1 = (lt1[0] ** 2 + lt1[1] ** 2) ** 0.5
# if original vector is (0,0, z) then we've got dir1 = (0,0,0) but we can use (1,0,0) as our vector
if abs(dl1) < absEps:
dir1 = (rc, 0, 0)
dir2 = (0, rc, 0)
else:
dir1 = map(lambda c: rc * c / dl1, dir1)
dir2 = cross_product(lt1, dir1)
dl2 = sum(map(lambda c: c ** 2, dir2)) ** 0.5
dir2 = map(lambda c: rc * c / dl2, dir2)
p1 = map(lambda c0, c1, c2: c0 + c1 + c2, lsc, lcc, dir1)
p2 = map(lambda c0, c1, c2: c0 + c1 + c2, lsc, lcc, dir2)
p3 = map(lambda c0, c1, c2: c0 + c1 - c2, lsc, lcc, dir1)
p4 = map(lambda c0, c1, c2: c0 + c1 - c2, lsc, lcc, dir2)
return [tuple(p1), tuple(p2), tuple(p3), tuple(p4)]
For an extreme case
find_marks((0, 0, 0), (12, 5, 0), 13.0 * 2 ** 0.5)
i.e. for a circle of radius 13 with a center at (0,0,0), the target point lying on the big circle in the plane parallel to the xy-plane and d = sqrt(2)*R, the answer is
[(4.999999999999996, -12.000000000000004, 0.0),
(-5.329070518200751e-15, -2.220446049250313e-15, -13.0),
(-5.000000000000006, 12.0, 0.0),
(-5.329070518200751e-15, -2.220446049250313e-15, 13.0)]
So two points (2-nd and 4-th) are just two z-extremes and the other two are 90° rotations of the target point in the xy-plane which looks quite OK.
For a less extreme example:
find_marks((1, 2, 3), (13, 7, 3), 1)
which is the previous example with d reduced to 1 and with the original center moved to (1,2,3)
[(13.34882784191617, 6.06281317940119, 3.0),
(12.964497041420119, 6.985207100591716, 2.000739918710263),
(12.580166240924067, 7.907601021782242, 3.0),
(12.964497041420119, 6.985207100591716, 3.999260081289737)]
which also looks plausible
Related
I want to rotate a multiple points that are in the same 3D plane ax+by+cz=d,to a 2D plane xy. I was able to rotate the plane but not fully.
as you can see in this picture(the red points is circule in the xy plane, the yellow is the circule I want to rotate, and the red is the points are that I was able to rotate.
I tried to create the rotation matrix calculating the normal vector of the plane (a,b,c) and then calculating this rotation matrix! , by rotating the plane by the vector perpendicular to (a,b,c) and (0,0,1).
def grid2d_perplane(plane): # creates a 2d grid in a plane ax+by+cz=d by calculating a,b,c,d
p1 = copy.copy(plane[0])
p2 = copy.copy(plane[13])
p3 = copy.copy(plane[30])
# These two vectors are in the plane
v1 = p3 - p1
v2 = p2 - p1
# the cross product is a vector normal to the plane
cp = np.cross(v1, v2)
cp /= np.sqrt(cp[0]*cp[0] + cp[1]*cp[1] +cp[2]*cp[2])
a, b, c = cp
# This evaluates a * x3 + b * y3 + c * z3 which equals d
d = np.dot(cp, p3)
maxx = np.max(plane[:,0:1])
maxy = np.max(plane[:,1:2])
minx = np.min(plane[:,0:1])
miny = np.min(plane[:,1:2])
x = np.arange(minx*2,maxx*2,0.25)
y = np.arange(miny*2,maxy*2,0.25)
xx,yy= np.meshgrid(x,y)
zz = (d - a * xx - b * yy) / c
return xx, yy, zz, a , b , c, d
def transfor2d(p,a,b,c):
"The function will transform a set of points from a 3d plane to xy plane we have to keep the indexes intact for us to use later"
#TODO make it only rotation, and try to translate after wards
norm = np.sqrt(a**2+ b**2 +c **2)
cos = c /norm
theta = np.arccos(cos)
# if cos < 0:
# cos = -cos # this is a hack I have to test it again.
sen = np.sqrt((a**2+ b**2)/(norm**2))
theta1 = np.arcsin(sen)
print(theta,theta1)
u_one = b /norm
u_two = -a /norm
u_one_s = u_one**2
u_two_s = u_two**2
T11 = cos + (u_one_s*(1-cos))
T12 = ((u_one)*(u_two))*(1-cos)
T13 = u_two * sen
T21 = ((u_one)*(u_two))*(1-cos)
T22 = cos + (u_two_s*(1-cos))
T23 = -u_one * sen
T31 = -u_two * sen
T32 = u_one * sen
T33 = cos
matrix = np.zeros((3,3),np.float32)
matrix[0][0] = T11
matrix[0][1] = T12
matrix[0][2] = T13
matrix[1][0] = T21
matrix[1][1] = T22
matrix[1][2] = T23
matrix[2][0] = T31
matrix[2][1] = T32
matrix[2][2] = T33
m = matrix.dot(p)
return np.array([m[0],m[1],m[2]])
The expected results should to make the yellow points go to the red points. However the rotation matrix is not doing this. Can you see any errors that I'm making? I think that theoretically should work.
I have dataframe with measurements coordinates and cell coordinates.
I need to find for each row angle (azimuth angle) between a line that connects these two points and the north pole.
df:
id cell_lat cell_long meas_lat meas_long
1 53.543643 11.636235 53.44758 11.03720
2 52.988823 10.0421645 53.03501 9.04165
3 54.013442 9.100981 53.90384 10.62370
I have found some code online, but none if that really helps me get any closer to the solution.
I have used this function but not sure if get it right and I guess there is simplier solution.
Any help or hint is welcomed, thanks in advance.
The trickiest part of this problem is converting geodetic (latitude, longitude) coordinates to Cartesian (x, y, z) coordinates. If you look at https://en.wikipedia.org/wiki/Geographic_coordinate_conversion you can see how to do this, which involves choosing a reference system. Assuming we choose ECEF (https://en.wikipedia.org/wiki/ECEF), the following code calculates the angles you are looking for:
def vector_calc(lat, long, ht):
'''
Calculates the vector from a specified point on the Earth's surface to the North Pole.
'''
a = 6378137.0 # Equatorial radius of the Earth
b = 6356752.314245 # Polar radius of the Earth
e_squared = 1 - ((b ** 2) / (a ** 2)) # e is the eccentricity of the Earth
n_phi = a / (np.sqrt(1 - (e_squared * (np.sin(lat) ** 2))))
x = (n_phi + ht) * np.cos(lat) * np.cos(long)
y = (n_phi + ht) * np.cos(lat) * np.sin(long)
z = ((((b ** 2) / (a ** 2)) * n_phi) + ht) * np.sin(lat)
x_npole = 0.0
y_npole = 6378137.0
z_npole = 0.0
v = ((x_npole - x), (y_npole - y), (z_npole - z))
return v
def angle_calc(lat1, long1, lat2, long2, ht1=0, ht2=0):
'''
Calculates the angle between the vectors from 2 points to the North Pole.
'''
# Convert from degrees to radians
lat1_rad = (lat1 / 180) * np.pi
long1_rad = (long1 / 180) * np.pi
lat2_rad = (lat2 / 180) * np.pi
long2_rad = (long2 / 180) * np.pi
v1 = vector_calc(lat1_rad, long1_rad, ht1)
v2 = vector_calc(lat2_rad, long2_rad, ht2)
# The angle between two vectors, vect1 and vect2 is given by:
# arccos[vect1.vect2 / |vect1||vect2|]
dot = np.dot(v1, v2) # The dot product of the two vectors
v1_mag = np.linalg.norm(v1) # The magnitude of the vector v1
v2_mag = np.linalg.norm(v2) # The magnitude of the vector v2
theta_rad = np.arccos(dot / (v1_mag * v2_mag))
# Convert radians back to degrees
theta = (theta_rad / np.pi) * 180
return theta
angles = []
for row in range(df.shape[0]):
cell_lat = df.iloc[row]['cell_lat']
cell_long = df.iloc[row]['cell_long']
meas_lat = df.iloc[row]['meas_lat']
meas_long = df.iloc[row]['meas_long']
angle = angle_calc(cell_lat, cell_long, meas_lat, meas_long)
angles.append(angle)
This will read each row out of your dataframe, calculate the angle and append it to the list angles. Obviously you can do what you like with those angles after they've been calculated.
Hope that helps!
given a plane equation, how can you generate four points that comprise a rectangle? I only have the plane equation ax+by+cz=d.
I am following the approach listed here Find Corners of Rectangle, Given Plane equation, height and width
#generate horizontal vector U
temp_normal=np.array([a,b,c])
temp_vertical=np.array([0,0,1])
U=np.cross(temp_normal, temp_vertical)
# for corner 3 and 4
neg_U=np.multiply([-1.0, -1.0, -1.0], U)
#generate vertical vector W
W=np.cross(temp_normal,U)
#for corner 2 and 4
neg_W=np.multiply([-1.0, -1.0, -1.0], W)
#make the four corners
#C1 = P0 + (width / 2) * U + (height / 2) * W
C1=np.sum([centroid,np.multiply(U, width_array),np.multiply(W, height_array)], axis=0)
corner1=C1.tolist()
#C2 = P0 + (width / 2) * U - (height / 2) * W
C2=np.sum([centroid,np.multiply(U, width_array),np.multiply(neg_W, height_array)], axis=0)
corner2=C2.tolist()
#C3 = P0 - (width / 2) * U + (height / 2) * W
C3=np.sum([centroid,np.multiply(neg_U, width_array),np.multiply(W, height_array)], axis=0)
corner3=C3.tolist()
#C4 = P0 - (width / 2) * U - (height / 2) * W
C4=np.sum([centroid,np.multiply(neg_U, width_array),np.multiply(neg_W, height_array)], axis=0)
self.theLw.WriteLine("C4 is " +str(type(C4))+" "+str(C4.tolist()))
corner4=C4.tolist()
corners_list.append([corner1, corner2, corner3, corner4])
Find a vector inside that plane using the equation. Find a second one inside that plane, perpendicular to the first one, using the cross-product (of the first and a normal vector to the plane). Then add these vectors (with +- signs, 4 possibilities) to generate 4 corners.
Edit: to help you a bit more:
(a,b,c) is the vector normal to the plane;
(0,0,d/c), (0,d/b,0) and (d/a,0,0) are points belonging to the plane, i.e. for instance b1 = (0,d/b,-d/c) is a vector tangent to the plane;
The cross-product of two vectors returns a vector that is perpendicular to both. So the product b2 = (a,b,c) x (0,d/b,-d/c) is a vector tangent to the plane, perpendicular to the other one. With that, you have constructed a normal basis of the plane [b1,b2].
Start from a point, say (0,0,d/c), and add b1+b2, b1-b2, -b1+b2, -b1-b2 to have 4 corners.
Ok here is the answer:
import numpy as np
a = 2; b = 3; c = 4; d = 5
n = np.array([a,b,c])
x1 = np.array([0,0,d/c])
x2 = np.array([0,d/b,0])
def is_equal(n,m):
return n-m < 1e-10
def is_on_the_plane(v):
return is_equal(v[0]*a + v[1]*b + v[2]*c, d)
assert is_on_the_plane(x1)
assert is_on_the_plane(x2)
# Get the normal basis
b1 = x2 - x1
b2 = np.cross(n, b1)
c1 = x1 + b1 + b2
c2 = x1 + b1 - b2
c3 = x1 - b1 + b2
c4 = x1 - b1 - b2
assert is_on_the_plane(c1)
assert is_on_the_plane(c2)
assert is_on_the_plane(c3)
assert is_on_the_plane(c4)
assert is_equal(np.dot(c1-c3, c1-x2), 0)
assert is_equal(np.dot(c2-c1, c2-c4), 0)
# etc. :
# c3 c1
#
# x1
#
# c4 c2
It is actually a square, but you can surely find out how to make it a less specific rectangle.
I have been not using math for a long time and this should be a simple problem to solve.
Suppose I have two points A: (1, 0) and B: (1, -1).
I want to use a program (Python or whatever programming language) to calculate the clockwise angle between A, origin (0, 0) and B. It will be something like this:
angle_clockwise(point1, point2)
Note that the order of the parameters matters. Since the angle calculation will be clockwise:
If I call angle_clockwise(A, B), it returns 45.
If I call angle_clockwise(B, A), it returns 315.
In other words, the algorithm is like this:
Draw a line (line 1) between the first point param with (0, 0).
Draw a line (line 2) between the second point param with (0, 0).
Revolve line 1 around (0, 0) clockwise until it overlaps line 2.
The angular distance line 1 traveled will be the returned angle.
Is there any way to code this problem?
Numpy's arctan2(y, x) will compute the counterclockwise angle (a value in radians between -π and π) between the origin and the point (x, y).
You could do this for your points A and B, then subtract the second angle from the first to get the signed clockwise angular difference. This difference will be between -2π and 2π, so in order to get a positive angle between 0 and 2π you could then take the modulo against 2π. Finally you can convert radians to degrees using np.rad2deg.
import numpy as np
def angle_between(p1, p2):
ang1 = np.arctan2(*p1[::-1])
ang2 = np.arctan2(*p2[::-1])
return np.rad2deg((ang1 - ang2) % (2 * np.pi))
For example:
A = (1, 0)
B = (1, -1)
print(angle_between(A, B))
# 45.
print(angle_between(B, A))
# 315.
If you don't want to use numpy, you could use math.atan2 in place of np.arctan2, and use math.degrees (or just multiply by 180 / math.pi) in order to convert from radians to degrees. One advantage of the numpy version is that you can also pass two (2, ...) arrays for p1 and p2 in order to compute the angles between multiple pairs of points in a vectorized way.
Use the inner product and the determinant of the two vectors. This is really what you should understand if you want to understand how this works. You'll need to know/read about vector math to understand.
See: https://en.wikipedia.org/wiki/Dot_product and https://en.wikipedia.org/wiki/Determinant
from math import acos
from math import sqrt
from math import pi
def length(v):
return sqrt(v[0]**2+v[1]**2)
def dot_product(v,w):
return v[0]*w[0]+v[1]*w[1]
def determinant(v,w):
return v[0]*w[1]-v[1]*w[0]
def inner_angle(v,w):
cosx=dot_product(v,w)/(length(v)*length(w))
rad=acos(cosx) # in radians
return rad*180/pi # returns degrees
def angle_clockwise(A, B):
inner=inner_angle(A,B)
det = determinant(A,B)
if det<0: #this is a property of the det. If the det < 0 then B is clockwise of A
return inner
else: # if the det > 0 then A is immediately clockwise of B
return 360-inner
In the determinant computation, you're concatenating the two vectors to form a 2 x 2 matrix, for which you're computing the determinant.
Here's a solution that doesn't require cmath.
import math
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
v1 = Vector(0, 1)
v2 = Vector(0, -1)
v1_theta = math.atan2(v1.y, v1.x)
v2_theta = math.atan2(v2.y, v2.x)
r = (v2_theta - v1_theta) * (180.0 / math.pi)
if r < 0:
r += 360.0
print r
A verified 0° to 360° solution
It is an old thread, but for me the other solutions didn't work well, so I implemented my own version.
My function will return a number between 0 and 360 (excluding 360) for two points on the screen (i.e. 'y' starts at the top and increasing towards the bottom), where results are as in a compass, 0° at the top, increasing clockwise:
def angle_between_points(p1, p2):
d1 = p2[0] - p1[0]
d2 = p2[1] - p1[1]
if d1 == 0:
if d2 == 0: # same points?
deg = 0
else:
deg = 0 if p1[1] > p2[1] else 180
elif d2 == 0:
deg = 90 if p1[0] < p2[0] else 270
else:
deg = math.atan(d2 / d1) / pi * 180
lowering = p1[1] < p2[1]
if (lowering and deg < 0) or (not lowering and deg > 0):
deg += 270
else:
deg += 90
return deg
Check out the cmath python library.
>>> import cmath
>>> a_phase = cmath.phase(complex(1,0))
>>> b_phase = cmath.phase(complex(1,-1))
>>> (a_phase - b_phase) * 180 / cmath.pi
45.0
>>> (b_phase - a_phase) * 180 / cmath.pi
-45.0
You can check if a number is less than 0 and add 360 to it if you want all positive angles, too.
Chris St Pierre: when using your function with:
A = (x=1, y=0)
B = (x=0, y=1)
This is supposed to be a 90 degree angle from A to B. Your function will return 270.
Is there an error in how you process the sign of the det or am I missing something?
A formula that calculates an angle clockwise, and is used in surveying:
f(E,N)=pi()-pi()/2*(1+sign(N))* (1-sign(E^2))-pi()/4*(2+sign(N))*sign(E)
-sign(N*E)*atan((abs(N)-abs(E))/(abs(N)+abs(E)))
The formula gives angles from 0 to 2pi,start from the North and
is working for any value of N and E. (N=N2-N1 and E=E2-E1)
For N=E=0 the result is undefined.
in radians, clockwise, from 0 to PI * 2
static angle(center:Coord, p1:Coord, p2:Coord) {
var a1 = Math.atan2(p1.y - center.y, p1.x - center.x);
var a2 = Math.atan2(p2.y - center.y, p2.x -center.x);
a1 = a1 > 0 ? a1 : Math.PI * 2 + a1;//make angle from 0 to PI * 2
a2 = a2 > 0 ? a2 : Math.PI * 2 + a2;
if(a1 > a2) {
return a1 - a2;
} else {
return Math.PI * 2 - (a2 - a1)
}
}
I am trying to approximate a sphere using the instructions at http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/sphere_cylinder/, but it doesn't look right at all. This is my code:
def draw_sphere(facets, radius=100):
"""approximate a sphere using a certain number of facets"""
dtheta = 180.0 / facets
dphi = 360.0 / facets
global sphere_list
sphere_list = glGenLists(2)
glNewList(sphere_list, GL_COMPILE)
glBegin(GL_QUADS)
for theta in range(-90, 90, int(dtheta)):
for phi in range(0, 360, int(dphi)):
print theta, phi
a1 = theta, phi
a2 = theta + dtheta, phi
a3 = theta + dtheta, phi + dphi
a4 = theta, phi + dphi
angles = [a1, a2, a3, a4]
print 'angles: %s' % (angles)
glColor4f(theta/360.,phi/360.,1,0.5)
for angle in angles:
x, y, z = angle_to_coords(angle[0], angle[1], radius)
print 'coords: %s,%s,%s' % (x, y, z)
glVertex3f(x, y, z)
glEnd()
glEndList()
def angle_to_coords(theta, phi, radius):
"""return coordinates of point on sphere given angles and radius"""
x = cos(theta) * cos(phi)
y = cos(theta) * sin(phi)
z = sin(theta)
return x * radius, y * radius, z * radius
It seems that some of the quads aren't simple, i.e. the edges are crossing, but changing the order of the vertices doesn't seem to make any difference.
I don't have a system here that can run Python and OpenGL together, but I can see a few issues anyway:
You're rounding dphi and dtheta in the range statements. This means that the facets will always start on a whole degree, but then when you add the unrounded delta values the far edge isn't guaranteed to do so. This is the probable cause of your overlaps.
It would be better to have your range values go from 0 .. facets-1 and then multiply those indices by 360 / facets (or 180 for the latitude lines) exactly. This would avoid the rounding errors, e.g.:
dtheta = 180.0 / facets
dphi = 360.0 / facets
for y in range(facets):
theta = y * dtheta - 90
for x in range(facets):
phi = x * dphi
...
Also, are you converting to radians somewhere else? Python's default trig functions take radians rather than degrees.