Skip to content

Commit 8f21a46

Browse files
committed
loxodrome_direct: stability near 90,-90,270,-270 azimuth. Fixes #42
1 parent 94c2ebb commit 8f21a46

File tree

5 files changed

+37
-23
lines changed

5 files changed

+37
-23
lines changed

Examples/lox_stability.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ def matlab_func(lat1: float, lon1: float, rng: float, az: float) -> tuple[float,
2828
clat, clon, rng = 40.0, -80.0, 10.0 # arbitrary
2929

3030
for i in range(20):
31-
azi = 90.0 + 10.0 ** (-i)
32-
33-
lat, lon = loxodrome_direct(clat, clon, rng, azi)
34-
35-
assert lat != lat_last
36-
assert lon != lon_last
37-
38-
lat_matlab, lon_matlab = matlab_func(clat, clon, rng, azi)
39-
rstr = f"azimuth: {azi} lat,lon: Python: {lat}, {lon} Matlab: {lat_matlab}, {lon_matlab}"
40-
if not (isclose(lat_matlab, lat, rel_tol=0.005) and isclose(lon_matlab, lon, rel_tol=0.001)):
41-
logging.error(rstr)
31+
for azi in (90 + 10.0 ** (-i), -90 + 10.0 ** (-i), 270 + 10.0 ** (-i), -270 + 10.0 ** (-i)):
32+
lat, lon = loxodrome_direct(clat, clon, rng, azi)
33+
34+
assert lat != lat_last
35+
assert lon != lon_last
36+
37+
lat_matlab, lon_matlab = matlab_func(clat, clon, rng, azi)
38+
rstr = f"azimuth: {azi} lat,lon: Python: {lat}, {lon} Matlab: {lat_matlab}, {lon_matlab}"
39+
if not (
40+
isclose(lat_matlab, lat, rel_tol=0.005) and isclose(lon_matlab, lon, rel_tol=0.001)
41+
):
42+
logging.error(rstr)

src/pymap3d/enu.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def enu2aer(
4747
slant range [meters]
4848
"""
4949

50-
# 1 millimeter precision for singularity
50+
# 1 millimeter precision for singularity stability
5151

5252
try:
5353
e[abs(e) < 1e-3] = 0.0

src/pymap3d/lox.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from math import radians, degrees, cos, atan2, tan, pi # type: ignore
1010

1111
from .ellipsoid import Ellipsoid
12+
from .utils import sign
1213
from . import rcurve
1314
from . import rsphere
1415
from .latitude import (
@@ -146,15 +147,17 @@ def loxodrome_inverse(
146147

147148
# compute azimuth
148149
az12 = atan2(dlon, disolat)
149-
cosaz12 = cos(az12)
150+
aux = abs(cos(az12))
150151

151152
# compute distance along loxodromic curve
152-
dist = meridian_arc(lat2, lat1, deg=False, ell=ell) / abs(cos(az12))
153+
dist = meridian_arc(lat2, lat1, deg=False, ell=ell) / aux
154+
155+
# straight east or west
153156
try:
154-
if (abs(cosaz12) < 1e-9).any():
155-
dist[abs(cosaz12) < 1e-9] = departure(lon2, lon1, lat1, ell, deg=False)
157+
if (aux < 1e-9).any():
158+
dist[aux < 1e-9] = departure(lon2, lon1, lat1, ell, deg=False)
156159
except (AttributeError, TypeError):
157-
if abs(cosaz12) < 1e-9: # straight east or west
160+
if aux < 1e-9:
158161
dist = departure(lon2, lon1, lat1, ell, deg=False)
159162

160163
if deg:
@@ -167,14 +170,15 @@ def loxodrome_direct(
167170
lat1: float | ndarray,
168171
lon1: float | ndarray,
169172
rng: float | ndarray,
170-
a12: float | float,
173+
a12: float,
171174
ell: Ellipsoid = None,
172175
deg: bool = True,
173-
) -> tuple[ndarray, ndarray]:
176+
) -> tuple[float | ndarray, float | ndarray]:
174177
"""
175178
Given starting lat, lon with arclength and azimuth, compute final lat, lon
176179
177180
like Matlab reckon('rh', ...)
181+
except that "rng" in meters instead of "arclen" degrees of arc
178182
179183
Parameters
180184
----------
@@ -226,14 +230,23 @@ def loxodrome_direct(
226230
newiso = geodetic2isometric(lat2, ell, deg=False)
227231
iso = geodetic2isometric(lat1, ell, deg=False)
228232

233+
# stability near singularities
234+
i = abs(cos(a12)) < 1e-6
229235
dlon = tan(a12) * (newiso - iso)
236+
237+
try:
238+
dlon[i] = sign(dlon[i]) * rng[i] / rcurve.transverse(lat1[i], ell=ell, deg=False) # type: ignore
239+
except (AttributeError, TypeError):
240+
if i: # straight east or west
241+
dlon = sign(dlon) * rng / rcurve.transverse(lat1, ell=ell, deg=False)
242+
230243
lon2 = lon1 + dlon
231244

232245
if deg:
233246
lat2, lon2 = degrees(lat2), degrees(lon2)
234247

235248
try:
236-
return lat2.squeeze()[()], lon2.squeeze()[()]
249+
return lat2.squeeze()[()], lon2.squeeze()[()] # type: ignore
237250
except AttributeError:
238251
return lat2, lon2
239252

src/pymap3d/rcurve.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def parallel(lat: ndarray, ell: Ellipsoid = None, deg: bool = True) -> float:
5353
Returns
5454
-------
5555
radius: float
56-
radius of ellipsoid
56+
radius of ellipsoid (meters)
5757
"""
5858

5959
if deg:
@@ -111,7 +111,7 @@ def transverse(lat: float | ndarray, ell: Ellipsoid = None, deg: bool = True) ->
111111
Returns
112112
-------
113113
radius: float
114-
radius of ellipsoid
114+
radius of ellipsoid (meters)
115115
"""
116116

117117
lat, ell = sanitize(lat, ell, deg)

src/pymap3d/tests/test_rcurve.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,4 @@ def test_numpy_meridian():
4141

4242
def test_numpy_transverse():
4343
pytest.importorskip("numpy")
44-
assert rcurve.transverse([0, 90]) == approx([A, 6399593.626])
44+
assert rcurve.transverse([-90, 0, 90]) == approx([6399593.6258, A, 6399593.6258])

0 commit comments

Comments
 (0)