Basemap-Legende: Auswertung und Legendenelement im Drucklayout (#88)

Beim Erstellen einer §38-/§41-Vorlage wertet BasemapLegende die Kacheln des sichtbaren
basemap.de-Layers im Verfahrensgebiet (wahlweise Druckbereich) kachelweise über
selectByGeometry aus, prüft die Renderer-Styles mit derselben Zoomlogik wie QGIS und
bildet daraus Legendeneinträge. Ein Mehrfachauswahl-Dialog (neu: ask_mehrfachauswahl)
lässt Kategorien abwählen; die Auswahl wird im Projekt gemerkt. Die gewählten Einträge
stehen mit der Signatur des Projektlayers (zoomabhängige Werte festgeschrieben) in
einem eigenen Legendenelement über der Quellenangabe. Ein eigenes Element ist nötig,
weil die nach Karteninhalt filternde Hauptlegende die Hilfslayer ausblenden würde.

Wrapper um die nötigen QGIS-/Qt-Symbole mit Mock ergänzt (eigener Block in
qgiscore_wrapper; QgsRectangle hatte bisher keinen Mock).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 11:57:09 +02:00
co-authored by Claude Opus 5.5
parent 7408740dfe
commit b549cb8a5a
11 changed files with 2223 additions and 0 deletions
+255
View File
@@ -872,6 +872,261 @@ except Exception:
QgsJsonExporter = _MockQgsJsonExporter
# ---------------------------------------------------------
# Vektorkacheln, Ausdrücke, Symbole und Legende (Basemap-Legende, Basis #88)
# Eigener Block, damit ein fehlendes Symbol den übrigen Wrapper nicht in den Mock-Modus zwingt.
# ---------------------------------------------------------
QgsVectorTileLayer: Type[Any]
QgsSelectionContext: Type[Any]
QgsTileXYZ: Type[Any]
QgsExpression: Type[Any]
QgsExpressionContext: Type[Any]
QgsExpressionContextScope: Type[Any]
QgsProperty: Type[Any]
QgsCategorizedSymbolRenderer: Type[Any]
QgsRendererCategory: Type[Any]
QgsLegendRenderer: Type[Any]
QgsLegendStyle: Type[Any]
QgsMapLayer: Type[Any]
QgsApplication: Type[Any]
QgsSymbolLayerUtils: Type[Any]
QgsLayoutMeasurement: Type[Any]
QgsLayoutUtils: Type[Any]
SELECT_BEHAVIOR_SET: Any = None
SELECT_RELATION_INTERSECT: Any = None
PROPERTY_TYPE_EXPRESSION: Any = None
LEGEND_STYLE_HIDDEN: Any = None
LEGEND_STYLE_SUBGROUP: Any = None
STYLE_CATEGORIES_SYMBOLOGIE_BESCHRIFTUNG: Any = None
VECTOR_TILES_AVAILABLE = False
try:
from qgis.core import (
QgsVectorTileLayer as _QgsVectorTileLayer,
QgsSelectionContext as _QgsSelectionContext,
QgsTileXYZ as _QgsTileXYZ,
QgsExpression as _QgsExpression,
QgsExpressionContext as _QgsExpressionContext,
QgsExpressionContextScope as _QgsExpressionContextScope,
QgsProperty as _QgsProperty,
QgsCategorizedSymbolRenderer as _QgsCategorizedSymbolRenderer,
QgsRendererCategory as _QgsRendererCategory,
QgsLegendRenderer as _QgsLegendRenderer,
QgsLegendStyle as _QgsLegendStyle,
QgsMapLayer as _QgsMapLayer,
QgsApplication as _QgsApplication,
QgsSymbolLayerUtils as _QgsSymbolLayerUtils,
QgsLayoutMeasurement as _QgsLayoutMeasurement,
QgsLayoutUtils as _QgsLayoutUtils,
Qgis as _QgisVT,
)
QgsVectorTileLayer = _QgsVectorTileLayer
QgsSelectionContext = _QgsSelectionContext
QgsTileXYZ = _QgsTileXYZ
QgsExpression = _QgsExpression
QgsExpressionContext = _QgsExpressionContext
QgsExpressionContextScope = _QgsExpressionContextScope
QgsProperty = _QgsProperty
QgsCategorizedSymbolRenderer = _QgsCategorizedSymbolRenderer
QgsRendererCategory = _QgsRendererCategory
QgsLegendRenderer = _QgsLegendRenderer
QgsLegendStyle = _QgsLegendStyle
QgsMapLayer = _QgsMapLayer
QgsApplication = _QgsApplication
QgsSymbolLayerUtils = _QgsSymbolLayerUtils
QgsLayoutMeasurement = _QgsLayoutMeasurement
QgsLayoutUtils = _QgsLayoutUtils
SELECT_BEHAVIOR_SET = _QgisVT.SelectBehavior.SetSelection
SELECT_RELATION_INTERSECT = _QgisVT.SelectGeometryRelationship.Intersect
PROPERTY_TYPE_EXPRESSION = getattr(getattr(_QgsProperty, "Type", None), "ExpressionBasedProperty", None)
if PROPERTY_TYPE_EXPRESSION is None:
PROPERTY_TYPE_EXPRESSION = getattr(_QgsProperty, "ExpressionBasedProperty", None)
LEGEND_STYLE_HIDDEN = getattr(getattr(_QgsLegendStyle, "Style", _QgsLegendStyle), "Hidden", None)
LEGEND_STYLE_SUBGROUP = getattr(getattr(_QgsLegendStyle, "Style", _QgsLegendStyle), "Subgroup", None)
_kategorie = getattr(_QgsMapLayer, "StyleCategory", _QgsMapLayer)
STYLE_CATEGORIES_SYMBOLOGIE_BESCHRIFTUNG = _kategorie.Symbology | _kategorie.Labeling
VECTOR_TILES_AVAILABLE = True
except Exception:
VECTOR_TILES_AVAILABLE = False
class _MockQgsVectorTileLayer:
"""Platzhalter: Vektorkachel-Layer gibt es im Mock-Modus nicht."""
class _MockQgsSelectionContext:
def __init__(self):
self._scale = 0.0
def setScale(self, scale: float) -> None:
self._scale = scale
def scale(self) -> float:
return self._scale
class _MockQgsTileXYZ:
def __init__(self, column: int = 0, row: int = 0, zoom: int = 0):
self._column, self._row, self._zoom = column, row, zoom
def column(self) -> int:
return self._column
def row(self) -> int:
return self._row
def zoomLevel(self) -> int:
return self._zoom
class _MockQgsExpression:
def __init__(self, text: str = ""):
self._text = text
def prepare(self, context: Any) -> bool:
return True
def evaluate(self, context: Any = None) -> Any:
return True
class _MockQgsExpressionContextScope:
def __init__(self, name: str = ""):
self._variables: dict[str, Any] = {}
def setVariable(self, name: str, value: Any, *args: Any) -> None:
self._variables[name] = value
def variable(self, name: str) -> Any:
return self._variables.get(name)
class _MockQgsExpressionContext:
def __init__(self):
self._scopes: list[Any] = []
self._feature: Any = None
def appendScope(self, scope: Any) -> None:
self._scopes.append(scope)
def setFeature(self, feature: Any) -> None:
self._feature = feature
class _MockQgsProperty:
def __init__(self, value: Any = None):
self._value = value
@staticmethod
def fromValue(value: Any) -> "_MockQgsProperty":
return _MockQgsProperty(value)
class _MockQgsRendererCategory:
def __init__(self, value: Any = None, symbol: Any = None, label: str = ""):
self._value, self._symbol, self._label = value, symbol, label
def value(self) -> Any:
return self._value
def symbol(self) -> Any:
return self._symbol
def label(self) -> str:
return self._label
class _MockQgsCategorizedSymbolRenderer:
def __init__(self, attribute: str = "", categories: list | None = None):
self._attribute = attribute
self._categories = list(categories or [])
def categories(self) -> list:
return list(self._categories)
class _MockQgsLegendStyle:
Hidden = 0
Group = 1
Subgroup = 2
class _MockQgsLegendRenderer:
@staticmethod
def setNodeLegendStyle(node: Any, style: Any) -> None:
setattr(node, "_legend_style", style)
class _MockQgsMapLayer:
class StyleCategory:
Symbology = 1
Labeling = 2
class _MockQgsApplication:
@staticmethod
def qgisSettingsDirPath() -> str:
return ""
class _MockQgsSymbolLayerUtils:
@staticmethod
def symbolPreviewIcon(symbol: Any, size: Any, *args: Any) -> Any:
return None
QgsVectorTileLayer = _MockQgsVectorTileLayer
QgsSelectionContext = _MockQgsSelectionContext
QgsTileXYZ = _MockQgsTileXYZ
QgsExpression = _MockQgsExpression
QgsExpressionContext = _MockQgsExpressionContext
QgsExpressionContextScope = _MockQgsExpressionContextScope
QgsProperty = _MockQgsProperty
QgsCategorizedSymbolRenderer = _MockQgsCategorizedSymbolRenderer
QgsRendererCategory = _MockQgsRendererCategory
QgsLegendRenderer = _MockQgsLegendRenderer
QgsLegendStyle = _MockQgsLegendStyle
QgsMapLayer = _MockQgsMapLayer
QgsApplication = _MockQgsApplication
QgsSymbolLayerUtils = _MockQgsSymbolLayerUtils
class _MockQgsLayoutMeasurement:
def __init__(self, length: float = 0.0, units: Any = None):
self._length = length
def length(self) -> float:
return self._length
QgsLayoutMeasurement = _MockQgsLayoutMeasurement
class _MockQgsLayoutUtils:
@staticmethod
def createRenderContextForLayout(layout: Any, painter: Any, dpi: float = -1) -> Any:
return None
QgsLayoutUtils = _MockQgsLayoutUtils
# QgsRectangle hatte im allgemeinen Mock-Block keinen Ersatz (nur Typannotation).
if "QgsRectangle" not in globals():
class _MockQgsRectangle:
def __init__(self, x_min: float = 0.0, y_min: float = 0.0, x_max: float = 0.0, y_max: float = 0.0):
self._werte = (x_min, y_min, x_max, y_max)
def xMinimum(self) -> float:
return self._werte[0]
def yMinimum(self) -> float:
return self._werte[1]
def xMaximum(self) -> float:
return self._werte[2]
def yMaximum(self) -> float:
return self._werte[3]
def width(self) -> float:
return self._werte[2] - self._werte[0]
def height(self) -> float:
return self._werte[3] - self._werte[1]
QgsRectangle = _MockQgsRectangle
SELECT_BEHAVIOR_SET = 0
SELECT_RELATION_INTERSECT = 0
PROPERTY_TYPE_EXPRESSION = 2
LEGEND_STYLE_HIDDEN = _MockQgsLegendStyle.Hidden
LEGEND_STYLE_SUBGROUP = _MockQgsLegendStyle.Subgroup
STYLE_CATEGORIES_SYMBOLOGIE_BESCHRIFTUNG = 3
# ---------------------------------------------------------
# Netzwerk
# ---------------------------------------------------------