commit 2bb698a6e440dbd9a3091a573dc735b0ae903f30
parent 0ede6e3ba4b40f5c2fdfcfdecdab6f78cdc0b989
Author: Anders Damsgaard <anders@adamsgaard.dk>
Date: Fri, 15 May 2026 23:32:18 +0200
feat(qgis): add DEM sampling helpers
Diffstat:
2 files changed, 129 insertions(+), 2 deletions(-)
diff --git a/tem_loader/tem_loader.py b/tem_loader/tem_loader.py
@@ -100,6 +100,36 @@ def _layer_filter(name):
return getattr(layer_filter, name)
+def _warn_dem_sample_failed(raster_layer, x, y):
+ print(
+ 'TEM Loader warning: '
+ f'point ({x}, {y}) is outside DEM raster {raster_layer.name()}'
+ )
+
+
+def _sample_dem_elevation(raster_layer, source_crs, project, x, y):
+ transform = QgsCoordinateTransform(source_crs, raster_layer.crs(), project)
+ try:
+ raster_point = transform.transform(QgsPointXY(x, y))
+ except QgsCsException as exc:
+ print(
+ 'TEM Loader warning: '
+ f'could not transform point ({x}, {y}) to DEM raster '
+ f'{raster_layer.name()}: {exc}'
+ )
+ return None
+
+ value, ok = raster_layer.dataProvider().sample(raster_point, 1)
+ try:
+ elevation = float(value)
+ except (TypeError, ValueError):
+ elevation = math.nan
+ if not ok or math.isnan(elevation):
+ _warn_dem_sample_failed(raster_layer, x, y)
+ return None
+ return elevation
+
+
def _write_geopackage_layer(rows, gpkg_path, layer_name, wkb_type, crs,
transform_context, action):
fields = _build_fields(rows)
diff --git a/test/test_core.py b/test/test_core.py
@@ -1,4 +1,5 @@
import importlib
+import math
from pathlib import Path
from tempfile import TemporaryDirectory
import shutil
@@ -735,13 +736,21 @@ class PluginTests(unittest.TestCase):
return self._authid
class FakeCoordinateTransform:
- pass
+ def __init__(self, source_crs, destination_crs, project):
+ self.source_crs = source_crs
+ self.destination_crs = destination_crs
+ self.project = project
+
+ def transform(self, point):
+ return point
class FakeCsException(Exception):
pass
class FakePointXY:
- pass
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
class FakeLayerGroup:
def __init__(self, name):
@@ -1049,6 +1058,94 @@ class PluginTests(unittest.TestCase):
self.assertEqual(module._layer_filter("RasterLayer"), "LegacyRasterLayer")
+ def test_sample_dem_elevation_samples_band_one(self):
+ module, _, _ = self._import_plugin_module()
+ provider = Mock()
+
+ def sample(point, band):
+ self.assertEqual(point.x, 1.5)
+ self.assertEqual(point.y, 2.5)
+ self.assertEqual(band, 1)
+ return "123.5", True
+
+ provider.sample.side_effect = sample
+ raster_layer = Mock()
+ raster_layer.crs.return_value = module.QgsCoordinateReferenceSystem(
+ "EPSG:3857"
+ )
+ raster_layer.dataProvider.return_value = provider
+ raster_layer.name.return_value = "DEM"
+
+ elevation = module._sample_dem_elevation(
+ raster_layer,
+ module.QgsCoordinateReferenceSystem("EPSG:25832"),
+ module.QgsProject.instance(),
+ 1.5,
+ 2.5,
+ )
+
+ self.assertEqual(elevation, 123.5)
+ raster_layer.crs.assert_called_once_with()
+ raster_layer.dataProvider.assert_called_once_with()
+
+ def test_sample_dem_elevation_warns_for_invalid_sample(self):
+ module, _, _ = self._import_plugin_module()
+ provider = Mock()
+ provider.sample.return_value = math.nan, False
+ raster_layer = Mock()
+ raster_layer.crs.return_value = module.QgsCoordinateReferenceSystem(
+ "EPSG:3857"
+ )
+ raster_layer.dataProvider.return_value = provider
+ raster_layer.name.return_value = "DEM"
+
+ with patch("builtins.print") as print_mock:
+ elevation = module._sample_dem_elevation(
+ raster_layer,
+ module.QgsCoordinateReferenceSystem("EPSG:25832"),
+ module.QgsProject.instance(),
+ 1.5,
+ 2.5,
+ )
+
+ self.assertIsNone(elevation)
+ print_mock.assert_called_once_with(
+ "TEM Loader warning: point (1.5, 2.5) is outside DEM raster DEM"
+ )
+
+ def test_sample_dem_elevation_warns_for_transform_failure(self):
+ module, _, _ = self._import_plugin_module()
+
+ class BrokenTransform:
+ def __init__(self, *_args):
+ pass
+
+ def transform(self, _point):
+ raise module.QgsCsException("bad transform")
+
+ module.QgsCoordinateTransform = BrokenTransform
+ raster_layer = Mock()
+ raster_layer.crs.return_value = module.QgsCoordinateReferenceSystem(
+ "EPSG:3857"
+ )
+ raster_layer.name.return_value = "DEM"
+
+ with patch("builtins.print") as print_mock:
+ elevation = module._sample_dem_elevation(
+ raster_layer,
+ module.QgsCoordinateReferenceSystem("EPSG:25832"),
+ module.QgsProject.instance(),
+ 1.5,
+ 2.5,
+ )
+
+ self.assertIsNone(elevation)
+ raster_layer.dataProvider.assert_not_called()
+ print_mock.assert_called_once_with(
+ "TEM Loader warning: could not transform point (1.5, 2.5) "
+ "to DEM raster DEM: bad transform"
+ )
+
def test_exec_dialog_supports_exec_and_exec_apis(self):
module, _, _ = self._import_plugin_module()
exec_dialog = Mock()