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.
Related
I am trying to randomly generate points along the curved surface of a cylinder that has a y up-axis. Following a SO question of creating points along a 2D circle, I have
def point(h, k, r):
theta = random.random() * 2 * pi
global x
global y
x = h + cos(theta) * r
y = k + sin(theta) * r
given the cylinder's (h,k) origin point (0, -21.56462) and r (radius = 7.625). I then made these points 3D by generating a z point within my range (-2.35, 12.31). However, this got me half the way there because the final result was a cylinder but rotated 90 degrees clockwise.
Image of generated cylinder
What formula can I use that will generate the points in the correct direction? I am not that familiar with trigonometry, unfortunately. Thanks in advance!
THE SOLUTION:
def point(h, k, r):
theta = random.random() * 2 * pi
global x
global z
x = h + cos(theta) * r
z = k + sin(theta) * r
The new (h,k) origin is now (x,z) where x and z are the coordinates for the center of the cylinder and y is randomly generated within its appropriate height range. The vector is still (x,y,z).
Updated generated cylinder
THE SOLUTION:
(thanks to David Huculak)
def point(h, k, r):
theta = random.random() * 2 * pi
global x
global z
x = h + cos(theta) * r
z = k + sin(theta) * r
The new (h,k) origin is now (x,z) where x and z are the coordinates for the center of the cylinder and y is randomly generated within its appropriate height range. The vector is still (x,y,z).
Updated Generated cylinder
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!
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
I've trying to simulate a 2D Sérsic profile and then testing an extraction routine on it. However, when I do a test by extracting all the points lying along an ellipse supposedly aligned with an image, I get a periodic function. It is meant to be a straight line since all points along the ellipse should have equal intensity, although there will be a small amount of deviation due to rounding errors in the rough coordinate estimation (get_I()).
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import NearestNDInterpolator
def rotate(x, y, angle):
x1 = x*np.cos(angle) + y*np.sin(angle)
y1 = y*np.cos(angle) - x*np.sin(angle)
return x1, y1
def sersic_1d(R, mu0, h, n, zp=0):
exponent = (R / h) ** (1 / n)
I0 = np.exp((zp - mu0) / 2.5)
return I0 * np.exp(-1.* exponent)
def sersic_2d(x, y, e, i, mu0, h, n, zp=0):
xp, yp = rotate(x, y, i)
alpha = np.arctan2(yp, xp * (1-e))
a = xp / np.cos(alpha)
b = a * (1 - e)
# R2 = (a*a) + ((1 - (e*e)) * yp*yp)
return sersic_1d(a, mu0, h, n, zp)
def ellipse(x0, y0, a, e, i, theta):
b = a * (1 - e)
x = a * np.cos(theta)
y = b * np.sin(theta)
x, y = rotate(x, y, i)
return x + x0, y + y0
def get_I(x, y, Z):
return Z[np.round(x).astype(int), np.round(y).astype(int)]
if __name__ == '__main__':
n = np.linspace(-100,100,1000)
nx, ny = np.meshgrid(n, n)
Z = sersic_2d(nx, ny, 0.5, 0., 0, 50, 1, 25)
theta = np.linspace(0, 2*np.pi, 1000.)
a = 100.
e = 0.5
i = np.pi / 4.
x, y = ellipse(0, 0, a, e, i, theta)
I = get_I(x, y, Z)
plt.plot(I)
# plt.imshow(Z)
plt.show()
However, What I actually get is a massive periodic function. I've checked the alignment and it's correct and the float-> int rounding errors can't account for this kind of shift?
Any ideas?
There are two things that strike me as odd, one of which for sure is not what you wanted, the other I'm not sure about because astronomy is not my field of expertise.
The first is in your function get_I:
def get_I(x, y, Z):
return Z[np.round(x).astype(int), np.round(y).astype(int)]
When you call that function, x an y outline an ellipse, with its center at the origin (0,0). That means x and y both become negative at some point. The indexing you perfom in that function will then take values from the array's last elements, because Z[0,0] is in fact the top left corner of the image (which you plotted, but commented), while Z[-1, -1] is the bottom right corner. What you want is to take the values of Z that are on the ellipse contour, but both have to have the same center. To do that, you would first make sure you use an uneven amount of samples for n (which ultimately defines the shape of Z) and second, you would add an indexing offset:
def get_I(x, y, Z):
offset = Z.shape[0]//2
return Z[np.round(y).astype(int) + offset, np.round(x).astype(int) + offset]
...
n = np.linspace(-100,100,1001) # changed from 1000 to 1001 to ensure a point of origin is present and that the image exhibits point symmetry
Also notice that I changed the order of y and x in get_I: that's because you first index along the rows (for which we usually take the y-coordinate) and only then along the columns (which map to the x-coordinate in most conventions).
The second item that struck me as unusual is that your ellipse has its axes at an angle of pi/4 with respect to the horizontal axis, whereas your sersic (which maps to the 2D array of Z) does not have a tilt at all.
Changing all that, I end up with this code:
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
def rotate(x, y, angle):
x1 = x*np.cos(angle) + y*np.sin(angle)
y1 = y*np.cos(angle) - x*np.sin(angle)
return x1, y1
def sersic_1d(R, mu0, h, n, zp=0):
exponent = (R / h) ** (1 / n)
I0 = np.exp((zp - mu0) / 2.5)
return I0 * np.exp(-1.* exponent)
def sersic_2d(x, y, e, ang, mu0, h, n, zp=0):
xp, yp = rotate(x, y, ang)
alpha = np.arctan2(yp, xp * (1-e))
a = xp / np.cos(alpha)
b = a * (1 - e)
return sersic_1d(a, mu0, h, n, zp)
def ellipse(x0, y0, a, e, i, theta):
b = a * (1 - e) # half of a
x = a * np.cos(theta)
y = b * np.sin(theta)
x, y = rotate(x, y, i) # rotated by 45deg
return x + x0, y + y0
def get_I(x, y, Z):
offset = Z.shape[0]//2
return Z[np.round(y).astype(int) + offset, np.round(x).astype(int) + offset]
#return Z[np.round(y).astype(int), np.round(x).astype(int)]
if __name__ == '__main__':
n = np.linspace(-100,100,1001) # changed
nx, ny = np.meshgrid(n, n)
ang = 0;#np.pi / 4.
Z = sersic_2d(nx, ny, 0.5, ang=0, mu0=0, h=50, n=1, zp=25)
f, ax = plt.subplots(1,2)
dn = n[1]-n[0]
ax[0].imshow(Z, cmap='gray', aspect='equal', extent=[-100-dn/2, 100+dn/2, -100-dn/2, 100+dn/2])
theta = np.linspace(0, 2*np.pi, 1000.)
a = 20. # decreased long axis of ellipse to see the intensity-map closer to the "center of the galaxy"
e = 0.5
x, y = ellipse(0,0, a, e, ang, theta)
I = get_I(x, y, Z)
ax[0].plot(x,y) # easier to see where you want the intensities
ax[1].plot(I)
plt.show()
and this image:
The intensity variations look like quantisation noise to me, with the exception of the peaks, which are due to the asymptote in sersic_1d.
I've been trying to rotate a bunch of lines by 90 degrees (that together form a polyline). Each line contains two vertices, say (x1, y1) and (x2, y2). What I'm currently trying to do is rotate around the center point of the line, given center points |x1 - x2| and |y1 - y2|. For some reason (I'm not very mathematically savvy) I can't get the lines to rotate correctly.
Could someone verify that the math here is correct? I'm thinking that it could be correct, however, when I set the line's vertices to the new rotated vertices, the next line may not be grabbing the new (x2, y2) vertex from the previous line, causing the lines to rotate incorrectly.
Here's what I've written:
def rotate_lines(self, deg=-90):
# Convert from degrees to radians
theta = math.radians(deg)
for pl in self.polylines:
self.curr_pl = pl
for line in pl.lines:
# Get the vertices of the line
# (px, py) = first vertex
# (ox, oy) = second vertex
px, ox = line.get_xdata()
py, oy = line.get_ydata()
# Get the center of the line
cx = math.fabs(px-ox)
cy = math.fabs(py-oy)
# Rotate line around center point
p1x = cx - ((px-cx) * math.cos(theta)) - ((py-cy) * math.sin(theta))
p1y = cy - ((px-cx) * math.sin(theta)) + ((py-cy) * math.cos(theta))
p2x = cx - ((ox-cx) * math.cos(theta)) - ((oy-cy) * math.sin(theta))
p2y = cy - ((ox-cx) * math.sin(theta)) + ((oy-cy) * math.cos(theta))
self.curr_pl.set_line(line, [p1x, p2x], [p1y, p2y])
The coordinates of the center point (cx,cy) of a line segment between points (x1,y1) and (x2,y2) are:
cx = (x1 + x2) / 2
cy = (y1 + y2) / 2
In other words it's just the average, or arithmetic mean, of the two pairs of x and y coordinate values.
For a multi-segmented line, or polyline, its logical center point's x and y coordinates are just the corresponding average of x and y values of all the points. An average is just the sum of the values divided by the number of them.
The general formulas to rotate a 2D point (x,y) θ radians around the origin (0,0) are:
x′ = x * cos(θ) - y * sin(θ)
y′ = x * sin(θ) + y * cos(θ)
To perform a rotation about a different center (cx, cy), the x and y values of the point need to be adjusted by first subtracting the coordinate of the desired center of rotation from the point's coordinate, which has the effect of moving (known in geometry as translating) it is expressed mathematically like this:
tx = x - cx
ty = y - cy
then rotating this intermediate point by the angle desired, and finally adding the x and y values of the point of rotation back to the x and y of each coordinate. In geometric terms, it's the following sequence of operations: Tʀᴀɴsʟᴀᴛᴇ ─► Rᴏᴛᴀᴛᴇ ─► Uɴᴛʀᴀɴsʟᴀᴛᴇ.
This concept can be extended to allow rotating a whole polyline about any arbitrary point—such as its own logical center—by just applying the math described to each point of each line segment within it.
To simplify implementation of this computation, the numerical result of all three sets of calculations can be combined and expressed with a pair of mathematical formulas which perform them all simultaneously. So a new point (x′,y′) can be obtained by rotating an existing point (x,y), θ radians around the point (cx, cy) by using:
x′ = ( (x - cx) * cos(θ) + (y - cy) * sin(θ) ) + cx
y′ = ( -(x - cx) * sin(θ) + (y - cy) * cos(θ) ) + cy
Incorporating this mathematical/geometrical concept into your function produces the following:
from math import sin, cos, radians
def rotate_lines(self, deg=-90):
""" Rotate self.polylines the given angle about their centers. """
theta = radians(deg) # Convert angle from degrees to radians
cosang, sinang = cos(theta), sin(theta)
for pl in self.polylines:
# Find logical center (avg x and avg y) of entire polyline
n = len(pl.lines)*2 # Total number of points in polyline
cx = sum(sum(line.get_xdata()) for line in pl.lines) / n
cy = sum(sum(line.get_ydata()) for line in pl.lines) / n
for line in pl.lines:
# Retrieve vertices of the line
x1, x2 = line.get_xdata()
y1, y2 = line.get_ydata()
# Rotate each around whole polyline's center point
tx1, ty1 = x1-cx, y1-cy
p1x = ( tx1*cosang + ty1*sinang) + cx
p1y = (-tx1*sinang + ty1*cosang) + cy
tx2, ty2 = x2-cx, y2-cy
p2x = ( tx2*cosang + ty2*sinang) + cx
p2y = (-tx2*sinang + ty2*cosang) + cy
# Replace vertices with updated values
pl.set_line(line, [p1x, p2x], [p1y, p2y])
Your center point is going to be:
centerX = (x2 - x1) / 2 + x1
centerY = (y2 - y1) / 2 + y1
because you take half the length (x2 - x1) / 2 and add it to where your line starts to get to the middle.
As an exercise, take two lines:
line1 = (0, 0) -> (5, 5)
then: |x1 - x2| = 5, when the center x value is at 2.5.
line2 = (2, 2) -> (7, 7)
then: |x1 - x2| = 5, which can't be right because that's the center for
the line that's parallel to it but shifted downwards and to the left