Can't reproduce distance value between sources obtained with astropy - python

I have two sources with equatorial coordinates (ra, dec) and (ra_0, dec_0) located at distances r and r_0, and I need to calculate the 3D distance between them.
I use two approaches that should give the same result as far as I understand, but do not.
The first approach is to apply astropy's separation_3d function. The second approach is to use the expression that gives the distance between two sources with spherical coordinates:
as shown here.
In the MCVE below, the values returned are:
91.3427173002 pc
93.8470493776 pc
Shouldn't these two values be equal?
MCVE:
from astropy.coordinates import SkyCoord
from astropy import units as u
import numpy as np
# Define some coordinates and distances for the sources.
c1 = SkyCoord(ra=9.7*u.degree, dec=-50.6*u.degree, distance=1500.3*u.pc)
c2 = SkyCoord(ra=7.5*u.degree, dec=-47.6*u.degree, distance=1470.2*u.pc)
# Obtain astropy's distance between c1 & c2 coords.
print c1.separation_3d(c2)
# Obtain distance between c1 & c2 coords using explicit expression.
ra_0, dec_0, r_0 = c1.ra.radian, c1.dec.radian, c1.distance
ra, dec, r = c2.ra.radian, c2.dec.radian, c2.distance
alpha_delta_par = np.sin(dec) * np.sin(dec_0) * np.cos(ra - ra_0) +\
np.cos(dec) * np.cos(dec_0)
d_pc = np.sqrt(r**2 + r_0**2 - 2*r*r_0*alpha_delta_par)
print d_pc

This is a problem with coordinate systems, and the difference between declination (astral coordinates) and polar angle θ (spherical coordinates) :-)
Astral coordinates define declination as north of the celestial equator, while spherical coordinates define polar angle θ as downward from from vertical.
If you change your alpha_delta_par to account for this 90° difference by adding np.pi/2 to all your declination terms, you get
alpha_delta_par = np.sin(np.pi/2 + dec)*np.sin(np.pi/2 + dec0)*np.cos(ra - ra0) +\
np.cos(np.pi/2 + dec)*np.cos(np.pi/2 + dec0)
Which gives the correct result: 91.3427173002 pc.
Turns out physicists usually use the symbol θ as the polar angle and mathemeticians usually use φ; I went with θ because I followed my heart. I'm not making this up I swear.

Related

Is there an algorithm to calculate the area of a Lissajous figure?

Suppose I have measurements of two signals
V = V(t) and U = U(t)
that are periodic in time with a phase difference between them. When plotted against each other in a graph V vs U they form a Lissajous figure, and I want to calculate the area inside it.
Is there an algorithm for such calculation?
I would like to solve this problem using Python. But a response in any language or an algorithm to do it will be very appreciated.
Examples of V and U signals can be generated using expressions like:
V(t) = V0*sin(2*pi*t) ; U(t) = U0*sin(2*pi*t + delta)
Figure 1 shows a graph of V,U vs t for V0=10, U0=5, t=np.arange(0.0,2.0,0.01) and delta = pi/5.
And Figure 2 shows the corresponding Lissajous figure V vs U.
This is an specific problem of a more general question: How to calculate a closed path integral obtained with a discrete (x_i,y_i) data set?
To find area of (closed) parametric curve in Cartesian coordinates, you can use Green's theorem (4-th formula here)
A = 1/2 * Abs(Integral[t=0..t=period] {(V(t) * U'(t) - V'(t) * U(t))dt})
But remember that interpretation - what is real area under self-intersected curves - is ambiguous, as #algrid noticed in comments
for the outer most curves area of usual Lissajous shapes I would try this:
find period of signal
so find T such:
U(t) = U(t+T)
V(t) = V(t+T)
sample data on t=<0,T>
I would use polar coordinate system with center equal to average U,V coordinate on interval t=<0,T> and call it U0,V0. Convert and store the data in polar coordinates so:
a(t)=atan2( V(t)-V0 , U(t)-U0 )
r(t)=sqrt( (U(t)-U0)^2 + (V(t)-V0)^2 )
and remember only the points with max radius for each angle position. That can be done either with arrays (limiting precision in angle) or geometricaly by computing polyline intersection with overlapping segments. and removing inside parts.
Compute the area from sampled data
So compute the the area by summing the pie triangles for each angular position covering whole circle.
This may not work for exotic shapes.
Both solutions above - by #MBo and by #Spektre (and #meowgoesthedog in the comments) - works fine. Thank you guys.
But I found another way to calculate the area A of an elliptical Lissajous curve: use the A = Pi*a*b formula (a and b are, respectively, the major and minor semi axis of the ellipse).
Steps:
1 - Find the period T of the V (or U) signal;
2 - In the time interval 0<t<T:
2.a - calculate the average values of V and U (V0 and U0), in order to determine the center of the ellipse;
2.b - calculate the distance r(t) from the point (V0,U0) using:
r(t)=sqrt( (U(t)-U0)^2 + (V(t)-V0)^2 )
3 - Find a and b values using:
a = max(r(t)); b = min(r(t))
4 - calculate A: A = Pi*a*b
The Lissajous curves will always be elliptical if the U,V signals are sinusoidal-like and have the same frequency.
Seizing the opportunity, I will propose a solution for the case where the V,U signals are triangular and have the same frequency. In this case, the Lissajous curve will be a parallelogram, then one can calculate its area A using A = 2*|D|*|d|*sin(q), where |D| and |d| are, respectively, the length of major and minor semi diagonals of the parallelogram and q is the angle between the vectors D and d.
Repeat steps 1 and 2 for the elliptical case.
In step 3 we will have:
|D| = max(r(t)) = r(t1); |d| = min(r(t)) = r(t2)
4' - Obtain t1 and t2 and use them to get the coordinates (V(t1)=V1,U(t1)=U1) and (V(t2)=V2,U(t2)=U2). Then the vectors D and d can be written as:
D=(V1,U1)-(V0,U0); d=(V2,U2)-(V0,U0)
5' - Calculate the angle q between D and d;
6' - Perform the calculation of A: A = 2*|D|*|d|*sin(q)

How to calculate 3D distance (including altitude) between two points in GeoDjango

Prologue:
This is a question arising often in SO:
3d distance calculations with GeoDjango
Calculating distance between two points using latitude longitude and altitude (elevation)
Distance between two 3D point in geodjango (postgis)
I wanted to compose an example on SO Documentation but the geodjango chapter never took off and since the Documentation got shut down on August 8, 2017, I will follow the suggestion of this widely upvoted and discussed meta answer and write my example as a self-answered post.
Of course, I would be more than happy to see any different approach as well!!
Question:
Assume the model:
class MyModel(models.Model):
name = models.CharField()
coordinates = models.PointField()
Where I store the point in the coordinate variable as a lan, lng, alt point:
MyModel.objects.create(
name='point_name',
coordinates='SRID=3857;POINT Z (100.00 10.00 150)')
I am trying to calculate the 3D distance between two such points:
p1 = MyModel.objects.get(name='point_1').coordinates
p2 = MyModel.objects.get(name='point_2').coordinates
d = Distance(m=p1.distance(p2))
Now d=X in meters.
If I change only the altitude of one of the points in question:
For example:
p1.coordinates = 'SRID=3857;POINT Z (100.00 10.00 200)'
from 150 previously, the calculation:
d = Distance(m=p1.distance(p2))
returns d=X again, like the elevation is ignored.
How can I calculate the 3D distance between my points?
Reading from the documentation on the GEOSGeometry.distance method:
Returns the distance between the closest points on this geometry and the given geom (another GEOSGeometry object).
Note
GEOS distance calculations are linear – in other words, GEOS does not perform a spherical calculation even if the SRID specifies a geographic coordinate system.
Therefore we need to implement a method to calculate a more accurate 2D distance between 2 points and then we can try to apply the altitude (Z) difference between those points.
1. Great-Circle 2D distance calculation (Take a look at the 2022 UPDATE below the explanation for a better approach using geopy):
The most common way to calculate the distance between 2 points on the surface of a sphere (as the Earth is simplistically but usually modeled) is the Haversine formula:
The haversine formula determines the great-circle distance between two points on a sphere given their longitudes and latitudes.
Although from the great-circle distance wiki page we read:
Although this formula is accurate for most distances on a sphere, it too suffers from rounding errors for the special (and somewhat unusual) case of antipodal points (on opposite ends of the sphere). A formula that is accurate for all distances is the following special case of the Vincenty formula for an ellipsoid with equal major and minor axes.
We can create our own implementation of the Haversine or the Vincenty formula (as shown here for Haversine: Haversine Formula in Python (Bearing and Distance between two GPS points)) or we can use one of the already implemented methods contained in geopy:
geopy.distance.great_circle (Haversine):
from geopy.distance import great_circle
newport_ri = (41.49008, -71.312796)
cleveland_oh = (41.499498, -81.695391)
# This call will result in 536.997990696 miles
great_circle(newport_ri, cleveland_oh).miles)
geopy.distance.vincenty (Vincenty):
from geopy.distance import vincenty
newport_ri = (41.49008, -71.312796)
cleveland_oh = (41.499498, -81.695391)
# This call will result in 536.997990696 miles
vincenty(newport_ri, cleveland_oh).miles
!!!2022 UPDATE: On 2D distance calculation using geopy:
GeoPy discourages the use of Vincenty as of version 1.14.0. Changelog states:
CHANGED: Vincenty usage now issues a warning. Geodesic should be used instead. Vincenty is planned to be removed in geopy 2.0. (#293)
So (especially if we are going to apply the calculation on a WGS84 ellipsoid) we should use geodesic distance instead:
from geopy.distance import geodesic
newport_ri = (41.49008, -71.312796)
cleveland_oh = (41.499498, -81.695391)
# This call will result in 538.390445368 miles
geodesic(newport_ri, cleveland_oh).miles
2. Adding altitude to the mix:
As mentioned, each of the above calculations yields a great circle distance between 2 points. That distance is also called "as the crow flies", assuming that the "crow" flies without changing altitude and as straight as possible from point A to point B.
We can have a better estimation of the "walking/driving" ("as the crow walks"??) distance by combining the result of one of the previous methods with the difference (delta) in altitude between point A and point B, inside the Euclidean Formula for distance calculation:
acw_dist = sqrt(great_circle(p1, p2).m**2 + (p1.z - p2.z)**2)
The previous solution is prone to errors especially the longer the real distance between the points is. I leave it here for comment continuation reasons.
GeoDjango Distance calculates the 2D distance between two points and doesn't take into consideration the altitude differences.
In order to get the 3D calculation, we need to create a distance function that will consider altitude differences in the calculation:
Theory:
The latitude, longitude and altitude are Polar coordinates and we need to translate them to Cartesian coordinates (x, y, z) in order to apply the Euclidean Formula on them and calculate their 3D distance.
Assume:
polar_point_1 = (long_1, lat_1, alt_1)
and polar_point_2 = (long_2, lat_2, alt_2)
Translate each point to it's Cartesian equivalent by utilizing this formula:
x = alt * cos(lat) * sin(long)
y = alt * sin(lat)
z = alt * cos(lat) * cos(long)
and you will have p_1 = (x_1, y_1, z_1) and p_2 = (x_2, y_2, z_2) points respectively.
Finally use the Euclidean formula:
dist = sqrt((x_2-x_1)**2 + (y_2-y_1)**2 + (z_2-z_1)**2)
Using geopy, this is the easiest and perfect solution.
https://geopy.readthedocs.io/en/stable/#geopy.distance.lonlat
>>> from geopy.distance import distance
>>> from geopy.point import Point
>>> a = Point(-71.312796, 41.49008, 0)
>>> b = Point(-81.695391, 41.499498, 0)
>>> print(distance(a, b).miles)
538.3904453677203
Once converted into Cartesian coordinates, you can compute the norm with numpy:
np.linalg.norm(point_1 - point_2)

Points on a geodesic line

I am working on a unit sphere. I am interested to place N points on a strait line over the surface of the sphere (geodesic) between two arbitrary points. The coordinate of these points are in spherical coordinate (radians).
How do I compute a set of N equally spaced points along such line. I would like to take the curvature of the sphere into account in my calculation.
I am using python 2.7.9
You may consider SLERP - spherical linear interpolation
P = P0*Sin(Omega*(1-t))/Sin(Omega) + P1*Sin(Omega * t)/Sin(Omega)
where Omega is central angle between start and end points (arc of great circle), t is parameter in range [0..1], for i-th point t(i) = i/N
Let us reason geometrically.
Convert the two given points to Cartesian coordinates.
The angle between the position vectors from the center to P0 and P1 is given by the dot product
cos A = P0.P1
Construct a linear combination of these:
P = (1-t).P0 + t.P1
The angle between P and P0 is given by the dot product with P normalized
cos a = cos kA/N = P.P0/|P| = ((1-t) + t.cos A)/ sqrt((1-t)² + 2.(1-t).t.cos A + t²)
Squaring and rewriting, you obtain a second degree equation in t:
cos²a.(1-t)² + 2.(1-t).t.cos²a.cos A + t².cos²a - (1-t)² - 2.(1-t).t.cos A - t².cos²A = 0
- sin²a.(1-t)² - 2.(1-t).t.sin²a.cos A - t².(cos²A - cos² a) = 0
t²(-sin²a + 2.sin²a.cos A - cos²A + cos²a) + 2.t.sin²a.(1 - cos A) - sin²a = 0
Solve the equation, compute the vector P from its definition and normalize it.
Then revert to spherical coordinates. Varying k between 1 and N-1 will give you the required intermediate points.
Alternatively, you can use the Rodrigue's rotation formula around an axis in 3D. The axis is given by the cross-product P0 x P1.

convert latitude and longitude to x and y grid system using python

I have file with latitude and longitude values, that i want to convert the x and y in km
I want to measure the distance from each point.
for instance I make the first points of latitude and longitude(which are 51.58, -124.6 respectfully)
to (0,0) in my x and y system so than basically i want to find out what the other points are and their location from the origin so i want to find what 51.56(lat) -123.64(long) is in (x,y) in km and so on for the rest of the file.
I want to do this all in python, is there some sort code ?
I found sites online , for instance
http://www.whoi.edu/marine/ndsf/cgi-bin/NDSFutility.cgi?form=0&from=LatLon&to=XY
does exactly want i want to do, I just don't know how they do it.
The following gets you pretty close (answer in km). If you need to be better than this, you have to work harder at the math - for example by following some of the links given.
import math
dx = (lon1-lon2)*40000*math.cos((lat1+lat2)*math.pi/360)/360
dy = (lat1-lat2)*40000/360
Variable names should be pretty obvious. This gives you
dx = 66.299 km (your link gives 66.577)
dy = 2.222 km (link gives 2.225)
Once you pick coordinates (for example, lon1, lat1) as your origin, it should be easy to see how to compute all the other XY coordinates.
Note - the factor 40,000 is the circumference of the earth in km (measured across the poles). This gets you close. If you look at the source of the link you provided (you have to dig around a bit to find the javascript which is in a separate file) you find that they use a more complex equation:
function METERS_DEGLON(x)
{
with (Math)
{
var d2r=DEG_TO_RADIANS(x);
return((111415.13 * cos(d2r))- (94.55 * cos(3.0*d2r)) + (0.12 * cos(5.0*d2r)));
}
}
function METERS_DEGLAT(x)
{
with (Math)
{
var d2r=DEG_TO_RADIANS(x);
return(111132.09 - (566.05 * cos(2.0*d2r))+ (1.20 * cos(4.0*d2r)) - (0.002 * cos(6.0*d2r)));
}
}
It looks to me like they are actually taking account of the fact that the earth is not exactly a sphere... but even so when you are making the assumption you can treat a bit of the earth as a plane you are going to have some errors. I'm sure with their formulas the errors are smaller...
UTM projections are in meters. So you could use something like the utm lib at this link:
https://pypi.python.org/pypi/utm
Googling python lat lon to UTM will point to several options.
UTM zones are 6 degrees of longitude wide and start from 0 at the prime meridian. The origin of each UTM zone is on the equator (x-axis) with the y-axis at the western most degree of longitude. This makes the grid positive to the north and east. You could calculate your distance from these results. Values are most accurate in the middle of the UTM zone.
You also should know what datum your original lat lon values are based on and use the same datum in your conversion.
if you were to use a 3D system, these functions will do:
def arc_to_deg(arc):
"""convert spherical arc length [m] to great circle distance [deg]"""
return float(arc)/6371/1000 * 180/math.pi
def deg_to_arc(deg):
"""convert great circle distance [deg] to spherical arc length [m]"""
return float(deg)*6371*1000 * math.pi/180
def latlon_to_xyz(lat,lon):
"""Convert angluar to cartesian coordiantes
latitude is the 90deg - zenith angle in range [-90;90]
lonitude is the azimuthal angle in range [-180;180]
"""
r = 6371 # https://en.wikipedia.org/wiki/Earth_radius
theta = math.pi/2 - math.radians(lat)
phi = math.radians(lon)
x = r * math.sin(theta) * math.cos(phi) # bronstein (3.381a)
y = r * math.sin(theta) * math.sin(phi)
z = r * math.cos(theta)
return [x,y,z]
def xyz_to_latlon (x,y,z):
"""Convert cartesian to angular lat/lon coordiantes"""
r = math.sqrt(x**2 + y**2 + z**2)
theta = math.asin(z/r) # https://stackoverflow.com/a/1185413/4933053
phi = math.atan2(y,x)
lat = math.degrees(theta)
lon = math.degrees(phi)
return [lat,lon]
You can get the distance between GPS points using the Great Circle Distance formula. Latitude and longitude are in an geodectic coordinate system, so you can't just convert to a flat 2D grid and use euclidean distances. You can convert sufficiently close points to an approximate grid by taking an arbitrary point like your (X,Y), setting it to the origin (like you've done) and then using great circle distance together with bearing to plot the points relative to each other on the plane, but it's an approximation.
You can use UTM:
pip install utm
Here is an example:
>>> import utm
>>> utm.from_latlon(51.2, 7.5)
(395201.3103811303, 5673135.241182375, 32, 'U')
The return has the form (EASTING, NORTHING, ZONE_NUMBER, ZONE_LETTER).
Notes
It works with NumPy arrays too:
>>> utm.from_latlon(np.array([51.2, 49.0]), np.array([7.5, 8.4]))
(array([395201.31038113, 456114.59586214]),
array([5673135.24118237, 5427629.20426126]),
32,
'U')
And in reverse:
>>> utm.to_latlon(340000, 5710000, 32, 'U')
(51.51852098408468, 6.693872395145327)

Trigonometry to find landscape visibility

I'm trying to find the degrees of visibility of natural environments from a number of viewpoints. I have a DEM, and a raster defining the natural environments. For each cell I have slope, height, aspect and distance from viewpoint. I want to calculate the angle between the viewpoint and each cell.
I originally did this by getting the x,y,z coordinates for the viewpoint and the both the bottom and top of a sloped cell. Then get the 3D distance between each coordinate and use these distances to get the angles. I would then sum for each cell to get total degrees of visibilty.
Problem is that this approach only works when the bearing between the viewpoint and the visible cell is in line with the x and y axis of the coordinate system. This because to calculate the points coordinates for the cell corners I'm subtracting and adding the cell resolution/2 to the cell centroid.
Code below
def Cell_Loc (VPX, VPY, VPZ, cell_x, cell_y, cell_z, aspect, cell_resolution, bearing):
import math
from math import sqrt
#Get change in height of cell using slope and cell resolution (trig)
AspectTan = math.tan(math.radians(aspect))
Opposite = (AspectTan * cell_res)
#Get coordinates for cell corners
CloseCornerX = cent_x - 2.5
CloseCornerY = cent_y - 2.5
FarCornerX = cent_x + 2.5
FarCornerY = cent_y + 2.5
CloseCornerZ = cent_z - (Opposite/2)
FarCornerZ = cent_z + (Opposite/2)
#Get Distance between coordinates
VP = (VPX, VPY, VPZ) #data point 1
cellcrner1 = (CloseCornerX, CloseCornerY, CloseCornerZ) #data point 2
cellcrner2 = (FarCornerX, FarCornerY, FarCornerZ)
dist_to_far_corner = sqrt(sum( (VP - cellcrner2)**2 for VP, cellcrner2 in zip(VP, cellcrner2)))
dist_to_close_corner = sqrt(sum( (VP - cellcrner1)**2 for VP, cellcrner1 in zip(VP, cellcrner1)))
cell_dist = sqrt(sum( (cellcrner1 - cellcrner2)**2 for cellcrner1, cellcrner2 in zip(cellcrner1, cellcrner2)))
#Input above distances into ViewAngle to find angle
def ViewAngle (a, b, c):
"calculates the viewing angle of each visible cell"
import math
a2 = a*a
b2 = b*b
c2 = c*c
VA = (a2-b2+c2)/ (2.0 * (a*c))
rads = math.acos(VA)
return math.degrees(rads)
How can I improve this code, or maybe another approach is a better way of doing it. The output I need is to say "for this viewpoint, there are X amount of degrees for visible natural environments". Again I have, DEM, visible areas from viewshed analysis, slope, aspect and distances between viewpoint and cell centroid. I use python programming language and have ArcGIS10.2 with arcpy
One improvement would be to use coordinates and inner products instead of distances to calculate angles: this would help avoid some (though not all) expensive and messy square roots. So if your view point is at v=(x,y,z), and two other points are P=(a,b,c) and Q=(d,e,f), the subtended angle is acos(sqrt(((V-P)(V-Q))^2/(V-P)^2(V-Q)^2))=acos(sqrt(<(V-P),(V-Q)>^2/<(V-P),(V-P)><(V-Q),(V-Q)>)) where the (internal) products are inner products, i.e., say PQ = ad + be + cf. Also, some of the products and inner products can be 'recycled' instead of recalculating.

Categories