Skip to content

Commit 2917d40

Browse files
ecef2geodetic: test/fix float32
Co-authored-by: unjambonakap <[email protected]>
1 parent 88dc0a4 commit 2917d40

File tree

4 files changed

+79
-27
lines changed

4 files changed

+79
-27
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ where tuple `lla` is comprised of scalar or N-D arrays `(lat,lon,alt)`.
7676

7777
Example scripts are in the [examples](./Examples) directory.
7878

79+
Native Python float is typically [64 bit](https://docs.python.org/3/library/stdtypes.html#typesnumeric).
80+
Numpy can select real precision bits: 32, 64, 128, etc.
81+
7982
### Functions
8083

8184
Popular mapping toolbox functions ported to Python include the

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = pymap3d
3-
version = 2.7.2
3+
version = 2.7.3
44
author = Michael Hirsch, Ph.D.
55
author_email = [email protected]
66
description = pure Python (no prereqs) coordinate conversions, following convention of several popular Matlab routines.

src/pymap3d/ecef.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,19 @@
33
import typing
44

55
try:
6-
from numpy import radians, sin, cos, tan, arctan as atan, hypot, degrees, arctan2 as atan2, sqrt
6+
from numpy import (
7+
radians,
8+
sin,
9+
cos,
10+
tan,
11+
arctan as atan,
12+
hypot,
13+
degrees,
14+
arctan2 as atan2,
15+
sqrt,
16+
finfo,
17+
where,
18+
)
719
from .eci import eci2ecef, ecef2eci
820
except ImportError:
921
from math import radians, sin, cos, tan, atan, hypot, degrees, atan2, sqrt # type: ignore
@@ -146,18 +158,36 @@ def ecef2geodetic(
146158
Beta = -pi / 2
147159

148160
# eqn. 13
149-
eps = ((ell.semiminor_axis * u - ell.semimajor_axis * huE + E ** 2) * sin(Beta)) / (
161+
dBeta = ((ell.semiminor_axis * u - ell.semimajor_axis * huE + E ** 2) * sin(Beta)) / (
150162
ell.semimajor_axis * huE * 1 / cos(Beta) - E ** 2 * cos(Beta)
151163
)
152164

153-
Beta += eps
165+
Beta += dBeta
166+
167+
# eqn. 4c
154168
# %% final output
155169
lat = atan(ell.semimajor_axis / ell.semiminor_axis * tan(Beta))
156170

171+
try:
172+
# patch latitude for float32 precision loss
173+
lim_pi2 = pi / 2 - finfo(dBeta.dtype).eps
174+
lat = where(Beta >= lim_pi2, pi / 2, lat)
175+
lat = where(Beta <= -lim_pi2, -pi / 2, lat)
176+
except NameError:
177+
pass
178+
157179
lon = atan2(y, x)
158180

159181
# eqn. 7
160-
alt = hypot(z - ell.semiminor_axis * sin(Beta), Q - ell.semimajor_axis * cos(Beta))
182+
cosBeta = cos(Beta)
183+
try:
184+
# patch altitude for float32 precision loss
185+
cosBeta = where(Beta >= lim_pi2, 0, cosBeta)
186+
cosBeta = where(Beta <= -lim_pi2, 0, cosBeta)
187+
except NameError:
188+
pass
189+
190+
alt = hypot(z - ell.semiminor_axis * sin(Beta), Q - ell.semimajor_axis * cosBeta)
161191

162192
# inside ellipsoid?
163193
inside = (

src/pymap3d/tests/test_geodetic.py

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,28 @@
1313
A = ELL.semimajor_axis
1414
B = ELL.semiminor_axis
1515

16+
xyzlla = [
17+
((A - 1, 0, 0), (0, 0, -1)),
18+
((0, A - 1, 0), (0, 90, -1)),
19+
((0, 0, B - 1), (90, 0, -1)),
20+
((0, 0, B - 1), (89.999999, 0, -1)),
21+
((0, 0, B - 1), (89.99999, 0, -1)),
22+
((0, 0, -B + 1), (-90, 0, -1)),
23+
((0, 0, -B + 1), (-89.999999, 0, -1)),
24+
((0, 0, -B + 1), (-89.99999, 0, -1)),
25+
((-A + 1, 0, 0), (0, 180, -1)),
26+
]
27+
28+
llaxyz = [
29+
((0, 0, -1), (A - 1, 0, 0)),
30+
((0, 90, -1), (0, A - 1, 0)),
31+
((0, -90, -1), (0, -A + 1, 0)),
32+
((90, 0, -1), (0, 0, B - 1)),
33+
((90, 15, -1), (0, 0, B - 1)),
34+
((-90, 0, -1), (0, 0, -B + 1)),
35+
]
36+
37+
1638
atol_dist = 1e-6 # 1 micrometer
1739

1840

@@ -94,7 +116,7 @@ def test_xarray():
94116
xyz = pm.geodetic2ecef(*xr_lla)
95117

96118
assert xyz == approx(xyz0)
97-
# %%
119+
98120
xr_xyz = xarray.DataArray(list(xyz0))
99121

100122
lla = pm.ecef2geodetic(*xr_xyz)
@@ -136,31 +158,12 @@ def test_ecef():
136158
assert pm.ecef2geodetic((A - 1) / sqrt(2), (A - 1) / sqrt(2), 0) == approx([0, 45, -1])
137159

138160

139-
@pytest.mark.parametrize(
140-
"lla, xyz",
141-
[
142-
((0, 0, -1), (A - 1, 0, 0)),
143-
((0, 90, -1), (0, A - 1, 0)),
144-
((0, -90, -1), (0, -A + 1, 0)),
145-
((90, 0, -1), (0, 0, B - 1)),
146-
((90, 15, -1), (0, 0, B - 1)),
147-
((-90, 0, -1), (0, 0, -B + 1)),
148-
],
149-
)
161+
@pytest.mark.parametrize("lla, xyz", llaxyz)
150162
def test_geodetic2ecef(lla, xyz):
151163
assert pm.geodetic2ecef(*lla) == approx(xyz, abs=atol_dist)
152164

153165

154-
@pytest.mark.parametrize(
155-
"xyz, lla",
156-
[
157-
((A - 1, 0, 0), (0, 0, -1)),
158-
((0, A - 1, 0), (0, 90, -1)),
159-
((0, 0, B - 1), (90, 0, -1)),
160-
((0, 0, -B + 1), (-90, 0, -1)),
161-
((-A + 1, 0, 0), (0, 180, -1)),
162-
],
163-
)
166+
@pytest.mark.parametrize("xyz, lla", xyzlla)
164167
def test_ecef2geodetic(xyz, lla):
165168
lat, lon, alt = pm.ecef2geodetic(*xyz)
166169
assert lat == approx(lla[0])
@@ -221,3 +224,19 @@ def test_somenan():
221224

222225
lat, lon, alt = pm.ecef2geodetic(xyz[:, 0], xyz[:, 1], xyz[:, 2])
223226
assert (lat[0], lon[0], alt[0]) == approx(lla0)
227+
228+
229+
@pytest.mark.parametrize("xyz, lla", xyzlla)
230+
def test_numpy_ecef2geodetic(xyz, lla):
231+
np = pytest.importorskip("numpy")
232+
lat, lon, alt = pm.ecef2geodetic(
233+
*np.array(
234+
[
235+
[xyz],
236+
],
237+
dtype=np.float32,
238+
).T
239+
)
240+
assert lat[0] == approx(lla[0])
241+
assert lon[0] == approx(lla[1])
242+
assert alt[0] == approx(lla[2])

0 commit comments

Comments
 (0)