Skip to content

Commit c9cf676

Browse files
committed
parametrize test
test different input types allow longitude to wrap pandas.DataFrame better testing and pandas cleanup tests don't squeeze unnecessarily coverage handle scalar if meta
1 parent 4a5bac9 commit c9cf676

File tree

7 files changed

+121
-36
lines changed

7 files changed

+121
-36
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Includes some relevant
2727

2828
* Python ≥ 3.5 or PyPy3
2929

30-
References to AstroPy are optional, algorithms from Vallado and Meeus are used if AstroPy is not present.
30+
References to AstroPy are *optional*, algorithms from Vallado and Meeus are used if AstroPy is not present.
3131

3232

3333
## Install
@@ -128,3 +128,11 @@ As compared to [PyProj](https://github.com/jswhit/pyproj):
128128
PyMap3D handles points on or above the planet surface equally well,
129129
particularly important for airborne vehicles and remote sensing.
130130

131+
### AstroPy.Units.Quantity
132+
133+
At this time,
134+
[AstroPy.Units.Quantity](http://docs.astropy.org/en/stable/units/)
135+
is not supported.
136+
Let us know if this is of interest.
137+
Impacts on performance would have to be considered before making Quantity a first-class citizen.
138+
For now, you can workaround by passing in the `.value` of the variable.

pymap3d/ecef.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
try:
77
from math import tau # py >= 3.6
88
except ImportError:
9-
tau = 2 * np.pi
9+
tau = 2 * pi
1010

1111
from .eci import eci2ecef
1212

@@ -138,9 +138,6 @@ def geodetic2ecef(lat: float, lon: float, alt: float,
138138
if np.any((lat < -pi / 2) | (lat > pi / 2)):
139139
raise ValueError('-90 <= lat <= 90')
140140

141-
if np.any((lon < -pi) | (lon > tau)):
142-
raise ValueError('-180 <= lat <= 360')
143-
144141
# radius of curvature of the prime vertical section
145142
N = get_radius_normal(lat, ell)
146143
# Compute cartesian (geocentric) coordinates given (curvilinear) geodetic
@@ -186,6 +183,10 @@ def ecef2geodetic(x: float, y: float, z: float,
186183
if ell is None:
187184
ell = Ellipsoid()
188185

186+
x = np.asarray(x)
187+
y = np.asarray(y)
188+
z = np.asarray(z)
189+
189190
r = sqrt(x**2 + y**2 + z**2)
190191

191192
E = sqrt(ell.a**2 - ell.b**2)
@@ -214,17 +215,19 @@ def ecef2geodetic(x: float, y: float, z: float,
214215
alt = hypot(z - ell.b * sin(Beta),
215216
Q - ell.a * cos(Beta))
216217

217-
alt = np.atleast_1d(alt)
218-
219218
# inside ellipsoid?
220219
with np.errstate(invalid='ignore'):
221220
inside = x**2 / ell.a**2 + y**2 / ell.a**2 + z**2 / ell.b**2 < 1
222-
alt[inside] = -alt[inside]
221+
if isinstance(inside, np.ndarray):
222+
alt[inside] = -alt[inside]
223+
elif inside:
224+
alt = -alt
223225

224226
if deg:
225-
return degrees(lat), degrees(lon), alt.squeeze()[()]
226-
else:
227-
return lat, lon, alt.squeeze()[()] # radians
227+
lat = degrees(lat)
228+
lon = degrees(lon)
229+
230+
return lat, lon, alt
228231

229232

230233
def ecef2enuv(u: float, v: float, w: float,

pymap3d/enu.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,25 +41,25 @@ def enu2aer(e: np.ndarray, n: np.ndarray, u: np.ndarray, deg: bool = True) -> Tu
4141
"""
4242
# 1 millimeter precision for singularity
4343

44-
e = np.atleast_1d(e)
45-
n = np.atleast_1d(n)
46-
u = np.atleast_1d(u)
44+
e = np.asarray(e)
45+
n = np.asarray(n)
46+
u = np.asarray(u)
4747

4848
with np.errstate(invalid='ignore'):
4949
e[abs(e) < 1e-3] = 0.
5050
n[abs(n) < 1e-3] = 0.
5151
u[abs(u) < 1e-3] = 0.
5252

53-
r = hypot(e, n)
54-
slantRange = hypot(r, u)
55-
elev = arctan2(u, r)
56-
az = arctan2(e, n) % tau
53+
r = hypot(e, n)
54+
slantRange = hypot(r, u)
55+
elev = arctan2(u, r)
56+
az = arctan2(e, n) % tau
5757

5858
if deg:
5959
az = degrees(az)
6060
elev = degrees(elev)
6161

62-
return az[()].squeeze(), elev[()].squeeze(), slantRange[()].squeeze()
62+
return az, elev, slantRange
6363

6464

6565
def aer2enu(az: float, el: float, srange: float, deg: bool = True) -> Tuple[float, float, float]:

pymap3d/vallado.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ def azel2radec(az_deg: float, el_deg: float,
6363
raise ValueError('need one observer and one or more (az,el).')
6464
if ((lat < -90) | (lat > 90)).any():
6565
raise ValueError('-90 <= lat <= 90')
66-
if ((lon < -180) | (lon > 360)).any():
67-
raise ValueError('-180 <= lat <= 360')
6866

6967
az = radians(az)
7068
el = radians(el)
@@ -130,8 +128,6 @@ def radec2azel(ra_deg: float, dec_deg: float,
130128
raise ValueError('need one observer and one or more (az,el).')
131129
if ((lat < -90) | (lat > 90)).any():
132130
raise ValueError('-90 <= lat <= 90')
133-
if ((lon < -180) | (lon > 360)).any():
134-
raise ValueError('-180 <= lat <= 360')
135131

136132
ra = radians(ra)
137133
dec = radians(dec)

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 = 1.7.14
3+
version = 1.7.15
44
author = Michael Hirsch, Ph.D.
55
author_email = [email protected]
66
description = pure Python coordinate conversions, following convention of several popular Matlab routines.

tests/test_astropy.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,16 @@
1414
eci0 = (-3.977913815668146e6, -2.582332196263046e6, 4.250818828152067e6)
1515

1616

17-
def test_sidereal():
17+
@pytest.mark.parametrize('time', [t0, [t0]], ids=('scalar', 'list'))
18+
def test_sidereal(time):
1819
pytest.importorskip('astropy')
1920
# http://www.jgiesen.de/astro/astroJS/siderealClock/
20-
assert pmd.datetime2sidereal(t0, np.radians(lon), False) == approx(sra, rel=1e-5)
21+
assert pmd.datetime2sidereal(time, np.radians(lon), False) == approx(sra, rel=1e-5)
2122

22-
assert pmd.datetime2sidereal([t0], np.radians(lon), False) == approx(sra, rel=1e-5)
2323

24-
25-
def test_sidereal_vallado():
26-
assert pmd.datetime2sidereal(t0, np.radians(lon), True) == approx(sra, rel=1e-5)
27-
28-
assert pmd.datetime2sidereal([t0], np.radians(lon), True) == approx(sra, rel=1e-5)
24+
@pytest.mark.parametrize('time', [t0, [t0]], ids=('scalar', 'list'))
25+
def test_sidereal_vallado(time):
26+
assert pmd.datetime2sidereal(time, np.radians(lon), True) == approx(sra, rel=1e-5)
2927

3028

3129
def test_anglesep():

tests/test_geodetic.py

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,89 @@
2323
atol_dist = 1e-6 # 1 micrometer
2424

2525

26+
@pytest.mark.parametrize('lla',
27+
[(42, -82, 200),
28+
([42], [-82], [200]),
29+
(np.array(42), np.array(-82), np.array(200)),
30+
(np.array([42]), np.array([-82]), np.array([200])),
31+
(np.atleast_3d(42), np.atleast_3d(-82), np.atleast_3d(200))],
32+
ids=('scalar', 'list', '0d', '1d', '3d'))
33+
def test_scalar_geodetic2ecef(lla):
34+
"""
35+
verify we can handle the wide variety of input data type users might use
36+
"""
37+
x0, y0, z0 = pm.geodetic2ecef(*lla)
38+
39+
assert (x0, y0, z0) == approx(xyz0)
40+
41+
42+
@pytest.mark.parametrize('xyz',
43+
[(xyz0[0], xyz0[1], xyz0[2]),
44+
([xyz0[0]], [xyz0[1]], [xyz0[2]]),
45+
(np.array(xyz0[0]), np.array(xyz0[1]), np.array(xyz0[2])),
46+
(np.array([xyz0[0]]), np.array([xyz0[1]]), np.array([xyz0[2]])),
47+
(np.atleast_3d(xyz0[0]), np.atleast_3d(xyz0[1]), np.atleast_3d(xyz0[2]))],
48+
ids=('scalar', 'list', '0d', '1d', '3d'))
49+
def test_scalar_ecef2geodetic(xyz):
50+
"""
51+
verify we can handle the wide variety of input data type users might use
52+
"""
53+
lat, lon, alt = pm.ecef2geodetic(*xyz)
54+
55+
assert [lat, lon, alt] == approx(lla0, rel=1e-4)
56+
57+
58+
@pytest.mark.parametrize('xyz',
59+
[(0, E.a, 50),
60+
([0], [E.a], [50]),
61+
(np.array(0), np.array(E.a), np.array(50)),
62+
(np.array([0]), np.array([E.a]), np.array([50])),
63+
(np.atleast_3d(0), np.atleast_3d(E.a), np.atleast_3d(50))],
64+
ids=('scalar', 'list', '0d', '1d', '3d'))
65+
def test_scalar_aer_enu(xyz):
66+
"""
67+
verify we can handle the wide variety of input data type users might use
68+
"""
69+
enu = pm.ecef2enu(*xyz, 0, 90, -100)
70+
71+
assert pm.enu2ecef(*enu, 0, 90, -100) == approx([0, E.a, 50])
72+
73+
74+
def test_xarray():
75+
xarray = pytest.importorskip('xarray')
76+
xr_lla = xarray.DataArray(list(lla0))
77+
78+
xyz = pm.geodetic2ecef(*xr_lla)
79+
80+
assert xyz == approx(xyz0)
81+
assert isinstance(xyz[0], xarray.DataArray)
82+
# %%
83+
xr_xyz = xarray.DataArray(list(xyz0))
84+
85+
lla = pm.ecef2geodetic(*xr_xyz)
86+
87+
assert lla == approx(lla0)
88+
assert isinstance(lla[0], float) # xarrayness is lost, possibly expensive to keep due to isinstance()
89+
90+
91+
def test_pandas():
92+
pandas = pytest.importorskip('pandas')
93+
pd_lla = pandas.Series(lla0)
94+
95+
xyz = pm.geodetic2ecef(*pd_lla)
96+
97+
assert xyz == approx(xyz0)
98+
assert isinstance(xyz[0], float) # series degenerates to scalars by pandas itself
99+
# %% dataframe degenerates to series
100+
pd_lla = pandas.DataFrame([[*lla0], [*lla0]], columns=['lat', 'lon', 'alt_m'])
101+
xyz = pm.geodetic2ecef(pd_lla['lat'], pd_lla['lon'], pd_lla['alt_m'])
102+
103+
assert xyz[0].values == approx(xyz0[0])
104+
assert xyz[1].values == approx(xyz0[1])
105+
assert xyz[2].values == approx(xyz0[2])
106+
assert isinstance(xyz[0], pandas.Series)
107+
108+
26109
def test_ecef():
27110
xyz = pm.geodetic2ecef(*lla0)
28111

@@ -32,9 +115,6 @@ def test_ecef():
32115
with pytest.raises(ValueError):
33116
pm.geodetic2ecef(-100, lla0[1], lla0[2])
34117

35-
with pytest.raises(ValueError):
36-
pm.geodetic2ecef(lla0[0], -200, lla0[2])
37-
38118
assert pm.ecef2geodetic(*xyz) == approx(lla0)
39119
assert pm.ecef2geodetic(*xyz, deg=False) == approx(rlla0)
40120

@@ -93,4 +173,4 @@ def test_somenan():
93173

94174

95175
if __name__ == '__main__':
96-
pytest.main([__file__])
176+
pytest.main(['-x', __file__])

0 commit comments

Comments
 (0)