Skip to content

Commit 7b2ac9d

Browse files
committed
add geod2geoc, geocentric_radius
1 parent c145db6 commit 7b2ac9d

22 files changed

+154
-53
lines changed

README.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Python 3-D coordinate conversions
2+
13
[![image](https://zenodo.org/badge/DOI/10.5281/zenodo.213676.svg)](https://doi.org/10.5281/zenodo.213676)
24
[![image](http://joss.theoj.org/papers/10.21105/joss.00580/status.svg)](https://doi.org/10.21105/joss.00580)
35
[![astronomer](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fastronomer.ullaakut.eu%2Fshields%3Fowner%3Dscivision%26name%3Dpymap3d)](https://github.com/Ullaakut/astronomer/)
@@ -10,8 +12,6 @@
1012
[![image](https://img.shields.io/pypi/pyversions/pymap3d.svg)](https://pypi.python.org/pypi/pymap3d)
1113
[![PyPi Download stats](http://pepy.tech/badge/pymap3d)](http://pepy.tech/project/pymap3d)
1214

13-
# Python 3-D coordinate conversions
14-
1515
Pure Python (no prerequistes beyond Python itself) 3-D geographic coordinate conversions and geodesy.
1616
API similar to popular $1000 Matlab Mapping Toolbox routines for:
1717

@@ -32,7 +32,6 @@ Thanks to our [contributors](./contributors.md).
3232
Pymap3d is compatible with Python ≥ 3.5 including PyPy.
3333
Numpy and AstroPy are optional; algorithms from Vallado and Meeus are used if AstroPy is not present.
3434

35-
3635
## Install
3736

3837
```sh
@@ -48,6 +47,7 @@ pip install -e pymap3d
4847
```
4948

5049
One can verify Python functionality after installation by:
50+
5151
```sh
5252
pytest pymap3d -r a -v
5353
```
@@ -79,7 +79,6 @@ lla = pm.aer2geodetic(*aer,*obslla)
7979

8080
where tuple `lla` is comprised of scalar or N-D arrays `(lat,lon,alt)`.
8181

82-
8382
Example scripts are in the [examples](./examples) directory.
8483

8584
### Functions
@@ -97,17 +96,16 @@ converted to the desired coordinate system:
9796
azel2radec radec2azel
9897
vreckon vdist
9998
lookAtSpheroid
100-
track2 departure meanm
101-
rcurve rsphere
102-
99+
track2 departure meanm
100+
rcurve rsphere
101+
geod2geoc geoc2geod
103102

104103
Additional functions:
105104

106105
* loxodrome_inverse: rhumb line distance and azimuth between ellipsoid points (lat,lon) akin to Matlab `distance('rh', ...)` and `azimuth('rh', ...)`
107106
* loxodrome_direct
108107
* geodetic latitude transforms to/from: parametric, authalic, isometric, and more in pymap3d.latitude
109108

110-
111109
Abbreviations:
112110

113111
* [AER: Azimuth, Elevation, Range](https://en.wikipedia.org/wiki/Spherical_coordinate_system)
@@ -128,7 +126,6 @@ pymap3d seamlessly falls back to Python's math module if Numpy isn't present.
128126
To keep the code clean, only scalar data can be used without Numpy.
129127
As noted above, use list comprehension if you need vector data without Numpy.
130128

131-
132129
### Caveats
133130

134131
* Atmospheric effects neglected in all functions not invoking AstroPy.

pymap3d/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
from .ecef import geodetic2ecef, ecef2geodetic, eci2geodetic, ecef2enuv, enu2ecef, ecef2enu, enu2uvw, uvw2enu
3939
from .sidereal import datetime2sidereal
4040
from .latitude import (
41+
geod2geoc,
42+
geoc2geod,
4143
geodetic2geocentric,
4244
geocentric2geodetic,
4345
geodetic2isometric,
@@ -51,7 +53,7 @@
5153
geodetic2parametric,
5254
parametric2geodetic,
5355
)
54-
from .rcurve import rcurve_parallel, rcurve_meridian, rcurve_transverse
56+
from .rcurve import geocentric_radius, rcurve_parallel, rcurve_meridian, rcurve_transverse
5557
from .rsphere import (
5658
rsphere_eqavol,
5759
rsphere_authalic,

pymap3d/latitude.py

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from .ellipsoid import Ellipsoid
55
from .utils import sanitize
6+
from .rcurve import rcurve_transverse
67

78
try:
89
from numpy import radians, degrees, tan, sin, exp, pi, sqrt, inf, vectorize
@@ -27,22 +28,69 @@
2728
"geocentric2geodetic",
2829
"geodetic2authalic",
2930
"authalic2geodetic",
31+
"geod2geoc",
32+
"geoc2geod",
3033
]
3134

3235
if typing.TYPE_CHECKING:
3336
from numpy import ndarray
3437

3538

36-
def geodetic2geocentric(geodetic_lat: "ndarray", ell: Ellipsoid = None, deg: bool = True) -> "ndarray":
39+
def geoc2geod(geocentric_lat: "ndarray", geocentric_distance: "ndarray", ell: Ellipsoid = None, deg: bool = True) -> "ndarray":
3740
"""
38-
convert geodetic latitude to geocentric latitude.
41+
convert geocentric latitude to geodetic latitude, consider mean sea level altitude
3942
40-
like Matlab geocentricLatitude()
43+
like Matlab geoc2geod()
44+
45+
Parameters
46+
----------
47+
geocentric_lat : "ndarray"
48+
geocentric latitude
49+
geocentric_distance: "ndarray"
50+
distance from planet center, meters (NOT altitude above ground!)
51+
ell : Ellipsoid, optional
52+
reference ellipsoid (default WGS84)
53+
deg : bool, optional
54+
degrees input/output (False: radians in/out)
55+
56+
Returns
57+
-------
58+
geodetic_lat : "ndarray"
59+
geodetic latiude
60+
61+
62+
References
63+
----------
64+
Long, S.A.T. "General-Altitude Transformation between Geocentric
65+
and Geodetic Coordinates. Celestial Mechanics (12), 2, p. 225-230 (1975)
66+
doi: 10.1007/BF01230214"
67+
"""
68+
geocentric_lat, ell = sanitize(geocentric_lat, ell, deg)
69+
70+
r = geocentric_distance / ell.semimajor_axis
71+
72+
geodetic_lat = (
73+
geocentric_lat
74+
+ (sin(2 * geocentric_lat) / r) * ell.flattening
75+
+ ((1 / r ** 2 + 1 / (4 * r)) * sin(4 * geocentric_lat)) * ell.flattening ** 2
76+
)
77+
78+
return degrees(geodetic_lat) if deg else geodetic_lat
79+
80+
81+
def geodetic2geocentric(geodetic_lat: "ndarray", alt_m: "ndarray", ell: Ellipsoid = None, deg: bool = True) -> "ndarray":
82+
"""
83+
convert geodetic latitude to geocentric latitude on spheroid surface
84+
85+
like Matlab geocentricLatitude() with alt_m = 0
86+
like Matlab geod2geoc()
4187
4288
Parameters
4389
----------
4490
geodetic_lat : "ndarray"
4591
geodetic latitude
92+
alt_m: "ndarray"
93+
altitude above ellipsoid
4694
ell : Ellipsoid, optional
4795
reference ellipsoid (default WGS84)
4896
deg : bool, optional
@@ -60,22 +108,28 @@ def geodetic2geocentric(geodetic_lat: "ndarray", ell: Ellipsoid = None, deg: boo
60108
Office, Washington, DC, 1987, pp. 13-18.
61109
"""
62110
geodetic_lat, ell = sanitize(geodetic_lat, ell, deg)
63-
64-
geocentric_lat = atan((1 - (ell.eccentricity) ** 2) * tan(geodetic_lat))
111+
r = rcurve_transverse(geodetic_lat, ell, deg=False)
112+
geocentric_lat = atan((1 - ell.eccentricity ** 2 * (r / (r + alt_m))) * tan(geodetic_lat))
65113

66114
return degrees(geocentric_lat) if deg else geocentric_lat
67115

68116

69-
def geocentric2geodetic(geocentric_lat: "ndarray", ell: Ellipsoid = None, deg: bool = True) -> "ndarray":
117+
geod2geoc = geodetic2geocentric
118+
119+
120+
def geocentric2geodetic(geocentric_lat: "ndarray", alt_m: "ndarray", ell: Ellipsoid = None, deg: bool = True) -> "ndarray":
70121
"""
71122
converts from geocentric latitude to geodetic latitude
72123
73-
like Matlab geodeticLatitudeFromGeocentric()
124+
like Matlab geodeticLatitudeFromGeocentric() when alt_m = 0
125+
like Matlab geod2geoc() but with sea level altitude rather than planet center distance
74126
75127
Parameters
76128
----------
77129
geocentric_lat : "ndarray"
78130
geocentric latitude
131+
alt_m: "ndarray"
132+
altitude above ellipsoid
79133
ell : Ellipsoid, optional
80134
reference ellipsoid (default WGS84)
81135
deg : bool, optional
@@ -93,8 +147,8 @@ def geocentric2geodetic(geocentric_lat: "ndarray", ell: Ellipsoid = None, deg: b
93147
Office, Washington, DC, 1987, pp. 13-18.
94148
"""
95149
geocentric_lat, ell = sanitize(geocentric_lat, ell, deg)
96-
97-
geodetic_lat = atan(tan(geocentric_lat) / (1 - (ell.eccentricity) ** 2))
150+
r = rcurve_transverse(geocentric_lat, ell, deg=False)
151+
geodetic_lat = atan(tan(geocentric_lat) / (1 - ell.eccentricity ** 2 * (r / (r + alt_m))))
98152

99153
return degrees(geodetic_lat) if deg else geodetic_lat
100154

pymap3d/rcurve.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,30 @@
66
from numpy import radians, sin, cos, sqrt
77
except ImportError:
88
from math import radians, sin, cos, sqrt
9+
910
from .ellipsoid import Ellipsoid
11+
from .utils import sanitize
1012

11-
__all__ = ["rcurve_parallel", "rcurve_meridian", "rcurve_transverse"]
13+
__all__ = ["rcurve_parallel", "rcurve_meridian", "rcurve_transverse", "geocentric_radius"]
1214

1315
if typing.TYPE_CHECKING:
1416
from numpy import ndarray
1517

1618

19+
def geocentric_radius(geodetic_lat: "ndarray", ell: Ellipsoid = None, deg: bool = True) -> "ndarray":
20+
"""
21+
compute geocentric radius at geodetic latitude
22+
23+
https://en.wikipedia.org/wiki/Earth_radius#Geocentric_radius
24+
"""
25+
geodetic_lat, ell = sanitize(geodetic_lat, ell, deg)
26+
27+
return sqrt(
28+
((ell.semimajor_axis ** 2 * cos(geodetic_lat)) ** 2 + (ell.semiminor_axis ** 2 * sin(geodetic_lat)) ** 2)
29+
/ ((ell.semimajor_axis * cos(geodetic_lat)) ** 2 + (ell.semiminor_axis * sin(geodetic_lat)) ** 2)
30+
)
31+
32+
1733
def rcurve_parallel(lat: "ndarray", ell: Ellipsoid = None, deg: bool = True) -> "ndarray":
1834
"""
1935
computes the radius of the small circle encompassing the globe at the specified latitude
@@ -42,6 +58,8 @@ def rcurve_parallel(lat: "ndarray", ell: Ellipsoid = None, deg: bool = True) ->
4258
def rcurve_meridian(lat: "ndarray", ell: Ellipsoid = None, deg: bool = True) -> "ndarray":
4359
"""computes the meridional radius of curvature for the ellipsoid
4460
61+
like Matlab rcurve('meridian', ...)
62+
4563
Parameters
4664
----------
4765
lat : "ndarray"
@@ -72,10 +90,12 @@ def rcurve_transverse(lat: "ndarray", ell: Ellipsoid = None, deg: bool = True) -
7290
intersecting the ellipsoid at the latitude which is
7391
normal to the surface of the ellipsoid
7492
93+
like Matlab rcurve('transverse', ...)
94+
7595
Parameters
7696
----------
7797
lat : "ndarray"
78-
geodetic latitude (degrees)
98+
latitude (degrees)
7999
ell : Ellipsoid, optional
80100
reference ellipsoid
81101
deg : bool, optional
@@ -87,9 +107,6 @@ def rcurve_transverse(lat: "ndarray", ell: Ellipsoid = None, deg: bool = True) -
87107
radius of ellipsoid
88108
"""
89109

90-
if ell is None:
91-
ell = Ellipsoid()
92-
if deg:
93-
lat = radians(lat)
110+
lat, ell = sanitize(lat, ell, deg)
94111

95112
return ell.semimajor_axis / sqrt(1 - (ell.eccentricity * sin(lat)) ** 2)

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.1.1
3+
version = 2.2.0
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.

tests/test_aer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,4 @@ def test_aer_ned(aer, ned):
6969

7070

7171
if __name__ == "__main__":
72-
pytest.main(["-v", __file__])
72+
pytest.main([__file__])

tests/test_astropy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,4 @@ def test_eci_aer(useastropy):
6767

6868

6969
if __name__ == "__main__":
70-
pytest.main(["-v", __file__])
70+
pytest.main([__file__])

tests/test_eci.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ def test_eci_times(useastropy):
3333

3434

3535
if __name__ == "__main__":
36-
pytest.main(["-x", __file__])
36+
pytest.main([__file__])

tests/test_enu.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,4 @@ def test_enu_ecef(enu, lla, xyz):
5353

5454

5555
if __name__ == "__main__":
56-
pytest.main(["-v", __file__])
56+
pytest.main([__file__])

tests/test_geodetic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,4 @@ def test_somenan():
184184

185185

186186
if __name__ == "__main__":
187-
pytest.main(["-v", __file__])
187+
pytest.main([__file__])

0 commit comments

Comments
 (0)