Source code for geovista.crs

# Copyright (c) 2021, GeoVista Contributors.
#
# This file is part of GeoVista and is distributed under the 3-Clause BSD license.
# See the LICENSE file in the package root directory for licensing details.

"""Coordinate reference system (CRS) utility functions.

Notes
-----
.. versionadded:: 0.1.0

"""

from __future__ import annotations

from typing import TYPE_CHECKING, TypeAlias

import lazy_loader as lazy
from pyproj import CRS

from .common import GV_FIELD_CRS

if TYPE_CHECKING:
    import pyvista as pv

# lazy import third-party dependencies
np = lazy.load("numpy")

__all__ = [
    "CRSLike",
    "PlateCarree",
    "WGS84",
    "from_wkt",
    "get_central_meridian",
    "has_wkt",
    "projected",
    "set_central_meridian",
    "to_wkt",
]

# type aliases
CRSLike: TypeAlias = int | str | dict | CRS
"""Type alias for a Coordinate Reference System."""

# constants
EPSG_CENTRAL_MERIDIAN: str = "8802"
"""EPSG projection parameter for longitude of natural origin/central meridian."""

PlateCarree = CRS.from_user_input("epsg:32662")
"""WGS84 / Plate Carree (Equidistant Cylindrical)."""

WGS84 = CRS.from_user_input("epsg:4326")
"""Geographic WGS84."""


[docs] def from_wkt(mesh: pv.PolyData) -> CRS: """Get the :class:`~pyproj.crs.CRS` associated with the mesh. Parameters ---------- mesh : :class:`~pyvista.PolyData` The mesh containing the :class:`~pyproj.crs.CRS` serialized as OGC Well-Known-Text (WKT). Returns ------- :class:`~pyproj.crs.CRS` The Coordinate Reference System. Notes ----- .. versionadded:: 0.1.0 """ crs = None if has_wkt(mesh): wkt = str(mesh.field_data[GV_FIELD_CRS][0]) crs = CRS.from_wkt(wkt) return crs
[docs] def get_central_meridian(crs: CRS) -> float | None: """Retrieve the longitude of natural origin of the `CRS`. THe natural origin is also known as the central meridian. Parameters ---------- crs : :class:`~pyproj.crs.CRS` The Coordinate Reference System. Returns ------- float The central meridian, or ``None`` if the `crs` has no such parameter. Notes ----- .. versionadded:: 0.1.0 """ result = None if crs.coordinate_operation is not None: params = crs.coordinate_operation.params cm_param = list( filter(lambda param: param.code == EPSG_CENTRAL_MERIDIAN, params) ) if len(cm_param) == 1: (cm_param,) = cm_param result = cm_param.value return result
[docs] def has_wkt(mesh: pv.PolyData) -> bool: """Determine whether the provided mesh has a CRS serialized as WKT attached. Parameters ---------- mesh : :class:`~pyvista.PolyData` The mesh to be inspected for a serialized :class:`~pyproj.crs.CRS`. Returns ------- bool Whether the mesh has a :class:`~pyproj.crs.CRS` serialized as WKT attached. Notes ----- .. versionadded:: 0.4.0 """ return GV_FIELD_CRS in mesh.field_data
[docs] def projected(mesh: pv.PolyData) -> bool: """Determine if the mesh is a planar projection. Simple heuristic approach achieved by attempting to inspect the associated :class:`~pyproj.crs.CRS` of the mesh. If the mesh :class:`~pyproj.crs.CRS` is unavailable then the weaker contract of inspecting the mesh geometry is used to detect for a flat plane. Parameters ---------- mesh : :class:`~pyvista.PolyData` The mesh to be inspected. Returns ------- bool Whether the mesh is projected. Notes ----- .. versionadded:: 0.1.0 """ crs = from_wkt(mesh) if crs is None: xmin, xmax, ymin, ymax, zmin, zmax = mesh.bounds xdelta, ydelta, zdelta = (xmax - xmin), (ymax - ymin), (zmax - zmin) result = np.isclose(xdelta, 0) or np.isclose(ydelta, 0) or np.isclose(zdelta, 0) else: result = crs.is_projected return result
[docs] def set_central_meridian(crs: CRS, meridian: float) -> CRS | None: """Replace the longitude of natural origin in the :class:`~pyproj.crs.CRS`. The natural origin is also known as the central meridian. Note that, the `crs` is immutable, therefore a new instance will be returned with the specified central meridian. Parameters ---------- crs : :class:`~pyproj.crs.CRS` The Coordinate Reference System. meridian : float The replacement central meridian. Returns ------- :class:`~pyproj.crs.CRS` or None The :class:`~pyproj.crs.CRS` with the specified central meridian, or ``None`` if the :class:`~pyproj.crs.CRS` has no such parameter. Notes ----- .. versionadded:: 0.1.0 """ # https://proj.org/development/reference/cpp/operation.html#classosgeo_1_1proj_1_1operation_1_1Conversion_1center_longitude result = None crs_json = crs.to_json_dict() found = False if (conversion := crs_json.get("conversion")) and ( parameters := conversion.get("parameters") ): for param in parameters: if found := param["id"]["code"] == int(EPSG_CENTRAL_MERIDIAN): param["value"] = meridian break if found: result = CRS.from_json_dict(crs_json) return result
[docs] def to_wkt(mesh: pv.PolyData, crs: CRS) -> None: """Attach serialized :class:`~pyproj.crs.CRS` as Well-Known-Text (WKT) to the mesh. The serialized OGC WKT is attached to the ``field_data`` of the mesh in-place. Parameters ---------- mesh : :class:`~pyvista.PolyData` The mesh to contain the serialized OGC WKT. crs : :class:`~pyproj.crs.CRS` The Coordinate Reference System to be serialized. Notes ----- .. versionadded:: 0.2.0 """ wkt = crs.to_wkt() mesh.field_data[GV_FIELD_CRS] = np.array([wkt])