Geopy: calculating GPS heading / bearing - python

First time poster here.
I am doing some data analyses on collected GPS data for a bridge inspection ROV octorotor. We have the octorotor running on ROS using a 3D scanning LIDAR, stereo vision, INS, and some other neat tech. I'm currently using a ublox LEA-6T in a similar setup as Doug Weibel's setup to collect raw GPS data like carrier phase, doppler shift, and satellite ephemeris. Then I use an opensource project RTKLIB to do some DGPS post processing with local NOAA CORS stations to obtain cm accuracy for better pose estimation when reconstructing the 3D point cloud of the bridge.
Anyhow, I'm using most of scipy to statistically verify my test results.
Specifically for this portion though, I'm just using:
python-3.3
numpy
geopy
I've been studding my positional covariance with respect to offset from my measured ground truth using geopy's handy distance function. With little massaging the arguments, I can find the distance respect to each direction depicted by each standard deviation element in the matrix; North, East, Up and the three directions between.
However, these distances are absolute and do not describe direction.
Say: positive, negative would correlate to northward or southward respectively.
I could simply use the latitude and longitude to detect polarity of direction,
But I'd like to be able to find the precise point to point bearing of the distance described instead,
As I believe a value of global heading could be useful for further applications other than my current one.
I've found someone else pose a similar question
But its seem to be assuming a great circle approximation
Where I would prefer using at least the WGS-84 ellipsoidal model, or any of the same models that can be used in geopy:
Jump to Calculating distances
Any suggestion appreciated,
-ruffsl
Sources if interested:
python-3.3: http:// www.python.org/download/releases/3.3.0/
numpy: http:// www.numpy.org/
geopy: https:// code.google.com/p/geopy/
scipy: http:// www.scipy.org/
ublox LEA-6T: http:// www.u-blox.com/en/gps-modules/u-blox-6-timing-module/lea-6t.html
Doug Weibel's: http:// diydrones.com/profiles/blogs/proof-of-concept-test-extremely-accurate-3d-velocity-measurement
RTKLIB: http:// www.rtklib.com/
NOAA CORS: http:// geodesy.noaa.gov/CORS/
ROS: http:// www.ros.org/wiki/

Use the geographiclib package for python. This computes distances and bearings on the ellipsoid and much more. (You can interpolate paths, measure areas, etc.) For example, after
pip install geographiclib
you can do
>>> from geographiclib.geodesic import Geodesic
>>> Geodesic.WGS84.Inverse(-41.32, 174.81, 40.96, -5.50)
{'lat1': -41.32, 'a12': 179.6197069334283, 's12': 19959679.26735382, 'lat2': 40.96, 'azi2': 18.825195123248392, 'azi1': 161.06766998615882, 'lon1': 174.81, 'lon2': -5.5}
This computes the geodesic from Wellington, New Zealand (41.32S 174.81E) to Salamanca, Spain (40.96N 5.50W). The distance is given by s12 (19959679 meters) and the initial azimuth (bearing) is given by azi1 (161.067... degrees clockwise from north).

Bearing between two lat/long coordinates: (lat1, lon1), (lat2, lon2)
In the code below lat1,lon1,lat2,lon2 are asumend to be in radians.
Convert before from degrees to radians.
dLon = lon2 - lon1;
y = Math.sin(dLon) * Math.cos(lat2);
x = Math.cos(lat1)*Math.sin(lat2) -
Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
brng = Math.atan2(y, x).toDeg();
Bearing is now in range -180/180.
to normalize to compass bearing (0-360)
if brng < 0: brng+= 360

#AlexWien's answer in Python
import math, numpy as np
def get_bearing(lat1,lon1,lat2,lon2):
dLon = lon2 - lon1;
y = math.sin(dLon) * math.cos(lat2);
x = math.cos(lat1)*math.sin(lat2) - math.sin(lat1)*math.cos(lat2)*math.cos(dLon);
brng = np.rad2deg(math.atan2(y, x));
if brng < 0: brng+= 360
return brng

Related

Calculate lat/lon of 4 corners of rectangle using Python

I need to find the latitude and longitude coordinates of the four corners of a rectangle in a Python script, given the center coordinate, length, width, and bearing of the shape. Length and width are in statute miles, but honestly converting those to meters is probably one of the easiest parts. I have some examples of how to use haversine to calculate distance between 2 points, but I'm at a loss on this one. Would anyone be able to at least point me in the right direction?
Picture of rectangle
Update
This is the formula I came up with for my Python script, based on the link that #Mbo provided:
lat2 = asin(sin(lat1)*cos(length2/(_AVG_EARTH_RADIUS_KM)) + cos(lat1) * sin(length2/(_AVG_EARTH_RADIUS_KM)) * cos(bearing1))
lon2 = lon1 + atan2(sin(bearing1) * sin(length2/(_AVG_EARTH_RADIUS_KM)) * cos(lat1), cos(length2/(_AVG_EARTH_RADIUS_KM)) - sin(lat1) * sin(lat2))
Unfortunately the results don't make sense. I used a center point of 32° N 77° W, length of 20 miles, width 10 miles, and bearing 0 deg and I'm getting the result 0.586599511812, -77.0.
When I plot it out on my mapping application, it tells me that the coordinate for the new point should be 32.14513° N, -77.0° W.
Edit to add: I converted length1 and width1 to kilometers, and converted bearing1 to radians before using in the formulas above.
Rectangle on the earth sphere.. this is doubtful thing.
Anyway, look at this page.
Using formula from section Destination point given distance and bearing from start point, calculate two middles at distance width/2 and bearings bearing, bearing + 180.
For every middle point do the same with height/2 and bearing + 90, bearing - 90 to calculate corner points.
(note that width as corner-corner distance will be inexact for such approximation)
With pyproj you have the tooling to make the calculation.
fwd() supports calculating the end point, giving start, bearing, and distance.
You still need basic geometry to calculate the necessary distance/angles.
I ended up finding 2 answers.
The first one, from python - Get lat/long given current point, distance and bearing was simple. The Destination point given distance and bearing from start point formula works after all, I just forgot to convert the lat/long for the start point to radians, and then convert the answer back to degrees at the end.
The resulting code looks like:
import math
R = 6378.1 #Radius of the Earth
brng = 1.57 #Bearing is 90 degrees converted to radians.
d = 15 #Distance in km
#lat2 52.20444 - the lat result I'm hoping for
#lon2 0.36056 - the long result I'm hoping for.
lat1 = math.radians(52.20472) #Current lat point converted to radians
lon1 = math.radians(0.14056) #Current long point converted to radians
lat2 = math.asin( math.sin(lat1)*math.cos(d/R) +
math.cos(lat1)*math.sin(d/R)*math.cos(brng))
lon2 = lon1 + math.atan2(math.sin(brng)*math.sin(d/R)*math.cos(lat1),
math.cos(d/R)-math.sin(lat1)*math.sin(lat2))
lat2 = math.degrees(lat2)
lon2 = math.degrees(lon2)
print(lat2)
print(lon2)
The second answer I found, from python - calculating a gps coordinate given a point, bearing and distance, uses geopy and is much simpler, so I ended up going with this as my preferred solution:
from geopy import Point
from geopy.distance import distance, VincentyDistance
# given: lat1, lon1, bearing, distMiles
lat2, lon2 = VincentyDistance(miles=distMiles).destination(Point(lat1, lon1), bearing)

Get New GPS with Known start GPS, bearing and distance in feet

I am brand new to Python in general and have a limited but aged knowledge of Javascript. I am trying to accomplish a small project using Python, but I have gotten stuck with my own comprehension.
I have a GPS coordinate in decimal degrees as a starting point (30.456025341663068, -86.41408883615411), a distance in feet (86 feet) from the start point to the expected endpoint and I have a bearing in degrees (0 to 360) from start point to endpoint. Given these values, I am attempting to simply return the GPS coordinate in decimal degrees of the endpoint.
I found one StackExchange post that appears to give me a viable option using GeoPy, but I'm simply getting lost in trying to implement it. Can someone assist with how to accomplish this? GeoPy is not a requirement for me, I just imported it based on the answer in the other StackExchange question:
calculating a gps coordinate given a point, bearing and distance
Perhaps you could do something like this:
import geopy
import geopy.distance
lat = 30.456025341663068
lon = -86.41408883615411
distance_ft = 86
bearing = 0
start_point = geopy.Point(lat, lon)
end_point = geopy.distance.geodesic(feet=distance_ft).destination(start_point, bearing)
print(end_point.latitude, end_point.longitude)
This should output something like:
30.456261790886277 -86.41408883615411
You can then also use the geodesic method to calculate the distance between the points:
print(geopy.distance.geodesic(start_point, end_point).feet)
And get something like:
86.0000000020017
Alternative answer which is not using geopy
from pyproj import Geod
g = Geod(ellps="WGS84")
lat = 30.456025341663068
long = -86.41408883615411
feet_in_meters = .3048
distance_in_feet = 86
distance_in_meters = distance_in_feet * feet_in_meters
long_destination, lat_destination, azi_back = g.fwd(lons=long, lats=lat, az=0, dist=distance_in_meters)
which print(lat_destination, long_destination)
30.456261790886273 -86.41408883615411

Solar Zenith Angle for many coordinates using PVLIB

I need calculate the solar zenith angle for approximately 106.000.000 of different coordinates. This coordinates are referrals to the pixels from an image projected at Earth Surface after the image had been taken by camera into the airplane.
I am using the pvlib.solarposition.get_position() to calculate the solar zenith angle. The values returned are being calculated correctly (I compared some results with NOOA website) but, how I need calculate the solar zenith angle for many couple of coordinates, the python is spending many days (about 5 days) to finish the execution of the function.
How I am a beginner in programming, I wonder is there is any way to accelerate the solar zenith angle calculation.
Below found the part of the code implemented which calculate the solar zenith angle:
sol_apar_zen = []
for i in range(size3):
solar_position = np.array(pvl.solarposition.get_solarposition(Data_time_index, lat_long[i][0], lat_long[i][1]))
sol_apar_zen.append(solar_position[0][0])
print(len(sol_apar_zen))
Technically, if you need to compute Solar Zenith Angle quickly for a large list (array), there are more efficient algorithms than the PVLIB's one. For example, the one described by Roberto Grena in 2012 (https://doi.org/10.1016/j.solener.2012.01.024).
I found a suitable implementation here: https://github.com/david-salac/Fast-SZA-and-SAA-computation (you mind need some tweaks, but it's simple to use it, plus it's also implemented for other languages than Python like C/C++ & Go).
Example of how to use it:
from sza_saa_grena import solar_zenith_and_azimuth_angle
# ...
# A random time series:
time_array = pd.date_range("2020/1/1", periods=87_600, freq="10T", tz="UTC")
sza, saa = solar_zenith_and_azimuth_angle(longitude=-0.12435, # London longitude
latitude=51.48728, # London latitude
time_utc=time_array)
That unit-test (in the project's folder) shows that in the normal latitude range, an error is minimal.
Since your coordinates represent a grid, another option would be to calculate the zenith angle for a subset of your coordinates, and the do a 2-d interpolation to obtain the remainder. 1 in 100 in both directions would reduce your calculation time by a factor of 10000.
If you want to fasten up this calculation you can use the numba core (if installed)
location.get_solarposition(
datetimes,
method='nrel_numba'
)
Otherwise you have to implement your own calculation based on vectorized numpy arrays. I know it is possible but I am not allowed to share. You can find the formulation if you search for spencer 1971 solar position

Convert Latitude longitude to meters / feet in ( haversine in python ) [duplicate]

I want to compute the distance between two lon / lat points by using Geod class from pyproj library.
from pyproj import Geod
g = Geod(ellps='WGS84')
lonlat1 = 10.65583081724002, -7.313341167341917
lonlat2 = 10.655830383300781, -7.313340663909912
_, _, dist = g.inv(lonlat1[0], lonlat1[1], lonlat2[0], lonlat2[1])
I get the following error :
ValueError Traceback (most recent call last)
<ipython-input-5-8ba490aa5fcc> in <module>()
----> 1 _, _, dist = g.inv(lonlat1[0], lonlat1[1], lonlat2[0], lonlat2[1])
/usr/lib/python2.7/dist-packages/pyproj/__init__.pyc in inv(self, lons1, lats1, lons2, lats2, radians)
558 ind, disfloat, dislist, distuple = _copytobuffer(lats2)
559 # call geod_inv function. inputs modified in place.
--> 560 _Geod._inv(self, inx, iny, inz, ind, radians=radians)
561 # if inputs were lists, tuples or floats, convert back.
562 outx = _convertback(xisfloat,xislist,xistuple,inx)
_geod.pyx in _geod.Geod._inv (_geod.c:1883)()
ValueError: undefined inverse geodesic (may be an antipodal point)
Where does this error message come from ?
Those two points are only a few centimetres apart. It looks like pyproj / Geod doesn't cope well with points which are that close together. That's a bit strange, since simple plane geometry is more than adequate at such distances. Also, that error message is a bit suspicious, since it's suggesting that the two points are antipodal, i.e., diametrically opposite, which is clearly not the case! OTOH, maybe the antipodal point it mentions is some intermediate point that arises somehow in the calculation... Still, I'd be rather hesitant in using a library that behaves like this.
Given this defect, I suspect that pyproj has other flaws. In particular, it probably uses the old Vincenty's formulae for its ellipsoid geodesic calculations, which is known to be unstable when dealing with near-antipodal points, and not particularly accurate over large distances. I recommend using the modern algorithms of C. F. F. Karney.
Dr Karney is a major contributor to the Wikipedia articles on geodesics, in particular Geodesics on an ellipsoid, and his geographiclib is available on PyPi, so you can easily install it using pip. See his SourceForge site for further information, and geographiclib binding in other languages.
FWIW, here's a short demo of using geographiclib to compute the distance in your question.
from geographiclib.geodesic import Geodesic
Geo = Geodesic.WGS84
lat1, lon1 = -7.313341167341917, 10.65583081724002
lat2, lon2 = -7.313340663909912, 10.655830383300781
d = Geo.Inverse(lat1, lon1, lat2, lon2)
print(d['s12'])
output
0.07345528623159624
That figure is in metres, so those two points are a little over 73mm apart.
If you'd like to see geographiclib being used to solve a complex geodesic problem, please see this math.stackexchange answer I wrote last year, with Python 2 / 3 source code on gist.
Hopefully, this is no longer an issue, since pyproj now uses code from geographiclib.

Getting J2000 XYZ coordinates for a location on earth in Python

EDIT: Just found out that I need to convert latitude, longitude and elevation of a location on earth to J2000 coordinates and nothing to do with ra/dec or the moon. Sorry for this. Your answers did give me a lot of insights. Please see the edited question below.
Question: how do i convert latitude, longitude and elevation to J2000 coordinates (XYZ). Is there a conversion present in ephem? I checked the docs but I couldnt find something I need (or mightve overlooked something due to my lack of knowledge in this field). Thanks
***************** OLD (Disregard) ******************
I have the moon position in Right Ascension (RA) and Declination (Dec) and I want to convert them into X Y Z coordinates. Is there a built-in PyEphem function for this? Also, what is the math behind it? Thanks.
EDIT: I am using the J2000 coordinate system (which is equatorial i think, this is my first time working with astronomy). I have the distance to moon available. The ra/dec values are already in J2000 (equatorial) coordinates.
X points North
Y points West
Z points towards the sky
Best answer:
It has just come to my attention that, in June 2011, the Naval Observatory released a Python interface to the powerful NOVAS reference software with which the highest-precision astronomical computations are performed:
http://aa.usno.navy.mil/software/novas/novas_py/novaspy_intro.php
With this library you can get the answer you are seeking, at far higher precision than PyEphem has ever offered:
from novas import compat as novas
jd_tt = novas.julian_date(2012, 9, 8, 12.00)
delta_t = 66.603 # from http://maia.usno.navy.mil/ser7/deltat.preds
lat = 42.3583 # positive is north
lon = -71.0603 # negative is west
observer = novas.make_observer_on_surface(lat, lon, 0, 0, 0)
print novas.geo_posvel(jd_tt, delta_t, observer)
On my machine this gives the answer:
((-3.5081406460494928e-06, 3.135277713623258e-05, 2.858239912567112e-05),
(-0.00019753847060164693, -2.2329942271278055e-05, 2.4885824275734915e-07))
Try it yourself and see if this gives you the kind of results that you need!
Newer answer:
It appears that the answer is “no” — PyEphem, to my surprise, gives no easy way to get the answer to the question "where, in x, y, z coordinates, is (say) Boston at time t ?”
This is a surprise because “libastro”, the library behind PyEphem, of course has to compute this internally in order to figure out where other objects are relative to an observer. It seems to do so in two places. In parallax.c it defines ta_par() which talks only about angles on the outside, but on its inside you can see that it temporarily computes the x, y, z of the observer. You can even see the important constant 298.257 hidden inside there, which measures how flat the earth is, since it is not a perfect sphere.
The other place is in earthsat.c which looks like a completely different code base from the rest of “libastro”, and so it duplicates some of the logic. Its EarthFlat constant of 298.25 is a bit less precise, but is doing the same job. And its function, GetSitPosition(), actually exposes x,y,z coordinates instead of keeping them hidden. But it is declared static so there is no way to call in to this useful function from outside!
So for the moment, PyEphem gives you no way to compute your x,y,z directly. But it does provide one important piece of information: the current sidereal time, which you will (I think) be able to use to figure how far around the earth Boston (or wherever) has traveled by time t, which will be important in figuring out your coordinates.
I will see if I can work up a quick solution in Python that combines the hour angle from PyEphem with some explicit trigonometry to get you an answer. But, for the moment, no: PyEphem does not expose this information directly, sadly enough; I will put it on the list of things for a future version!
Older answer, from when the question was about the x,y,z position of the Moon:
PyEphem does not, alas, have built-in functions for converting from the polar coordinates used in amateur astronomy to the x/y/z coordinates which will let you map out how objects are distributed in space around the Earth. But the conversion is easy to do yourself:
import ephem
import math
m = ephem.Mars('2012/8/1')
print m.ra, m.dec
x = math.cos(m.dec) * math.cos(m.ra)
y = math.cos(m.dec) * math.sin(m.ra)
z = math.sin(m.dec)
print x, y, z
print 'sanity check: vector length =', math.sqrt(x*x + y*y + z*z)
The output of this script is:
12:58:51.20 -6:24:05.6
-0.961178016954 -0.252399543786 -0.111495695074
sanity check: vector length = 1.0
The position of Mars for the random date that I used here are quite reasonable values: an RA that is almost one hour more than halfway around the great circle (since 12h would be exactly halfway), and a declination that pushes the position a bit south. Thus the x, y, and z that we get out: the z is a slightly negative number since -6° is indeed south of the equator, and x and y are both negative since going 13h around a 24h circle puts you down in the negative/negative quadrant of a normal unit circle.
Note that although J2000 has a north and south — so that we can truthfully say that the slightly negative z is a southward direction — it does not have an east and west, since the earth turning below it is constantly swinging east and west in all directions. Instead, RA measures from “the first point of Ares” which is the direction in which the sun lies during the spring Equinox. So x and y are not east or west; they are coordinates pointing out into the solar system on a fixed axis defined by the direction that the Earth sits in every Spring.
This x y z vector I have created is a “unit vector” — a little vector that has the magnitude 1.0, as I verified in the script to make sure I had the formulae correct. If you were computing x y and z coordinates for objects whose distance from the earth you knew, then you could get a real vector — whose magnitude were distances, instead of fractions of 1 — by multiplying each of the three x y and z by the distance to the object.
Does that help you out? From your description — and your question about east and west — I could not tell if you wanted RA and dec turned into x y z or whether you are actually wanting the azimuth and altitude converted (but the math is the same either way). That would look something like:
x = math.cos(m.alt) * math.cos(m.az)
y = math.cos(m.alt) * math.sin(m.az)
z = math.sin(m.alt)
What are you trying to accomplish with these coordinates? That could help us make sure that we are giving them to you in a useful format.
I suggest you look at the PyEphem documentation for coordinate conversion.
Basically, PyEphem only deals with three coordinate systems - equatorial, ecliptic, and galactic - each defined by two angles and an epoch (adjustment offset for polar progression).
Depending on what your coordinate scheme looks like, you should be able to use trigonometry to convert to it if you also have the object's distance.
Edit: your "X-Y-Z" coordinates seem to be lefthanded ecliptic coordinates.
from ephem import Equatorial, Ecliptic, degree
def convert_equatorial_to_XYZ(ra, dec, dist=1.0, epoch='2000'):
"""
Given
ra right ascension (in hours)
dec declination (in degrees)
dist distance (optional, defaults to 1.0)
epoch epoch (optional, assumes J2000)
Return
degrees North, degrees West, distance
"""
eq = Equatorial(ra, dec, epoch=epoch)
ec = Ecliptic(eq)
return ec.lat/degree, 360.0 - ec.lon/degree, dist

Categories

Resources