From 84e03d208c0a4bea40c83945bba4f9284bcbd481 Mon Sep 17 00:00:00 2001 From: daniel Date: Thu, 17 Sep 2026 16:29:19 +0200 Subject: [PATCH] Grenzpunkte aus ALKIS werden im ALKIS-Stil angezeigt (#53) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grenzpunkte_gemarkungsweise() ruft nach project.addMapLayer(joined_layer) jetzt apply_style(joined_layer, "alkis_grenzpunkte.qml") auf - analog zum bereits etablierten Muster bei Flurstücken/Verfahrensgebiet/BROR in working_tab.py bzw. verfahrensgebiet_alkis_komplett.py. Neuer Test test_stil_wird_nach_dem_laden_auf_den_ergebnis_layer_angewendet mockt die komplette interne WFS-Kette (Extents, Hits, GML-Parsing, Grenzpunkt-Batch, Join) und prüft, dass apply_style() mit dem Ergebnis- Layer und dem richtigen Dateinamen aufgerufen wird. Co-Authored-By: Claude Sonnet 5 --- changelog.txt | 1 + functions/grenzpunkt_alkis.py | 2 + styles/alkis_grenzpunkte.qml | 913 +++++++++++++++++++++++++++++++++ tests/test_grenzpunkt_alkis.py | 130 +++++ 4 files changed, 1046 insertions(+) create mode 100644 styles/alkis_grenzpunkte.qml diff --git a/changelog.txt b/changelog.txt index f02bd08..5005014 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,4 @@ +- Neu: Grenzpunkte aus ALKIS werden nach dem Laden im ALKIS-Stil dargestellt (styles/alkis_grenzpunkte.qml), statt ungestylt zu bleiben (#53) - Intern: Tests von TestFlurstueckAlkis an die seit längerem geänderte Ladelogik angeglichen (Mocks zeigten noch auf die alte Ein-Query-/Zwei-Knopf-Variante), bbox-Dokumentation in flurstueck_alkis.py korrigiert (bbox wird nicht mehr ausgewertet) – keine Verhaltensänderung (Test-Audit #16) - Intern: Der Button "aus gewählten Gemarkungen laden" nutzt jetzt dieselbe Ladelogik wie "Flurstücke laden" (flurstueck_alkis) statt einer eigenen, parallelen Implementierung – konsolidiert drei bisher getrennte Code-Pfade zum Flurstücke-Laden auf einen (#47) - Bugfix: Flurstücke aus ALKIS an bestehenden Layer anhängen überspringt jetzt bereits vorhandene Flurstücke (Abgleich über flurstueckskennzeichen), statt sie doppelt zu laden – betrifft sowohl den Dialog in "Flurstücke laden" als auch den Button "aus gewählten Gemarkungen laden" (#28) diff --git a/functions/grenzpunkt_alkis.py b/functions/grenzpunkt_alkis.py index e7c5f6e..0297a6f 100644 --- a/functions/grenzpunkt_alkis.py +++ b/functions/grenzpunkt_alkis.py @@ -34,6 +34,7 @@ from sn_basis.functions import QgsVectorLayer, QgsProject from sn_basis.functions import QMessageBox from sn_basis.functions.message_wrapper import info, warning from sn_basis.functions.dialog_wrapper import create_progress_dialog +from sn_basis.functions.ly_style_wrapper import apply_style ALKIS_SF_URL = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs" PUNKTORT_TA_TYPENAME = "adv:AX_PunktortTA" @@ -1652,6 +1653,7 @@ def grenzpunkte_gemarkungsweise( return None project.addMapLayer(joined_layer) + apply_style(joined_layer, "alkis_grenzpunkte.qml") prog.set_value(prog_gesamt) prog.close() diff --git a/styles/alkis_grenzpunkte.qml b/styles/alkis_grenzpunkte.qml new file mode 100644 index 0000000..e0bf9d7 --- /dev/null +++ b/styles/alkis_grenzpunkte.qml @@ -0,0 +1,913 @@ + + + + 1 + 1 + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + generatedlayout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + COALESCE( "gml_id", '<NULL>' ) + + 0 + diff --git a/tests/test_grenzpunkt_alkis.py b/tests/test_grenzpunkt_alkis.py index 7411d51..fc441ac 100644 --- a/tests/test_grenzpunkt_alkis.py +++ b/tests/test_grenzpunkt_alkis.py @@ -43,6 +43,7 @@ if "qgis" not in sys.modules: from sn_verfahrensgebiet.functions.grenzpunkt_alkis import ( grenzpunkt_alkis, + grenzpunkte_gemarkungsweise, _extrahiere_gml_ids, ) @@ -297,5 +298,134 @@ class TestGrenzpunktAlkis(unittest.TestCase): self.assertIs(gp, gp_layer) +# --------------------------------------------------------------------------- +# Tests: grenzpunkte_gemarkungsweise – Stilanbringung (#53) +# --------------------------------------------------------------------------- + +_G_PATCH_EXTENTS = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._lade_gemarkung_extents" +_G_PATCH_HITS = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._wfs_hits_count" +_G_PATCH_GET_GML = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._wfs_get_gml" +_G_PATCH_PARSE_GML = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._parse_alkis_gml_memory_layer" +_G_PATCH_ERKENNE_FELD = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._erkenne_gehoert_zu_feld" +_G_PATCH_EXTRAHIERE_ID = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._extrahiere_feature_gml_id" +_G_PATCH_LADE_GP_DICT = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._lade_grenzpunkte_als_dict" +_G_PATCH_ERSTELLE_JOINED = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._erstelle_joined_layer" +_G_PATCH_WFS_LAYER = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.QgsVectorLayer" +_G_PATCH_PROJECT = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.QgsProject" +_G_PATCH_PROGRESS = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.create_progress_dialog" +_G_PATCH_APPLY_STIL = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.apply_style" +# QgsField/QgsFeature werden innerhalb der Funktion per lokalem Import geholt +# (from sn_basis.functions import QgsField/QgsFeature) - Patch-Ziel ist daher +# die Quelle, nicht der Aufrufort. +_G_PATCH_QGSFIELD = "sn_basis.functions.QgsField" +_G_PATCH_QGSFEATURE = "sn_basis.functions.QgsFeature" + + +class _DummyProgress: + def set_value(self, wert): pass + def set_label(self, text): pass + def set_total(self, wert): pass + def is_canceled(self): return False + def close(self): pass + + +class _DummyBbox: + def xMinimum(self): return 414000.0 + def yMinimum(self): return 5657000.0 + def xMaximum(self): return 415000.0 + def yMaximum(self): return 5658000.0 + + +class _DummyGmlFeld: + def __init__(self, name): + self._name = name + + def name(self): + return self._name + + +class _DummyGmlFeature: + def __init__(self, gehoert_zu_wert): + self._wert = gehoert_zu_wert + + def geometry(self): + return object() + + def __getitem__(self, key): + if key == "gehoertZu": + return self._wert + raise KeyError(key) + + +class _DummyGmlLayer: + """Minimaler Ersatz für das Ergebnis von _parse_alkis_gml_memory_layer().""" + + def __init__(self, feature): + self._feature = feature + + def fields(self): + return [_DummyGmlFeld("gehoertZu")] + + def getFeatures(self): + return [self._feature] + + +class TestGrenzpunkteGemarkungsweiseStil(unittest.TestCase): + + @patch(_G_PATCH_APPLY_STIL) + @patch(_G_PATCH_QGSFEATURE) + @patch(_G_PATCH_QGSFIELD) + @patch(_G_PATCH_PROGRESS) + @patch(_G_PATCH_PROJECT) + @patch(_G_PATCH_WFS_LAYER) + @patch(_G_PATCH_ERSTELLE_JOINED) + @patch(_G_PATCH_LADE_GP_DICT) + @patch(_G_PATCH_EXTRAHIERE_ID) + @patch(_G_PATCH_ERKENNE_FELD) + @patch(_G_PATCH_PARSE_GML) + @patch(_G_PATCH_GET_GML) + @patch(_G_PATCH_HITS) + @patch(_G_PATCH_EXTENTS) + def test_stil_wird_nach_dem_laden_auf_den_ergebnis_layer_angewendet( + self, + mock_extents, + mock_hits, + mock_get_gml, + mock_parse_gml, + mock_erkenne_feld, + mock_extrahiere_id, + mock_lade_gp_dict, + mock_erstelle_joined, + mock_wfs_layer_cls, + mock_project_cls, + mock_progress, + mock_qgsfield, + mock_qgsfeature, + mock_apply_style, + ): + """Prüft, dass grenzpunkte_gemarkungsweise() nach dem Laden apply_style() mit dem neuen ALKIS-Grenzpunkt-Stil auf den fertigen Join-Layer anwendet (#53).""" + mock_extents.return_value = {"1234": _DummyBbox()} + mock_hits.return_value = 1 + mock_get_gml.return_value = b"" + mock_parse_gml.return_value = _DummyGmlLayer(_DummyGmlFeature("urn:adv:oid:GP1")) + mock_erkenne_feld.return_value = "gehoertZu" + mock_extrahiere_id.return_value = "PTA1" + mock_lade_gp_dict.return_value = ({"GP1": object()}, ["gp_feld"]) + + joined_layer = MagicMock() + mock_erstelle_joined.return_value = joined_layer + mock_wfs_layer_cls.return_value = MagicMock() + + project = MagicMock() + mock_project_cls.instance.return_value = project + mock_progress.return_value = _DummyProgress() + + ergebnis = grenzpunkte_gemarkungsweise(None, ["1234"]) + + self.assertIs(ergebnis, joined_layer) + project.addMapLayer.assert_called_once_with(joined_layer) + mock_apply_style.assert_called_once_with(joined_layer, "alkis_grenzpunkte.qml") + + if __name__ == "__main__": unittest.main()