Source code for adam.util.geodesy
import numpy as np
# WGS84 defining parameters.
_WGS84_A = 6378137.0 # semi-major axis, metres
_WGS84_F = 1.0 / 298.257223563 # flattening
_WGS84_B = _WGS84_A * (1.0 - _WGS84_F)
[docs]
def aeqd_to_lonlat(x, y, lon_0, lat_0):
"""
Convert azimuthal equidistant coordinates to longitude and latitude on the
WGS84 ellipsoid.
This is a self-contained replacement for projecting radar gate positions
with PROJ (``+proj=aeqd +ellps=WGS84``). ADAM feeds the resulting image to a
neural network, so the geolocation has to be reproducible: a change in the
projection library shifts every gate, which moves the inferred lake breeze
front. Computing it here means the model input depends only on this
repository. The result agrees with PROJ to well under a micron.
Parameters
----------
x: array-like
Distance east of the projection centre in metres.
y: array-like
Distance north of the projection centre in metres.
lon_0: float
The longitude of the projection centre in degrees.
lat_0: float
The latitude of the projection centre in degrees.
Returns
-------
lon: :func:`numpy.ndarray`
The longitude of each point in degrees.
lat: :func:`numpy.ndarray`
The latitude of each point in degrees.
"""
x = np.asarray(x, dtype=float)
y = np.asarray(y, dtype=float)
# In an azimuthal equidistant projection the distance from the centre is
# true, so each point is just a geodesic of that length on that bearing.
azimuth = np.arctan2(x, y)
distance = np.hypot(x, y)
return _geodesic_direct(lat_0, lon_0, azimuth, distance)
def _geodesic_direct(lat_0, lon_0, azimuth, distance):
"""
Solve the direct geodesic problem on WGS84 using Vincenty's formulae.
Given a starting point, an initial bearing and a distance, find the end
point. Vincenty converges to well below millimetre accuracy for the
distances involved in a radar scan.
Parameters
----------
lat_0: float
The latitude of the starting point in degrees.
lon_0: float
The longitude of the starting point in degrees.
azimuth: array-like
The initial bearing in radians, measured clockwise from north.
distance: array-like
The distance to travel in metres.
Returns
-------
lon: :func:`numpy.ndarray`
The longitude of the end point in degrees.
lat: :func:`numpy.ndarray`
The latitude of the end point in degrees.
"""
a, f, b = _WGS84_A, _WGS84_F, _WGS84_B
phi_0 = np.radians(lat_0)
sin_az, cos_az = np.sin(azimuth), np.cos(azimuth)
# Reduced latitude of the starting point.
u_0 = np.arctan((1.0 - f) * np.tan(phi_0))
sin_u0, cos_u0 = np.sin(u_0), np.cos(u_0)
# Angular distance on the sphere from the equator to the starting point.
sigma_0 = np.arctan2(np.tan(u_0), cos_az)
# Azimuth of the geodesic at the equator.
sin_alpha = cos_u0 * sin_az
cos_sq_alpha = 1.0 - sin_alpha**2
u_sq = cos_sq_alpha * (a**2 - b**2) / b**2
coeff_a = 1.0 + u_sq / 16384.0 * (
4096.0 + u_sq * (-768.0 + u_sq * (320.0 - 175.0 * u_sq)))
coeff_b = u_sq / 1024.0 * (
256.0 + u_sq * (-128.0 + u_sq * (74.0 - 47.0 * u_sq)))
# Iterate on the angular distance until it stops moving.
sigma = distance / (b * coeff_a)
for _ in range(100):
cos_2sigma_m = np.cos(2.0 * sigma_0 + sigma)
sin_sigma, cos_sigma = np.sin(sigma), np.cos(sigma)
delta_sigma = coeff_b * sin_sigma * (
cos_2sigma_m + coeff_b / 4.0 * (
cos_sigma * (-1.0 + 2.0 * cos_2sigma_m**2)
- coeff_b / 6.0 * cos_2sigma_m * (-3.0 + 4.0 * sin_sigma**2)
* (-3.0 + 4.0 * cos_2sigma_m**2)))
sigma_next = distance / (b * coeff_a) + delta_sigma
if np.all(np.abs(sigma_next - sigma) < 1e-13):
sigma = sigma_next
break
sigma = sigma_next
cos_2sigma_m = np.cos(2.0 * sigma_0 + sigma)
sin_sigma, cos_sigma = np.sin(sigma), np.cos(sigma)
lat = np.arctan2(
sin_u0 * cos_sigma + cos_u0 * sin_sigma * cos_az,
(1.0 - f) * np.hypot(sin_alpha, sin_u0 * sin_sigma - cos_u0 * cos_sigma * cos_az))
lam = np.arctan2(
sin_sigma * sin_az,
cos_u0 * cos_sigma - sin_u0 * sin_sigma * cos_az)
coeff_c = f / 16.0 * cos_sq_alpha * (4.0 + f * (4.0 - 3.0 * cos_sq_alpha))
delta_lon = lam - (1.0 - coeff_c) * f * sin_alpha * (
sigma + coeff_c * sin_sigma * (
cos_2sigma_m + coeff_c * cos_sigma * (-1.0 + 2.0 * cos_2sigma_m**2)))
return lon_0 + np.degrees(delta_lon), np.degrees(lat)