pycsamt.gis.utils#

GIS utilities for pycsamt (v2). Provides coordinate transformations between geographic (lat/lon) and UTM using GDAL (preferred) or a PROJ fallback (pyproj).

Core features#

  • Geographic ⇄ UTM conversions (scalar and vectorized).

  • Flexible latitude/longitude parsing (decimal and DMS).

  • Small helpers for UTM zones and EPSG lookup.

Examples

>>> from pycsamt.gis.utils import project_point_ll2utm
>>> project_point_ll2utm(-118.34, 34.05)

Note

Parts of this module are adapted from the MTPy project (MTgeophysics/mtpy-v2). Original work: Krieger, L., & Peacock, J. (2014), MTpy: A Python toolbox for magnetotellurics, Computers & Geosciences, 72, 167–175, https://doi.org/10.1016/j.cageo.2014.07.013.

The implementation here has been revised, fixed, and extended for pycsamt v2, with additional validation, deprecations, and compatibility layers.

Functions

assert_elevation_value(elevation)

Validate and coerce an elevation to a floating number.

assert_lat_value(latitude)

Validate and coerce a latitude to decimal degrees.

assert_lon_value(longitude)

Validate and coerce a longitude to decimal degrees.

assert_xy_coordinate_system(x, y)

Infer the coordinate system of paired x and y arrays.

calculate_azimuth(easting, northing)

Calculates the forward azimuth between consecutive points.

convert_position_float2str(position)

Convert a decimal-degree value to DD:MM:SS.ss string.

convert_position_str2float(position_str)

Convert DMS string 'DD:MM:SS.sss' or decimal string to a float in decimal degrees.

decimal_to_dms(position)

Convert decimal degrees to a DD:MM:SS.ss string.

dms_to_decimal(position_str)

Convert a DMS string or decimal degrees to a float.

epsg_project(x, y, epsg_from, epsg_to)

Project coordinates between two EPSG-defined CRSs.

get_elevation_from_api(-> float)

Fetches elevation from sea level for geographic coords.

get_elevation_from_utm(-> float)

Fetches elevation from sea level for UTM coordinates.

get_epsg(latitude, longitude)

Get the WGS84 UTM EPSG code for a geographic coordinate.

get_utm_string_from_sr(spatial_ref)

[DEPRECATED] GDAL SpatialReference → UTM string is deprecated; use 'get_utm_zone' for standard UTM formatting.

get_utm_zone(latitude, longitude)

Compute the UTM zone, hemisphere flag, and zone string for a geographic coordinate.

ll_to_utm(reference_ellipsoid, lat, lon)

Convert latitude/longitude to UTM coordinates.

normalize_lat_lon(-> tuple[float, float])

Resolve input pair to (lat, lon) regardless of order.

project_point_ll2utm(lat, lon[, datum, ...])

Transform one geographic point to UTM coordinates.

project_point_utm2ll(easting, northing, utm_zone)

Transform a UTM point to latitude/longitude.

project_points_ll2utm(lat, lon[, datum, ...])

Transform latitude/longitude to UTM coordinates.

to_ll(-> ~pandas.DataFrame  -> tuple[float, ...)

Convert UTM coordinates to geographic (lat, lon) using project_point_utm2ll().

to_utm(-> ~pandas.DataFrame  -> tuple[float, ...)

Convert geographic coordinates to UTM using project_point_ll2utm().

transform_ll_to_utm(lon, lat[, ...])

[DEPRECATED] Deprecated; will be removed in a future release.

transform_utm_to_ll(easting, northing, zone)

[DEPRECATED] Deprecated; will be removed in a future release.

utm_to_ll(reference_ellipsoid, northing, ...)

Convert UTM coordinates to latitude/longitude.

utm_wgs84_conv(lat, lon)

Convert WGS84 (lat, lon) to UTM and verify round-trip.

utm_zone_to_epsg(zone_number, is_northern)

Resolve the WGS84 UTM EPSG code for a given UTM zone.

pycsamt.gis.utils.project_point_ll2utm(lat, lon, datum='WGS84', utm_zone=None, epsg=None)[source]#

Transform one geographic point to UTM coordinates.

Accepts a single point or 1-D arrays/Series of points in decimal degrees (or DMS strings) and returns UTM easting, northing, and zone. Uses GDAL/OSR when available; falls back to PROJ via pyproj with explicit XY order.

Parameters:
  • lat (float or str, array-like, or pd.Series) – Latitude in decimal degrees or sexagesimal string ("DD:MM:SS.sss").

  • lon (float, str, array-like, or pd.Series) – Longitude in decimal degrees or sexagesimal string ("DD:MM:SS.sss").

  • datum (str, default "WGS84") – Geodetic datum name (e.g., "WGS84", "NAD83"). May also be an EPSG integer if GDAL is used.

  • utm_zone (str, optional) – UTM zone designator like "55S". If omitted or set to a non-string (or "none"), the zone is inferred from the input point.

  • epsg (int, optional) – EPSG code for the projected CRS. When provided, it takes precedence over utm_zone and datum.

Returns:

For scalar input: (easting, northing, zone). For array input: record array with fields ('easting','northing','elev','utm_zone').

  • eastingfloat or None

    UTM easting in meters, or None when inputs are None.

  • northingfloat or None

    UTM northing in meters, or None when inputs are None.

  • zonestr or None

    UTM zone string (e.g., "55S"). None when not determinable or inputs are None.

Return type:

tuple or np.recarray

Notes

  • This variant accepts a single point. For vectorized inputs, use project_points_ll2utm().

  • Inputs are validated with assert_lat_value and assert_lon_value before projection.

  • With GDAL, transforms use osr.CoordinateTransformation; with PROJ they use pyproj.Proj.

Notes

  • Enforces traditional GIS order (lon, lat) in the GDAL path to avoid invalid-latitude errors with GDAL ≥ 3.

  • The pyproj path uses a Transformer with always_xy=True for the same reason.

Examples

>>> e, n, z = project_point_ll2utm(34.05, -118.34)
>>> z
'11S'
pycsamt.gis.utils.project_point_utm2ll(easting, northing, utm_zone, datum='WGS84', epsg=3149)[source]#

Transform a UTM point to latitude/longitude.

Converts UTM easting/northing to geographic latitude and longitude (decimal degrees). Uses GDAL/OSR when available, else falls back to PROJ via pyproj.

Parameters:
  • easting (float) – Easting in meters.

  • northing (float) – Northing in meters.

  • utm_zone (str or int) – UTM zone designator. Either a string like "10S" or an integer UTM code (negative for south). Ignored when epsg is provided.

  • datum (str, default "WGS84") – Geodetic datum name (e.g., "WGS84", "NAD27").

  • epsg (int, optional) – EPSG code defining the projected CRS. When provided, it takes precedence over utm_zone and datum. The default is 3149 for historical compatibility.

Returns:

  • lat (float) – Latitude in decimal degrees, rounded to 6 decimals.

  • lon (float) – Longitude in decimal degrees, rounded to 6 decimals.

Return type:

tuple[float, float]

Notes

  • With GDAL, the conversion uses osr.CoordinateTransformation to a geographic CRS cloned from the projected CRS.

  • With PROJ, the conversion uses pyproj.Proj with inverse=True.

  • When epsg refers to a non-UTM CRS, utm_zone is not required.

  • The function validates easting and northing can be cast to float and raises GisError otherwise.

Examples

Using an explicit zone:

>>> project_point_utm2ll(377274.0, 3762150.0, "11S")

Using an EPSG code (preferred when known):

>>> project_point_utm2ll(
...     377274.0, 3762150.0, utm_zone="11S", epsg=32611
... )

See also

project_points_ll2utm

Forward transform from geographic to UTM.

References

pycsamt.gis.utils.ll_to_utm(reference_ellipsoid, lat, lon)[source]#

Convert latitude/longitude to UTM coordinates. Equations from USGS Bulletin 1532.

Parameters:
  • reference_ellipsoid (int) – ID of ellipsoid from ELLIPSOIDS (e.g. 23 for WGS-84).

  • lat (float) – Geographic coordinates in decimal degrees.

  • lon (float) – Geographic coordinates in decimal degrees.

Returns:

  • utm_zone (str) – UTM zone string (e.g. ‘11N’).

  • easting (float)

  • northing (float)

Return type:

tuple[str, float, float]

pycsamt.gis.utils.utm_to_ll(reference_ellipsoid, northing, easting, zone)[source]#

Convert UTM coordinates to latitude/longitude. Inverse of ll_to_utm, equations from USGS Bulletin 1532.

Parameters:
  • reference_ellipsoid (int) – ID of ellipsoid from ELLIPSOIDS.

  • northing (float) – UTM coordinates in meters.

  • easting (float) – UTM coordinates in meters.

  • zone (str) – UTM zone string (e.g. ‘11N’).

Returns:

lat, lon – Geographic coordinates in decimal degrees.

Return type:

float

pycsamt.gis.utils.get_utm_string_from_sr(spatial_ref)[source]#

[DEPRECATED] GDAL SpatialReference → UTM string is deprecated; use ‘get_utm_zone’ for standard UTM formatting.

Return a UTM zone string (e.g., '11N') from a GDAL SpatialReference.

Parameters:

spatial_ref (osr.SpatialReference) – GDAL spatial reference object with a UTM projection.

Returns:

UTM zone string such as '11N' or '55S'. If a valid UTM zone is not encoded, returns '0'.

Return type:

str

Notes

This helper is deprecated. Prefer get_utm_zone(), which derives a zone from latitude/longitude using the standard UTM rules.

Examples

>>> # sr is an osr.SpatialReference set to a UTM CRS
>>> # get_utm_string_from_sr(sr)
pycsamt.gis.utils.get_utm_zone(latitude, longitude)[source]#

Compute the UTM zone, hemisphere flag, and zone string for a geographic coordinate.

Parameters:
  • latitude (float) – Latitude in decimal degrees.

  • longitude (float) – Longitude in decimal degrees.

Returns:

  • zone_number (int) – UTM zone number in [1, 60].

  • is_northern (bool) – True for the northern hemisphere, else False.

  • zone_string (str) – Concatenation of zone and latitude band letter, e.g., '11N'.

Return type:

tuple[int, bool, str]

Notes

  • Zone width is 6 degrees, starting at -180°. Values are wrapped into [1, 60].

  • The latitude band letter is obtained via utm_letter_designator().

Examples

>>> get_utm_zone(34.05, -118.34)[2]
'11S'
pycsamt.gis.utils.utm_zone_to_epsg(zone_number, is_northern)[source]#

Resolve the WGS84 UTM EPSG code for a given UTM zone.

Parameters:
  • zone_number (int) – UTM zone number in [1, 60].

  • is_northern (bool) – True for northern hemisphere, False for southern hemisphere.

Returns:

Matching EPSG code (e.g., 32611 or 32755) when found, else None.

Return type:

int or None

Notes

The lookup scans EPSG_DICT for a PROJ string that matches +zone, +datum=WGS84, and an optional +south flag for the southern hemisphere.

Examples

>>> utm_zone_to_epsg(11, True) in (32611, None)
True
pycsamt.gis.utils.get_epsg(latitude, longitude)[source]#

Get the WGS84 UTM EPSG code for a geographic coordinate.

Parameters:
  • latitude (float) – Latitude in decimal degrees.

  • longitude (float) – Longitude in decimal degrees.

Returns:

EPSG code for the inferred UTM CRS, or None if no match is found.

Return type:

int or None

Notes

This is a convenience wrapper that calls get_utm_zone() and utm_zone_to_epsg().

Examples

>>> epsg = get_epsg(34.05, -118.34)
>>> epsg in (32611, 32711, None)
True
pycsamt.gis.utils.assert_xy_coordinate_system(x, y)[source]#

Infer the coordinate system of paired x and y arrays.

Heuristics detect one of three systems:

  • 'll' — longitude/latitude in decimal degrees.

  • 'dms' — degree-minute-second strings DD:MM:SS.

  • 'utm' — any other numeric system (fallback).

Parameters:
  • x (array-like (1D)) – Arrays of horizontal (x) and vertical (y) positions. Elements may be numeric or DMS strings.

  • y (array-like (1D)) – Arrays of horizontal (x) and vertical (y) positions. Elements may be numeric or DMS strings.

Returns:

cs – Inferred coordinate system token.

Return type:

{‘utm’, ‘dms’, ‘ll’}

Notes

  • DMS is detected if any array consists entirely of strings containing the ':' separator.

  • Decimal degrees are detected when either orientation matches bounds:

    • |x| <= 180 and |y| <= 90 (lon, lat), or

    • |x| <= 90 and |y| <= 180 (lat, lon).

  • If neither DMS nor decimal-degree bounds match, the function returns 'utm' by default.

Examples

>>> import numpy as np
>>> np.random.seed(42)
>>> x, y = np.random.rand(7) * 1.0, np.arange(7) * 0.1
>>> assert_xy_coordinate_system(x, y)
'll'
>>> assert_xy_coordinate_system(x * 1000, y * 1000)
'utm'
>>> x = ['28:24:43.08', '28:24:42.69', '28:24:42.31']
>>> y = ['109:19:58.34', '109:19:58.93', '109:19:59.51']
>>> assert_xy_coordinate_system(x, y)
'dms'
pycsamt.gis.utils.decimal_to_dms(position)[source]#

Convert decimal degrees to a DD:MM:SS.ss string.

Parameters:

position (float) – Decimal degrees for latitude or longitude.

Returns:

Sexagesimal string in "DD:MM:SS.ss" format.

Return type:

str

See also

convert_position_float2str

Underlying converter used by this wrapper.

Examples

>>> decimal_to_dms(-118.34563)
'-118:20:44.27'
pycsamt.gis.utils.dms_to_decimal(position_str)[source]#

Convert a DMS string or decimal degrees to a float.

This is a convenience wrapper around convert_position_str2float(). It accepts either a decimal degrees string/number or a sexagesimal string in 'DD:MM:SS.sss' format.

Parameters:

position_str (str or float) – Coordinate in decimal degrees or DMS string.

Returns:

Decimal degrees value, or None for invalid input.

Return type:

float or None

Examples

>>> dms_to_decimal("34:03:00")
34.05
>>> dms_to_decimal("-118.34")
-118.34
pycsamt.gis.utils.to_utm(lat: Any, lon: Any, data: Any | None = None, *, datum: str = 'WGS84', utm_zone: str | None = None, epsg: int | None = None, as_frame: Literal[True]) DataFrame[source]#
pycsamt.gis.utils.to_utm(lat: float, lon: float, data: Any | None = None, *, datum: str = 'WGS84', utm_zone: str | None = None, epsg: int | None = None, as_frame: Literal[False] = False) tuple[float, float, str | None]
pycsamt.gis.utils.to_utm(lat: Sequence[Any] | NDArray[float64], lon: Sequence[Any] | NDArray[float64], data: Any | None = None, *, datum: str = 'WGS84', utm_zone: str | None = None, epsg: int | None = None, as_frame: Literal[False] = False) tuple[NDArray[float64], NDArray[float64], NDArray[object]]

Convert geographic coordinates to UTM using project_point_ll2utm().

Accepts flexible latitude/longitude inputs (scalars, arrays, pandas Series, or column names referencing a passed data frame). Returns either arrays/tuples or a DataFrame with original and UTM fields.

Parameters:
  • lat (float, str, array-like, or pd.Series) – Geographic coordinates in decimal degrees or DMS strings. If a string is given, it is treated as a column name and data must be provided.

  • lon (float, str, array-like, or pd.Series) – Geographic coordinates in decimal degrees or DMS strings. If a string is given, it is treated as a column name and data must be provided.

  • data (pd.DataFrame, optional) – Source of columns when lat or lon are strings.

  • datum (str, default "WGS84") – Datum name or EPSG (int) accepted by the underlying transformer.

  • utm_zone (str, optional) – UTM zone (e.g., "11S"). If absent, it is inferred from the input centroid (or per-point for scalar).

  • epsg (int, optional) – EPSG code for the projected CRS. Overrides utm_zone when provided.

  • as_frame (bool, default False) – If True, return a pandas DataFrame with columns ['lat','lon','easting','northing','zone']. If False, return (easting, northing, zone) as arrays (or scalars for scalar inputs).

Returns:

When as_frame=True, a frame with original and UTM columns. Otherwise a tuple (e, n, zone) where each element matches the input shape (scalar or 1-D array).

Return type:

DataFrame or tuple

Notes

  • Parsing of DMS strings and range checks are handled by the underlying projection helper.

  • For array inputs, this function preserves order and length; indices are preserved when built from data[<col>] and as_frame=True.

Examples

Scalar input:

>>> to_utm(34.05, -118.34)
(..., ..., '11S')

From a DataFrame:

>>> # df has columns 'lat_deg' and 'lon_deg'
>>> # to_utm('lat_deg','lon_deg', data=df, as_frame=True)
...
pycsamt.gis.utils.to_ll(easting: Any, northing: Any, zone: Any | None = None, data: Any | None = None, *, datum: str = 'WGS84', epsg: int | None = None, as_frame: Literal[True]) DataFrame[source]#
pycsamt.gis.utils.to_ll(easting: float, northing: float, zone: Any | None = None, data: Any | None = None, *, datum: str = 'WGS84', epsg: int | None = None, as_frame: Literal[False] = False) tuple[float, float]
pycsamt.gis.utils.to_ll(easting: Sequence[float] | NDArray[float64], northing: Sequence[float] | NDArray[float64], zone: Sequence[Any] | NDArray[object] | None = None, data: Any | None = None, *, datum: str = 'WGS84', epsg: int | None = None, as_frame: Literal[False] = False) tuple[NDArray[float64], NDArray[float64]]

Convert UTM coordinates to geographic (lat, lon) using project_point_utm2ll().

Accepts flexible inputs (scalars, arrays, Series, or column names referencing a provided DataFrame). Returns a tuple or a DataFrame with original and converted fields.

Parameters:
  • easting (float, array-like, or pd.Series) – UTM coordinates in meters. When strings are given, they are treated as column names in data.

  • northing (float, array-like, or pd.Series) – UTM coordinates in meters. When strings are given, they are treated as column names in data.

  • zone (str, int, array-like, or pd.Series, optional) – UTM zone designator (e.g., "11S") or signed UTM code (negative for south). If strings are given, they are treated as column names in data. May be omitted when epsg is provided.

  • data (pd.DataFrame, optional) – Source of columns when any of the inputs are strings.

  • datum (str, default "WGS84") – Datum name for GDAL-backed paths.

  • epsg (int, optional) – EPSG code that defines the projected CRS. When set, it takes precedence and zone is not required.

  • as_frame (bool, default False) – If True, return a DataFrame with columns ['easting','northing','zone','lat','lon']. Otherwise, return (lat, lon) as scalars or arrays.

Returns:

A DataFrame when as_frame=True; otherwise a tuple (lat, lon) matching the input shape.

Return type:

DataFrame or tuple

Notes

  • Index is preserved when inputs originate from a Series or from data[<column>] with as_frame=True.

  • When arrays are given and epsg is used, the same EPSG applies to all rows.

  • zone may be a scalar or an array/Series matching the shape of easting and northing.

Examples

Scalars:

>>> to_ll(377274.0, 3762150.0, "11S")
(34.05, -118.34)

From a DataFrame:

>>> # df has 'E', 'N', 'zone' columns
>>> # to_ll('E','N','zone', data=df, as_frame=True)
...
pycsamt.gis.utils.normalize_lat_lon(a: float | str, b: float | str, *, assume: Literal['lonlat', 'latlon', 'auto'] = 'lonlat', error: Literal['ignore', 'raise'] = 'ignore', clip: bool = False) tuple[float, float][source]#
pycsamt.gis.utils.normalize_lat_lon(a: Sequence[float | str], b: Sequence[float | str], *, assume: Literal['lonlat', 'latlon', 'auto'] = 'lonlat', error: Literal['ignore', 'raise'] = 'ignore', clip: bool = False) tuple[ndarray, ndarray]

Resolve input pair to (lat, lon) regardless of order.

Accepts values in either legacy order (lat, lon) or the new expected order (lon, lat). Returns a tuple in the canonical order (lat, lon) and avoids common |Latitude| > 90 errors by swapping when clear.

Parameters:
  • a (float or str, or 1-D sequences) – Coordinate components. Strings may be decimal or DMS ("DD:MM:SS"). Sequences must be same length.

  • b (float or str, or 1-D sequences) – Coordinate components. Strings may be decimal or DMS ("DD:MM:SS"). Sequences must be same length.

  • assume ({"lonlat","latlon","auto"}, default "lonlat") –

    Tie-break for ambiguous pairs (both |val| <= 90).

    • "lonlat": treat input as (lon, lat).

    • "latlon": treat input as (lat, lon).

    • "auto": prefer (lon, lat) when ambiguous.

  • error ({"ignore","raise"}, default "ignore") – On impossible pairs (both |val| > 90 or both |val| > 180), either raise or return ``nan``s.

  • clip (bool, default False) – If True, clip lat to [-90, 90] and lon to [-180, 180] when slightly out-of-range.

Returns:

lat, lon – Coordinates in canonical order.

Return type:

float or ndarray

Notes

Heuristics:

  • If one value is in (90, 180] and the other in [-90, 90], the former is lon and the latter is lat.

  • If both are |val| <= 90, use assume.

  • Values with |val| > 180 are invalid unless clip.

exception pycsamt.gis.utils.GisError#

Bases: Exception

Base exception for GIS utilities.