diff -Nru pymap3d-2.7.2/debian/changelog pymap3d-2.7.3/debian/changelog --- pymap3d-2.7.2/debian/changelog 2021-10-20 12:22:22.000000000 +0000 +++ pymap3d-2.7.3/debian/changelog 2021-11-27 12:20:20.000000000 +0000 @@ -1,3 +1,11 @@ +pymap3d (2.7.3-1) unstable; urgency=medium + + * New upstream release. + * Update d/watch. + * Update d/copyright. + + -- Antonio Valentino Sat, 27 Nov 2021 12:20:20 +0000 + pymap3d (2.7.2-2) unstable; urgency=medium [ Bas Couwenberg ] diff -Nru pymap3d-2.7.2/debian/copyright pymap3d-2.7.3/debian/copyright --- pymap3d-2.7.2/debian/copyright 2021-10-20 12:22:22.000000000 +0000 +++ pymap3d-2.7.3/debian/copyright 2021-11-27 12:20:20.000000000 +0000 @@ -10,7 +10,7 @@ License: BSD-2-Clause Files: debian/* -Copyright: 2020, Antonio Valentino +Copyright: 2020-2021, Antonio Valentino License: BSD-2-Clause License: BSD-2-Clause diff -Nru pymap3d-2.7.2/debian/watch pymap3d-2.7.3/debian/watch --- pymap3d-2.7.2/debian/watch 2021-10-20 12:22:22.000000000 +0000 +++ pymap3d-2.7.3/debian/watch 2021-11-27 12:20:20.000000000 +0000 @@ -2,6 +2,6 @@ opts=\ dversionmangle=s/\+(debian|dfsg|ds|deb)\.?\d*$//,\ uversionmangle=s/(\d)[_\.\-\+]?((RC|rc|pre|dev|b|beta|a|alpha)\d*)$/$1~$2/;s/RC/rc/;s/\-/\./g,\ -filenamemangle=s/.+\/(?:rel|v|pymap3d)?[\-\_]?(\d\S+)\.(tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))/pymap3d-$1.$2/ \ +filenamemangle=s/(?:.*?)?(?:rel|v|pymap3d)?[\-\_]?(\d\S*)\.(tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))/pymap3d-$1.$2/ \ https://github.com/geospace-code/pymap3d/tags \ (?:.*?/archive/(?:.*?/)?)?(?:rel|v|pymap3d)?[\-\_]?(\d\S+)\.(?:tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz))) diff -Nru pymap3d-2.7.2/.github/workflows/ci.yml pymap3d-2.7.3/.github/workflows/ci.yml --- pymap3d-2.7.2/.github/workflows/ci.yml 2021-10-18 21:04:17.000000000 +0000 +++ pymap3d-2.7.3/.github/workflows/ci.yml 2021-11-22 06:27:56.000000000 +0000 @@ -7,7 +7,7 @@ - .github/workflows/ci.yml pull-request: - "**.py" - - .github/workflows/ci_stdlib_only.yml + - .github/workflows/ci.yml jobs: diff -Nru pymap3d-2.7.2/pyproject.toml pymap3d-2.7.3/pyproject.toml --- pymap3d-2.7.2/pyproject.toml 2021-10-18 21:04:17.000000000 +0000 +++ pymap3d-2.7.3/pyproject.toml 2021-11-22 06:27:56.000000000 +0000 @@ -1,6 +1,6 @@ [project] name = "pymap3d" -version = "2.6.1" +version = "2.7.2" description = "pure Python (no prereqs) coordinate conversions, following convention of several popular Matlab routines." readme = "README.md" requires-python = ">=3.7" diff -Nru pymap3d-2.7.2/README.md pymap3d-2.7.3/README.md --- pymap3d-2.7.2/README.md 2021-10-18 21:04:17.000000000 +0000 +++ pymap3d-2.7.3/README.md 2021-11-22 06:27:56.000000000 +0000 @@ -76,6 +76,9 @@ Example scripts are in the [examples](./Examples) directory. +Native Python float is typically [64 bit](https://docs.python.org/3/library/stdtypes.html#typesnumeric). +Numpy can select real precision bits: 32, 64, 128, etc. + ### Functions Popular mapping toolbox functions ported to Python include the diff -Nru pymap3d-2.7.2/setup.cfg pymap3d-2.7.3/setup.cfg --- pymap3d-2.7.2/setup.cfg 2021-10-18 21:04:17.000000000 +0000 +++ pymap3d-2.7.3/setup.cfg 2021-11-22 06:27:56.000000000 +0000 @@ -1,6 +1,6 @@ [metadata] name = pymap3d -version = 2.7.2 +version = 2.7.3 author = Michael Hirsch, Ph.D. author_email = scivision@users.noreply.github.com description = pure Python (no prereqs) coordinate conversions, following convention of several popular Matlab routines. diff -Nru pymap3d-2.7.2/src/pymap3d/aer.py pymap3d-2.7.3/src/pymap3d/aer.py --- pymap3d-2.7.2/src/pymap3d/aer.py 2021-10-18 21:04:17.000000000 +0000 +++ pymap3d-2.7.3/src/pymap3d/aer.py 2021-11-22 06:27:56.000000000 +0000 @@ -11,7 +11,7 @@ try: from .eci import eci2ecef, ecef2eci except ImportError: - eci2ecef = ecef2eci = None # type: ignore + pass if typing.TYPE_CHECKING: from numpy import ndarray @@ -212,10 +212,11 @@ srange : float slant range [meters] """ - if eci2ecef is None: - raise ImportError("pip install numpy") - xecef, yecef, zecef = eci2ecef(x, y, z, t, use_astropy=use_astropy) + try: + xecef, yecef, zecef = eci2ecef(x, y, z, t, use_astropy=use_astropy) + except NameError: + raise ImportError("pip install numpy") return ecef2aer(xecef, yecef, zecef, lat0, lon0, h0, deg=deg) @@ -271,12 +272,13 @@ z : float ECEF z coordinate (meters) """ - if ecef2eci is None: - raise ImportError("pip install numpy") x, y, z = aer2ecef(az, el, srange, lat0, lon0, h0, ell, deg=deg) - return ecef2eci(x, y, z, t, use_astropy=use_astropy) + try: + return ecef2eci(x, y, z, t, use_astropy=use_astropy) + except NameError: + raise ImportError("pip install numpy") def aer2ecef( diff -Nru pymap3d-2.7.2/src/pymap3d/ecef.py pymap3d-2.7.3/src/pymap3d/ecef.py --- pymap3d-2.7.2/src/pymap3d/ecef.py 2021-10-18 21:04:17.000000000 +0000 +++ pymap3d-2.7.3/src/pymap3d/ecef.py 2021-11-22 06:27:56.000000000 +0000 @@ -3,7 +3,20 @@ import typing try: - from numpy import radians, sin, cos, tan, arctan as atan, hypot, degrees, arctan2 as atan2, sqrt + from numpy import ( + radians, + sin, + cos, + tan, + arctan as atan, + hypot, + degrees, + arctan2 as atan2, + sqrt, + finfo, + where, + ) + from .eci import eci2ecef, ecef2eci except ImportError: from math import radians, sin, cos, tan, atan, hypot, degrees, atan2, sqrt # type: ignore @@ -13,11 +26,6 @@ from .ellipsoid import Ellipsoid from .utils import sanitize -try: - from .eci import eci2ecef, ecef2eci -except ImportError: - eci2ecef = ecef2eci = None # type: ignore - if typing.TYPE_CHECKING: from numpy import ndarray @@ -80,8 +88,7 @@ N = ell.semimajor_axis ** 2 / sqrt( ell.semimajor_axis ** 2 * cos(lat) ** 2 + ell.semiminor_axis ** 2 * sin(lat) ** 2 ) - # Compute cartesian (geocentric) coordinates given (curvilinear) geodetic - # coordinates. + # Compute cartesian (geocentric) coordinates given (curvilinear) geodetic coordinates. x = (N + alt) * cos(lat) * cos(lon) y = (N + alt) * cos(lat) * sin(lon) z = (N * (ell.semiminor_axis / ell.semimajor_axis) ** 2 + alt) * sin(lat) @@ -150,18 +157,36 @@ Beta = -pi / 2 # eqn. 13 - eps = ((ell.semiminor_axis * u - ell.semimajor_axis * huE + E ** 2) * sin(Beta)) / ( + dBeta = ((ell.semiminor_axis * u - ell.semimajor_axis * huE + E ** 2) * sin(Beta)) / ( ell.semimajor_axis * huE * 1 / cos(Beta) - E ** 2 * cos(Beta) ) - Beta += eps + Beta += dBeta + + # eqn. 4c # %% final output lat = atan(ell.semimajor_axis / ell.semiminor_axis * tan(Beta)) + try: + # patch latitude for float32 precision loss + lim_pi2 = pi / 2 - finfo(dBeta.dtype).eps + lat = where(Beta >= lim_pi2, pi / 2, lat) + lat = where(Beta <= -lim_pi2, -pi / 2, lat) + except NameError: + pass + lon = atan2(y, x) # eqn. 7 - alt = hypot(z - ell.semiminor_axis * sin(Beta), Q - ell.semimajor_axis * cos(Beta)) + cosBeta = cos(Beta) + try: + # patch altitude for float32 precision loss + cosBeta = where(Beta >= lim_pi2, 0, cosBeta) + cosBeta = where(Beta <= -lim_pi2, 0, cosBeta) + except NameError: + pass + + alt = hypot(z - ell.semiminor_axis * sin(Beta), Q - ell.semimajor_axis * cosBeta) # inside ellipsoid? inside = ( @@ -395,10 +420,11 @@ eci2geodetic() a.k.a. eci2lla() """ - if eci2ecef is None: - raise ImportError("pip install numpy") - xecef, yecef, zecef = eci2ecef(x, y, z, t, use_astropy=use_astropy) + try: + xecef, yecef, zecef = eci2ecef(x, y, z, t, use_astropy=use_astropy) + except NameError: + raise ImportError("pip install numpy") return ecef2geodetic(xecef, yecef, zecef, ell, deg) @@ -446,12 +472,13 @@ geodetic2eci() a.k.a lla2eci() """ - if ecef2eci is None: - raise ImportError("pip install numpy") x, y, z = geodetic2ecef(lat, lon, alt, ell, deg) - return ecef2eci(x, y, z, t, use_astropy=use_astropy) + try: + return ecef2eci(x, y, z, t, use_astropy=use_astropy) + except NameError: + raise ImportError("pip install numpy") def enu2ecef( diff -Nru pymap3d-2.7.2/src/pymap3d/rsphere.py pymap3d-2.7.3/src/pymap3d/rsphere.py --- pymap3d-2.7.2/src/pymap3d/rsphere.py 2021-10-18 21:04:17.000000000 +0000 +++ pymap3d-2.7.3/src/pymap3d/rsphere.py 2021-11-22 06:27:56.000000000 +0000 @@ -8,8 +8,6 @@ except ImportError: from math import radians, sin, cos, log, sqrt, degrees # type: ignore - asarray = None # type: ignore - from .ellipsoid import Ellipsoid from . import rcurve from .vincenty import vdist @@ -123,8 +121,11 @@ """ if not deg: lat1, lon1, lat2, lon2 = degrees(lat1), degrees(lon1), degrees(lat2), degrees(lon2) - if asarray is not None: + + try: lat1, lat2 = asarray(lat1), asarray(lat2) + except NameError: + pass latmid = lat1 + (lat2 - lat1) / 2 # compute the midpoint diff -Nru pymap3d-2.7.2/src/pymap3d/tests/test_geodetic.py pymap3d-2.7.3/src/pymap3d/tests/test_geodetic.py --- pymap3d-2.7.2/src/pymap3d/tests/test_geodetic.py 2021-10-18 21:04:17.000000000 +0000 +++ pymap3d-2.7.3/src/pymap3d/tests/test_geodetic.py 2021-11-22 06:27:56.000000000 +0000 @@ -13,6 +13,28 @@ A = ELL.semimajor_axis B = ELL.semiminor_axis +xyzlla = [ + ((A - 1, 0, 0), (0, 0, -1)), + ((0, A - 1, 0), (0, 90, -1)), + ((0, 0, B - 1), (90, 0, -1)), + ((0, 0, B - 1), (89.999999, 0, -1)), + ((0, 0, B - 1), (89.99999, 0, -1)), + ((0, 0, -B + 1), (-90, 0, -1)), + ((0, 0, -B + 1), (-89.999999, 0, -1)), + ((0, 0, -B + 1), (-89.99999, 0, -1)), + ((-A + 1, 0, 0), (0, 180, -1)), +] + +llaxyz = [ + ((0, 0, -1), (A - 1, 0, 0)), + ((0, 90, -1), (0, A - 1, 0)), + ((0, -90, -1), (0, -A + 1, 0)), + ((90, 0, -1), (0, 0, B - 1)), + ((90, 15, -1), (0, 0, B - 1)), + ((-90, 0, -1), (0, 0, -B + 1)), +] + + atol_dist = 1e-6 # 1 micrometer @@ -94,7 +116,7 @@ xyz = pm.geodetic2ecef(*xr_lla) assert xyz == approx(xyz0) - # %% + xr_xyz = xarray.DataArray(list(xyz0)) lla = pm.ecef2geodetic(*xr_xyz) @@ -136,31 +158,12 @@ assert pm.ecef2geodetic((A - 1) / sqrt(2), (A - 1) / sqrt(2), 0) == approx([0, 45, -1]) -@pytest.mark.parametrize( - "lla, xyz", - [ - ((0, 0, -1), (A - 1, 0, 0)), - ((0, 90, -1), (0, A - 1, 0)), - ((0, -90, -1), (0, -A + 1, 0)), - ((90, 0, -1), (0, 0, B - 1)), - ((90, 15, -1), (0, 0, B - 1)), - ((-90, 0, -1), (0, 0, -B + 1)), - ], -) +@pytest.mark.parametrize("lla, xyz", llaxyz) def test_geodetic2ecef(lla, xyz): assert pm.geodetic2ecef(*lla) == approx(xyz, abs=atol_dist) -@pytest.mark.parametrize( - "xyz, lla", - [ - ((A - 1, 0, 0), (0, 0, -1)), - ((0, A - 1, 0), (0, 90, -1)), - ((0, 0, B - 1), (90, 0, -1)), - ((0, 0, -B + 1), (-90, 0, -1)), - ((-A + 1, 0, 0), (0, 180, -1)), - ], -) +@pytest.mark.parametrize("xyz, lla", xyzlla) def test_ecef2geodetic(xyz, lla): lat, lon, alt = pm.ecef2geodetic(*xyz) assert lat == approx(lla[0]) @@ -221,3 +224,37 @@ lat, lon, alt = pm.ecef2geodetic(xyz[:, 0], xyz[:, 1], xyz[:, 2]) assert (lat[0], lon[0], alt[0]) == approx(lla0) + + +@pytest.mark.parametrize("xyz, lla", xyzlla) +def test_numpy_ecef2geodetic(xyz, lla): + np = pytest.importorskip("numpy") + lat, lon, alt = pm.ecef2geodetic( + *np.array( + [ + [xyz], + ], + dtype=np.float32, + ).T + ) + assert lat[0] == approx(lla[0]) + assert lon[0] == approx(lla[1]) + assert alt[0] == approx(lla[2]) + + +@pytest.mark.parametrize("lla, xyz", llaxyz) +def test_numpy_geodetic2ecef(lla, xyz): + np = pytest.importorskip("numpy") + x, y, z = pm.geodetic2ecef( + *np.array( + [ + [lla], + ], + dtype=np.float32, + ).T + ) + + atol_dist = 1 # meters + assert x[0] == approx(xyz[0], abs=atol_dist) + assert y[0] == approx(xyz[1], abs=atol_dist) + assert z[0] == approx(xyz[2], abs=atol_dist) diff -Nru pymap3d-2.7.2/src/pymap3d/utils.py pymap3d-2.7.3/src/pymap3d/utils.py --- pymap3d-2.7.2/src/pymap3d/utils.py 2021-10-18 21:04:17.000000000 +0000 +++ pymap3d-2.7.3/src/pymap3d/utils.py 2021-11-22 06:27:56.000000000 +0000 @@ -4,15 +4,14 @@ from __future__ import annotations import typing +from math import pi from .ellipsoid import Ellipsoid try: - from numpy import hypot, cos, sin, arctan2 as atan2, radians, pi, asarray, sign + from numpy import hypot, cos, sin, arctan2 as atan2, radians, asarray, sign except ImportError: - from math import atan2, hypot, cos, sin, radians, pi # type: ignore - - asarray = None # type: ignore + from math import atan2, hypot, cos, sin, radians # type: ignore def sign(x: float) -> float: # type: ignore """signum function""" @@ -63,17 +62,22 @@ def sanitize( lat: float | ndarray, ell: typing.Optional[Ellipsoid], deg: bool ) -> tuple[float | ndarray, Ellipsoid]: + if ell is None: ell = Ellipsoid() - if asarray is not None: + + try: lat = asarray(lat) + except NameError: + pass + if deg: lat = radians(lat) - if asarray is not None: + try: if (abs(lat) > pi / 2).any(): # type: ignore raise ValueError("-pi/2 <= latitude <= pi/2") - else: + except AttributeError: if abs(lat) > pi / 2: # type: ignore raise ValueError("-pi/2 <= latitude <= pi/2")