Skip to content

sylva.geo.coords

Shifting, rotating and recentring are methods of PointCloud: translate, rotate and recentre.

sylva.geo.coords

Coordinate systems: shifts, rotations, reprojection and transform files.

Shifting and rotating are methods of ~sylva.PointCloud (~sylva.PointCloud.translate, ~sylva.PointCloud.rotate, ~sylva.PointCloud.recentre); this module builds their matrices, reprojects between coordinate reference systems and applies registration results to whole surveys.

A CRS is given as an EPSG code ("EPSG:7855", 7855, a compound "EPSG:7855+5711"), a PROJ string ("+proj=utm +zone=55 +south +ellps=GRS80") or WKT (as found in LAS files, ESRI .prj files or exported by GIS). EPSG definitions are built in (the EPSG registry's horizontal CRSs with codes up to 65535) and reprojection runs in the Rust core without PROJ or GDAL.

How exact a reprojection is depends on the datums involved:

Transformation Example Result
Same datum, other projection MGA zone 55 to GDA2020 latitude/longitude; UTM to WGS 84 exact (float rounding)
Datums tied to WGS 84 by zero parameters GDA94, NAD83, ETRS89, NZGD2000 to WGS 84 exact under that convention
Helmert (towgs84) datum change OSGB36 to WGS 84 exact to the published parameters; heights change
Datums without parameters between them GDA2020 to WGS 84 or GDA94 approximate: not applied (null transformation)
Grid-based datum change NAD27; NTv2 grids refused (grids are not available)
Vertical datum change AHD to ellipsoidal heights approximate: heights not changed

Approximate transformations issue an ApproximateTransformationWarning naming what was not applied; transformation reports it beforehand.

ApproximateTransformationWarning

Bases: UserWarning

A reprojection could not apply part of the transformation (a datum shift without known parameters, or a change of vertical datum).

CRS dataclass

What Sylva understands of a coordinate reference system.

Attributes:

Name Type Description
name str

Name from the definition ("GDA2020 / MGA zone 55").

label str

"EPSG:7855" ("EPSG:7855+5711" for a compound CRS) when the code is known, otherwise the name.

epsg int | None

EPSG code of the horizontal part, or None.

vertical_epsg int | None

EPSG code of the vertical part, or None.

proj4 str

PROJ string the coordinates are transformed with.

wkt str | None

WKT written to LAS files, or None (PROJ strings have none).

datum str | None

Datum identity used to decide whether two CRSs share a datum.

geographic bool | None

True when x and y are longitude and latitude in degrees.

Transformation dataclass

How reproject gets from one CRS to another.

Attributes:

Name Type Description
kind str

"identity" (same CRS), "conversion" (same datum, other projection), "helmert" (datum change by published towgs84 parameters) or "null datum" (datums without parameters between them; not applied).

exact bool

True when the result is exact to float rounding (and to the published parameters of a Helmert change).

changes_z bool

True when heights change (Helmert datum changes, which treat z as ellipsoidal height).

note str

Explanation; says what is not applied when exact is False.

translation_matrix

translation_matrix(dx: float, dy: float, dz: float = 0.0) -> np.ndarray

A 4x4 translation.

Parameters:

Name Type Description Default
dx float

Offset (m).

required
dy float

Offset (m).

required
dz float

Offset (m).

required

Returns:

Type Description
ndarray

(4, 4) matrix for PointCloud.transform.

Raises:

Type Description
ValueError

If an offset is not finite.

rotation_matrix

rotation_matrix(angle_deg: float, axis: str | Iterable[float] = 'z', about: Iterable[float] | None = None) -> np.ndarray

A 4x4 rotation about an axis through a point.

Parameters:

Name Type Description Default
angle_deg float

Angle in degrees, right-handed (counter-clockwise looking down the axis towards the origin). Multiples of 90 degrees are exact.

required
axis str | Iterable[float]

"x", "y", "z" or a 3-vector of any non-zero length.

'z'
about Iterable[float] | None

A point on the axis; the origin if None.

None

Returns:

Type Description
ndarray

(4, 4) matrix [R | c - R c] for centre c.

Raises:

Type Description
ValueError

If the angle, axis or centre is not finite, or the axis is zero or an unknown name.

crs_info

crs_info(crs) -> CRS

Parse a CRS.

Parameters:

Name Type Description Default
crs

EPSG code ("EPSG:7855", 7855, "EPSG:7855+5711", "urn:ogc:def:crs:EPSG::7855"), PROJ string, WKT (1, ESRI or 2), or an object with a to_wkt() method (pyproj, rasterio).

required

Returns:

Type Description
CRS

Raises:

Type Description
ValueError

If the definition is not recognised, the EPSG code is not in the built-in tables, or WKT without an EPSG code uses a projection Sylva cannot convert.

same_crs

same_crs(a, b) -> bool

Whether two CRS definitions describe the same system.

Parameters:

Name Type Description Default
a

CRS definitions, as for crs_info.

required
b

CRS definitions, as for crs_info.

required

Returns:

Type Description
bool

True for equal definitions, for the same EPSG code(s) written differently (7855, "EPSG:7855", its WKT), or for the same PROJ definition. False if either cannot be parsed.

transformation

transformation(src_crs, dst_crs) -> Transformation

Describe the transformation between two CRSs without applying it.

Parameters:

Name Type Description Default
src_crs

CRS definitions, as for crs_info.

required
dst_crs

CRS definitions, as for crs_info.

required

Returns:

Type Description
Transformation

Raises:

Type Description
ValueError

If a CRS cannot be parsed or the transformation needs a datum grid.

reproject

reproject(data, dst_crs, src_crs=None)

Transform coordinates into another coordinate reference system.

Parameters:

Name Type Description Default
data

A ~sylva.PointCloud, or an (N, 2) or (N, 3) array of x, y (, z). Geographic coordinates are x = longitude and y = latitude in degrees.

required
dst_crs

Target CRS: EPSG code, PROJ string or WKT (see crs_info).

required
src_crs

CRS of data; defaults to the cloud's crs.

None

Returns:

Type Description
PointCloud or ndarray

Same type as data. A cloud keeps its attributes and gets crs = dst_crs (an integer code becomes "EPSG:n"). Points outside a projection's domain, or with NaN coordinates, become NaN. z changes only when the datum does by Helmert parameters (see transformation).

Raises:

Type Description
ValueError

If there is no source CRS, a CRS cannot be parsed, the transformation needs a datum grid, or an array has the wrong shape.

TypeError

For a ~sylva.Raster or another unsupported type. Rasters are not reprojected: a regular grid does not stay regular under a change of projection, so it must be resampled (e.g. with GDAL's gdalwarp) rather than have its extent moved.

Warns:

Type Description
ApproximateTransformationWarning

When part of the transformation cannot be applied (a datum change without known parameters, or a vertical datum change).

Notes

Uses proj4rs (a Rust port of PROJ.4) with the EPSG registry's PROJ.4 definitions. Transformations that PROJ carries out with grids (NTv2, NADCON, geoid models) are not available; the GDA94 to GDA2020 Helmert can be applied by giving GDA2020 as a PROJ string with its towgs84 parameters (see the coordinates guide).

Examples:

>>> cloud = sylva.read("plot.laz")                 # crs from the LAS header
>>> lonlat = sylva.geo.coords.reproject(cloud, "EPSG:7844")

apply_transforms

apply_transforms(scans, transforms, out: str | Path | None = None, merge: bool = False) -> list[PointCloud] | PointCloud

Put scans into one frame with their registration matrices.

Parameters:

Name Type Description Default
scans

Point cloud paths or ~sylva.PointCloud objects: a list, a {name: path or cloud} mapping, one path, or a directory (every point cloud file in it). Paths are read with sylva.read.

required
transforms

One of

  • {name: (4, 4)}, matched to scans by name (see Notes);
  • a list of (4, 4) matrices, one per scan in order, or a single matrix applied to every scan;
  • a transforms.json written by sylva.coreg.SurveyResult.save (read with sylva.coreg.load_transforms);
  • a .DAT file holding one matrix, applied to every scan;
  • a directory of RiSCAN .DAT matrices (ScanPos001.DAT, ...), named by file stem, as RiSCAN exports and sylva coreg writes;
  • a RiSCAN project directory (.RiSCAN or .PROJ); each position's SOP is used (the POP is not applied).
required
out str | Path | None

Where to write: a directory for one file per scan (named after the scan, keeping its format, .laz for clouds without a path and for .rxp), or a file when merge is True. Nothing is written if None.

None
merge bool

Return (and write) one cloud with a scan_id attribute (int32, the scan's index), keeping the attributes all scans share.

False

Returns:

Type Description
list of PointCloud or PointCloud

The transformed scans in input order, or the merged cloud.

Raises:

Type Description
ValueError

If a scan has no matching transform, a name matches several, the numbers of scans and positional transforms differ, or a matrix is not 4x4.

OSError

If a file cannot be read or written.

Notes

A scan is matched to a named transform by its mapping key, or by its path: the file stem or any parent folder that equals a transform name or starts with it followed by a non-alphanumeric character. So ScanPos001.rxp, ScanPos001_2cm.laz and ScanPos001/scans/240101_1200.rxp all get ScanPos001, and ScanPos0011 does not. Unnamed clouds need positional transforms.

Examples:

>>> clouds = sylva.geo.coords.apply_transforms("scans/", "plot_coreg/transforms.json",
...                                        out="plot.laz", merge=True)