qgis-tem-loader

qgis plugin for loading TEM geophysical inversion XYZ files as 3D objects
git clone git://src.adamsgaard.dk/qgis-tem-loader # fast
git clone https://src.adamsgaard.dk/qgis-tem-loader.git # slow
Log | Files | Refs | README | LICENSE Back to index

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:
Mtem_loader/tem_loader.py | 30++++++++++++++++++++++++++++++
Mtest/test_core.py | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
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()