pycsamt.site.location#

Functions

apply_topography(ed_or_sites, frame, *[, ...])

Update site coordinates from a tabular frame by station id.

bearing(a, b, *[, mode, crs_to])

Compute the azimuth from point a to point b.

chainage_along(origin, azimuth, pts)

Project points onto a profile axis and return chainages.

distance(a, b, *[, mode, crs_to])

Compute distance between two geographic points.

ensure_head_coords(ed, *[, lat, lon, elev, ...])

Create or update HEAD coordinates on an EDI-like object.

parse_elev(x)

Parse elevation from a numeric or string value.

parse_lat(x)

Parse a latitude value from decimal or DMS text.

parse_lon(x)

Parse a longitude value from decimal or DMS text.

project(pts, *, crs_from, crs_to)

Project points from one CRS to another using pyproj or GDAL.

Classes

Coord(lat, lon[, elev])

Lightweight geographic coordinate container.

class pycsamt.site.location.Coord(lat, lon, elev=0.0)[source]#

Bases: object

Lightweight geographic coordinate container.

Parameters:
  • lat (float) – Latitude in decimal degrees. North is positive.

  • lon (float) – Longitude in decimal degrees. East is positive.

  • elev (float, optional) – Elevation in meters. Defaults to 0.0.

Notes

This class is a convenience container used across the site tools. Values are not validated at construction time. Use pycsamt.gis.utils.assert_lat_value and related helpers if you need strict range checking.

Examples

>>> from pycsamt.site.location import Coord
>>> c = Coord(10.0, 20.0, 100.0)
>>> (c.lat, c.lon, c.elev)
(10.0, 20.0, 100.0)
lat: float#
lon: float#
elev: float = 0.0#
pycsamt.site.location.parse_lat(x)[source]#

Parse a latitude value from decimal or DMS text.

Accepts floats, ints, or strings like "10", "-3.2", "12 30 00 N", "12:30:00N", "12d30mE" (hemisphere letter required for disambiguation when not using sign).

Parameters:

x (any) – Numeric or string representation of a latitude value.

Returns:

Parsed latitude in decimal degrees. Returns nan on parsing failure.

Return type:

float

Notes

Hemisphere letters are interpreted as N=+1 and S=-1. Signed decimal values override hemisphere if both are given.

Examples

>>> from pycsamt.site.location import parse_lat
>>> parse_lat("12 30 0 N")
12.5
>>> parse_lat("-7.25")
-7.25

See also

parse_lon, parse_elev

pycsamt.site.location.parse_lon(x)[source]#

Parse a longitude value from decimal or DMS text.

Accepts floats, ints, or strings like "20", "3.5W", "12 30 00 E", or "12:30:00W".

Parameters:

x (any) – Numeric or string representation of a longitude value.

Returns:

Parsed longitude in decimal degrees. Returns nan on parsing failure.

Return type:

float

Notes

Hemisphere letters are interpreted as E=+1 and W=-1. Signed decimal values override hemisphere if both are given.

Examples

>>> from pycsamt.site.location import parse_lon
>>> parse_lon("3.5W")
-3.5

See also

parse_lat, parse_elev

pycsamt.site.location.parse_elev(x)[source]#

Parse elevation from a numeric or string value.

Parameters:

x (any) – Numeric or string representation of elevation in meters.

Returns:

Elevation in meters. Returns nan on parsing failure.

Return type:

float

Examples

>>> from pycsamt.site.location import parse_elev
>>> parse_elev("123.4")
123.4
pycsamt.site.location.ensure_head_coords(ed, *, lat=None, lon=None, elev=None, empty=None)[source]#

Create or update HEAD coordinates on an EDI-like object.

This function guarantees that the EDI “head” section exists and that the lat, lon and long aliases, and elev fields are present and numeric. Inputs are parsed with parse_lat(), parse_lon() and parse_elev(), validated with pycsamt.gis.utils.assert_*, and then written back. When a value is missing or not finite, a default empty sentinel is used (0.0 by default).

Parameters:
  • ed (any) – EDI-like object providing get_section('head') and optionally set_section('head', head). A fallback attribute-based path is used when the API is not available.

  • lat (any, optional) – Optional overrides. If omitted, the current values are read from the head and re-validated. If still missing, the empty value is used.

  • lon (any, optional) – Optional overrides. If omitted, the current values are read from the head and re-validated. If still missing, the empty value is used.

  • elev (any, optional) – Optional overrides. If omitted, the current values are read from the head and re-validated. If still missing, the empty value is used.

  • empty (float, optional) – Empty sentinel for lat, lon and elev. Default is 0.0.

Returns:

The head section object that now carries lat, lon (and alias long) and elev.

Return type:

any

Notes

This routine writes both lon and long to maximize compatibility with various EDI headers.

Examples

>>> from pycsamt.site.location import ensure_head_coords
>>> class H: pass
>>> class EDI:
...     def __init__(self): self._h = H()
...     def get_section(self, k): return self._h if k=='head' else None
...     def set_section(self, k, v): self._h = v
...
>>> ed = EDI()
>>> h = ensure_head_coords(ed, lat="12N", lon="3E", elev="10")
>>> (h.lat, h.lon, h.elev)
(12.0, 3.0, 10.0)
pycsamt.site.location.apply_topography(ed_or_sites, frame, *, empty=None, inplace=True)[source]#

Update site coordinates from a tabular frame by station id.

Matches rows in frame against the EDI “station” or related identifiers and writes the associated latitude, longitude and elevation into the EDI head section.

Parameters:
  • ed_or_sites (any or iterable) – A single EDI-like object, an iterable of EDI-like objects, or a container with a private ._items list of site objects having an .edi attribute.

  • frame (pandas.DataFrame or dict-like) – Table with columns naming a station id and coordinates. Station id columns are matched in order among ["station","site","dataid","id","name"]. Coordinate columns are searched among ["latitude","lat"], ["longitude","lon","long"], and ["elevation","elev","alt"].

  • empty (float, optional) – Empty sentinel applied when values are missing. Default is 0.0.

  • inplace (bool, optional) – If True (default), update the provided objects. If False, return a deep-copied updated structure.

Returns:

The updated object(s). For lists or containers, the return type mirrors the input.

Return type:

any

Notes

Matching is case-insensitive and robust to whitespace. For containers, the function duck-types a ._items attribute and attempts to update the underlying .edi objects.

Examples

>>> import pandas as pd
>>> from pycsamt.site.location import apply_topography
>>> # ed is an EDI-like object with a valid head and dataid
>>> df = pd.DataFrame({'station':['S1'],
...                    'latitude':[10.0],
...                    'longitude':[2.0],
...                    'elevation':[100.0]})
>>> _ = apply_topography(ed, df, inplace=True)
pycsamt.site.location.project(pts, *, crs_from, crs_to)[source]#

Project points from one CRS to another using pyproj or GDAL.

Parameters:
  • pts (sequence of (float, float) or (float, float)) – Either a single coordinate pair or a sequence of pairs. Coordinates are interpreted according to crs_from.

  • crs_from (any) – Source CRS. For pyproj this can be an EPSG code, PROJ string, or CRS object. For GDAL, EPSG code, WKT, PROJ4, or a SpatialReference.

  • crs_to (any) – Target CRS in the same form as crs_from.

Returns:

Arrays (X, Y) holding transformed coordinates.

Return type:

numpy.ndarray, numpy.ndarray

Raises:
  • RuntimeError – If neither pyproj nor GDAL are available.

  • TypeError – If CRS specification cannot be parsed.

Notes

When both pyproj and GDAL are available, pyproj is used. The axis order is forced to the traditional GIS order (x, y).

Examples

>>> from pycsamt.site.location import project
>>> X, Y = project([(0.0, 0.0)], crs_from="EPSG:4326",
...                crs_to="EPSG:3857")
pycsamt.site.location.distance(a, b, *, mode='geodetic', crs_to=None)[source]#

Compute distance between two geographic points.

Supports three modes: geodetic uses a haversine approximation on a spherical Earth of radius _EARTH_R. flat uses a local small-extent planar approximation (about the mid-latitude). utm projects both points to UTM (auto zone unless crs_to is given) and computes the Euclidean distance.

Parameters:
  • a (Coord or (float, float)) – Points given as Coord or (lat, lon).

  • b (Coord or (float, float)) – Points given as Coord or (lat, lon).

  • mode ({'geodetic', 'flat', 'utm'}, optional) – Distance model. Default is 'geodetic'.

  • crs_to (any, optional) – Target CRS for 'utm' mode. If omitted, the UTM zone is inferred from the mean coordinate.

Returns:

Distance in meters.

Return type:

float

Raises:

ValueError – If mode is not one of the supported options.

Notes

Geodetic mode implements the haversine formula:

\[d = 2 R \arcsin\left( \sqrt{ \sin^2\Delta\phi/2 + \cos\phi_1 \cos\phi_2 \sin^2\Delta\lambda/2 } \right)\]

where angles are in radians.

Examples

>>> from pycsamt.site.location import distance, Coord
>>> distance(Coord(0,0,0), Coord(0,1,0), mode='geodetic')
111000.0
pycsamt.site.location.bearing(a, b, *, mode='geodetic', crs_to=None)[source]#

Compute the azimuth from point a to point b.

Supports the same three modes as distance(): geodetic, flat, and utm. The azimuth is expressed in degrees with 0 deg pointing to north and 90 deg to east.

Parameters:
  • a (Coord or (float, float)) – Points given as Coord or (lat, lon).

  • b (Coord or (float, float)) – Points given as Coord or (lat, lon).

  • mode ({'geodetic', 'flat', 'utm'}, optional) – Bearing model. Default is 'geodetic'.

  • crs_to (any, optional) – Target CRS for 'utm' mode. If omitted, the UTM zone is inferred.

Returns:

Azimuth in degrees in the range [0, 360).

Return type:

float

Raises:

ValueError – If mode is not supported.

Notes

Geodetic mode uses the spherical forward azimuth:

\[\theta = \operatorname{atan2}(\sin\Delta\lambda\cos\phi_2,\, \cos\phi_1\sin\phi_2 - \sin\phi_1\cos\phi_2\cos\Delta\lambda)\]

Angles are converted to degrees and wrapped to [0, 360).

Examples

>>> from pycsamt.site.location import bearing, Coord
>>> bearing(Coord(0,0,0), Coord(1,0,0))
0.0
>>> bearing(Coord(0,0,0), Coord(0,1,0))
90.0
pycsamt.site.location.chainage_along(origin, azimuth, pts)[source]#

Project points onto a profile axis and return chainages.

Chainage is the signed distance along the axis defined by an origin and an azimuth (0 deg north, 90 deg east). A local flat-Earth approximation is used, with 1 deg roughly equal to 111 km scaled by cosine of latitude for the east axis.

Parameters:
  • origin (Coord or (float, float)) – Profile origin as Coord or (lat, lon).

  • azimuth (float) – Axis azimuth in degrees, 0 deg north, 90 deg east.

  • pts (sequence of Coord or (float, float) or single) – Single point or a sequence of points to be projected.

Returns:

Chainage(s) in meters. Returns a scalar for a single point, or a 1-D array for multiple points.

Return type:

numpy.ndarray or float

Notes

Let local offsets be dx east and dy north from the origin. The chainage uses

\[s = dx \sin A + dy \cos A\]

where \(A\) is the azimuth in radians and \(dx, dy\) are derived from degree differences using a local metric scale.

Examples

>>> from pycsamt.site.location import chainage_along
>>> s = chainage_along((0.0, 0.0), 90.0, (0.0, 1.0))
>>> s > 100000.0
True