pycsamt.format.topo_source#
Attach real per-station spatial coordinates (and elevation) to a PCSF
adapter’s StationTable from an external
topo source.
Every PCSF adapter (pycsamt.format.adapters) already accepts a
station_elevations/station_lonlat-style mapping the caller has
to build by hand. This module is the “smart” layer on top of that: a
single topo= argument that can be
a
.bln(Golden Software Surfer blanking) file — a barex, ypoint list, no station identity, matched to stations positionally (in survey order) since the format itself carries none;a
.stn(Zonge station-location) file, parsed by the same battle-tested low-level readerpycsamt.zongealready uses (pycsamt.zonge.utils.read_stn()) — never re-implemented here;a
.csv(or.txt) file with a header pycsamt can recognise (station/lat/lon/easting/northing/elevationand common synonyms);an already-geo-located
Sites/MapData/iterable-of-StationRecord-like object (duck-typed: anything exposing a station id pluslatitude/longitude/elevation) — so a survey’s own EDI collection can supply topography directly, with no intermediate file at all;a plain
{station_name: (lon, lat)}/{station_name: (lon, lat, elevation)}mapping — the same shapestation_lonlatalready accepted, now handled by the same code path;for a multiline build, a
{line_id: <any of the above>}mapping, or a sequence of one source per line in line order — one.bln/.stnfile per surveyed line is a common real-world layout.
Design choices, stated explicitly because they are the crux of “smart” attribution rather than an implementation detail:
A named source (``.stn``, ``.csv`` with a station column, a plain dict, or a Sites/MapData object) matches by station id — exact, then a normalized fallback (
pycsamt.map._core.normalize_station_id(), the same one every other cross-source station match in this codebase already uses). Unmatched stations on either side are reported, never silently dropped or fabricated.A name-less source (a bare ``.bln``, or a ``.csv`` without a station column) is positional and therefore requires an exact station-count match — the whole reason to “first detect the number of stations passed and compare to the inversion stations” before attributing anything. A mismatch raises by default (
on_mismatch="raise");on_mismatch="warn"instead issues aUserWarningand attributes only the overlapping prefix, a deliberate best-effort escape hatch rather than the default.When ``topo`` is given, it takes precedence over any other lon/lat source (
station_lonlat, a backend’s own native coordinates) for every station it successfully attributes — with aUserWarningwhen both were supplied, so the override is never silent. Elevation merges rather than fully overriding: a station the topo source has no elevation for keeps whateverstation_elevationsalready gave it.Passing no ``topo`` behaves exactly as before this module existed — every adapter’s existing
station_elevations/station_lonlatparameters are unaffected whentopoisNone.Projected coordinates (easting/northing) need `epsg` or `utm_zone` to become lon/lat — conversion is delegated entirely to
pycsamt.gis.utils.to_ll()(the project’s one existing UTM/EPSG conversion utility;pyprojstays an optional dependency, imported lazily only when a conversion is actually requested, matching PCSF’s own “no new mandatory dependency” principle inSPEC.mdS2). A source already carrying lat/lon columns, or a.blnexplicitly markedlatlon=True, needs neither.
Functions
|
Match a parsed |
|
Parse a |
|
Resolve any accepted |
|
Extract lon/lat/elevation from an already-geo-located |
Classes
|
Per-station real coordinates resolved from a topo source, ready to populate |
|
A parsed topo source, before it is matched to any station names. |
- class pycsamt.format.topo_source.TopoTable(lon, lat, elevation=None, names=None, source='<unknown>')[source]
Bases:
objectA parsed topo source, before it is matched to any station names.
- Variables:
names (list of str, optional) – Station identity carried by the source itself.
Nonefor a positional-only source (e.g. a bare.bln).lat (lon,) – WGS84 decimal degrees — already converted from easting/northing if the source needed that.
elevation (ndarray, shape (n,), optional) – Metres,
nanwhere genuinely unknown.source (str) – Human-readable provenance (file path, or a short description for an in-memory source), surfaced in error/warning messages.
- Parameters:
- lon: ndarray
- lat: ndarray
- source: str = '<unknown>'
- class pycsamt.format.topo_source.TopoAttribution(lon=<factory>, lat=<factory>, elevation=<factory>, matched=<factory>, unmatched_stations=<factory>, source='none')[source]
Bases:
objectPer-station real coordinates resolved from a topo source, ready to populate
lon/lat(and merge into elevation).- Parameters:
- source: str = 'none'
- pycsamt.format.topo_source.read_topo_file(path, *, epsg=None, utm_zone=None, latlon=False)[source]
Parse a
.bln/.csv/.stntopo file into aTopoTable.- Parameters:
path (path-like) – A
.bln,.csv/.txt, or.stnfile.epsg (int, optional) – EPSG code of the source’s projected CRS, when it stores easting/northing rather than lat/lon. Takes precedence over utm_zone when both are given (matches
pycsamt.gis.utils.to_ll()’s own precedence).utm_zone (optional) – UTM zone designator (e.g.
"32N"), an alternative to epsg.latlon (bool, default False) –
.blnonly: setTruewhen the file’s ownx, ycolumns are alreadylon, lat(a.blncarries no CRS metadata to detect this from). Ignored for.csv/.stn, which are only treated as already-geographic when their own header sayslat/lon.
- Raises:
ValueError – Unrecognised extension, a malformed
.blnheader/body, no recognisable coordinate columns in a.csv/.stnfile, or projected coordinates with neither epsg nor utm_zone.- Return type:
- pycsamt.format.topo_source.topo_from_sites(source)[source]
Extract lon/lat/elevation from an already-geo-located
Sites/MapData/iterable-of-station-record object.Duck-typed on purpose: works with anything iterable whose items (or whose
.stationsattribute’s items, for aMapData-like container) expose a station identifier (id/name/station) pluslongitude/latitudeand, optionally,elevation–pycsamt.map._core.StationRecordand similar objects all qualify without any adapter code.- Raises:
ValueError – If no station in source carries a usable id + lon/lat pair.
- Parameters:
source (Any)
- Return type:
- pycsamt.format.topo_source.attribute_topo(topo, station_names, *, on_mismatch='raise')[source]
Match a parsed
TopoTableonto station_names.Name-based when
topo.namesis populated (exact match, then a normalized fallback); positional (in order, requiring an exact count match) otherwise. See the module docstring for the full rationale.- Parameters:
on_mismatch ({"raise", "warn"}, default "raise") – Only consulted for a positional (name-less) source.
"raise"rejects a station-count mismatch outright;"warn"issues aUserWarningand attributes only the overlapping prefix (min(topo.n, len(station_names))points, in order).topo (TopoTable)
- Return type:
- pycsamt.format.topo_source.resolve_topo(topo, station_names, *, epsg=None, utm_zone=None, latlon=False, on_mismatch='raise')[source]
Resolve any accepted
topo=argument into aTopoAttributionagainst station_names.- Parameters:
topo (None, path-like, TopoTable, Sites/MapData-like, mapping, or sequence) –
None– returns an empty attribution (every adapter’s existingstation_elevations/station_lonlatbehaviour is unaffected).a
.bln/.csv/.stnpath, or an already-parsedTopoTable.a
Sites/MapData/iterable-of-station-record object (seetopo_from_sites()).a plain
{station_name: (lon, lat[, elevation])}mapping.when station_names is a mapping (multiline,
{line_id: [names, ...]}): a{line_id: <any of the above>}mapping, or a sequence with exactly one source per line, in the same order as station_names’s own keys. A single non-mapping, non-per-line-sequence source is instead matched by name across all lines’ stations combined – the natural choice for one combined.stn/.csv/Sites source covering a whole multiline survey.
station_names (sequence of str, or mapping of str to sequence of str) – The inversion’s own station names (flat), or
{line_id: names}for a multiline build.epsg (int | None) – Forwarded to
read_topo_file()/attribute_topo()for every file-based source encountered.utm_zone (Any | None) – Forwarded to
read_topo_file()/attribute_topo()for every file-based source encountered.latlon (bool) – Forwarded to
read_topo_file()/attribute_topo()for every file-based source encountered.on_mismatch (str) – Forwarded to
read_topo_file()/attribute_topo()for every file-based source encountered.
- Returns:
Empty (all fields blank,
source="none") when topo isNone.- Return type: