"""Class-based service to retrieve Wallonia DTM as :class:`WolfArray`.
The main entry point is :class:`DtmWalloniaService`, which exposes 1 m and
2 m retrieval helpers based on polygons or raw bounding coordinates.
"""
from __future__ import annotations
import logging
from pathlib import Path
from tempfile import TemporaryFile
from typing import TYPE_CHECKING
import numpy as np
if TYPE_CHECKING:
from wolfhece.wolf_array import WolfArray
from wolfhece.pyvertexvectors import vector
[docs]
_logger = logging.getLogger(__name__)
[docs]
class _DtmWalloniaTiles:
"""Thin wrapper around the tile-index shapefile for Wallonia 1 m DTM.
On construction it downloads (or reuses from cache) the index shapefile
and loads it as a :class:`~wolfhece.wolf_tiles.Tiles` instance.
"""
def __init__(self) -> None:
from wolfhece.pydownloader import toys_dtm_wallonia_1m, DATADIR, WALLONIE_1M
from wolfhece.wolf_tiles import Tiles
from wolfhece.pyvertexvectors import VectorOGLRenderer
[docs]
self.idx_path: Path = toys_dtm_wallonia_1m()
[docs]
self._data_dir: Path = DATADIR / 'dtm' / WALLONIE_1M
[docs]
self._tiles = Tiles(filename=str(self.idx_path), linked_data_dir=str(self._data_dir))
# Display lists are far faster than the shader path for large tile grids.
self._tiles.rendering_machine = VectorOGLRenderer.LIST
[docs]
class DtmWalloniaService:
"""Stateful service to retrieve Wallonia DTM arrays.
This class owns its tile-manager cache so callers can control lifecycle
explicitly and avoid relying on module-level global state.
"""
def __init__(self) -> None:
[docs]
self._tiles: '_DtmWalloniaTiles | None' = None
[docs]
def get_tiles_manager(self, reset: bool = False) -> _DtmWalloniaTiles:
"""Return a cached tile manager instance.
:param reset: If True, drop and recreate the cached manager.
:returns: The tile-manager instance used by this service.
"""
if reset or self._tiles is None:
self._tiles = _DtmWalloniaTiles()
return self._tiles
[docs]
def force_download_index(self) -> Path:
"""Force a re-download of the tile-index shapefile and reset cache.
:returns: Path to the downloaded index shapefile.
"""
from wolfhece.pydownloader import toys_dtm_wallonia_1m
idx_path = toys_dtm_wallonia_1m(load_from_cache=False)
self.get_tiles_manager(reset=True)
return idx_path
[docs]
def get_dtm_1m(self, bounds_vec: 'vector', force: bool = False) -> 'WolfArray':
"""Return a 1 m WolfArray cropped to *bounds_vec*.
:param bounds_vec: Closed polygon defining the area of interest.
:param force: If True, bypass the >100-tiles safeguard.
:returns: 1 m DTM cropped to the polygon bounding box.
"""
tiles = self.get_tiles_manager()
try:
return tiles.extract(bounds_vec, force=force)
except Exception:
_logger.exception('DtmWallonia service: extract failed (force=%s)', force)
raise
[docs]
def get_dtm_2m(self, bounds_vec: 'vector', force: bool = False) -> 'WolfArray':
"""Return a 2 m WolfArray by mean-rebinning the 1 m raster.
:param bounds_vec: Closed polygon defining the area of interest.
:param force: If True, bypass the >100-tiles safeguard.
:returns: 2 m DTM derived from the 1 m extraction.
"""
try:
dtm1m = self.get_dtm_1m(bounds_vec, force=force)
return dtm1m.rebin(factor=2, operation='mean')
except Exception:
_logger.exception('DtmWallonia service: 2m extract failed (force=%s)', force)
raise
[docs]
def get_dtm_1m_xx_yy(
self,
xmin: float,
xmax: float,
ymin: float,
ymax: float,
force: bool = False,
) -> 'WolfArray':
"""Return a 1 m WolfArray from raw bounding coordinates.
Convenience wrapper around :meth:`get_dtm_1m`.
"""
from wolfhece.pyvertexvectors import vector
bounds_vec = vector()
bounds_vec.add_vertices_from_array(np.array([[xmin, ymin],
[xmax, ymin],
[xmax, ymax],
[xmin, ymax]]))
bounds_vec.force_to_close()
try:
return self.get_dtm_1m(bounds_vec, force=force)
except Exception:
_logger.exception(
'DtmWallonia service: get_dtm_xx_yy failed for bounds '
'(xmin=%s, xmax=%s, ymin=%s, ymax=%s, force=%s)',
xmin,
xmax,
ymin,
ymax,
force,
)
raise
[docs]
def get_dtm_2m_xx_yy(
self,
xmin: float,
xmax: float,
ymin: float,
ymax: float,
force: bool = False,
) -> 'WolfArray':
"""Return a 2 m WolfArray from raw bounding coordinates.
Convenience wrapper around :meth:`get_dtm_2m`.
"""
from wolfhece.pyvertexvectors import vector
bounds_vec = vector()
bounds_vec.add_vertices_from_array(np.array([[xmin, ymin],
[xmax, ymin],
[xmax, ymax],
[xmin, ymax]]))
bounds_vec.force_to_close()
try:
return self.get_dtm_2m(bounds_vec, force=force)
except Exception:
_logger.exception(
'DtmWallonia service: get_dtm_xx_yy failed for bounds '
'(xmin=%s, xmax=%s, ymin=%s, ymax=%s, force=%s)',
xmin,
xmax,
ymin,
ymax,
force,
)
raise