[Git][debian-gis-team/pymap3d][upstream] New upstream version 2.6.0

Antonio Valentino gitlab at salsa.debian.org
Sun Mar 21 11:32:25 GMT 2021



Antonio Valentino pushed to branch upstream at Debian GIS Project / pymap3d


Commits:
486084e0 by Antonio Valentino at 2021-03-21T11:27:56+00:00
New upstream version 2.6.0
- - - - -


22 changed files:

- .github/workflows/ci.yml
- .github/workflows/ci_stdlib_only.yml
- README.md
- pyproject.toml
- + scripts/benchmark_ecef2geo.py
- scripts/benchmark_vincenty.py
- setup.cfg
- src/pymap3d/aer.py
- src/pymap3d/ecef.py
- src/pymap3d/enu.py
- src/pymap3d/latitude.py
- src/pymap3d/los.py
- src/pymap3d/lox.py
- src/pymap3d/ned.py
- src/pymap3d/rcurve.py
- src/pymap3d/rsphere.py
- + src/pymap3d/tests/__init__.py
- src/pymap3d/tests/test_aer.py
- src/pymap3d/tests/test_geodetic.py
- src/pymap3d/tests/test_vincenty_vreckon.py
- src/pymap3d/utils.py
- src/pymap3d/vincenty.py


Changes:

=====================================
.github/workflows/ci.yml
=====================================
@@ -4,6 +4,7 @@ on:
   push:
     paths:
     - "**.py"
+    - .github/workflows/ci.yml
 
 
 jobs:


=====================================
.github/workflows/ci_stdlib_only.yml
=====================================
@@ -4,6 +4,7 @@ on:
   push:
     paths:
     - "**.py"
+    - .github/workflows/ci_stdlib_only.yml
 
 
 jobs:


=====================================
README.md
=====================================
@@ -2,7 +2,6 @@
 
 [![image](https://zenodo.org/badge/DOI/10.5281/zenodo.213676.svg)](https://doi.org/10.5281/zenodo.213676)
 [![image](http://joss.theoj.org/papers/10.21105/joss.00580/status.svg)](https://doi.org/10.21105/joss.00580)
-[![astronomer](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fastronomer.ullaakut.eu%2Fshields%3Fowner%3Dscivision%26name%3Dpymap3d)](https://github.com/Ullaakut/astronomer/)
 [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/geospace-code/pymap3d.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/geospace-code/pymap3d/context:python)
 ![Actions Status](https://github.com/geospace-code/pymap3d/workflows/ci/badge.svg)
 ![Actions Status](https://github.com/geospace-code/pymap3d/workflows/ci_stdlib_only/badge.svg)


=====================================
pyproject.toml
=====================================
@@ -1,8 +1,44 @@
-[build-system]
-requires = ["setuptools", "wheel"]
+[project]
+name = "pymap3d"
+version = "2.5.1"
+description = "pure Python (no prereqs) coordinate conversions, following convention of several popular Matlab routines."
+readme = "README.md"
+requires-python = ">=3.7"
+license = {file = "LICENSE.txt"}
+keywords = ["geodesy"]
+classifiers = [
+  'Development Status :: 5 - Production/Stable',
+  'Environment :: Console',
+  'Intended Audience :: Science/Research',
+  'Operating System :: OS Independent',
+  'Programming Language :: Python :: 3',
+  'Topic :: Scientific/Engineering :: GIS'
+]
+authors = [{name = "Michael Hirsch"}]
+
+[project.urls]
+homepage = "https://github.com/geospace-code/pymap3d"
+
+[project.optional-dependencies]
+test = [
+  "pytest"
+]
+lint = [
+  "flake8",
+  "flake8-bugbear",
+  "flake8-builtins",
+  "flake8-blind-except",
+  "mypy >= 0.800"
+]
+full = [
+  "python-dateutil",
+  "numpy >= 1.10.0",
+  "astropy",
+  "xarray"
+]
 
 [tool.black]
 line-length = 100
 
 [tool.pytest.ini_options]
-addopts = "-ra -v"
+addopts = "-ra"


=====================================
scripts/benchmark_ecef2geo.py
=====================================
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+"""
+benchmark ecef2geodetic
+"""
+import time
+from pymap3d.ecef import ecef2geodetic
+import numpy as np
+import argparse
+
+ll0 = (42.0, 82.0)
+
+
+def bench(N: int) -> float:
+
+    x = np.random.random(N)
+    y = np.random.random(N)
+    z = np.random.random(N)
+
+    tic = time.monotonic()
+    lat, lon, alt = ecef2geodetic(x, y, z)
+
+    return time.monotonic() - tic
+
+
+if __name__ == "__main__":
+    p = argparse.ArgumentParser()
+    p.add_argument("N", type=int)
+    p = p.parse_args()
+    N = p.N
+
+    print(f"ecef2geodetic: {bench(N):.3f} seconds")


=====================================
scripts/benchmark_vincenty.py
=====================================
@@ -31,7 +31,7 @@ def bench_vreckon(N: int) -> float:
     az = np.random.random(N)
 
     tic = time.monotonic()
-    a, b = vreckon(*ll0, sr, az)
+    a, b = vreckon(ll0[0], ll0[1], sr, az)
 
     return time.monotonic() - tic
 
@@ -41,7 +41,7 @@ def bench_vdist(N: int) -> float:
     lon = np.random.random(N)
 
     tic = time.monotonic()
-    asr, aaz = vdist(*ll0, lat, lon)
+    asr, aaz = vdist(ll0[0], ll0[1], lat, lon)
 
     return time.monotonic() - tic
 


=====================================
setup.cfg
=====================================
@@ -1,9 +1,11 @@
 [metadata]
 name = pymap3d
-version = 2.5.0
+version = 2.6.0
 author = Michael Hirsch, Ph.D.
 author_email = scivision at users.noreply.github.com
 description = pure Python (no prereqs) coordinate conversions, following convention of several popular Matlab routines.
+long_description = file: README.md
+long_description_content_type = text/markdown
 url = https://github.com/geospace-code/pymap3d
 keywords =
   coordinate conversion


=====================================
src/pymap3d/aer.py
=====================================
@@ -20,15 +20,15 @@ __all__ = ["aer2ecef", "ecef2aer", "geodetic2aer", "aer2geodetic", "eci2aer", "a
 
 
 def ecef2aer(
-    x: float | ndarray,
-    y: float | ndarray,
-    z: float | ndarray,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    x: ndarray,
+    y: ndarray,
+    z: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float, float, float]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     compute azimuth, elevation and slant range from an Observer to a Point with ECEF coordinates.
 
@@ -69,15 +69,15 @@ def ecef2aer(
 
 
 def geodetic2aer(
-    lat: float,
-    lon: float,
-    h: float,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    lat: ndarray,
+    lon: ndarray,
+    h: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float, float, float]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     gives azimuth, elevation and slant range from an Observer to a Point with geodetic coordinates.
 
@@ -117,15 +117,15 @@ def geodetic2aer(
 
 
 def aer2geodetic(
-    az: float,
-    el: float,
-    srange: float,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    az: ndarray,
+    el: ndarray,
+    srange: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     gives geodetic coordinates of a point with az, el, range
     from an observer at lat0, lon0, h0
@@ -167,17 +167,17 @@ def aer2geodetic(
 
 
 def eci2aer(
-    x: float,
-    y: float,
-    z: float,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    x: ndarray,
+    y: ndarray,
+    z: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     t: datetime,
     *,
     deg: bool = True,
     use_astropy: bool = True
-) -> tuple[float, float, float]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     takes Earth Centered Inertial x,y,z ECI coordinates of point and gives az, el, slant range from Observer
 
@@ -221,18 +221,18 @@ def eci2aer(
 
 
 def aer2eci(
-    az: float,
-    el: float,
-    srange: float,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    az: ndarray,
+    el: ndarray,
+    srange: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     t: datetime,
     ell=None,
     *,
     deg: bool = True,
     use_astropy: bool = True
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     gives ECI of a point from an observer at az, el, slant range
 
@@ -280,15 +280,15 @@ def aer2eci(
 
 
 def aer2ecef(
-    az: float,
-    el: float,
-    srange: float,
-    lat0: float,
-    lon0: float,
-    alt0: float,
+    az: ndarray,
+    el: ndarray,
+    srange: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    alt0: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float, float, float]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     converts target azimuth, elevation, range from observer at lat0,lon0,alt0 to ECEF coordinates.
 


=====================================
src/pymap3d/ecef.py
=====================================
@@ -15,12 +15,10 @@ try:
         arctan2 as atan2,
         sqrt,
         pi,
-        vectorize,
     )
 except ImportError:
     from math import radians, sin, cos, tan, atan, hypot, degrees, atan2, sqrt, pi  # type: ignore
 
-    vectorize = None
     ndarray = typing.Any  # type: ignore
 
 from datetime import datetime
@@ -51,12 +49,12 @@ __all__ = [
 
 
 def geodetic2ecef(
-    lat: float | ndarray,
-    lon: float | ndarray,
-    alt: float | ndarray,
+    lat: ndarray,
+    lon: ndarray,
+    alt: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     point transformation from Geodetic of specified ellipsoid (default WGS-84) to ECEF
 
@@ -105,27 +103,12 @@ def geodetic2ecef(
 
 
 def ecef2geodetic(
-    x: float | ndarray,
-    y: float | ndarray,
-    z: float | ndarray,
-    ell: Ellipsoid = None,
-    deg: bool = True,
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
-    if vectorize is not None:
-        fun = vectorize(ecef2geodetic_point)
-        lat, lon, alt = fun(x, y, z, ell, deg)
-        return lat[()], lon[()], alt[()]
-    else:
-        return ecef2geodetic_point(x, y, z, ell, deg)
-
-
-def ecef2geodetic_point(
-    x: float | ndarray,
-    y: float | ndarray,
-    z: float | ndarray,
+    x: ndarray,
+    y: ndarray,
+    z: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     convert ECEF (meters) to geodetic coordinates
 
@@ -155,6 +138,7 @@ def ecef2geodetic_point(
     You, Rey-Jer. (2000). Transformation of Cartesian to Geodetic Coordinates without Iterations.
     Journal of Surveying Engineering. doi: 10.1061/(ASCE)0733-9453
     """
+
     if ell is None:
         ell = Ellipsoid()
 
@@ -199,8 +183,14 @@ def ecef2geodetic_point(
         + z ** 2 / ell.semiminor_axis ** 2
         < 1
     )
-    if inside:
-        alt = -alt
+
+    try:
+        if inside.any():  # type: ignore
+            # avoid all false assignment bug
+            alt[inside] = -alt
+    except (TypeError, AttributeError):
+        if inside:
+            alt = -alt
 
     if deg:
         lat = degrees(lat)
@@ -255,15 +245,15 @@ def ecef2enuv(
 
 
 def ecef2enu(
-    x: float | ndarray,
-    y: float | ndarray,
-    z: float | ndarray,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    x: ndarray,
+    y: ndarray,
+    z: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     from observer to target, ECEF => ENU
 
@@ -302,13 +292,13 @@ def ecef2enu(
 
 
 def enu2uvw(
-    east: float,
-    north: float,
-    up: float,
-    lat0: float,
-    lon0: float,
+    east: ndarray,
+    north: ndarray,
+    up: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
     deg: bool = True,
-) -> tuple[float, float, float]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     Parameters
     ----------
@@ -342,8 +332,8 @@ def enu2uvw(
 
 
 def uvw2enu(
-    u: float, v: float, w: float, lat0: float, lon0: float, deg: bool = True
-) -> tuple[float, float, float]:
+    u: ndarray, v: ndarray, w: ndarray, lat0: ndarray, lon0: ndarray, deg: bool = True
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     Parameters
     ----------
@@ -384,7 +374,7 @@ def eci2geodetic(
     *,
     deg: bool = True,
     use_astropy: bool = True
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     convert Earth Centered Internal ECI to geodetic coordinates
 
@@ -427,9 +417,9 @@ def eci2geodetic(
 
 
 def geodetic2eci(
-    lat: float | ndarray,
-    lon: float | ndarray,
-    alt: float | ndarray,
+    lat: ndarray,
+    lon: ndarray,
+    alt: ndarray,
     t: datetime,
     ell: Ellipsoid = None,
     *,
@@ -478,15 +468,15 @@ def geodetic2eci(
 
 
 def enu2ecef(
-    e1: float,
-    n1: float,
-    u1: float,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    e1: ndarray,
+    n1: ndarray,
+    u1: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float, float, float]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     ENU to ECEF
 


=====================================
src/pymap3d/enu.py
=====================================
@@ -3,11 +3,10 @@ from __future__ import annotations
 import typing
 
 try:
-    from numpy import radians, sin, cos, hypot, arctan2 as atan2, degrees, pi, vectorize, ndarray
+    from numpy import asarray, radians, sin, cos, hypot, arctan2 as atan2, degrees, pi, ndarray
 except ImportError:
     from math import radians, sin, cos, hypot, atan2, degrees, pi  # type: ignore
 
-    vectorize = None
     ndarray = typing.Any  # type: ignore
 
 from .ecef import geodetic2ecef, ecef2geodetic, enu2ecef, uvw2enu
@@ -20,8 +19,8 @@ __all__ = ["enu2aer", "aer2enu", "enu2geodetic", "geodetic2enu"]
 
 
 def enu2aer(
-    e: float | ndarray, n: float | ndarray, u: float | ndarray, deg: bool = True
-) -> tuple[float, float, float]:
+    e: ndarray, n: ndarray, u: ndarray, deg: bool = True
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     ENU to Azimuth, Elevation, Range
 
@@ -48,26 +47,19 @@ def enu2aer(
         slant range [meters]
     """
 
-    if vectorize is not None:
-        fun = vectorize(enu2aer_point)
-        az, el, rng = fun(e, n, u, deg)
-        return az[()], el[()], rng[()]
-    else:
-        return enu2aer_point(e, n, u, deg)
-
-
-def enu2aer_point(
-    e: float | ndarray, n: float | ndarray, u: float | ndarray, deg: bool = True
-) -> tuple[float, float, float]:
-
     # 1 millimeter precision for singularity
 
-    if abs(e) < 1e-3:
-        e = 0.0
-    if abs(n) < 1e-3:
-        n = 0.0
-    if abs(u) < 1e-3:
-        u = 0.0
+    try:
+        e[abs(e) < 1e-3] = 0.0
+        n[abs(n) < 1e-3] = 0.0
+        u[abs(u) < 1e-3] = 0.0
+    except TypeError:
+        if abs(e) < 1e-3:
+            e = 0.0  # type: ignore
+        if abs(n) < 1e-3:
+            n = 0.0  # type: ignore
+        if abs(u) < 1e-3:
+            u = 0.0  # type: ignore
 
     r = hypot(e, n)
     slantRange = hypot(r, u)
@@ -81,18 +73,9 @@ def enu2aer_point(
     return az, elev, slantRange
 
 
-def aer2enu(az: float, el: float, srange: float, deg: bool = True) -> tuple[float, float, float]:
-    if vectorize is not None:
-        fun = vectorize(aer2enu_point)
-        e, n, u = fun(az, el, srange, deg)
-        return e[()], n[()], u[()]
-    else:
-        return aer2enu_point(az, el, srange, deg)
-
-
-def aer2enu_point(
-    az: float, el: float, srange: float, deg: bool = True
-) -> tuple[float, float, float]:
+def aer2enu(
+    az: ndarray, el: ndarray, srange: float | ndarray, deg: bool = True
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     Azimuth, Elevation, Slant range to target to East, North, Up
 
@@ -120,8 +103,12 @@ def aer2enu_point(
         el = radians(el)
         az = radians(az)
 
-    if srange < 0:
-        raise ValueError("Slant range  [0, Infinity)")
+    try:
+        if (asarray(srange) < 0).any():
+            raise ValueError("Slant range  [0, Infinity)")
+    except NameError:
+        if srange < 0:
+            raise ValueError("Slant range  [0, Infinity)")
 
     r = srange * cos(el)
 
@@ -129,15 +116,15 @@ def aer2enu_point(
 
 
 def enu2geodetic(
-    e: float,
-    n: float,
-    u: float,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    e: ndarray,
+    n: ndarray,
+    u: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     East, North, Up to target to geodetic coordinates
 
@@ -177,15 +164,15 @@ def enu2geodetic(
 
 
 def geodetic2enu(
-    lat: float,
-    lon: float,
-    h: float,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    lat: ndarray,
+    lon: ndarray,
+    h: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float, float, float]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     Parameters
     ----------


=====================================
src/pymap3d/latitude.py
=====================================
@@ -7,7 +7,7 @@ from .utils import sanitize
 from .rcurve import rcurve_transverse
 
 try:
-    from numpy import radians, degrees, tan, sin, exp, pi, sqrt, inf, vectorize, ndarray
+    from numpy import radians, degrees, tan, sin, exp, pi, sqrt, inf, ndarray
     from numpy import arctan as atan, arcsinh as asinh, arctanh as atanh  # noqa: A001
 
     use_numpy = True
@@ -37,8 +37,8 @@ __all__ = [
 
 
 def geoc2geod(
-    geocentric_lat: float,
-    geocentric_distance: float,
+    geocentric_lat: ndarray,
+    geocentric_distance: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
 ) -> float:
@@ -162,28 +162,7 @@ def geocentric2geodetic(
     return degrees(geodetic_lat) if deg else geodetic_lat
 
 
-def geodetic2isometric_point(geodetic_lat: float, ell: Ellipsoid = None, deg: bool = True) -> float:
-    geodetic_lat, ell = sanitize(geodetic_lat, ell, deg)
-
-    e = ell.eccentricity
-
-    if abs(geodetic_lat - pi / 2) <= 1e-9:
-        isometric_lat = inf
-    elif abs(-geodetic_lat - pi / 2) <= 1e-9:
-        isometric_lat = -inf
-    else:
-        isometric_lat = asinh(tan(geodetic_lat)) - e * atanh(e * sin(geodetic_lat))
-        # same results
-        # a1 = e * sin(geodetic_lat)
-        # y = (1 - a1) / (1 + a1)
-        # a2 = pi / 4 + geodetic_lat / 2
-        # isometric_lat = log(tan(a2) * (y ** (e / 2)))
-        # isometric_lat = log(tan(a2)) + e/2 * log((1-e*sin(geodetic_lat)) / (1+e*sin(geodetic_lat)))
-
-    return degrees(isometric_lat) if deg else isometric_lat
-
-
-def geodetic2isometric(geodetic_lat: float, ell: Ellipsoid = None, deg: bool = True) -> float:
+def geodetic2isometric(geodetic_lat: ndarray, ell: Ellipsoid = None, deg: bool = True) -> float:
     """
     computes isometric latitude on an ellipsoid
 
@@ -212,11 +191,29 @@ def geodetic2isometric(geodetic_lat: float, ell: Ellipsoid = None, deg: bool = T
     School of Mathematical and Geospatial Sciences, RMIT University,
     January 2010
     """
-    if use_numpy:
-        fun = vectorize(geodetic2isometric_point)
-        return fun(geodetic_lat, ell, deg)[()]
-    else:
-        return geodetic2isometric_point(geodetic_lat, ell, deg)
+
+    geodetic_lat, ell = sanitize(geodetic_lat, ell, deg)
+
+    e = ell.eccentricity
+
+    isometric_lat = asinh(tan(geodetic_lat)) - e * atanh(e * sin(geodetic_lat))
+    # same results
+    # a1 = e * sin(geodetic_lat)
+    # y = (1 - a1) / (1 + a1)
+    # a2 = pi / 4 + geodetic_lat / 2
+    # isometric_lat = log(tan(a2) * (y ** (e / 2)))
+    # isometric_lat = log(tan(a2)) + e/2 * log((1-e*sin(geodetic_lat)) / (1+e*sin(geodetic_lat)))
+
+    try:
+        isometric_lat[abs(geodetic_lat - pi / 2) <= 1e-9] = inf
+        isometric_lat[abs(-geodetic_lat - pi / 2) <= 1e-9] = -inf
+    except TypeError:
+        if abs(geodetic_lat - pi / 2) <= 1e-9:
+            isometric_lat = inf
+        elif abs(-geodetic_lat - pi / 2) <= 1e-9:
+            isometric_lat = -inf
+
+    return degrees(isometric_lat) if deg else isometric_lat
 
 
 def isometric2geodetic(isometric_lat: float, ell: Ellipsoid = None, deg: bool = True) -> float:
@@ -327,14 +324,7 @@ def geodetic2conformal(geodetic_lat: float, ell: Ellipsoid = None, deg: bool = T
     Office, Washington, DC, 1987, pp. 13-18.
 
     """
-    if use_numpy:
-        fun = vectorize(geodetic2conformal_point)
-        return fun(geodetic_lat, ell, deg)[()]
-    else:
-        return geodetic2conformal_point(geodetic_lat, ell, deg)
-
 
-def geodetic2conformal_point(geodetic_lat: float, ell: Ellipsoid = None, deg: bool = True) -> float:
     geodetic_lat, ell = sanitize(geodetic_lat, ell, deg)
 
     e = ell.eccentricity
@@ -353,7 +343,7 @@ def geodetic2conformal_point(geodetic_lat: float, ell: Ellipsoid = None, deg: bo
 
 
 # %% rectifying
-def geodetic2rectifying(geodetic_lat: float, ell: Ellipsoid = None, deg: bool = True) -> float:
+def geodetic2rectifying(geodetic_lat: ndarray, ell: Ellipsoid = None, deg: bool = True) -> float:
     """
     converts from geodetic latitude to rectifying latitude
 
@@ -399,7 +389,9 @@ def geodetic2rectifying(geodetic_lat: float, ell: Ellipsoid = None, deg: bool =
     return degrees(rectifying_lat) if deg else rectifying_lat
 
 
-def rectifying2geodetic(rectifying_lat: float, ell: Ellipsoid = None, deg: bool = True) -> float:
+def rectifying2geodetic(
+    rectifying_lat: ndarray, ell: Ellipsoid = None, deg: bool = True
+) -> ndarray:
     """
     converts from rectifying latitude to geodetic latitude
 


=====================================
src/pymap3d/los.py
=====================================
@@ -4,11 +4,10 @@ from __future__ import annotations
 import typing
 
 try:
-    from numpy import pi, nan, sqrt, vectorize, ndarray
+    from numpy import pi, nan, sqrt, ndarray, atleast_1d
 except ImportError:
     from math import pi, nan, sqrt  # type: ignore
 
-    vectorize = None
     ndarray = typing.Any  # type: ignore
 
 from .aer import aer2enu
@@ -19,14 +18,14 @@ __all__ = ["lookAtSpheroid"]
 
 
 def lookAtSpheroid(
-    lat0: float,
-    lon0: float,
-    h0: float,
-    az: float,
-    tilt: float,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
+    az: ndarray,
+    tilt: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     Calculates line-of-sight intersection with Earth (or other ellipsoid) surface from above surface / orbit
 
@@ -62,23 +61,6 @@ def lookAtSpheroid(
 
     Algorithm based on https://medium.com/@stephenhartzell/satellite-line-of-sight-intersection-with-earth-d786b4a6a9b6 Stephen Hartzell
     """
-    if vectorize is not None:
-        fun = vectorize(lookAtSpheroid_point)
-        lat, lon, d = fun(lat0, lon0, h0, az, tilt, ell, deg)
-        return lat[()], lon[()], d[()]
-    else:
-        return lookAtSpheroid_point(lat0, lon0, h0, az, tilt, ell, deg)
-
-
-def lookAtSpheroid_point(
-    lat0: float,
-    lon0: float,
-    h0: float,
-    az: float,
-    tilt: float,
-    ell: Ellipsoid = None,
-    deg: bool = True,
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
 
     if h0 < 0:
         raise ValueError("Intersection calculation requires altitude  [0, Infinity)")
@@ -86,6 +68,13 @@ def lookAtSpheroid_point(
     if ell is None:
         ell = Ellipsoid()
 
+    try:
+        lat0 = atleast_1d(lat0)
+        lon0 = atleast_1d(lon0)
+        tilt = atleast_1d(tilt)
+    except NameError:
+        pass
+
     a = ell.semimajor_axis
     b = ell.semimajor_axis
     c = ell.semiminor_axis
@@ -115,14 +104,22 @@ def lookAtSpheroid_point(
     magnitude = a ** 2 * b ** 2 * w ** 2 + a ** 2 * c ** 2 * v ** 2 + b ** 2 * c ** 2 * u ** 2
 
     # %%   Return nan if radical < 0 or d < 0 because LOS vector does not point towards Earth
-    if radical > 0:
+    try:
         d = (value - a * b * c * sqrt(radical)) / magnitude
-    else:
-        d = nan
+        d[radical < 0] = nan
+        d[d < 0] = nan
+    except ValueError:
+        if radical < 0:
+            d = nan
+        if d < 0:
+            d = nan
+    except TypeError:
+        pass
 
-    if d < 0:
-        d = nan
     # %% cartesian to ellipsodal
     lat, lon, _ = ecef2geodetic(x + d * u, y + d * v, z + d * w, deg=deg)
 
-    return lat, lon, d
+    try:
+        return lat.squeeze()[()], lon.squeeze()[()], d.squeeze()[()]
+    except AttributeError:
+        return lat, lon, d


=====================================
src/pymap3d/lox.py
=====================================
@@ -4,11 +4,10 @@ from __future__ import annotations
 import typing
 
 try:
-    from numpy import radians, degrees, cos, arctan2 as atan2, tan, pi, vectorize, ndarray
+    from numpy import radians, degrees, cos, arctan2 as atan2, tan, pi, ndarray, atleast_1d
 except ImportError:
     from math import radians, degrees, cos, atan2, tan, pi  # type: ignore
 
-    vectorize = None
     ndarray = typing.Any  # type: ignore
 
 import typing
@@ -34,7 +33,7 @@ __all__ = [
 ]
 
 
-def meridian_dist(lat: float, ell: Ellipsoid = None, deg: bool = True) -> float:
+def meridian_dist(lat: ndarray, ell: Ellipsoid = None, deg: bool = True) -> float:
     """
     Computes the ground distance on an ellipsoid from the equator to the input latitude.
 
@@ -52,10 +51,10 @@ def meridian_dist(lat: float, ell: Ellipsoid = None, deg: bool = True) -> float:
     dist : float
          distance (meters)
     """
-    return meridian_arc(0, lat, ell, deg)
+    return meridian_arc(0.0, lat, ell, deg)
 
 
-def meridian_arc(lat1: float, lat2: float, ell: Ellipsoid = None, deg: bool = True) -> float:
+def meridian_arc(lat1, lat2: ndarray, ell: Ellipsoid = None, deg: bool = True) -> float:
     """
     Computes the ground distance on an ellipsoid between two latitudes.
 
@@ -84,10 +83,10 @@ def meridian_arc(lat1: float, lat2: float, ell: Ellipsoid = None, deg: bool = Tr
 
 
 def loxodrome_inverse(
-    lat1: float,
-    lon1: float,
-    lat2: float,
-    lon2: float,
+    lat1: ndarray,
+    lon1: ndarray,
+    lat2: ndarray,
+    lon2: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
 ) -> tuple[float, float]:
@@ -135,17 +134,7 @@ def loxodrome_inverse(
     Survey, U.S. Department of Commerce, Washington, DC: U.S.
     Government Printing Office, p. 66.
     """
-    if vectorize is not None:
-        fun = vectorize(loxodrome_inverse_point)
-        lox, az = fun(lat1, lon1, lat2, lon2, ell, deg)
-        return lox[()], az[()]
-    else:
-        return loxodrome_inverse_point(lat1, lon1, lat2, lon2, ell, deg)
 
-
-def loxodrome_inverse_point(
-    lat1: float, lon1: float, lat2: float, lon2: float, ell: Ellipsoid = None, deg: bool = True
-) -> typing.Tuple[float, float]:
     if deg:
         lat1, lon1, lat2, lon2 = radians(lat1), radians(lon1), radians(lat2), radians(lon2)
 
@@ -160,10 +149,13 @@ def loxodrome_inverse_point(
     cosaz12 = cos(az12)
 
     # compute distance along loxodromic curve
-    if abs(cosaz12) < 1e-9:  # straight east or west
-        dist = departure(lon2, lon1, lat1, ell, deg=False)
-    else:
-        dist = meridian_arc(lat2, lat1, deg=False, ell=ell) / abs(cos(az12))
+    dist = meridian_arc(lat2, lat1, deg=False, ell=ell) / abs(cos(az12))
+    try:
+        if (abs(cosaz12) < 1e-9).any():
+            dist[abs(cosaz12) < 1e-9] = departure(lon2, lon1, lat1, ell, deg=False)
+    except (AttributeError, TypeError):
+        if abs(cosaz12) < 1e-9:  # straight east or west
+            dist = departure(lon2, lon1, lat1, ell, deg=False)
 
     if deg:
         az12 = degrees(az12) % 360.0
@@ -172,13 +164,13 @@ def loxodrome_inverse_point(
 
 
 def loxodrome_direct(
-    lat1: float,
-    lon1: float,
-    rng: float,
+    lat1: ndarray,
+    lon1: ndarray,
+    rng: ndarray,
     a12: float,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float, float]:
+) -> tuple[ndarray, ndarray]:
     """
     Given starting lat, lon with arclength and azimuth, compute final lat, lon
 
@@ -206,25 +198,22 @@ def loxodrome_direct(
     lon2 : float
         final geodetic longitude (degrees)
     """
-    if vectorize is not None:
-        fun = vectorize(loxodrome_direct_point)
-        lat2, lon2 = fun(lat1, lon1, rng, a12, ell, deg)
-        return lat2[()], lon2[()]
-    else:
-        return loxodrome_direct_point(lat1, lon1, rng, a12, ell, deg)
-
-
-def loxodrome_direct_point(
-    lat1: float, lon1: float, rng: float, a12: float, ell: Ellipsoid = None, deg: bool = True
-) -> typing.Tuple[float, float]:
 
     if deg:
         lat1, lon1, a12 = radians(lat1), radians(lon1), radians(a12)
 
-    if abs(lat1) > pi / 2:
-        raise ValueError("-90 <= latitude <= 90")
-    if rng < 0:
-        raise ValueError("ground distance must be >= 0")
+    try:
+        lat1 = atleast_1d(lat1)
+        rng = atleast_1d(rng)
+        if (abs(lat1) > pi / 2).any():
+            raise ValueError("-90 <= latitude <= 90")
+        if (rng < 0).any():
+            raise ValueError("ground distance must be >= 0")
+    except NameError:
+        if abs(lat1) > pi / 2:
+            raise ValueError("-90 <= latitude <= 90")
+        if rng < 0:
+            raise ValueError("ground distance must be >= 0")
 
     #   compute rectifying sphere latitude and radius
     reclat = geodetic2rectifying(lat1, ell, deg=False)
@@ -243,11 +232,14 @@ def loxodrome_direct_point(
     if deg:
         lat2, lon2 = degrees(lat2), degrees(lon2)
 
-    return lat2, lon2
+    try:
+        return lat2.squeeze()[()], lon2.squeeze()[()]
+    except AttributeError:
+        return lat2, lon2
 
 
 def departure(
-    lon1: float, lon2: float, lat: float, ell: Ellipsoid = None, deg: bool = True
+    lon1: ndarray, lon2: ndarray, lat: ndarray, ell: Ellipsoid = None, deg: bool = True
 ) -> float:
     """
     Computes the distance along a specific parallel between two meridians.


=====================================
src/pymap3d/ned.py
=====================================
@@ -14,8 +14,8 @@ from .ellipsoid import Ellipsoid
 
 
 def aer2ned(
-    az: float, elev: float, slantRange: float, deg: bool = True
-) -> tuple[float, float, float]:
+    az: ndarray, elev: ndarray, slantRange: ndarray, deg: bool = True
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     converts azimuth, elevation, range to target from observer to North, East, Down
 
@@ -45,7 +45,9 @@ def aer2ned(
     return n, e, -u
 
 
-def ned2aer(n: float, e: float, d: float, deg: bool = True) -> tuple[float, float, float]:
+def ned2aer(
+    n: ndarray, e: ndarray, d: ndarray, deg: bool = True
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     converts North, East, Down to azimuth, elevation, range
 
@@ -75,15 +77,15 @@ def ned2aer(n: float, e: float, d: float, deg: bool = True) -> tuple[float, floa
 
 
 def ned2geodetic(
-    n: float,
-    e: float,
-    d: float,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    n: ndarray,
+    e: ndarray,
+    d: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     Converts North, East, Down to target latitude, longitude, altitude
 
@@ -124,15 +126,15 @@ def ned2geodetic(
 
 
 def ned2ecef(
-    n: float,
-    e: float,
-    d: float,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    n: ndarray,
+    e: ndarray,
+    d: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float, float, float]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     North, East, Down to target ECEF coordinates
 
@@ -170,15 +172,15 @@ def ned2ecef(
 
 
 def ecef2ned(
-    x: float,
-    y: float,
-    z: float,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    x: ndarray,
+    y: ndarray,
+    z: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     Convert ECEF x,y,z to North, East, Down
 
@@ -219,15 +221,15 @@ def ecef2ned(
 
 
 def geodetic2ned(
-    lat: float,
-    lon: float,
-    h: float,
-    lat0: float,
-    lon0: float,
-    h0: float,
+    lat: ndarray,
+    lon: ndarray,
+    h: ndarray,
+    lat0: ndarray,
+    lon0: ndarray,
+    h0: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> tuple[float | ndarray, float | ndarray, float | ndarray]:
+) -> tuple[ndarray, ndarray, ndarray]:
     """
     convert latitude, longitude, altitude of target to North, East, Down from observer
 


=====================================
src/pymap3d/rcurve.py
=====================================
@@ -36,7 +36,7 @@ def geocentric_radius(geodetic_lat: float, ell: Ellipsoid = None, deg: bool = Tr
     )
 
 
-def rcurve_parallel(lat: float, ell: Ellipsoid = None, deg: bool = True) -> float:
+def rcurve_parallel(lat: ndarray, ell: Ellipsoid = None, deg: bool = True) -> float:
     """
     computes the radius of the small circle encompassing the globe at the specified latitude
 


=====================================
src/pymap3d/rsphere.py
=====================================
@@ -94,13 +94,13 @@ def rsphere_rectifying(ell: Ellipsoid = None) -> float:
 
 
 def rsphere_euler(
-    lat1: float | ndarray,
-    lon1: float,
-    lat2: float | ndarray,
-    lon2: float,
+    lat1: ndarray,
+    lon1: ndarray,
+    lat2: ndarray,
+    lon2: ndarray,
     ell: Ellipsoid = None,
     deg: bool = True,
-) -> float:
+) -> ndarray:
     """computes the Euler radii of curvature at the midpoint of the
      great circle arc defined by the endpoints (lat1,lon1) and (lat2,lon2)
 


=====================================
src/pymap3d/tests/__init__.py
=====================================


=====================================
src/pymap3d/tests/test_aer.py
=====================================
@@ -16,6 +16,9 @@ def test_aer2ecef(aer, lla, xyz):
     assert x == approx(xyz[0])
     assert y == approx(xyz[1])
     assert z == approx(xyz[2])
+    assert isinstance(x, float)
+    assert isinstance(y, float)
+    assert isinstance(z, float)
 
     raer = (radians(aer[0]), radians(aer[1]), aer[2])
     rlla = (radians(lla[0]), radians(lla[1]), lla[2])


=====================================
src/pymap3d/tests/test_geodetic.py
=====================================
@@ -37,17 +37,11 @@ def test_3d_geodetic2ecef():
     assert (x0, y0, z0) == approx(xyz0)
 
 
- at pytest.mark.parametrize(
-    "xyz", [(xyz0[0], xyz0[1], xyz0[2]), ([xyz0[0]], [xyz0[1]], [xyz0[2]])], ids=("scalar", "list")
-)
-def test_scalar_ecef2geodetic(xyz):
+def test_scalar_ecef2geodetic():
     """
     verify we can handle the wide variety of input data type users might use
     """
-    if isinstance(xyz[0], list):
-        pytest.importorskip("numpy")
-
-    lat, lon, alt = pm.ecef2geodetic(*xyz)
+    lat, lon, alt = pm.ecef2geodetic(xyz0[0], xyz0[1], xyz0[2])
 
     assert [lat, lon, alt] == approx(lla0, rel=1e-4)
 


=====================================
src/pymap3d/tests/test_vincenty_vreckon.py
=====================================
@@ -30,7 +30,10 @@ def test_unit(lat, lon, srange, az, lato, lono):
     lat1, lon1 = vincenty.vreckon(lat, lon, srange, az)
 
     assert lat1 == approx(lato)
+    assert isinstance(lat1, float)
+
     assert lon1 == approx(lono, rel=0.001)
+    assert isinstance(lon1, float)
 
 
 def test_az_vector():


=====================================
src/pymap3d/utils.py
=====================================
@@ -8,26 +8,26 @@ import typing
 from .ellipsoid import Ellipsoid
 
 try:
-    from numpy import hypot, cos, sin, arctan2 as atan2, radians, pi, asarray, ndarray
+    from numpy import hypot, cos, sin, arctan2 as atan2, radians, pi, asarray, ndarray, sign
 except ImportError:
     from math import atan2, hypot, cos, sin, radians, pi  # type: ignore
 
     asarray = None
     ndarray = typing.Any  # type: ignore
 
-__all__ = ["cart2pol", "pol2cart", "cart2sph", "sph2cart", "sign"]
+    def sign(x: float) -> float:  # type: ignore
+        """ signum function """
+        if x < 0:
+            y = -1.0
+        elif x > 0:
+            y = 1.0
+        else:
+            y = 0.0
 
+        return y
 
-def sign(x: float) -> float:
-    """ signum function """
-    if x < 0:
-        y = -1.0
-    elif x > 0:
-        y = 1.0
-    else:
-        y = 0.0
 
-    return y
+__all__ = ["cart2pol", "pol2cart", "cart2sph", "sph2cart", "sign"]
 
 
 def cart2pol(x: float, y: float) -> tuple[float, float]:


=====================================
src/pymap3d/vincenty.py
=====================================
@@ -5,13 +5,27 @@ Vincenty's methods for computing ground distance and reckoning
 from __future__ import annotations
 import typing
 import logging
-from math import sqrt, tan, sin, cos, isnan, atan, atan2, asin, nan, pi, radians, degrees
+from math import nan, pi
 from copy import copy
 
 try:
-    from numpy import ndarray, vectorize
+    from numpy import (
+        ndarray,
+        atleast_1d,
+        sqrt,
+        tan,
+        sin,
+        cos,
+        isnan,
+        arctan as atan,
+        arctan2 as atan2,
+        arcsin as asin,
+        radians,
+        degrees,
+    )
 except ImportError:
-    vectorize = None
+    from math import sqrt, tan, sin, cos, isnan, atan, atan2, asin, radians, degrees  # type: ignore
+
     ndarray = typing.Any  # type: ignore
 
 from .ellipsoid import Ellipsoid
@@ -21,8 +35,12 @@ __all__ = ["vdist", "vreckon", "track2"]
 
 
 def vdist(
-    Lat1: float | ndarray, Lon1: float, Lat2: float | ndarray, Lon2: float, ell: Ellipsoid = None
-) -> tuple[float, float]:
+    Lat1: float | ndarray,
+    Lon1: float | ndarray,
+    Lat2: float | ndarray,
+    Lon2: float | ndarray,
+    ell: Ellipsoid = None,
+) -> tuple[ndarray, ndarray]:
     """
     Using the reference ellipsoid, compute the distance between two points
     within a few millimeters of accuracy, compute forward azimuth,
@@ -93,22 +111,20 @@ def vdist(
      11. Azimuths agree with the Mapping Toolbox, version 2.2 (R14SP3) to within about a hundred-thousandth of a degree, except when traversing to or from a pole, where the convention for this function is described in (10), and except in the cases noted above in (9).
      12. No warranties; use at your own risk.
     """
-    if vectorize is not None:
-        fun = vectorize(vdist_point)
-        dist, az = fun(Lat1, Lon1, Lat2, Lon2, ell)
-        return dist[()], az[()]
-    else:
-        return vdist_point(Lat1, Lon1, Lat2, Lon2, ell)
-
 
-def vdist_point(
-    Lat1: float | ndarray, Lon1: float, Lat2: float | ndarray, Lon2: float, ell: Ellipsoid = None
-) -> tuple[float, float]:
     if ell is None:
         ell = Ellipsoid()
     # %% Input check:
-    if (abs(Lat1) > 90) | (abs(Lat2) > 90):
-        raise ValueError("Input latitudes must be in [-90, 90] degrees.")
+    try:
+        Lat1 = atleast_1d(Lat1)
+        Lon1 = atleast_1d(Lon1)
+        Lat2 = atleast_1d(Lat2)
+        Lon2 = atleast_1d(Lon2)
+        if (abs(Lat1) > 90).any() | (abs(Lat2) > 90).any():
+            raise ValueError("Input latitudes must be in [-90, 90] degrees.")
+    except NameError:
+        if (abs(Lat1) > 90) | (abs(Lat2) > 90):
+            raise ValueError("Input latitudes must be in [-90, 90] degrees.")
     # %% Supply WGS84 earth ellipsoid axis lengths in meters:
     a = ell.semimajor_axis
     b = ell.semiminor_axis
@@ -119,19 +135,30 @@ def vdist_point(
     lat2 = radians(Lat2)
     lon2 = radians(Lon2)
     # %% correct for errors at exact poles by adjusting 0.6 millimeters:
-    if abs(pi / 2 - abs(lat1)) < 1e-10:
-        lat1 = sign(lat1) * (pi / 2 - 1e-10)
+    try:
+        i = abs(pi / 2 - abs(lat1)) < 1e-10
+        lat1[i] = sign(lat1[i]) * (pi / 2 - 1e-10)
 
-    if abs(pi / 2 - abs(lat2)) < 1e-10:
-        lat2 = sign(lat2) * (pi / 2 - 1e-10)
+        i = abs(pi / 2 - abs(lat2)) < 1e-10
+        lat2[i] = sign(lat2[i]) * (pi / 2 - 1e-10)
+    except TypeError:
+        if abs(pi / 2 - abs(lat1)) < 1e-10:
+            lat1 = sign(lat1) * (pi / 2 - 1e-10)
+
+        if abs(pi / 2 - abs(lat2)) < 1e-10:
+            lat2 = sign(lat2) * (pi / 2 - 1e-10)
 
     U1 = atan((1 - f) * tan(lat1))
     U2 = atan((1 - f) * tan(lat2))
     lon1 = lon1 % (2 * pi)
     lon2 = lon2 % (2 * pi)
     L = abs(lon2 - lon1)
-    if L > pi:
-        L = 2 * pi - L
+
+    try:
+        L[L > pi] = 2 * pi - L[L > pi]
+    except TypeError:
+        if L > pi:
+            L = 2 * pi - L
 
     lamb = copy(L)  # NOTE: program will fail without copy!
     itercount = 0
@@ -161,15 +188,23 @@ def vdist_point(
 
         try:
             sinAlpha = cos(U1) * cos(U2) * sin(lamb) / sin(sigma)
-        except ZeroDivisionError:
-            sinAlpha = 0.0
-
-        if isnan(sinAlpha):
-            alpha = 0.0
-        elif sinAlpha > 1 or abs(sinAlpha - 1) < 1e-16:
-            alpha = pi / 2
-        else:
+
             alpha = asin(sinAlpha)
+            alpha[isnan(sinAlpha)] = 0
+            alpha[(sinAlpha > 1) | (abs(sinAlpha - 1) < 1e-16)] = pi / 2
+
+        except (ZeroDivisionError, TypeError, ValueError):
+            try:
+                sinAlpha = cos(U1) * cos(U2) * sin(lamb) / sin(sigma)
+            except ZeroDivisionError:
+                sinAlpha = 0.0
+
+            if isnan(sinAlpha):
+                alpha = 0.0
+            elif sinAlpha > 1 or abs(sinAlpha - 1) < 1e-16:
+                alpha = pi / 2
+            else:
+                alpha = asin(sinAlpha)
 
         cos2sigmam = cos(sigma) - 2 * sin(U1) * sin(U2) / cos(alpha) ** 2
 
@@ -180,7 +215,12 @@ def vdist_point(
         )
         # print(f'then, lambda(21752) = {lamb[21752],20})
         # correct for convergence failure for essentially antipodal points
-        if lamb > pi:
+        try:
+            i = (lamb > pi).any()
+        except AttributeError:
+            i = lamb > pi
+
+        if i:
             logging.warning(
                 "Essentially antipodal points encountered. Precision may be reduced slightly."
             )
@@ -188,7 +228,10 @@ def vdist_point(
             lambdaold = pi
             lamb = pi
 
-        notdone = abs(lamb - lambdaold) > 1e-12
+        try:
+            notdone = (abs(lamb - lambdaold) > 1e-12).any()
+        except AttributeError:
+            notdone = abs(lamb - lambdaold) > 1e-12
 
     u2 = cos(alpha) ** 2 * (a ** 2 - b ** 2) / b ** 2
     A = 1 + u2 / 16384 * (4096 + u2 * (-768 + u2 * (320 - 175 * u2)))
@@ -212,8 +255,14 @@ def vdist_point(
     # %% From point #1 to point #2
     # correct sign of lambda for azimuth calcs:
     lamb = abs(lamb)
-    if sign(sin(lon2 - lon1)) * sign(sin(lamb)) < 0:
-        lamb = -lamb
+
+    try:
+        i = sign(sin(lon2 - lon1)) * sign(sin(lamb)) < 0
+        lamb[i] = -lamb[i]
+    except TypeError:
+        if sign(sin(lon2 - lon1)) * sign(sin(lamb)) < 0:
+            lamb = -lamb
+
     numer = cos(U2) * sin(lamb)
     denom = cos(U1) * sin(U2) - sin(U1) * cos(U2) * cos(lamb)
     a12 = atan2(numer, denom)
@@ -221,12 +270,15 @@ def vdist_point(
 
     az = degrees(a12)
 
-    return dist_m, az
+    try:
+        return dist_m.squeeze()[()], az.squeeze()[()]
+    except AttributeError:
+        return dist_m, az
 
 
 def vreckon(
-    Lat1: float, Lon1: float, Rng: float, Azim: float, ell: Ellipsoid = None
-) -> tuple[float, float]:
+    Lat1: float | ndarray, Lon1: float | ndarray, Rng: ndarray, Azim: ndarray, ell: Ellipsoid = None
+) -> tuple[ndarray, ndarray]:
     """
     This is the Vincenty "forward" solution.
 
@@ -283,20 +335,21 @@ def vreckon(
     Added ellipsoid and vectorized whenever possible. Also, lon2 is always converted to the [-180 180] interval.
     Joaquim Luis
     """
-    if vectorize is not None:
-        fun = vectorize(vreckon_point)
-        return fun(Lat1, Lon1, Rng, Azim, ell)
-    else:
-        return vreckon_point(Lat1, Lon1, Rng, Azim, ell)
 
-
-def vreckon_point(
-    Lat1: float, Lon1: float, Rng: float, Azim: float, ell: Ellipsoid = None
-) -> tuple[float, float]:
-    if abs(Lat1) > 90:
-        raise ValueError("Input lat. must be between -90 and 90 deg., inclusive.")
-    if Rng < 0:
-        raise ValueError("Ground distance must be positive")
+    try:
+        Lat1 = atleast_1d(Lat1)
+        Lon1 = atleast_1d(Lon1)
+        Rng = atleast_1d(Rng)
+        Azim = atleast_1d(Azim)
+        if (abs(Lat1) > 90).any():
+            raise ValueError("Input lat. must be between -90 and 90 deg., inclusive.")
+        if (Rng < 0).any():
+            raise ValueError("Ground distance must be positive")
+    except NameError:
+        if abs(Lat1) > 90:
+            raise ValueError("Input lat. must be between -90 and 90 deg., inclusive.")
+        if Rng < 0:
+            raise ValueError("Ground distance must be positive")
 
     if ell is not None:
         a = ell.semimajor_axis
@@ -311,8 +364,12 @@ def vreckon_point(
     lon1 = radians(Lon1)  # intial longitude in radians
 
     # correct for errors at exact poles by adjusting 0.6 millimeters:
-    if abs(pi / 2 - abs(lat1)) < 1e-10:
-        lat1 = sign(lat1) * (pi / 2 - (1e-10))
+    try:
+        i = abs(pi / 2 - abs(lat1)) < 1e-10
+        lat1[i] = sign(lat1[i]) * (pi / 2 - (1e-10))
+    except TypeError:
+        if abs(pi / 2 - abs(lat1)) < 1e-10:
+            lat1 = sign(lat1) * (pi / 2 - (1e-10))
 
     alpha1 = radians(Azim)  # inital azimuth in radians
     sinAlpha1 = sin(alpha1)
@@ -334,7 +391,13 @@ def vreckon_point(
     sinSigma = nan
     cosSigma = nan
     cos2SigmaM = nan
-    while abs(sigma - sigmaP) > 1e-12:
+
+    try:
+        i = (abs(sigma - sigmaP) > 1e-12).any()
+    except AttributeError:
+        i = abs(sigma - sigmaP) > 1e-12
+
+    while i:
         cos2SigmaM = cos(2 * sigma1 + sigma)
         sinSigma = sin(sigma)
         cosSigma = cos(sigma)
@@ -357,6 +420,10 @@ def vreckon_point(
         )
         sigmaP = sigma
         sigma = Rng / (b * A) + deltaSigma
+        try:
+            i = (abs(sigma - sigmaP) > 1e-12).any()
+        except AttributeError:
+            i = abs(sigma - sigmaP) > 1e-12
 
     tmp = sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1
     lat2 = atan2(
@@ -383,18 +450,21 @@ def vreckon_point(
 
     lon2 = lon2 % 360  # follow [0, 360) convention
 
-    return degrees(lat2), lon2
+    try:
+        return degrees(lat2).squeeze()[()], lon2.squeeze()[()]
+    except AttributeError:
+        return degrees(lat2), lon2
 
 
 def track2(
-    lat1: float,
-    lon1: float,
-    lat2: float,
-    lon2: float,
+    lat1: ndarray,
+    lon1: ndarray,
+    lat2: ndarray,
+    lon2: ndarray,
     ell: Ellipsoid = None,
     npts: int = 100,
     deg: bool = True,
-) -> tuple[list[float], list[float]]:
+) -> tuple[list[ndarray], list[ndarray]]:
     """
     computes great circle tracks starting at the point lat1, lon1 and ending at lat2, lon2
 



View it on GitLab: https://salsa.debian.org/debian-gis-team/pymap3d/-/commit/486084e0fba575d50c2a1c56e23acbde439e2a5a

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/pymap3d/-/commit/486084e0fba575d50c2a1c56e23acbde439e2a5a
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20210321/8017ec90/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list