Files
Plugin_SN_Basis/tests/test_basemap_stilkorrektur.py
T
DanielandClaude Opus 5.5 d6d4403e46 Basemap: Punktlinien in Karte und Legende sichtbar machen (#90)
basemap.de zeichnet Punktlinien mit Strichlänge 0 und runder Linienkappe (MapLibre-Muster);
QGIS übernimmt das beim Import 1:1, Qt zeichnet Striche der Länge 0 aber nicht. Baumreihen,
Hecken, Durchlässe, Seilbahnen u. a. (12 Styles) fehlten dadurch in Karte und Legende.

Neues Modul basemap_stilkorrektur: korrigiere_punktlinien(layer) ersetzt Strichlänge 0 durch
0,01 im festen Strichmuster und in den array(...)-Mustern des zoomabhängigen Ausdrucks;
idempotent, andere Styles unverändert. Ausgelöst per Knopf „Punktlinien korrigieren“ im
Basemap-Bereich, sicherbar als Stilvorlage. Wrapper: PROPERTY_CUSTOM_DASH,
QgsProperty.fromExpression im Mock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 12:33:00 +02:00

150 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Tests für modules/basemap_stilkorrektur.py – Punktlinien des Basemap-Layers sichtbar machen (Basis #90).
basemap.de zeichnet Punktlinien mit Strichlänge 0 und runder Kappe; Qt zeichnet Striche der
Länge 0 nicht. Die Korrektur ersetzt Strichlänge 0 durch einen kleinen Wert – im festen
Strichmuster und im zoomabhängigen Ausdruck. Symbole und Layer sind Fakes; die Wirkung in
Karte und Legende ist im QGIS-Test geprüft.
"""
import unittest
from sn_basis.functions.qgiscore_wrapper import PROPERTY_CUSTOM_DASH, PROPERTY_TYPE_EXPRESSION
from sn_basis.modules.basemap_stilkorrektur import (
MINDESTSTRICH,
korrigiere_ausdruck,
korrigiere_muster,
korrigiere_punktlinien,
)
# Ausdruck, wie ihn der QGIS-Konverter für line-dasharray mit Stopps erzeugt (gekürzt)
AUSDRUCK_BAUMREIHE = (
"array_to_string(array_foreach(CASE WHEN @vector_tile_zoom <= 12 THEN array(0,1.75) "
"WHEN @vector_tile_zoom > 12 AND @vector_tile_zoom <= 14 THEN array(0,2) "
"ELSE array(0,4) END,@element * (scale_linear(@vector_tile_zoom,14,17,3,8))),';')"
)
class _Eigenschaft:
def __init__(self, ausdruck):
self._ausdruck = ausdruck
def propertyType(self): return PROPERTY_TYPE_EXPRESSION
def expressionString(self): return self._ausdruck
class _Eigenschaften:
def __init__(self, ausdruck=None):
self.werte = {PROPERTY_CUSTOM_DASH: _Eigenschaft(ausdruck)} if ausdruck else {}
def propertyKeys(self): return list(self.werte)
def property(self, schluessel): return self.werte[schluessel]
def setProperty(self, schluessel, eigenschaft):
text = getattr(eigenschaft, "_ausdruck", None) or getattr(eigenschaft, "_value", None)
self.werte[schluessel] = _Eigenschaft(text)
class _Linie:
def __init__(self, muster=None, ausdruck=None):
self.muster = list(muster) if muster is not None else None
self.eigenschaften = _Eigenschaften(ausdruck)
def useCustomDashPattern(self): return self.muster is not None
def customDashVector(self): return list(self.muster or [])
def setCustomDashVector(self, werte): self.muster = list(werte)
def dataDefinedProperties(self): return self.eigenschaften
def subSymbol(self): return None
class _Symbol:
def __init__(self, *ebenen): self.ebenen = list(ebenen)
def symbolLayers(self): return self.ebenen
def clone(self): return self # die Fakes prüfen nur das Ergebnis
class _Stil:
def __init__(self, name, symbol): self._name, self._symbol = name, symbol
def styleName(self): return self._name
def symbol(self): return self._symbol
def setSymbol(self, symbol): self._symbol = symbol
class _Renderer:
def __init__(self, stile):
self.stile = stile
self.gesetzt = []
def styles(self): return list(self.stile)
def setStyle(self, index, stil):
self.gesetzt.append(index)
self.stile[index] = stil
class _Layer:
def __init__(self, stile):
self._renderer = _Renderer(stile)
self.neu_gezeichnet = 0
def renderer(self): return self._renderer
def triggerRepaint(self): self.neu_gezeichnet += 1
def _baumreihe():
return _Stil("VegetationsL_Baumreihe", _Symbol(_Linie([0.0, 0.93], AUSDRUCK_BAUMREIHE)))
class TestMusterUndAusdruck(unittest.TestCase):
def test_strichlaenge_null_im_festen_muster(self):
"""Prüft, dass Strichlängen 0 (gerade Positionen) im Strichmuster ersetzt und Lücken der Länge 0 nicht verändert werden."""
self.assertEqual(korrigiere_muster([0.0, 0.93]), [MINDESTSTRICH, 0.93])
self.assertEqual(korrigiere_muster([2.0, 0.0, 0.0, 1.0]), [2.0, 0.0, MINDESTSTRICH, 1.0])
self.assertEqual(korrigiere_muster([5.0, 2.0]), [5.0, 2.0])
def test_strichlaenge_null_im_zoomabhaengigen_ausdruck(self):
"""Prüft, dass jede Strichlänge 0 in den array(...)-Mustern des Konverter-Ausdrucks ersetzt wird und der übrige Ausdruck gleich bleibt."""
neu = korrigiere_ausdruck(AUSDRUCK_BAUMREIHE)
self.assertNotIn("array(0,", neu)
self.assertEqual(neu.count(f"array({MINDESTSTRICH},"), 3)
self.assertIn("@element * (scale_linear(@vector_tile_zoom,14,17,3,8))", neu)
self.assertEqual(korrigiere_ausdruck("array(3,0,1,2)"), "array(3,0,1,2)")
self.assertEqual(korrigiere_ausdruck("array(1,0,0,2)"), f"array(1,0,{MINDESTSTRICH},2)")
class TestLayerkorrektur(unittest.TestCase):
def test_punktlinien_werden_korrigiert_andere_styles_nicht(self):
"""Prüft, dass nur Styles mit Strichlänge 0 korrigiert werden – Muster und Ausdruck – und die Anzahl zurückkommt."""
weg = _Stil("Kontur_Wirtschaftsweg", _Symbol(_Linie()))
strichlinie = _Stil("Grenze", _Symbol(_Linie([4.0, 2.0], "array_to_string(array(4,2),';')")))
layer = _Layer([weg, _baumreihe(), strichlinie])
self.assertEqual(korrigiere_punktlinien(layer), 1)
linie = layer.renderer().styles()[1].symbol().symbolLayers()[0]
self.assertEqual(linie.customDashVector(), [MINDESTSTRICH, 0.93])
self.assertNotIn("array(0,", linie.dataDefinedProperties().property(PROPERTY_CUSTOM_DASH).expressionString())
self.assertEqual(layer.renderer().gesetzt, [1])
self.assertEqual(layer.neu_gezeichnet, 1)
def test_zweiter_lauf_korrigiert_nichts(self):
"""Prüft, dass ein zweiter Lauf keine Styles mehr korrigiert und nicht neu zeichnen lässt."""
layer = _Layer([_baumreihe()])
korrigiere_punktlinien(layer)
self.assertEqual(korrigiere_punktlinien(layer), 0)
self.assertEqual(layer.neu_gezeichnet, 1)
def test_nur_ausdruck_ohne_festes_muster(self):
"""Prüft, dass auch ein Style korrigiert wird, dessen Strichlänge 0 nur im zoomabhängigen Ausdruck steht."""
layer = _Layer([_Stil("Hecke", _Symbol(_Linie([1.0, 2.0], "array_to_string(array(0,3),';')")))])
self.assertEqual(korrigiere_punktlinien(layer), 1)
def test_layer_ohne_renderer(self):
"""Prüft, dass ein Layer ohne Renderer 0 korrigierte Styles liefert."""
self.assertEqual(korrigiere_punktlinien(type("L", (), {"renderer": lambda s: None})()), 0)
if __name__ == "__main__":
unittest.main()