I need to convert latitude and longitude values to a point in the 3-dimensional space. I've been trying this for about 2 hours now, but I do not get the correct results.
The Equirectangular coordinates come from openflights.org. I've tried several combinations of cos and sin, but the result did never look like our little beloved earth.
In the following, you can see the result of applying the conversion Wikipedia suggests. I think one can guess from context what c4d.Vector is.
def llarToWorld(latit, longit, altid, rad):
x = math.sin(longit) * math.cos(latit)
z = math.sin(longit) * math.sin(latit)
y = math.cos(longit)
v = c4d.Vector(x, y, z)
v = v * altid + v * rad
return v
Red: X, Green: Y, Blue: Z
One can indeed identify North- and South America, especially the land around the Gulf of Mexico. However, it looks somewhat squished and kind of in the wrong place..
As the result looks somewhat rotated, I think, I tried swapping latitude and longitude. But that result is somewhat awkward.
def llarToWorld(latit, longit, altid, rad):
temp = latit
latit = longit
longit = temp
x = math.sin(longit) * math.cos(latit)
z = math.sin(longit) * math.sin(latit)
y = math.cos(longit)
v = c4d.Vector(x, y, z)
v = v * altid + v * rad
return v
This is what the result looks like without converting the values.
def llarToWorld(latit, longit, altid, rad):
return c4d.Vector(math.degrees(latit), math.degrees(longit), altid)
Question: How can I convert the longitude and latitude correctly?
Solution
Thanks to TreyA, I found this page on mathworks.com. The code that does it's work is the following:
def llarToWorld(lat, lon, alt, rad):
# see: http://www.mathworks.de/help/toolbox/aeroblks/llatoecefposition.html
f = 0 # flattening
ls = atan((1 - f)**2 * tan(lat)) # lambda
x = rad * cos(ls) * cos(lon) + alt * cos(lat) * cos(lon)
y = rad * cos(ls) * sin(lon) + alt * cos(lat) * sin(lon)
z = rad * sin(ls) + alt * sin(lat)
return c4d.Vector(x, y, z)
Actually, I switched y and z because the earth was rotated then, however, it works! That's the result:
I've reformatted the code that was previously mentioned here, but more importantly you have left out some of the equations mentioned in the link provided by Niklas R
def LLHtoECEF(lat, lon, alt):
# see http://www.mathworks.de/help/toolbox/aeroblks/llatoecefposition.html
rad = np.float64(6378137.0) # Radius of the Earth (in meters)
f = np.float64(1.0/298.257223563) # Flattening factor WGS84 Model
cosLat = np.cos(lat)
sinLat = np.sin(lat)
FF = (1.0-f)**2
C = 1/np.sqrt(cosLat**2 + FF * sinLat**2)
S = C * FF
x = (rad * C + alt)*cosLat * np.cos(lon)
y = (rad * C + alt)*cosLat * np.sin(lon)
z = (rad * S + alt)*sinLat
return (x, y, z)
Comparison output: finding ECEF for Los Angeles, CA (34.0522, -118.40806, 0 elevation)
My code:
X = -2516715.36114 meters or -2516.715 km
Y = -4653003.08089 meters or -4653.003 km
Z = 3551245.35929 meters or 3551.245 km
Your Code:
X = -2514072.72181 meters or -2514.072 km
Y = -4648117.26458 meters or -4648.117 km
Z = 3571424.90261 meters or 3571.424 km
Although in your earth rotation environment your function will produce right geographic region for display, it will NOT give the right ECEF equivalent coordinates. As you can see some of the parameters vary by as much as 20 KM which is rather a large error.
Flattening factor, f depends on the model you assume for your conversion. Typical, model is WGS 84; however, there are other models.
Personally, I like to use this link to Naval Postgraduate School for sanity checks on my conversions.
you're not doing what wikipedia suggests. read it again carefully.
they say:
x = r cos(phi) sin(theta)
y = r sin(phi) sin(theta)
z = r cos(theta)
and then:
theta == latitude
phi == longitude
and, in your case, r = radius + altitude
so you should be using:
r = radius + altitude
x = r cos(long) sin(lat)
y = r sin(long) sin(lat)
z = r cos(lat)
note that the final entry is cos(lat) (you are using longitude).
As TreyA statet, LLA to ECEF is the solution. See http://www.mathworks.de/help/toolbox/aeroblks/llatoecefposition.html
Related
I have a vector field of shape (width, height, 2) where the third axis holds the magnitude and angle of each vector.
I want to perform a transformation (rotation and translation) around a given pivot point. I would expect that either numpy or scipy have a function for calculating the transformation matrix, but have so far found nothing.
This is what I've tride so far btw. Just iterating through each coordinate and calculating the new magnitude. The vectors are in polar coordinates.
pivot = (magnitude.shape[1]/2, magnitude.shape[0])
for c in range(magnitude.shape[1]):
for r in range(magnitude.shape[0]):
magnitude[r, c] -= dist + 2*np.linalg.norm((pivot[0]-r, pivot[1]-c))*np.sin(np.abs(angle/2))
From your description I am assuming that
field_x = field[:,:,0] * np.sin(field[:,:,1])
field_y = field[:,:,1] * np.cos(field[:,:,1])
If you want to rotate around the origin with no translation it is just add the rotation angle to field[:,:,1].
Rotation matrix is useful when you want to apply the same transformation to all the points that are in an affine form. (you have a polar representation).
It is not clear to me how exactly you want to do the transformation, so I will give you a function that rotates the point around a given pivot and then translate.
To translate around a given pivot you have to first translate the center of rotation to the origin, apply the rotation and, then you translate the center of rotation back to the original position.
def translate(x, y, tx, ty):
return (x + tx, y + ty)
def rotate(x, y, angle):
c = np.cos(angle)
s = np.sin(angle)
return x * c - y * s, x * s + y * c
def polar2cartesian(field):
c = np.sin(field[:,:,1])
s = np.cos(field[:,:,1])
r = field[:,:,0]
return r*c, r*s
def cartesian2polar(x, y):
result = np.empty(x.shape + (2,))
result[:,:,0] = np.sqrt(x**2 + y**2)
result[:,:,1] = np.arctan2(y, x);
return result;
def transform(field1, pivot, angle, translation):
x,y = polar2cartesian(field1)
px,py = polar2cartesian(pivot)
tx,ty = polar2cartesian(translation)
# move the center of rotation to the origin
x, y = translate(x, y, -px, -py);
x, y = rotate(x, y, angle)
x, y = translate(x, y, tx, ty)
return cartesian2polar(x, y)
To illustrate the use I will use some 1000 x 1 field with constant radius and constant rotation.
r = np.linspace(0, 2*np.pi, 1000)
field1 = np.zeros((1000, 1, 2))
field2 = np.zeros((1000, 1, 2))
field3 = np.zeros((1000, 1, 2))
plt.figure(figsize=(5, 5))
R = 10;
field1[:,0,0] = 1
field1[:,0,1] = 0
field2[:,0,0] = R+1
field2[:,0,1] = r
field3[:,0,0] = 0
field3[:,0,1] = R*r;
field4[:,0,0] = R;
field4[:,0,1] = r
plt.plot(*polar2cartesian(transform(field1, field3, field3[:,:,1], field4)))
field4[:,0,0] = R+2
field3[:,0,1] = -(R+2)*r + np.pi
plt.plot(*polar2cartesian(transform(field1, field3, field3[:,:,1], field4)))
plt.plot(*polar2cartesian(field2))
Interpretation The first plot is a circle of radius R+1 the second is the trajectory of a point in a circle of radius one rolling inside the circle of radius R+1, and the third plot is the trajectory of a point in a circle of radius one rolling outside the circle of radius R+1
Is there's a library or a way to calculate the center point for several geolocations points?
This is my list of geolocations based in New York and want to find the approximate midpoint geolocation
L = [
(-74.2813611,40.8752222),
(-73.4134167,40.7287778),
(-74.3145014,40.9475244),
(-74.2445833,40.6174444),
(-74.4148889,40.7993333),
(-73.7789256,40.6397511)
]
After the comments I received and comment from HERE
With coordinates that close to each other, you can treat the Earth as being locally flat and simply find the centroid as though they were planar coordinates. Then you would simply take the average of the latitudes and the average of the longitudes to find the latitude and longitude of the centroid.
lat = []
long = []
for l in L :
lat.append(l[0])
long.append(l[1])
sum(lat)/len(lat)
sum(long)/len(long)
-74.07461283333332, 40.76800886666667
Based on: https://gist.github.com/tlhunter/0ea604b77775b3e7d7d25ea0f70a23eb
Assume you have a pandas DataFrame with latitude and longitude columns, the next code will return a dictionary with the mean coordinates.
import math
x = 0.0
y = 0.0
z = 0.0
for i, coord in coords_df.iterrows():
latitude = math.radians(coord.latitude)
longitude = math.radians(coord.longitude)
x += math.cos(latitude) * math.cos(longitude)
y += math.cos(latitude) * math.sin(longitude)
z += math.sin(latitude)
total = len(coords_df)
x = x / total
y = y / total
z = z / total
central_longitude = math.atan2(y, x)
central_square_root = math.sqrt(x * x + y * y)
central_latitude = math.atan2(z, central_square_root)
mean_location = {
'latitude': math.degrees(central_latitude),
'longitude': math.degrees(central_longitude)
}
Considering that you are using signed degrees format (more), simple averaging of latitude and longitudes would create problems for even small regions near to antimeridian (i.e. + or - 180-degree longitude) due to discontinuity of longitude value at this line (sudden jump between -180 to 180).
Consider two locations whose longitudes are -179 and 179, their mean would be 0, which is wrong.
This link can be useful, first convert lat/lon into an n-vector, then find average. A first stab at converting the code into Python
is below
import numpy as np
import numpy.linalg as lin
E = np.array([[0, 0, 1],
[0, 1, 0],
[-1, 0, 0]])
def lat_long2n_E(latitude,longitude):
res = [np.sin(np.deg2rad(latitude)),
np.sin(np.deg2rad(longitude)) * np.cos(np.deg2rad(latitude)),
-np.cos(np.deg2rad(longitude)) * np.cos(np.deg2rad(latitude))]
return np.dot(E.T,np.array(res))
def n_E2lat_long(n_E):
n_E = np.dot(E, n_E)
longitude=np.arctan2(n_E[1],-n_E[2]);
equatorial_component = np.sqrt(n_E[1]**2 + n_E[2]**2 );
latitude=np.arctan2(n_E[0],equatorial_component);
return np.rad2deg(latitude), np.rad2deg(longitude)
def average(coords):
res = []
for lat,lon in coords:
res.append(lat_long2n_E(lat,lon))
res = np.array(res)
m = np.mean(res,axis=0)
m = m / lin.norm(m)
return n_E2lat_long(m)
n = lat_long2n_E(30,20)
print (n)
print (n_E2lat_long(np.array(n)))
# find middle of france and libya
coords = [[30,20],[47,3]]
m = average(coords)
print (m)
I would like to improve on the #BBSysDyn'S answer.
The average calculation can be biased if you are calculating the center of a polygon with extra vertices on one side. Therefore the average function can be replaced with centroid calculation explained here
def get_centroid(points):
x = points[:,0]
y = points[:,1]
# Solving for polygon signed area
A = 0
for i, value in enumerate(x):
if i + 1 == len(x):
A += (x[i]*y[0] - x[0]*y[i])
else:
A += (x[i]*y[i+1] - x[i+1]*y[i])
A = A/2
#solving x of centroid
Cx = 0
for i, value in enumerate(x):
if i + 1 == len(x):
Cx += (x[i]+x[0]) * ( (x[i]*y[0]) - (x[0]*y[i]) )
else:
Cx += (x[i]+x[i+1]) * ( (x[i]*y[i+1]) - (x[i+1]*y[i]) )
Cx = Cx/(6*A)
#solving y of centroid
Cy = 0
for i , value in enumerate(y):
if i+1 == len(x):
Cy += (y[i]+y[0]) * ( (x[i]*y[0]) - (x[0]*y[i]) )
else:
Cy += (y[i]+y[i+1]) * ( (x[i]*y[i+1]) - (x[i+1]*y[i]) )
Cy = Cy/(6*A)
return Cx, Cy
Note: If it is a polygon or more than 2 points, they must be listed in order that the polygon or shape would be drawn.
I have a series of points, of right ascension and declination values.
These points correspond to the vertices of a polygon on the surface of a sphere.
What would be the best way to calculate the area enclosed by these points? I would assume that converting the points with an equal-area projection, and then carrying out typical polygonal area calculating on a flat surface would be an appropriate solution.
note: I cannot use custom python libraries. eg pyproj or shapely
Example code (works for latitude longitude, what modifications would be required to enure this works with sky coordinates?)
def reproject(latitude, longitude):
"""Returns the x & y coordinates in metres using a sinusoidal projection"""
from math import pi, cos, radians
earth_radius = 6371009
lat_dist = pi * earth_radius / 180.0
y = [lat * lat_dist for lat in latitude]
x = [long * lat_dist * cos(radians(lat))
for lat, long in zip(latitude, longitude)]
return x, y
def area_of_polygon(x, y):
"""Calculates the area of an arbitrary polygon given its vertices"""
area = 0.0
for i in xrange(-1, len(x)-1):
area += x[i] * (y[i+1] - y[i-1])
return abs(area) / 2.0
dec = [-15.,89.,89.,-15.,-15.]
ra = [105.,105.,285.,285.,105.]
x,y = reproject(dec, ra)
print area_of_polygon(x,y)
One of the ways is to perform a line integral based on Green's Theorem. See below an implementation, and this question for more details.
def polygon_area(lats, lons, algorithm = 0, radius = 6378137):
"""
Computes area of spherical polygon, assuming spherical Earth.
Returns result in ratio of the sphere's area if the radius is specified.
Otherwise, in the units of provided radius.
lats and lons are in degrees.
"""
from numpy import arctan2, cos, sin, sqrt, pi, power, append, diff, deg2rad
lats = np.deg2rad(lats)
lons = np.deg2rad(lons)
# Line integral based on Green's Theorem, assumes spherical Earth
#close polygon
if lats[0]!=lats[-1]:
lats = append(lats, lats[0])
lons = append(lons, lons[0])
#colatitudes relative to (0,0)
a = sin(lats/2)**2 + cos(lats)* sin(lons/2)**2
colat = 2*arctan2( sqrt(a), sqrt(1-a) )
#azimuths relative to (0,0)
az = arctan2(cos(lats) * sin(lons), sin(lats)) % (2*pi)
# Calculate diffs
# daz = diff(az) % (2*pi)
daz = diff(az)
daz = (daz + pi) % (2 * pi) - pi
deltas=diff(colat)/2
colat=colat[0:-1]+deltas
# Perform integral
integrands = (1-cos(colat)) * daz
# Integrate
area = abs(sum(integrands))/(4*pi)
area = min(area,1-area)
if radius is not None: #return in units of radius
return area * 4*pi*radius**2
else: #return in ratio of sphere total area
return area
Please find a somewhat more explicit version (and with many more references and TODOs...) here.
Looks like I can treat ra and dec like lat and long, work out the area on the Earth's surface in m^2, and use this value to convert into an area in sq degrees.
Please let me know if the solution I propose below is flawed:
def reproject(latitude, longitude):
"""Returns the x & y coordinates in metres using a sinusoidal projection"""
from math import pi, cos, radians
earth_radius = 6371009
lat_dist = pi * earth_radius / 180.0
y = [lat * lat_dist for lat in latitude]
x = [long * lat_dist * cos(radians(lat))
for lat, long in zip(latitude, longitude)]
return x, y
def area_of_polygon(x, y):
"""Calculates the area of an arbitrary polygon given its vertices"""
area = 0.0
for i in xrange(-1, len(x)-1):
area += x[i] * (y[i+1] - y[i-1])
return ((abs(area) / 2.0)/5.10100E14) * 41253
dec = [-15.,89.,89.,-15.,-15.]
ra = [105.,105.,285.,285.,105.]
x,y = reproject(dec, ra)
print area_of_polygon(x,y)
In python how can you say generate coordinates that would appear on a sphere surface. I know the equation is x^2 + y^2 + z^2 = r^2. But how can you write a function so that, given an integer say a which is greater than 1 and r, it will give a list lst of (x,y,z) coordinates such that
len(lst) = a
the distance between all adjacent points must be the same
(basically the points should be equidistant)
each point satisfies x^2 + y^2 + z^2 = r^2
the sphere is centered on (0,0).
Thanks
Check http://www.faqs.org/faqs/graphics/algorithms-faq/, have a look at topic 6.06.
a = 12
r = 0.2
theta = np.radians(np.linspace(0, 360, a+1))
phi = np.radians(np.linspace(0, 360, a+1))
x = r * np.einsum("i,j->ij", np.cos(phi), np.sin(theta))
y = r * np.einsum("i,j->ij", np.sin(phi), np.sin(theta))
z = r * np.einsum("i,j->ij", np.ones(len(theta)), np.cos(theta))
xyz = np.array([x.flatten(), y.flatten(), z.flatten()])
The only issue here is that the number of points is a^2. But I suppose you can tune it to as you need it.
I am trying to calculate the point of intersection (lat and lon in degrees) of two great circles that are each defined by two points on the circle. I have been trying to follow method outlined here.
But the answer I get is incorrect, my code is below does anyone see where I went wrong?
import numpy as np
from numpy import cross
from math import cos, sin, atan2, asin, asinh
################################################
#### Intersection of two great circles.
# Points on great circle 1.
glat1 = 54.8639587
glon1 = -8.177818
glat2 = 52.65297082
glon2 = -10.78064876
# Points on great circle 2.
cglat1 = 51.5641564
cglon1 = -9.2754284
cglat2 = 53.35422063
cglon2 = -12.5767799
# 1. Put in polar coords.
x1 = cos(glat1) * sin(glon1)
y1 = cos(glat1) * cos(glon1)
z1 = sin(glat1)
x2 = cos(glat2) * sin(glon2)
y2 = cos(glat2) * cos(glon2)
z2 = sin(glat2)
cx1 = cos(cglat1) * sin(cglon1)
cy1 = cos(cglat1) * cos(cglon1)
cz1 = sin(cglat1)
cx2 = cos(cglat2) * sin(cglon2)
cy2 = cos(cglat2) * cos(cglon2)
cz2 = sin(cglat2)
# 2. Get normal to planes containing great circles.
# It's the cross product of vector to each point from the origin.
N1 = cross([x1, y1, z1], [x2, y2, z2])
N2 = cross([cx1, cy1, cz1], [cx2, cy2, cz2])
# 3. Find line of intersection between two planes.
# It is normal to the poles of each plane.
L = cross(N1, N2)
# 4. Find intersection points.
X1 = L / abs(L)
X2 = -X1
ilat = asin(X1[2]) * 180./np.pi
ilon = atan2(X1[1], X1[0]) * 180./np.pi
I should also mention this is on the Earth's surface (assuming a sphere).
Solution from DSM in comments above, your angles are in degrees while sin and cos expect radians.
Also the line
X1 = L / abs(L)
should be,
X1 = L / np.sqrt(L[0]**2 + L[1]**2 + L[2]**2)
One more correction that needs to be done is to change cos/sin before "lon" in x and y dimensions:
x = cos(lat) * cos(lon)
y = cos(lat) * sin(lon)
z = sin(lat)
This is because original conversion from angle to spherical system is done using polar/azimuthal sphere angles and they are not the same as lat/lon angles (wiki it https://en.wikipedia.org/wiki/Spherical_coordinate_system).