Files
Plugin_SN_Basis/modules/basemap_stilvorlagen.py
T
DanielandClaude Opus 5.5 24ad564a08 Basemap hinzufügen und Thema wechseln im Basemap-Bereich (#100)
Neuer Abschnitt „Basemap-Layer“ im Basemap-Bereich des Druck-Tabs: Knopf
„Basemap hinzufügen“ und Auswahl „Thema“ (Farbe, Grau). Vorhandene Layer
werden an der Kachel-URL erkannt; Rückfrage Zusätzlich laden / Ersetzen /
Abbrechen. Vor dem Themenwechsel wird über einen Prüfwert der Darstellung
erkannt, ob sie ungespeichert verändert wurde.

Neues Modul modules/basemap_themen.py, neue Wrapper-Funktionen
ask_auswahl und get_active_layer, Stilvorlagen vermerken ihr Thema.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 21:27:35 +02:00

189 lines
7.6 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.
"""
sn_basis/modules/basemap_stilvorlagen.py – lokale Stilvorlagen für den Basemap-Layer
Speichert die Darstellung des Basemap-Layers (Symbologie und Beschriftung, also auch
abgeschaltete Styles wie Hausnummern) als benannte Stilvorlage im QGIS-Profil und lädt sie
wieder. Mehrere Varianten sind möglich; eine davon kann als Standard markiert werden.
Ablage: ``<QGIS-Profil>/sn_basis/basemap_stile/<name>.qml`` plus ``<name>.json`` mit
Anzeigename, Erstellungszeit, Thema des Layers (Basis #100), Style-Namen der Vorlage und –
optional – der Legendenauswahl.
Der Standard steht in ``QSettings`` unter ``sn_basis/basemap_standardstil``.
Die Vorlagen sind lokal (je Rechner und Profil) und werden nicht geteilt.
"""
from __future__ import annotations
import json
import os
from dataclasses import dataclass
from datetime import datetime
from typing import Any
from sn_basis.functions.qgiscore_wrapper import QgsApplication, STYLE_CATEGORIES_SYMBOLOGIE_BESCHRIFTUNG
from sn_basis.modules.basemap_themen import thema_des_layers
from sn_basis.functions.qt_wrapper import QSettings
from sn_basis.functions.text_wrapper import sanitize_bezeichner
STANDARD_SCHLUESSEL = "sn_basis/basemap_standardstil"
@dataclass
class Stilvorlage:
name: str # Dateiname ohne Endung
anzeigename: str
pfad: str # QML-Datei
erstellt: str = ""
standard: bool = False
thema: str = "" # Schlüssel aus basemap_themen.THEMEN; leer bei älteren Vorlagen
def standard_ordner() -> str:
"""Ordner der Stilvorlagen im aktiven QGIS-Profil (außerhalb von QGIS im Benutzerordner)."""
try:
basis = QgsApplication.qgisSettingsDirPath() or ""
except Exception:
basis = ""
if not basis:
basis = os.path.join(os.path.expanduser("~"), ".sn_basis")
return os.path.join(basis, "sn_basis", "basemap_stile")
def _stilnamen(layer: Any) -> list[str]:
try:
return [s.styleName() for s in layer.renderer().styles()]
except Exception:
return []
class BasemapStilvorlagen:
"""Verwaltet die lokalen Stilvorlagen des Basemap-Layers."""
def __init__(self, ordner: str | None = None, einstellungen: Any | None = None) -> None:
self.ordner = ordner or standard_ordner()
self.einstellungen = einstellungen if einstellungen is not None else QSettings()
# --- Abfragen -------------------------------------------------------
def _pfade(self, name: str) -> tuple[str, str]:
basis = os.path.join(self.ordner, name)
return basis + ".qml", basis + ".json"
def _metadaten(self, name: str) -> dict:
_, json_pfad = self._pfade(name)
try:
with open(json_pfad, encoding="utf-8") as datei:
daten = json.load(datei)
return daten if isinstance(daten, dict) else {}
except (OSError, ValueError):
return {}
@staticmethod
def dateiname(anzeigename: str) -> str:
return sanitize_bezeichner(anzeigename.strip())
def standard(self) -> str:
return str(self.einstellungen.value(STANDARD_SCHLUESSEL, "") or "")
def liste(self) -> list[Stilvorlage]:
"""Alle Stilvorlagen, alphabetisch nach Anzeigename. Fehlende Begleitdatei wird toleriert."""
if not os.path.isdir(self.ordner):
return []
standard = self.standard()
vorlagen = []
for datei in os.listdir(self.ordner):
if not datei.lower().endswith(".qml"):
continue
name = datei[:-4]
meta = self._metadaten(name)
vorlagen.append(Stilvorlage(
name=name,
anzeigename=str(meta.get("anzeigename") or name),
pfad=os.path.join(self.ordner, datei),
erstellt=str(meta.get("erstellt", "")),
standard=(name == standard),
thema=str(meta.get("thema") or ""),
))
return sorted(vorlagen, key=lambda v: v.anzeigename.lower())
def vorhanden(self, anzeigename: str) -> bool:
return os.path.exists(self._pfade(self.dateiname(anzeigename))[0])
def legendenauswahl(self, name: str) -> dict | None:
"""In der Vorlage gespeicherte Legendenauswahl (``ausgeblendet``/``eingeblendet``) oder ``None``."""
auswahl = self._metadaten(name).get("legende_auswahl")
return auswahl if isinstance(auswahl, dict) else None
def pruefe_passung(self, layer: Any, name: str) -> list[str]:
"""Style-Namen der Vorlage, die der Layer nicht kennt (z. B. Vorlage aus „Grau“ für „Farbe“)."""
vorlage = self._metadaten(name).get("stile")
if not isinstance(vorlage, list):
return []
vorhanden = set(_stilnamen(layer))
return [s for s in vorlage if s not in vorhanden]
# --- Ändern ---------------------------------------------------------
def speichern(self, layer: Any, anzeigename: str, legende_auswahl: dict | None = None) -> Stilvorlage:
"""Speichert die aktuelle Darstellung des Layers; eine gleichnamige Vorlage wird ersetzt.
Raises
------
ValueError
Bei leerem Namen oder wenn QGIS die Datei nicht schreiben konnte.
"""
name = self.dateiname(anzeigename)
if not name:
raise ValueError("Bitte einen Namen für die Stilvorlage angeben.")
os.makedirs(self.ordner, exist_ok=True)
qml_pfad, json_pfad = self._pfade(name)
ergebnis = layer.saveNamedStyle(qml_pfad, STYLE_CATEGORIES_SYMBOLOGIE_BESCHRIFTUNG)
meldung, ok = ergebnis if isinstance(ergebnis, tuple) else ("", bool(ergebnis))
if not ok:
raise ValueError(f"Die Stilvorlage konnte nicht gespeichert werden: {meldung}")
erstellt = datetime.now().isoformat(timespec="seconds")
meta = {
"anzeigename": anzeigename.strip(),
"erstellt": erstellt,
"layer": layer.name(),
"stile": _stilnamen(layer),
}
thema = thema_des_layers(layer)
if thema:
meta["thema"] = thema
if legende_auswahl:
meta["legende_auswahl"] = legende_auswahl
with open(json_pfad, "w", encoding="utf-8") as datei:
json.dump(meta, datei, ensure_ascii=False, indent=1)
return Stilvorlage(name, anzeigename.strip(), qml_pfad, erstellt, name == self.standard(), thema or "")
def laden(self, layer: Any, name: str) -> None:
"""Ersetzt Symbologie und Beschriftung des Layers durch die Vorlage.
Raises
------
ValueError
Wenn die Vorlage fehlt oder QGIS sie nicht laden konnte.
"""
qml_pfad, _ = self._pfade(name)
if not os.path.exists(qml_pfad):
raise ValueError(f"Die Stilvorlage „{name}“ gibt es nicht mehr.")
ergebnis = layer.loadNamedStyle(qml_pfad, STYLE_CATEGORIES_SYMBOLOGIE_BESCHRIFTUNG)
meldung, ok = ergebnis if isinstance(ergebnis, tuple) else ("", bool(ergebnis))
if not ok:
raise ValueError(f"Die Stilvorlage konnte nicht geladen werden: {meldung}")
nachzeichnen = getattr(layer, "triggerRepaint", None)
if callable(nachzeichnen):
nachzeichnen()
def loeschen(self, name: str) -> None:
for pfad in self._pfade(name):
if os.path.exists(pfad):
os.remove(pfad)
if self.standard() == name:
self.einstellungen.remove(STANDARD_SCHLUESSEL)
def setze_standard(self, name: str) -> None:
if not os.path.exists(self._pfade(name)[0]):
raise ValueError(f"Die Stilvorlage „{name}“ gibt es nicht.")
self.einstellungen.setValue(STANDARD_SCHLUESSEL, name)