Source code for weitsicht.mapping.base_class
# -----------------------------------------------------------------------
# Copyright 2026 Martin Wieser
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------
"""Base interfaces for mapping backends.
Mapping backends implement the conversion between rays/coordinates and mapped 3D coordinates
in a target domain (e.g. a horizontal plane, a raster DEM, a mesh).
"""
from __future__ import annotations
from abc import abstractmethod
from collections.abc import Mapping
from enum import Enum
from typing import Any, TypeVar
from pyproj import CRS
from weitsicht.transform.coordinates_transformer import CoordinateTransformer
from weitsicht.utils import ArrayNx2, ArrayNx3, MappingResult
__all__ = ["MappingType", "MappingBase"]
class MappingType(Enum):
"""Enum identifying available mapping backends.
The :attr:`fullname` attribute is a stable, human-readable identifier used for
(de-)serialization (e.g. in :meth:`~weitsicht.mapping.base_class.MappingBase.param_dict`).
"""
fullname: str # human-readable identifier set on each enum member
HorizontalPlane = 1, "horizontalPlane"
Raster = 2, "Raster"
GeoreferencedNumpyArray = 2, "GeorefArray"
Trimesh = 3, "Trimesh"
def __new__(cls, value: int, name: str):
member = object.__new__(cls)
member._value_ = value
member.fullname = name
return member
def __int__(self):
return self.value
_Self = TypeVar("_Self", bound="MappingBase")
[docs]
class MappingBase:
"""Abstract base class for all mapping backends."""
[docs]
def __init__(self):
"""Initialize a mapper with an optional CRS (set via :attr:`crs`)."""
self._crs: CRS | None = None
[docs]
@classmethod
@abstractmethod
def from_dict(cls: type[_Self], mapper_dict: Mapping[str, Any]) -> _Self:
"""Create a mapper instance from a configuration dictionary.
:param mapper_dict: Mapper configuration dictionary (typically created via ``mapper.param_dict``).
:type mapper_dict: Mapping[str, Any]
:return: Instantiated mapper.
:rtype: MappingBase
:raises KeyError: If a required dictionary key is missing.
:raises ValueError: If configuration values are invalid.
:raises CRSInputError: If CRS input in the configuration is invalid (e.g. malformed WKT).
:raises CRSnoZaxisError: If a required CRS does not define a Z axis.
:raises MappingError: If the mapper cannot be initialized.
"""
pass
@property
@abstractmethod
def type(self) -> MappingType:
"""Return the mapper type.
:return: Mapper type.
:rtype: MappingType
"""
pass
@property
@abstractmethod
def param_dict(self) -> Mapping[str, Any]:
"""Return the mapper configuration dictionary.
This dictionary is intended to be used with :meth:`from_dict`.
:return: Mapper configuration dictionary.
:rtype: Mapping[str, Any]
"""
pass
@property
def crs(self) -> CRS | None:
"""Return the CRS of the mapper (if set).
:return: CRS of the mapper.
:rtype: CRS | None
"""
return self._crs
@crs.setter
def crs(self, crs: CRS | None):
"""Set the CRS of the mapper.
:param crs: CRS to use, or ``None`` to unset.
:type crs: CRS | None
"""
self._crs = crs
@property
def crs_wkt(self) -> str | None:
"""Return the CRS as WKT string, or ``None`` if not set.
:return: CRS WKT string.
:rtype: str | None
"""
if self._crs is not None:
return self._crs.to_wkt()
return None
[docs]
@abstractmethod
def map_coordinates_from_rays(
self,
ray_vectors_crs_s: ArrayNx3,
ray_start_crs_s: ArrayNx3,
crs_s: CRS | None = None,
transformer: CoordinateTransformer | None = None,
) -> MappingResult:
"""Map 3D coordinates from ray definitions.
The input rays are provided by a ray start point and a ray direction vector. If a CRS
conversion is required, pass either ``crs_s`` (and the mapper's :attr:`crs` must be set)
or a preconfigured ``transformer``.
:param ray_vectors_crs_s: Ray direction vectors (N×3).
:type ray_vectors_crs_s: ArrayNx3
:param ray_start_crs_s: Ray start points (N×3), same shape as ``ray_vectors_crs_s``.
:type ray_start_crs_s: ArrayNx3
:param crs_s: CRS of the input rays, defaults to None.
:type crs_s: CRS | None
:param transformer: Coordinate transformer to mapper CRS, defaults to None.
:type transformer: CoordinateTransformer | None
:return: Mapping result containing mapped coordinates and a validity mask.
:rtype: MappingResult
:raises ValueError: If input arrays have incompatible shapes.
:raises CRSInputError: If both ``crs_s`` and ``transformer`` are provided.
:raises CoordinateTransformationError: If coordinate transformation fails.
"""
pass
[docs]
@abstractmethod
def map_heights_from_coordinates(
self,
coordinates_crs_s: ArrayNx3 | ArrayNx2,
crs_s: CRS | None = None,
transformer: CoordinateTransformer | None = None,
) -> MappingResult:
"""Sample heights for given 2D/3D coordinates.
This is typically used to lift planar coordinates (x, y) to 3D (x, y, z) using a DEM.
:param coordinates_crs_s: Coordinates to sample (N×2 or N×3). If N×2, Z is backend-defined.
:type coordinates_crs_s: ArrayNx3 | ArrayNx2
:param crs_s: CRS of the input coordinates, defaults to None.
:type crs_s: CRS | None
:param transformer: Coordinate transformer to mapper CRS, defaults to None.
:type transformer: CoordinateTransformer | None
:return: Mapping result containing coordinates with sampled heights and a validity mask.
:rtype: MappingResult
:raises ValueError: If input arrays have incompatible shapes.
:raises CRSInputError: If both ``crs_s`` and ``transformer`` are provided.
:raises CoordinateTransformationError: If coordinate transformation fails.
"""
pass