neues Layout "Baufreigabe" hinzugefügt

This commit is contained in:
2026-04-10 22:56:34 +02:00
parent 40c224959d
commit 0900c6d3b0
6 changed files with 975 additions and 53 deletions
+379 -13
View File
@@ -4,7 +4,7 @@ sn_basis/modules/print_logic.py - Fachlogik fuer den Druck-Tab (nur Einzelblatt)
from __future__ import annotations
import importlib
from typing import Any, cast
from typing import Any, cast, TypedDict
from sn_basis.functions.qgiscore_wrapper import QgsProject, get_layer_extent
from sn_basis.functions.qgisui_wrapper import iface
@@ -12,6 +12,48 @@ from sn_basis.functions.variable_wrapper import get_variable, set_variable
from sn_basis.modules.layerpruefer import Layerpruefer
from sn_basis.modules.Pruefmanager import Pruefmanager
from sn_basis.modules.print_layout import PrintLayout
from sn_basis.modules.print_layout_baufreigabe import PrintLayoutBaufreigabe
class AtlasConfig(TypedDict, total=False):
"""Strukturierte Konfiguration fuer Atlas-Optionen.
Alle Felder sind optional (total=False) und haben vordefinierte Standartwerte.
"""
# Basis-Konfiguration
enabled: bool # Atlas aktivieren (Standard: True nach Coverage-Layer-Setzung)
page_name_expression: str # QGIS Expression für Seitennamen (z.B. "@atlas_featurenumber")
# Filterung
filter_features: bool # Features filtern? (Standard: False)
filter_expression: str # QGIS Expression für Feature-Filter
# Sortierung
sort_features: bool # Features sortieren? (Standard: False)
sort_expression: str # QGIS Expression/Feldname zum Sortieren
sort_ascending: bool # Aufsteigend sortieren? (Standard: True)
# Rendering
hide_coverage: bool # Coverage-Layer in Layout verstecken? (Standard: False)
limit_render_to_current_feature: bool # Rendering auf aktuelles Feature limitieren (QGIS 4.0+, Standard: False)
# Metadaten
description: str # Beschreibung der Atlas-Konfiguration
# Vordefinierte Standardkonfigurationen für Baufreigabe
ATLAS_CONFIG_BAUFREIGABE_DEFAULT: AtlasConfig = {
"enabled": True,
"page_name_expression": "@atlas_featurenumber",
"filter_features": False,
"filter_expression": "",
"sort_features": False,
"sort_expression": "",
"sort_ascending": True,
"hide_coverage": False,
"limit_render_to_current_feature": False,
"description": "Baufreigabe Atlas mit Standardkonfiguration",
}
KARTENNAME_VAR = "kartenname"
@@ -25,6 +67,7 @@ DRUCKRECHTECK_LAYER_NAME = "Druckrechteck"
KARTENNAME_38 = "§38"
KARTENNAME_41 = "§41"
KARTENNAME_BAUFREIGABE = "Anlage zur Baufreigabe"
MASSSTAB_WIE_KARTENFENSTER = "Wie Kartenfenster"
THEMA_WIE_KARTENFENSTER = "wie kartenfenster"
@@ -35,6 +78,7 @@ DRUCKBEREICH_RECHTECK = "Rechteck"
KARTENNAME_BY_AUSWAHL = {
KARTENNAME_38: "Planungsübersicht §38 FlurbG",
KARTENNAME_41: "Karte zum Plan über die gemeinschaftlichen und öffentlichen Anlagen (§ 41 FlurbG)",
KARTENNAME_BAUFREIGABE: "Anlage zur Baufreigabe",
}
PLOTMASSSTAB_BY_AUSWAHL = {
@@ -253,6 +297,269 @@ class PrintLogic:
except Exception:
pass
def create_atlas_layer_from_selection(self, source_layer: object | None = None) -> tuple[object | None, str]:
"""Erstellt einen neuen Layer 'Atlaslayer' aus der Auswahl des Quell-Layers.
Args:
source_layer: Der Layer mit der Auswahl (wenn None, wird aktiver Layer verwendet)
Returns:
(created_layer, layer_id) oder (None, "") bei Fehler
"""
# Source-Layer bestimmen
if source_layer is None:
source_layer = self._resolve_active_layer()
if source_layer is None:
self.pruefmanager.zeige_hinweis(
"Atlas Layer erstellen",
"Kein Layer mit Auswahl gefunden.",
)
return None, ""
# Pruefen ob Auswahl vorhanden ist
selected_count = getattr(source_layer, "selectedFeatureCount", None)
if callable(selected_count):
try:
if int(cast(Any, selected_count())) == 0:
self.pruefmanager.zeige_hinweis(
"Atlas Layer erstellen",
"Auf dem Layer sind keine Features ausgewaehlt.",
)
return None, ""
except Exception:
pass
# Bestehenden Atlaslayer loeschen (optional Wiederverwedung)
existing_atlas = self._get_existing_layer_by_name("Atlaslayer")
if existing_atlas is not None:
try:
QgsProject.instance().removeMapLayer(existing_atlas)
except Exception:
pass
# Neuen Memory-Layer mit Geometrie-Type des Quell-Layers erstellen
qgs_vector_layer = self._import_qgis_symbol("qgis.core", "QgsVectorLayer")
if qgs_vector_layer is None:
self.pruefmanager.zeige_hinweis(
"Atlas Layer erstellen",
"QgsVectorLayer ist nicht verfuegbar.",
)
return None, ""
# CRS vom Quell-Layer übernehmen
crs_authid = "EPSG:25832"
try:
source_crs = getattr(source_layer, "crs", lambda: None)()
if source_crs is not None:
crs_authid_method = getattr(source_crs, "authid", lambda: None)
if callable(crs_authid_method):
crs_authid = crs_authid_method() or crs_authid
except Exception:
pass
# Geometrie-Type vom Quell-Layer bestimmen (vereinfacht: Polygon für Atlas)
# TODO: Intelligentere Bestimmung des Geometrie-Types
atlas_layer = qgs_vector_layer(f"Polygon?crs={crs_authid}", "Atlaslayer", "memory")
if not atlas_layer or not getattr(atlas_layer, "isValid", lambda: False)():
self.pruefmanager.zeige_hinweis(
"Atlas Layer erstellen",
"Der Atlaslayer konnte nicht erstellt werden.",
)
return None, ""
# Ausgewaehlte Features kopieren
try:
get_selected = getattr(source_layer, "selectedFeatures", None)
if not callable(get_selected):
raise Exception("selectedFeatures ist nicht aufrufbar")
selected_features = list(cast(Any, get_selected()))
if not selected_features:
raise Exception("Keine Features ausgewaehlt")
qgs_feature = self._import_qgis_symbol("qgis.core", "QgsFeature")
if qgs_feature is None:
raise Exception("QgsFeature Symbol nicht verfuegbar")
data_provider = getattr(atlas_layer, "dataProvider", lambda: None)()
if data_provider is None:
raise Exception("dataProvider nicht verfuegbar")
new_features = []
for source_feature in selected_features:
geometry = getattr(source_feature, "geometry", lambda: None)()
if geometry is None:
continue
new_feature = qgs_feature()
set_geometry = getattr(new_feature, "setGeometry", None)
if callable(set_geometry):
try:
set_geometry(geometry)
new_features.append(new_feature)
except Exception:
pass
if new_features:
add_features = getattr(data_provider, "addFeatures", None)
if callable(add_features):
add_features(new_features)
update_fields = getattr(atlas_layer, "updateFields", None)
if callable(update_fields):
update_fields()
update_extents = getattr(atlas_layer, "updateExtents", None)
if callable(update_extents):
update_extents()
except Exception as exc:
self.pruefmanager.zeige_hinweis(
"Atlas Layer erstellen",
f"Fehler beim Kopieren der Features: {exc}",
)
return None, ""
# Layer ins Projekt aufnehmen
try:
QgsProject.instance().addMapLayer(atlas_layer)
except Exception:
self.pruefmanager.zeige_hinweis(
"Atlas Layer erstellen",
"Der Layer konnte nicht ins Projekt aufgenommen werden.",
)
return None, ""
# Layer-ID speichern
layer_id = getattr(atlas_layer, "id", lambda: "")() or ""
set_variable("sn_abdeckungslayer", layer_id, scope="project")
self.pruefmanager.zeige_hinweis(
"Atlas Layer erstellen",
f"Atlas Layer 'Atlaslayer' erfolgreich erstellt.",
)
return atlas_layer, layer_id
def configure_atlas_for_baufreigabe_layout(
self,
layout_name: str = "",
atlas_config: AtlasConfig | None = None,
) -> bool:
"""Konfiguriert den Atlas fuer ein Baufreigabe-Layout mit erweiterten Optionen.
Wird nach erfolgreicher Baufreigabe-Layout-Erstellung aufgerufen.
Setzt den Coverage-Layer (aus sn_abdeckungslayer Variable) und konfiguriert den Atlas
mit allen Optionen aus der AtlasConfig.
"""
try:
if atlas_config is None:
atlas_config = ATLAS_CONFIG_BAUFREIGABE_DEFAULT
project = QgsProject.instance()
layout_manager = getattr(project, "layoutManager", lambda: None)()
if layout_manager is None:
self.pruefmanager.zeige_hinweis("Atlas konfigurieren", "Layout Manager nicht verfuegbar.")
return False
if not layout_name:
layouts_fn = getattr(layout_manager, "layouts", None)
if not callable(layouts_fn):
return False
try:
layout_list = layouts_fn()
if not layout_list:
return False
layout = layout_list[-1]
except Exception:
return False
else:
layout_by_name = getattr(layout_manager, "layoutByName", None)
if not callable(layout_by_name):
return False
layout = layout_by_name(layout_name)
if layout is None:
return False
atlas = getattr(layout, "atlas", lambda: None)()
if atlas is None:
return False
coverage_layer_id = get_variable("sn_abdeckungslayer", scope="project") or ""
if not coverage_layer_id:
return True
coverage_layer = project.mapLayer(coverage_layer_id)
if coverage_layer is None:
self.pruefmanager.zeige_hinweis(
"Atlas konfigurieren",
f"Atlas-Layer mit ID '{coverage_layer_id}' nicht gefunden.",
)
return False
set_coverage = getattr(atlas, "setCoverageLayer", None)
if callable(set_coverage):
set_coverage(coverage_layer)
page_name_expr = atlas_config.get("page_name_expression", "")
if page_name_expr:
set_page_name = getattr(atlas, "setPageNameExpression", None)
if callable(set_page_name):
set_page_name(page_name_expr)
if atlas_config.get("filter_features", False):
set_filter_features = getattr(atlas, "setFilterFeatures", None)
if callable(set_filter_features):
set_filter_features(True)
filter_expr = atlas_config.get("filter_expression", "")
if filter_expr:
set_filter_expr = getattr(atlas, "setFilterExpression", None)
if callable(set_filter_expr):
set_filter_expr(filter_expr)
if atlas_config.get("sort_features", False):
set_sort_features = getattr(atlas, "setSortFeatures", None)
if callable(set_sort_features):
set_sort_features(True)
sort_expr = atlas_config.get("sort_expression", "")
if sort_expr:
set_sort_expr = getattr(atlas, "setSortExpression", None)
if callable(set_sort_expr):
set_sort_expr(sort_expr)
set_sort_asc = getattr(atlas, "setSortAscending", None)
if callable(set_sort_asc):
set_sort_asc(atlas_config.get("sort_ascending", True))
set_hide_coverage = getattr(atlas, "setHideCoverage", None)
if callable(set_hide_coverage):
set_hide_coverage(atlas_config.get("hide_coverage", False))
set_limit_render = getattr(atlas, "setLimitCoverageLayerRenderToCurrentFeature", None)
if callable(set_limit_render):
try:
set_limit_render(atlas_config.get("limit_render_to_current_feature", False))
except Exception:
pass
num_features = 0
update_features = getattr(atlas, "updateFeatures", None)
if callable(update_features):
try:
num_features = update_features() or 0
except Exception:
num_features = 0
set_enabled = getattr(atlas, "setEnabled", None)
if callable(set_enabled):
set_enabled(atlas_config.get("enabled", True))
return True
except Exception as exc:
self.pruefmanager.zeige_hinweis(
"Atlas konfigurieren",
f"Fehler beim Konfigurieren des Atlas: {exc}",
)
return False
def _store_druckrechteck(self, layer: object, rectangle: Any) -> bool:
qgs_feature = self._import_qgis_symbol("qgis.core", "QgsFeature")
qgs_geometry = self._import_qgis_symbol("qgis.core", "QgsGeometry")
@@ -579,6 +886,43 @@ class PrintLogic:
auswahl = DRUCKBEREICH_VERFAHRENSGEBIET
set_variable(DRUCKBEREICH_VAR, auswahl, scope="project")
def _get_baufreigabe_page_dimensions(
self,
kartenbild_w: float,
kartenbild_h: float,
zielgroesse: str,
) -> tuple[str, float, float, float, float]:
"""Berechnet Seitenformat und verfuegbare Kartenfenster fuer Baufreigabe.
Args:
kartenbild_w: Berechnete Kartenbild-Breite aus Extent/Massstab
kartenbild_h: Berechnete Kartenbild-Hoehe aus Extent/Massstab
zielgroesse: "DIN A3" oder "DIN A4" (wird zu Querformat konvertiert)
Returns:
(din_format, page_width_mm, page_height_mm, adjusted_map_w, adjusted_map_h)
"""
# Nur A3/A4 im Querformat fuer Baufreigabe
if zielgroesse == "DIN A3":
din_format = "DIN A3"
page_w, page_h = 420.0, 297.0
else:
din_format = "DIN A4"
page_w, page_h = 297.0, 210.0
# Verfuegbare Flaeche fuer Kartenbild: Seite - Kartenschild und Maerchen
kartenschild_w = 120.0
kartenschild_h = 92.0
margin = 10.0
avail_w = page_w - 2 * margin - kartenschild_w - margin
avail_h = page_h - 2 * margin - kartenschild_h - margin
adjusted_map_w = min(kartenbild_w, avail_w)
adjusted_map_h = min(kartenbild_h, avail_h)
return din_format, page_w, page_h, adjusted_map_w, adjusted_map_h
def druckvorlage_anlegen(
self,
layer: object | None,
@@ -590,7 +934,7 @@ class PrintLogic:
) -> dict:
resolved_layer = layer or self._resolve_layer_from_project()
if kartenname_auswahl not in (KARTENNAME_38, KARTENNAME_41):
if kartenname_auswahl not in (KARTENNAME_38, KARTENNAME_41, KARTENNAME_BAUFREIGABE):
self.pruefmanager.zeige_hinweis("Kartennamen waehlen", "Kartennamen waehlen")
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
@@ -671,18 +1015,36 @@ class PrintLogic:
vorlage_name = (vorlage_name or "").strip() or kartenname
# Verzweigung: Baufreigabe vs. Standard-Layout
try:
PrintLayout().create_single_page_layout(
name=vorlage_name,
page_width_mm=ziel_w,
page_height_mm=ziel_h,
map_width_mm=kartenbild_w,
map_height_mm=kartenbild_h,
extent=extent,
plotmassstab=massstab_zahl,
thema=thema,
fit_extent_to_map=(druckbereich_auswahl != DRUCKBEREICH_VERFAHRENSGEBIET),
)
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
# Baufreigabe: feste DIN A3/A4 Querformat-Logik mit 120mm Kartenschild
din_format, page_w, page_h, map_w, map_h = self._get_baufreigabe_page_dimensions(
kartenbild_w, kartenbild_h, zielgroesse
)
PrintLayoutBaufreigabe().create_baufreigabe_layout(
name=vorlage_name if vorlage_name != kartenname else "Baufreigabe",
din_format=din_format,
map_width_mm=map_w,
map_height_mm=map_h,
extent=extent,
plotmassstab=massstab_zahl,
thema=thema,
fit_extent_to_map=(druckbereich_auswahl != DRUCKBEREICH_VERFAHRENSGEBIET),
)
else:
# Standard-Layout: dynamische Groessenanpassung
PrintLayout().create_single_page_layout(
name=vorlage_name,
page_width_mm=ziel_w,
page_height_mm=ziel_h,
map_width_mm=kartenbild_w,
map_height_mm=kartenbild_h,
extent=extent,
plotmassstab=massstab_zahl,
thema=thema,
fit_extent_to_map=(druckbereich_auswahl != DRUCKBEREICH_VERFAHRENSGEBIET),
)
except ValueError as exc:
self.pruefmanager.zeige_hinweis("Vorlage anlegen", str(exc))
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
@@ -693,4 +1055,8 @@ class PrintLogic:
)
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
# Nach erfolgreicher Layout-Erstellung: Atlas konfigurieren (falls Baufreigabe)
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
self.configure_atlas_for_baufreigabe_layout()
return {"ok": True, "switch_to_tab_a": False, "atlas_seiten": 1}