from __future__ import annotations
from functools import cached_property
import pyproj
[docs]class CoordinateReferenceSystems:
"""
Represents the horizontal (X, Y) and vertical (Z) coordinate reference systems of a 3D point.
"""
[docs] def __init__(self, horizontal: pyproj.CRS, vertical: pyproj.CRS) -> None:
"""
Initializes a CoordinateReferenceSystems object.
Parameters
----------
horizontal : pyproj.CRS
The coordinate reference system of the X and Y-coordinates.
It is a `pyproj.CRS` object (see https://pyproj4.github.io/pyproj/stable/api/crs/crs.html) of
type 'Projected' or 'Compound'.
vertical : pyproj.CRS
The coordinate reference system of the Z-coordinate.
It is a `pyproj.CRS` object (see https://pyproj4.github.io/pyproj/stable/api/crs/crs.html) of
type 'Vertical' or 'Compound'.
Raises
------
TypeError
If the input types are incorrect.
ValueError
If `horizontal` is not a projected or compound CRS.
If `vertical` is not a vertical or compound CRS.
"""
# Initialize all attributes using private setters.
self._set_horizontal(horizontal)
self._set_vertical(vertical)
[docs] @classmethod
def from_epsg(cls, horizontal: int, vertical: int) -> CoordinateReferenceSystems:
"""
Creates a CoordinateReferenceSystems object from the EPSG codes of the horizontal and vertical CRS.
Note
----
If your settlement rod is located in the Netherlands the horizontal coordinate reference systems is likely `28992`
(Amersfoort / RD New) and the vertical `5709` (NAP height). To combine use `7415` (Amersfoort / RD New + NAP height).
Parameters
----------
horizontal : int
The EPSG code of the horizontal CRS.
vertical : int
The EPSG code of the vertical CRS.
Returns
-------
CoordinateReferenceSystems
A CoordinateReferenceSystems object with the horizontal and vertical CRS.
Raises
------
pyproj.exceptions.CRSError
If the EPSG codes are not valid.
"""
return cls(
horizontal=pyproj.CRS.from_epsg(horizontal),
vertical=pyproj.CRS.from_epsg(vertical),
)
def _set_horizontal(self, value: pyproj.CRS) -> None:
"""
Private setter for horizontal attribute.
"""
if not isinstance(value, pyproj.CRS):
raise TypeError("Expected 'pyproj.CRS' type for 'horizontal' attribute.")
# Check whether the CRS is a projected or compound CRS.
if not value.is_projected and not value.is_compound:
raise ValueError(
"Expected 'is_projected' or 'is_compound' to be true for 'horizontal' attribute."
)
# Set the coordinate system in case of a projected CRS.
if value.is_projected:
self._horizontal = value
# Set the coordinate system in case of a compound CRS.
elif value.is_compound:
projected_crs = None
for crs in value.sub_crs_list:
if crs.is_projected:
projected_crs = crs
break
if projected_crs is None:
raise ValueError("No projected CRS found in the compound CRS.")
self._horizontal = projected_crs
def _set_vertical(self, value: pyproj.CRS) -> None:
"""
Private setter for z attribute.
"""
if not isinstance(value, pyproj.CRS):
raise TypeError("Expected 'pyproj.CRS' type for 'vertical' attribute.")
# Check whether the CRS is a vertical or compound CRS.
if not value.is_vertical and not value.is_compound:
raise ValueError(
"Expected 'is_vertical' or 'is_compound' to be true for 'vertical' attribute."
)
# Set the coordinate system in case of a vertical CRS.
if value.is_vertical:
self._vertical = value
# Set the coordinate system in case of a compound CRS.
elif value.is_compound:
vertical_crs = None
for crs in value.sub_crs_list:
if crs.is_vertical:
vertical_crs = crs
break
if vertical_crs is None:
raise ValueError("No vertical CRS found in the compound CRS.")
self._vertical = vertical_crs
@property
def horizontal(self) -> pyproj.CRS:
"""
The coordinate reference system of the horizontal X and Y-coordinates.
"""
return self._horizontal
@property
def vertical(self) -> pyproj.CRS:
"""
The coordinate reference system of the vertical Z-coordinate.
"""
return self._vertical
@property
def horizontal_units(self) -> str:
"""
The units of the horizontal CRS.
"""
return self._horizontal.axis_info[0].unit_name
@property
def vertical_units(self) -> str:
"""
The units of the vertical CRS
"""
return self._vertical.axis_info[0].unit_name
@property
def vertical_datum(self) -> str:
"""
The name of the vertical datum.
"""
return self._vertical.name
@cached_property
def vertical_datum_and_units(self) -> str:
"""
The vertical datum and units of the vertical CRS.
"""
return f"{self.vertical_datum} [{self.vertical_units}]"
def __str__(self) -> str:
"""Converts the object to a string."""
return f"CoordinateReferenceSystems(Horizontal: {self.horizontal.to_epsg()}, Vertical: {self.vertical.to_epsg()})"
def __eq__(self, value: object) -> bool:
"""Compares two CoordinateReferenceSystems objects."""
if not isinstance(value, CoordinateReferenceSystems):
return False
return self.horizontal.equals(value.horizontal) and self.vertical.equals(
value.vertical
)