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>
1361 lines
53 KiB
Python
1361 lines
53 KiB
Python
"""
|
|
sn_basis/modules/print_logic.py - Fachlogik für den Druck-Tab (nur Einzelblatt).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
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
|
|
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 für 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": True,
|
|
"description": "Baufreigabe Atlas mit Standardkonfiguration",
|
|
}
|
|
|
|
|
|
KARTENNAME_VAR = "kartenname"
|
|
PLOTMASSSTAB_VAR = "plotmassstab"
|
|
VIEW_VAR = "view"
|
|
ZIELGROESSE_VAR = "zielgroesse"
|
|
FORMFAKTOR_VAR = "formfaktor"
|
|
ATLAS_ERZEUGEN_UI_VAR = "tab_b_atlas_erzeugen"
|
|
DRUCKBEREICH_VAR = "druckbereich"
|
|
DRUCKPUFFER_VAR = "druckpuffer"
|
|
DRUCKRECHTECK_LAYER_NAME = "Druckrechteck"
|
|
|
|
KARTENNAME_38 = "§38"
|
|
KARTENNAME_41 = "§41"
|
|
KARTENNAME_BAUFREIGABE = "Anlage zur Baufreigabe"
|
|
# Layouts, die beim Anlegen eine Basemap-Legende erhalten (Basis #88). Neue Layouts hier aufnehmen.
|
|
LAYOUTS_MIT_BASEMAP_LEGENDE = frozenset({KARTENNAME_38, KARTENNAME_41})
|
|
MASSSTAB_WIE_KARTENFENSTER = "Wie Kartenfenster"
|
|
THEMA_WIE_KARTENFENSTER = "wie kartenfenster"
|
|
|
|
DRUCKBEREICH_VERFAHRENSGEBIET = "Verfahrensgebiet"
|
|
DRUCKBEREICH_AKTUELLE_AUSWAHL = "aktuelle Auswahl"
|
|
DRUCKBEREICH_RECHTECK = "Rechteck"
|
|
DRUCKBEREICH_WIE_KARTENFENSTER = "Wie Kartenfenster"
|
|
|
|
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 = {
|
|
"1:5.000": "5000",
|
|
"1:10.000": "10000",
|
|
"1:15.000": "15000",
|
|
"1:20.000": "20000",
|
|
"1:25.000": "25000",
|
|
"1:50.000": "50000",
|
|
"1:100.000": "100000",
|
|
}
|
|
|
|
DIN_GROESSEN: dict[str, tuple[int, int]] = {
|
|
"DIN A0": (841, 1189),
|
|
"DIN A1": (594, 841),
|
|
"DIN A2": (420, 594),
|
|
"DIN A3": (297, 420),
|
|
"DIN A4": (210, 297),
|
|
}
|
|
DIN_AUTO_REIHENFOLGE = ("DIN A4", "DIN A3", "DIN A2", "DIN A1", "DIN A0")
|
|
DIN_STANDARD = "DIN A0"
|
|
DRUCKPUFFER_STANDARD_CM = 1
|
|
ENDLOSROLLE_MAX_BREITE_MM = 2000.0
|
|
STANDARD_LAYOUT_FIXBREITE_MM = 210.0
|
|
STANDARD_LAYOUT_FIXHOEHE_MM = 20.0
|
|
BAUFREIGABE_LAYOUT_FIXBREITE_MM = 110.0
|
|
BAUFREIGABE_LAYOUT_FIXHOEHE_MM = 20.0
|
|
STANDARD_A4_LANDSCAPE_W_MM = 297.0
|
|
STANDARD_A4_LANDSCAPE_H_MM = 210.0
|
|
|
|
|
|
class PrintLogic:
|
|
"""Kapselt die Fachlogik für den Druck-Tab (ohne Atlas)."""
|
|
|
|
def __init__(self, pruefmanager: Pruefmanager) -> None:
|
|
self.pruefmanager = pruefmanager
|
|
self._rectangle_tool: Any | None = None
|
|
self._previous_map_tool: Any | None = None
|
|
|
|
def _failed_result(self) -> dict:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
|
|
|
def _show_target_size_too_small(self) -> dict:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
self.pruefmanager.zeige_hinweis(
|
|
"Zielgröße zu klein",
|
|
"Die gewählte Zielgröße reicht für den Einzelblatt-Druck nicht aus.",
|
|
)
|
|
return self._failed_result()
|
|
|
|
def _is_enabled_flag(self, raw_value: Any) -> bool:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
if raw_value in (None, ""):
|
|
return True
|
|
return str(raw_value).strip().lower() in {"1", "true", "ja", "yes", "on"}
|
|
|
|
def _resolve_layer_from_project(self) -> object | None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
project = QgsProject.instance()
|
|
# Primär: neue zentrale Variable aus sn_verfahrensgebiet_manager
|
|
layer_id = (get_variable("verfahrensgebiet_layer", scope="project") or "").strip()
|
|
# Rückwärtskompatibilität zu älteren Projekten
|
|
if not layer_id:
|
|
layer_id = (get_variable("verfahrensgebietslayer", scope="project") or "").strip()
|
|
if not layer_id:
|
|
layer_id = (get_variable("tab_a_layer_id", scope="project") or "").strip()
|
|
if not layer_id:
|
|
return None
|
|
|
|
map_layer = getattr(project, "mapLayer", None)
|
|
if not callable(map_layer):
|
|
return None
|
|
try:
|
|
return map_layer(layer_id)
|
|
except Exception:
|
|
return None
|
|
|
|
def _resolve_active_layer(self) -> object | None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
active_layer_fn = getattr(iface, "activeLayer", None)
|
|
if not callable(active_layer_fn):
|
|
return None
|
|
try:
|
|
return active_layer_fn()
|
|
except Exception:
|
|
return None
|
|
|
|
def _get_druckpuffer_cm(self) -> int:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
raw_value = get_variable(DRUCKPUFFER_VAR, scope="project")
|
|
try:
|
|
value = int(str(raw_value).strip())
|
|
except (TypeError, ValueError):
|
|
value = DRUCKPUFFER_STANDARD_CM
|
|
return max(0, value)
|
|
|
|
def _get_druckpuffer_m(self, plotmassstab: float) -> float:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
return (self._get_druckpuffer_cm() * 10.0 * plotmassstab) / 1000.0
|
|
|
|
def _import_qgis_symbol(self, module_name: str, symbol_name: str) -> Any | None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
try:
|
|
module = importlib.import_module(module_name)
|
|
except Exception:
|
|
return None
|
|
return getattr(module, symbol_name, None)
|
|
|
|
def _build_rectangle(self, x_min: float, y_min: float, x_max: float, y_max: float) -> Any | None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
qgs_rectangle = self._import_qgis_symbol("qgis.core", "QgsRectangle")
|
|
if qgs_rectangle is None:
|
|
return None
|
|
try:
|
|
return qgs_rectangle(x_min, y_min, x_max, y_max)
|
|
except Exception:
|
|
return None
|
|
|
|
def _clone_extent(self, extent: Any) -> Any | None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
if extent is None:
|
|
return None
|
|
|
|
buffered = getattr(extent, "buffered", None)
|
|
if callable(buffered):
|
|
try:
|
|
return buffered(0.0)
|
|
except Exception:
|
|
pass
|
|
|
|
x_min = getattr(extent, "xMinimum", lambda: None)()
|
|
y_min = getattr(extent, "yMinimum", lambda: None)()
|
|
x_max = getattr(extent, "xMaximum", lambda: None)()
|
|
y_max = getattr(extent, "yMaximum", lambda: None)()
|
|
if None in (x_min, y_min, x_max, y_max):
|
|
return None
|
|
assert x_min is not None
|
|
assert y_min is not None
|
|
assert x_max is not None
|
|
assert y_max is not None
|
|
x_min_f = float(x_min)
|
|
y_min_f = float(y_min)
|
|
x_max_f = float(x_max)
|
|
y_max_f = float(y_max)
|
|
return self._build_rectangle(x_min_f, y_min_f, x_max_f, y_max_f)
|
|
|
|
def _buffer_extent(self, extent: Any, plotmassstab: float) -> Any | None:
|
|
if extent is None:
|
|
return None
|
|
|
|
buffer_m = self._get_druckpuffer_m(plotmassstab)
|
|
if buffer_m <= 0:
|
|
return extent
|
|
|
|
buffered = getattr(extent, "buffered", None)
|
|
if callable(buffered):
|
|
try:
|
|
return buffered(buffer_m)
|
|
except Exception:
|
|
pass
|
|
|
|
x_min = getattr(extent, "xMinimum", lambda: None)()
|
|
y_min = getattr(extent, "yMinimum", lambda: None)()
|
|
x_max = getattr(extent, "xMaximum", lambda: None)()
|
|
y_max = getattr(extent, "yMaximum", lambda: None)()
|
|
if None in (x_min, y_min, x_max, y_max):
|
|
return extent
|
|
assert x_min is not None
|
|
assert y_min is not None
|
|
assert x_max is not None
|
|
assert y_max is not None
|
|
|
|
x_min_f = float(x_min)
|
|
y_min_f = float(y_min)
|
|
x_max_f = float(x_max)
|
|
y_max_f = float(y_max)
|
|
|
|
expanded = self._build_rectangle(
|
|
x_min_f - buffer_m,
|
|
y_min_f - buffer_m,
|
|
x_max_f + buffer_m,
|
|
y_max_f + buffer_m,
|
|
)
|
|
return expanded or extent
|
|
|
|
def _get_existing_layer_by_name(self, layer_name: str) -> object | None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
project = QgsProject.instance()
|
|
map_layers_by_name = getattr(project, "mapLayersByName", None)
|
|
if not callable(map_layers_by_name):
|
|
return None
|
|
try:
|
|
matches = map_layers_by_name(layer_name)
|
|
except Exception:
|
|
return None
|
|
if not isinstance(matches, (list, tuple)) or not matches:
|
|
return None
|
|
return matches[0]
|
|
|
|
def _create_druckrechteck_layer(self, reference_layer: object | None) -> object | None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
existing_layer = self._get_existing_layer_by_name(DRUCKRECHTECK_LAYER_NAME)
|
|
if existing_layer is not None:
|
|
return existing_layer
|
|
|
|
qgs_vector_layer = self._import_qgis_symbol("qgis.core", "QgsVectorLayer")
|
|
if qgs_vector_layer is None:
|
|
return None
|
|
|
|
crs_authid = "EPSG:25832"
|
|
if reference_layer is not None:
|
|
try:
|
|
layer_crs = getattr(reference_layer, "crs", lambda: None)()
|
|
if layer_crs is not None:
|
|
crs_authid = layer_crs.authid()
|
|
except Exception:
|
|
pass
|
|
|
|
layer = qgs_vector_layer(f"Polygon?crs={crs_authid}", DRUCKRECHTECK_LAYER_NAME, "memory")
|
|
if not layer or not getattr(layer, "isValid", lambda: False)():
|
|
return None
|
|
|
|
try:
|
|
QgsProject.instance().addMapLayer(layer)
|
|
except Exception:
|
|
return None
|
|
return layer
|
|
|
|
def _clear_layer_features(self, layer: object) -> None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
get_features = getattr(layer, "getFeatures", None)
|
|
data_provider = getattr(layer, "dataProvider", lambda: None)()
|
|
if not callable(get_features) or data_provider is None:
|
|
return
|
|
|
|
feature_ids: list[int] = []
|
|
try:
|
|
features = cast(Any, get_features())
|
|
for feature in features:
|
|
feature_id = getattr(feature, "id", lambda: None)()
|
|
if feature_id is not None:
|
|
feature_ids.append(int(feature_id))
|
|
except Exception:
|
|
feature_ids = []
|
|
|
|
if feature_ids:
|
|
delete_features = getattr(data_provider, "deleteFeatures", None)
|
|
if callable(delete_features):
|
|
try:
|
|
delete_features(feature_ids)
|
|
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
|
|
|
|
Test ausgesetzt, zunächst Refactor
|
|
"""
|
|
# 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 ausgewählt.",
|
|
)
|
|
return None, ""
|
|
except Exception:
|
|
pass
|
|
|
|
# Bestehenden Atlaslayer löschen (optional Wiederverwendung)
|
|
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 verfügbar.",
|
|
)
|
|
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, ""
|
|
|
|
# Ausgewählte 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 ausgewählt")
|
|
|
|
qgs_feature = self._import_qgis_symbol("qgis.core", "QgsFeature")
|
|
if qgs_feature is None:
|
|
raise Exception("QgsFeature Symbol nicht verfügbar")
|
|
|
|
data_provider = getattr(atlas_layer, "dataProvider", lambda: None)()
|
|
if data_provider is None:
|
|
raise Exception("dataProvider nicht verfügbar")
|
|
|
|
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 für 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.
|
|
|
|
Test ausgesetzt, zunächst Refactor
|
|
"""
|
|
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 verfügbar.")
|
|
return False
|
|
|
|
if not layout_name:
|
|
layouts_fn = getattr(layout_manager, "layouts", None)
|
|
if not callable(layouts_fn):
|
|
return False
|
|
try:
|
|
layout_iterable = layouts_fn()
|
|
if layout_iterable is None:
|
|
return False
|
|
last_layout: Any | None = None
|
|
for candidate_layout in cast(Any, layout_iterable):
|
|
last_layout = candidate_layout
|
|
if last_layout is None:
|
|
return False
|
|
layout = last_layout
|
|
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
|
|
|
|
atlas_enabled = bool(atlas_config.get("enabled", True))
|
|
if not atlas_enabled:
|
|
set_enabled = getattr(atlas, "setEnabled", None)
|
|
if callable(set_enabled):
|
|
try:
|
|
set_enabled(False)
|
|
except Exception:
|
|
pass
|
|
|
|
# Hauptkarte ebenfalls aus Atlas-Steuerung lösen
|
|
item_by_id = getattr(layout, "itemById", None)
|
|
if callable(item_by_id):
|
|
try:
|
|
hauptkarte = item_by_id("Hauptkarte")
|
|
set_atlas_driven = getattr(hauptkarte, "setAtlasDriven", None)
|
|
if callable(set_atlas_driven):
|
|
set_atlas_driven(False)
|
|
except Exception:
|
|
pass
|
|
return True
|
|
|
|
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:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
qgs_feature = self._import_qgis_symbol("qgis.core", "QgsFeature")
|
|
qgs_geometry = self._import_qgis_symbol("qgis.core", "QgsGeometry")
|
|
if qgs_feature is None or qgs_geometry is None:
|
|
return False
|
|
|
|
if layer is None or rectangle is None:
|
|
return False
|
|
|
|
data_provider = getattr(layer, "dataProvider", lambda: None)()
|
|
if data_provider is None:
|
|
return False
|
|
|
|
self._clear_layer_features(layer)
|
|
|
|
feature = qgs_feature()
|
|
try:
|
|
feature.setGeometry(qgs_geometry.fromRect(rectangle))
|
|
add_features = getattr(data_provider, "addFeatures", None)
|
|
if callable(add_features):
|
|
add_features([feature])
|
|
update_extents = getattr(layer, "updateExtents", None)
|
|
if callable(update_extents):
|
|
update_extents()
|
|
trigger_repaint = getattr(layer, "triggerRepaint", None)
|
|
if callable(trigger_repaint):
|
|
trigger_repaint()
|
|
except Exception:
|
|
return False
|
|
|
|
try:
|
|
set_variable("druckrechteck_xmin", str(rectangle.xMinimum()), scope="project")
|
|
set_variable("druckrechteck_ymin", str(rectangle.yMinimum()), scope="project")
|
|
set_variable("druckrechteck_xmax", str(rectangle.xMaximum()), scope="project")
|
|
set_variable("druckrechteck_ymax", str(rectangle.yMaximum()), scope="project")
|
|
except Exception:
|
|
pass
|
|
|
|
map_canvas = getattr(iface, "mapCanvas", lambda: None)()
|
|
if map_canvas is not None:
|
|
refresh = getattr(map_canvas, "refresh", None)
|
|
if callable(refresh):
|
|
try:
|
|
refresh()
|
|
except Exception:
|
|
pass
|
|
return True
|
|
|
|
def activate_druckrechteck_capture(self, reference_layer: object | None = None) -> bool:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
layer = self._create_druckrechteck_layer(reference_layer)
|
|
if layer is None:
|
|
self.pruefmanager.zeige_hinweis(
|
|
"Druckrechteck",
|
|
"Der temporaere Layer 'Druckrechteck' konnte nicht erzeugt werden.",
|
|
)
|
|
return False
|
|
|
|
map_canvas = getattr(iface, "mapCanvas", lambda: None)()
|
|
if map_canvas is None:
|
|
self.pruefmanager.zeige_hinweis(
|
|
"Druckrechteck",
|
|
"Das Kartenfenster ist nicht verfügbar.",
|
|
)
|
|
return False
|
|
|
|
qgs_geometry = self._import_qgis_symbol("qgis.core", "QgsGeometry")
|
|
qgs_rectangle = self._import_qgis_symbol("qgis.core", "QgsRectangle")
|
|
qgs_wkb_types = self._import_qgis_symbol("qgis.core", "QgsWkbTypes")
|
|
qgs_map_tool_emit_point = self._import_qgis_symbol("qgis.gui", "QgsMapToolEmitPoint")
|
|
qgs_rubber_band = self._import_qgis_symbol("qgis.gui", "QgsRubberBand")
|
|
if None in (qgs_geometry, qgs_rectangle, qgs_wkb_types, qgs_map_tool_emit_point, qgs_rubber_band):
|
|
self.pruefmanager.zeige_hinweis(
|
|
"Druckrechteck",
|
|
"Die Rechteck-Erfassung ist in dieser QGIS-Umgebung nicht verfügbar.",
|
|
)
|
|
return False
|
|
|
|
logic = self
|
|
self._previous_map_tool = getattr(map_canvas, "mapTool", lambda: None)()
|
|
base_tool_class = cast(type, qgs_map_tool_emit_point)
|
|
rubber_band_class = cast(Any, qgs_rubber_band)
|
|
geometry_class = cast(Any, qgs_geometry)
|
|
rectangle_class = cast(Any, qgs_rectangle)
|
|
polygon_geometry = cast(Any, getattr(qgs_wkb_types, "PolygonGeometry", None))
|
|
|
|
class RectangleCaptureTool(base_tool_class):
|
|
def __init__(self, canvas_widget: Any) -> None:
|
|
super().__init__(canvas_widget)
|
|
self.canvas = canvas_widget
|
|
self.start_point: Any | None = None
|
|
self.rubber_band: Any = rubber_band_class(canvas_widget, polygon_geometry)
|
|
|
|
def _to_rect(self, start_point: Any, end_point: Any) -> Any:
|
|
x_min = min(start_point.x(), end_point.x())
|
|
x_max = max(start_point.x(), end_point.x())
|
|
y_min = min(start_point.y(), end_point.y())
|
|
y_max = max(start_point.y(), end_point.y())
|
|
return rectangle_class(x_min, y_min, x_max, y_max)
|
|
|
|
def _show_rect(self, rect: Any) -> None:
|
|
try:
|
|
self.rubber_band.setToGeometry(geometry_class.fromRect(rect), None)
|
|
except Exception:
|
|
pass
|
|
|
|
def canvasPressEvent(self, event: Any) -> None:
|
|
self.start_point = self.toMapCoordinates(event.pos())
|
|
|
|
def canvasMoveEvent(self, event: Any) -> None:
|
|
if self.start_point is None:
|
|
return
|
|
end_point = self.toMapCoordinates(event.pos())
|
|
self._show_rect(self._to_rect(self.start_point, end_point))
|
|
|
|
def canvasReleaseEvent(self, event: Any) -> None:
|
|
if self.start_point is None:
|
|
return
|
|
end_point = self.toMapCoordinates(event.pos())
|
|
rectangle = self._to_rect(self.start_point, end_point)
|
|
self._show_rect(rectangle)
|
|
width = getattr(rectangle, "width", lambda: 0.0)()
|
|
height = getattr(rectangle, "height", lambda: 0.0)()
|
|
if width <= 0 or height <= 0:
|
|
logic.pruefmanager.zeige_hinweis(
|
|
"Druckrechteck",
|
|
"Das gezeichnete Rechteck ist ungültig.",
|
|
)
|
|
else:
|
|
if logic._store_druckrechteck(layer, rectangle):
|
|
logic.pruefmanager.zeige_hinweis(
|
|
"Druckrechteck",
|
|
"Das Druckrechteck wurde übernommen.",
|
|
)
|
|
try:
|
|
self.rubber_band.reset(polygon_geometry)
|
|
except Exception:
|
|
pass
|
|
if logic._previous_map_tool is not None:
|
|
try:
|
|
self.canvas.setMapTool(logic._previous_map_tool)
|
|
except Exception:
|
|
pass
|
|
logic._rectangle_tool = None
|
|
|
|
rectangle_tool = RectangleCaptureTool(map_canvas)
|
|
self._rectangle_tool = rectangle_tool
|
|
|
|
set_active_layer = getattr(iface, "setActiveLayer", None)
|
|
if callable(set_active_layer):
|
|
try:
|
|
set_active_layer(layer)
|
|
except Exception:
|
|
pass
|
|
|
|
start_editing = getattr(layer, "startEditing", None)
|
|
if callable(start_editing):
|
|
try:
|
|
start_editing()
|
|
except Exception:
|
|
pass
|
|
|
|
set_map_tool = getattr(map_canvas, "setMapTool", None)
|
|
if callable(set_map_tool):
|
|
try:
|
|
set_map_tool(rectangle_tool)
|
|
except Exception:
|
|
self.pruefmanager.zeige_hinweis(
|
|
"Druckrechteck",
|
|
"Das Rechteck-Werkzeug konnte nicht aktiviert werden.",
|
|
)
|
|
return False
|
|
|
|
self.pruefmanager.zeige_hinweis(
|
|
"Druckrechteck",
|
|
"Bitte ziehen Sie ein Rechteck in der Karte auf.",
|
|
)
|
|
return True
|
|
|
|
def _resolve_selection_layer(self, fallback_layer: object | None) -> object | None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
active_layer = self._resolve_active_layer()
|
|
if active_layer is not None:
|
|
selected_count = getattr(active_layer, "selectedFeatureCount", None)
|
|
if callable(selected_count):
|
|
try:
|
|
selected_count_value = selected_count()
|
|
if int(cast(Any, selected_count_value)) > 0:
|
|
return active_layer
|
|
except Exception:
|
|
pass
|
|
return fallback_layer
|
|
|
|
def _get_selected_extent(self, layer: object | None) -> Any | None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
if layer is None:
|
|
return None
|
|
|
|
get_selected_features = getattr(layer, "selectedFeatures", None)
|
|
if not callable(get_selected_features):
|
|
return None
|
|
|
|
try:
|
|
selected_features_any = get_selected_features()
|
|
selected_features = list(cast(Any, selected_features_any))
|
|
except Exception:
|
|
return None
|
|
if not selected_features:
|
|
return None
|
|
|
|
merged_extent: Any | None = None
|
|
for feature in selected_features:
|
|
geometry = getattr(feature, "geometry", lambda: None)()
|
|
if geometry is None:
|
|
continue
|
|
bounding_box = getattr(geometry, "boundingBox", lambda: None)()
|
|
if bounding_box is None:
|
|
continue
|
|
if merged_extent is None:
|
|
merged_extent = self._clone_extent(bounding_box)
|
|
continue
|
|
combine_extent = getattr(merged_extent, "combineExtentWith", None)
|
|
if callable(combine_extent):
|
|
try:
|
|
combine_extent(bounding_box)
|
|
continue
|
|
except Exception:
|
|
pass
|
|
|
|
x_min = min(float(merged_extent.xMinimum()), float(bounding_box.xMinimum()))
|
|
y_min = min(float(merged_extent.yMinimum()), float(bounding_box.yMinimum()))
|
|
x_max = max(float(merged_extent.xMaximum()), float(bounding_box.xMaximum()))
|
|
y_max = max(float(merged_extent.yMaximum()), float(bounding_box.yMaximum()))
|
|
merged_extent = self._build_rectangle(x_min, y_min, x_max, y_max)
|
|
return merged_extent
|
|
|
|
def _get_druckrechteck_extent(self) -> Any | None:
|
|
layer = self._get_existing_layer_by_name(DRUCKRECHTECK_LAYER_NAME)
|
|
extent = get_layer_extent(layer)
|
|
if extent is not None:
|
|
width = getattr(extent, "width", lambda: 0.0)()
|
|
height = getattr(extent, "height", lambda: 0.0)()
|
|
if width > 0 and height > 0:
|
|
return extent
|
|
|
|
try:
|
|
x_min = float(get_variable("druckrechteck_xmin", scope="project"))
|
|
y_min = float(get_variable("druckrechteck_ymin", scope="project"))
|
|
x_max = float(get_variable("druckrechteck_xmax", scope="project"))
|
|
y_max = float(get_variable("druckrechteck_ymax", scope="project"))
|
|
except (TypeError, ValueError):
|
|
return None
|
|
return self._build_rectangle(x_min, y_min, x_max, y_max)
|
|
|
|
def _get_canvas_extent(self) -> Any | None:
|
|
map_canvas_fn = getattr(iface, "mapCanvas", None)
|
|
if not callable(map_canvas_fn):
|
|
return None
|
|
|
|
try:
|
|
canvas = map_canvas_fn()
|
|
except Exception:
|
|
return None
|
|
|
|
if canvas is None:
|
|
return None
|
|
|
|
extent_fn = getattr(canvas, "extent", None)
|
|
if not callable(extent_fn):
|
|
return None
|
|
|
|
try:
|
|
extent = extent_fn()
|
|
except Exception:
|
|
return None
|
|
|
|
if extent is None:
|
|
return None
|
|
|
|
is_null = getattr(extent, "isNull", None)
|
|
if callable(is_null):
|
|
try:
|
|
if is_null():
|
|
return None
|
|
except Exception:
|
|
return None
|
|
|
|
width_fn = getattr(extent, "width", None)
|
|
height_fn = getattr(extent, "height", None)
|
|
if not callable(width_fn) or not callable(height_fn):
|
|
return None
|
|
try:
|
|
width_value = float(cast(Any, width_fn()))
|
|
height_value = float(cast(Any, height_fn()))
|
|
if width_value <= 0 or height_value <= 0:
|
|
return None
|
|
except Exception:
|
|
return None
|
|
|
|
return extent
|
|
|
|
def _resolve_extent(
|
|
self,
|
|
resolved_layer: object | None,
|
|
druckbereich_auswahl: str,
|
|
plotmassstab: float,
|
|
) -> tuple[Any | None, object | None]:
|
|
if druckbereich_auswahl == DRUCKBEREICH_WIE_KARTENFENSTER:
|
|
extent = self._get_canvas_extent()
|
|
if extent is None:
|
|
self.pruefmanager.zeige_hinweis(
|
|
"Druckbereich",
|
|
"Das aktuelle Kartenfenster liefert keine gültige Ausdehnung.",
|
|
)
|
|
return None, None
|
|
return extent, None
|
|
|
|
if druckbereich_auswahl == DRUCKBEREICH_AKTUELLE_AUSWAHL:
|
|
selection_layer = self._resolve_selection_layer(resolved_layer)
|
|
extent = self._get_selected_extent(selection_layer)
|
|
if extent is None:
|
|
self.pruefmanager.zeige_hinweis(
|
|
"Druckbereich",
|
|
"Auf dem aktuellen Layer sind keine gültigen Objekte ausgewählt.",
|
|
)
|
|
return None, None
|
|
return self._buffer_extent(extent, plotmassstab), None
|
|
|
|
if druckbereich_auswahl == DRUCKBEREICH_RECHTECK:
|
|
extent = self._get_druckrechteck_extent()
|
|
if extent is None:
|
|
self.activate_druckrechteck_capture(resolved_layer)
|
|
return None, None
|
|
return extent, None
|
|
|
|
lp = Layerpruefer(layer=resolved_layer)
|
|
ergebnis = lp.pruefe()
|
|
if not ergebnis.ok:
|
|
self.pruefmanager.zeige_hinweis(
|
|
"Verfahrensgebiets-Layer angeben",
|
|
"Kein gültiger Verfahrensgebiets-Layer gefunden. Bitte Layerauswahl im jeweiligen Plugin setzen.",
|
|
)
|
|
return None, None
|
|
|
|
extent = get_layer_extent(resolved_layer)
|
|
if extent is None:
|
|
self.pruefmanager.zeige_hinweis("Fehler", "Layer-Ausdehnung konnte nicht ermittelt werden.")
|
|
return None, None
|
|
return self._buffer_extent(extent, plotmassstab), resolved_layer
|
|
|
|
def set_kartenname_for_auswahl(self, auswahl: str) -> None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
kartenname = KARTENNAME_BY_AUSWAHL.get(auswahl, "")
|
|
set_variable(KARTENNAME_VAR, kartenname, scope="project")
|
|
|
|
def set_plotmassstab_for_auswahl(self, auswahl: str, aktueller_massstab: float | None = None) -> None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
if auswahl == MASSSTAB_WIE_KARTENFENSTER:
|
|
if aktueller_massstab and aktueller_massstab > 0:
|
|
set_variable(PLOTMASSSTAB_VAR, str(int(round(aktueller_massstab))), scope="project")
|
|
else:
|
|
set_variable(PLOTMASSSTAB_VAR, "", scope="project")
|
|
return
|
|
|
|
value = PLOTMASSSTAB_BY_AUSWAHL.get(auswahl, "")
|
|
set_variable(PLOTMASSSTAB_VAR, value, scope="project")
|
|
|
|
def set_view_for_auswahl(self, auswahl: str) -> None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
if auswahl == THEMA_WIE_KARTENFENSTER:
|
|
set_variable(VIEW_VAR, "aktuell", scope="project")
|
|
return
|
|
set_variable(VIEW_VAR, auswahl or "", scope="project")
|
|
|
|
def set_zielgroesse_for_auswahl(self, auswahl: str) -> None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
set_variable(ZIELGROESSE_VAR, auswahl if auswahl in DIN_GROESSEN else DIN_STANDARD, scope="project")
|
|
|
|
def set_formfaktor(self, endlosrolle: bool) -> None:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
set_variable(FORMFAKTOR_VAR, "Endlosrolle" if endlosrolle else "Blatt", scope="project")
|
|
|
|
def set_druckbereich_for_auswahl(self, auswahl: str) -> None:
|
|
if auswahl not in {
|
|
DRUCKBEREICH_VERFAHRENSGEBIET,
|
|
DRUCKBEREICH_WIE_KARTENFENSTER,
|
|
DRUCKBEREICH_AKTUELLE_AUSWAHL,
|
|
DRUCKBEREICH_RECHTECK,
|
|
}:
|
|
auswahl = DRUCKBEREICH_VERFAHRENSGEBIET
|
|
set_variable(DRUCKBEREICH_VAR, auswahl, scope="project")
|
|
|
|
def _get_baufreigabe_page_dimensions(
|
|
self,
|
|
required_map_width_mm: float,
|
|
required_map_height_mm: float,
|
|
max_format: str = "DIN A3",
|
|
) -> tuple[str, float, float, float, float, bool]:
|
|
"""Wählt für Baufreigabe ein Blatt im Querformat.
|
|
|
|
Args:
|
|
required_map_width_mm: benötigte Breite der Hauptkarte in mm
|
|
required_map_height_mm: benötigte Höhe der Hauptkarte in mm
|
|
max_format: maximale Blattgröße, standardmäßig DIN A3
|
|
|
|
Returns:
|
|
(din_format, page_width_mm, page_height_mm, map_w_mm, map_h_mm, fallback_applied)
|
|
"""
|
|
candidates = (
|
|
("DIN A4", 297.0, 210.0),
|
|
("DIN A3", 420.0, 297.0),
|
|
)
|
|
|
|
try:
|
|
max_index = [candidate[0] for candidate in candidates].index(max_format)
|
|
except ValueError:
|
|
max_index = len(candidates) - 1
|
|
|
|
limited_candidates = candidates[: max_index + 1]
|
|
|
|
for din_format, page_w, page_h in limited_candidates:
|
|
map_w = page_w - BAUFREIGABE_LAYOUT_FIXBREITE_MM
|
|
map_h = page_h - BAUFREIGABE_LAYOUT_FIXHOEHE_MM
|
|
if map_w >= required_map_width_mm and map_h >= required_map_height_mm:
|
|
return din_format, page_w, page_h, map_w, map_h, False
|
|
|
|
# Wenn der gewünschte Ausschnitt nicht passt, verwende das maximal erlaubte Blatt
|
|
# und erweitere die Hauptkarte auf die verfügbare Kartenfläche (zentriert per fit_extent_to_map).
|
|
din_format, page_w, page_h = limited_candidates[-1]
|
|
map_w = max(page_w - BAUFREIGABE_LAYOUT_FIXBREITE_MM, 50.0)
|
|
map_h = max(page_h - BAUFREIGABE_LAYOUT_FIXHOEHE_MM, 50.0)
|
|
return din_format, page_w, page_h, map_w, map_h, True
|
|
|
|
def _does_plot_fit_page(
|
|
self,
|
|
page_width_mm: float,
|
|
page_height_mm: float,
|
|
map_width_mm: float,
|
|
map_height_mm: float,
|
|
fixed_width_mm: float,
|
|
fixed_height_mm: float,
|
|
) -> bool:
|
|
required_w = map_width_mm + fixed_width_mm
|
|
required_h = map_height_mm + fixed_height_mm
|
|
return required_w <= page_width_mm and required_h <= page_height_mm
|
|
|
|
def _get_plot_dimensions_mm(
|
|
self,
|
|
map_width_mm: float,
|
|
map_height_mm: float,
|
|
fixed_width_mm: float,
|
|
fixed_height_mm: float,
|
|
) -> tuple[float, float]:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
return map_width_mm + fixed_width_mm, map_height_mm + fixed_height_mm
|
|
|
|
def _get_required_map_dimensions_mm(self, extent: Any, plotmassstab: float) -> tuple[float, float]:
|
|
"""Berechnet die benötigte Kartenfenstergröße in mm für Extent und Maßstab.
|
|
|
|
Test ausgesetzt, zunächst Refactor
|
|
"""
|
|
if extent is None or not isinstance(plotmassstab, (int, float)) or plotmassstab <= 0:
|
|
return 0.0, 0.0
|
|
|
|
try:
|
|
width_mm = float(extent.width()) * 1000.0 / float(plotmassstab)
|
|
height_mm = float(extent.height()) * 1000.0 / float(plotmassstab)
|
|
except Exception:
|
|
return 0.0, 0.0
|
|
|
|
return max(width_mm, 0.0), max(height_mm, 0.0)
|
|
|
|
def _select_standard_page_layout(
|
|
self,
|
|
required_map_width_mm: float,
|
|
required_map_height_mm: float,
|
|
max_format: str,
|
|
) -> tuple[float, float, float, float] | None:
|
|
"""Wählt das kleinste passende DIN-Blatt für das Standardlayout."""
|
|
try:
|
|
max_index = DIN_AUTO_REIHENFOLGE.index(max_format)
|
|
except ValueError:
|
|
max_index = len(DIN_AUTO_REIHENFOLGE) - 1
|
|
|
|
for fmt in DIN_AUTO_REIHENFOLGE[: max_index + 1]:
|
|
base_w, base_h = DIN_GROESSEN[fmt]
|
|
fitting_candidates: list[tuple[float, float, float, float]] = []
|
|
for page_w, page_h in ((float(base_w), float(base_h)), (float(base_h), float(base_w))):
|
|
map_w = page_w - STANDARD_LAYOUT_FIXBREITE_MM
|
|
map_h = page_h - STANDARD_LAYOUT_FIXHOEHE_MM
|
|
if map_w >= required_map_width_mm and map_h >= required_map_height_mm:
|
|
fitting_candidates.append((page_w, page_h, map_w, map_h))
|
|
|
|
if fitting_candidates:
|
|
return min(
|
|
fitting_candidates,
|
|
key=lambda candidate: (candidate[2] - required_map_width_mm) + (candidate[3] - required_map_height_mm),
|
|
)
|
|
|
|
return None
|
|
|
|
def _get_standard_layout_dimensions(
|
|
self,
|
|
required_map_width_mm: float,
|
|
required_map_height_mm: float,
|
|
max_format: str,
|
|
formfaktor: bool,
|
|
) -> tuple[float, float, float, float, bool] | None:
|
|
if formfaktor:
|
|
plotgroesse_w, plotgroesse_h = self._get_plot_dimensions_mm(
|
|
required_map_width_mm,
|
|
required_map_height_mm,
|
|
STANDARD_LAYOUT_FIXBREITE_MM,
|
|
STANDARD_LAYOUT_FIXHOEHE_MM,
|
|
)
|
|
din_dims = DIN_GROESSEN.get(max_format, DIN_GROESSEN[DIN_STANDARD])
|
|
max_page_h = float(max(din_dims))
|
|
page_w = min(plotgroesse_w, ENDLOSROLLE_MAX_BREITE_MM)
|
|
page_h = min(plotgroesse_h, max_page_h)
|
|
return self._apply_standard_minimum_a4_fallback(
|
|
page_w,
|
|
page_h,
|
|
required_map_width_mm,
|
|
required_map_height_mm,
|
|
)
|
|
|
|
selected_layout = self._select_standard_page_layout(
|
|
required_map_width_mm,
|
|
required_map_height_mm,
|
|
max_format,
|
|
)
|
|
if selected_layout is None:
|
|
return None
|
|
|
|
page_w, page_h, map_w, map_h = selected_layout
|
|
force_fit_extent = map_w > required_map_width_mm or map_h > required_map_height_mm
|
|
return page_w, page_h, map_w, map_h, force_fit_extent
|
|
|
|
def _apply_standard_minimum_a4_fallback(
|
|
self,
|
|
page_width_mm: float,
|
|
page_height_mm: float,
|
|
map_width_mm: float,
|
|
map_height_mm: float,
|
|
) -> tuple[float, float, float, float, bool]:
|
|
"""Erzwingt bei zu kleinem Standardlayout mindestens DIN A4 im Querformat.
|
|
|
|
Liefert zusätzlich ein Flag zurück, damit die Extent-Anpassung im Layout
|
|
zur zentrierten Darstellung aktiviert werden kann.
|
|
"""
|
|
fallback_page_w = max(page_width_mm, STANDARD_A4_LANDSCAPE_W_MM)
|
|
fallback_page_h = max(page_height_mm, STANDARD_A4_LANDSCAPE_H_MM)
|
|
if fallback_page_w == page_width_mm and fallback_page_h == page_height_mm:
|
|
return page_width_mm, page_height_mm, map_width_mm, map_height_mm, False
|
|
|
|
fallback_map_w = max(map_width_mm, fallback_page_w - STANDARD_LAYOUT_FIXBREITE_MM, 50.0)
|
|
fallback_map_h = max(map_height_mm, fallback_page_h - STANDARD_LAYOUT_FIXHOEHE_MM, 50.0)
|
|
return fallback_page_w, fallback_page_h, fallback_map_w, fallback_map_h, True
|
|
|
|
def _basemap_legende_erzeugen(self, kartenname_auswahl: str) -> bool:
|
|
"""Ob die neue Vorlage eine Basemap-Legende bekommt (Layoutart und Schalter im Druck-Tab)."""
|
|
from sn_basis.modules.basemap_legende import legende_erzeugen_aktiv
|
|
|
|
return kartenname_auswahl in LAYOUTS_MIT_BASEMAP_LEGENDE and legende_erzeugen_aktiv()
|
|
|
|
def druckvorlage_anlegen(
|
|
self,
|
|
layer: object | None,
|
|
kartenname_auswahl: str,
|
|
massstab_auswahl: str,
|
|
zielgroesse: str,
|
|
formfaktor: bool,
|
|
druckbereich_auswahl: str,
|
|
) -> dict:
|
|
"""Test ausgesetzt, zunächst Refactor"""
|
|
resolved_layer = layer or self._resolve_layer_from_project()
|
|
|
|
if kartenname_auswahl not in (KARTENNAME_38, KARTENNAME_41, KARTENNAME_BAUFREIGABE):
|
|
self.pruefmanager.zeige_hinweis("Kartennamen wählen", "Kartennamen wählen")
|
|
return self._failed_result()
|
|
|
|
if massstab_auswahl == MASSSTAB_WIE_KARTENFENSTER:
|
|
# Maßstab direkt vom aktuellen Canvas lesen; Projektvariable als Fallback.
|
|
try:
|
|
canvas = iface.mapCanvas()
|
|
live_scale = canvas.scale() if canvas is not None else 0.0
|
|
massstab_zahl = float(live_scale) if live_scale > 0 else 0.0
|
|
except Exception:
|
|
massstab_zahl = 0.0
|
|
if massstab_zahl <= 0:
|
|
massstab_str = get_variable(PLOTMASSSTAB_VAR, scope="project") or ""
|
|
try:
|
|
massstab_zahl = float(massstab_str)
|
|
except (ValueError, TypeError):
|
|
massstab_zahl = 0.0
|
|
else:
|
|
massstab_zahl = float(PLOTMASSSTAB_BY_AUSWAHL.get(massstab_auswahl, 0))
|
|
|
|
if massstab_zahl <= 0:
|
|
self.pruefmanager.zeige_hinweis("Maßstab fehlt", "Kein gültiger Maßstab angegeben.")
|
|
return self._failed_result()
|
|
|
|
extent, layer_for_extent = self._resolve_extent(resolved_layer, druckbereich_auswahl, massstab_zahl)
|
|
if extent is None:
|
|
return self._failed_result()
|
|
|
|
if layer_for_extent is not None:
|
|
layer_id = getattr(layer_for_extent, "id", lambda: "")() or ""
|
|
if layer_id:
|
|
# Neu: zentrale Variable
|
|
set_variable("verfahrensgebiet_layer", layer_id, scope="project")
|
|
# Alt: Rückwärtskompatibilität
|
|
set_variable("verfahrensgebietslayer", layer_id, scope="project")
|
|
|
|
kartenbild_w, kartenbild_h = self._get_required_map_dimensions_mm(extent, massstab_zahl)
|
|
|
|
force_fit_extent_for_min_a4 = False
|
|
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
|
|
_, auto_page_w, auto_page_h, auto_map_w, auto_map_h, _bf_fallback = self._get_baufreigabe_page_dimensions(
|
|
kartenbild_w,
|
|
kartenbild_h,
|
|
max_format=zielgroesse if zielgroesse in {"DIN A4", "DIN A3"} else "DIN A3",
|
|
)
|
|
else:
|
|
standard_layout = self._get_standard_layout_dimensions(
|
|
kartenbild_w,
|
|
kartenbild_h,
|
|
zielgroesse,
|
|
formfaktor,
|
|
)
|
|
if standard_layout is None:
|
|
return self._show_target_size_too_small()
|
|
|
|
auto_page_w, auto_page_h, auto_map_w, auto_map_h, force_fit_extent_for_min_a4 = standard_layout
|
|
|
|
# Finale Sicherheitspruefung: alle Layoutobjekte muessen auf die Seite passen.
|
|
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
|
|
fixed_width_mm = BAUFREIGABE_LAYOUT_FIXBREITE_MM
|
|
fixed_height_mm = BAUFREIGABE_LAYOUT_FIXHOEHE_MM
|
|
else:
|
|
fixed_width_mm = STANDARD_LAYOUT_FIXBREITE_MM
|
|
fixed_height_mm = STANDARD_LAYOUT_FIXHOEHE_MM
|
|
|
|
fits_page = self._does_plot_fit_page(
|
|
auto_page_w,
|
|
auto_page_h,
|
|
auto_map_w,
|
|
auto_map_h,
|
|
fixed_width_mm,
|
|
fixed_height_mm,
|
|
)
|
|
|
|
if not fits_page:
|
|
return self._show_target_size_too_small()
|
|
|
|
kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get(
|
|
kartenname_auswahl, "Vorlage"
|
|
)
|
|
thema = get_variable(VIEW_VAR, scope="project") or ""
|
|
|
|
vorlage_name, bestaetigt = self.pruefmanager.frage_text(
|
|
"Neue Vorlage anlegen",
|
|
"Bezeichnung der Vorlage:",
|
|
default_text=kartenname,
|
|
)
|
|
if not bestaetigt:
|
|
return self._failed_result()
|
|
|
|
vorlage_name = (vorlage_name or "").strip() or kartenname
|
|
|
|
# Atlas-Status vorab bestimmen, damit er sowohl an create_ als auch configure_ übergeben wird.
|
|
atlas_raw_bf = get_variable(ATLAS_ERZEUGEN_UI_VAR, scope="project")
|
|
atlas_erzeugen_bf = self._is_enabled_flag(atlas_raw_bf)
|
|
|
|
# Verzweigung: Baufreigabe vs. Standard-Layout
|
|
try:
|
|
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
|
|
# Baufreigabe: feste DIN A3/A4 Querformat-Logik mit 80mm Kartenschild
|
|
din_format = "DIN A3" if auto_page_w >= 420.0 else "DIN A4"
|
|
PrintLayoutBaufreigabe().create_baufreigabe_layout(
|
|
name=vorlage_name if vorlage_name != kartenname else "Baufreigabe",
|
|
din_format=din_format,
|
|
map_width_mm=auto_map_w,
|
|
map_height_mm=auto_map_h,
|
|
extent=extent,
|
|
plotmassstab=massstab_zahl,
|
|
thema=thema,
|
|
fit_extent_to_map=True,
|
|
atlas_driven=atlas_erzeugen_bf,
|
|
)
|
|
else:
|
|
# Standard-Layout: dynamische Größenanpassung
|
|
PrintLayout().create_single_page_layout(
|
|
name=vorlage_name,
|
|
page_width_mm=auto_page_w,
|
|
page_height_mm=auto_page_h,
|
|
map_width_mm=auto_map_w,
|
|
map_height_mm=auto_map_h,
|
|
extent=extent,
|
|
plotmassstab=massstab_zahl,
|
|
kartenname_auswahl=kartenname_auswahl,
|
|
thema=thema,
|
|
fit_extent_to_map=(druckbereich_auswahl != DRUCKBEREICH_VERFAHRENSGEBIET) or force_fit_extent_for_min_a4,
|
|
basemap_legende=self._basemap_legende_erzeugen(kartenname_auswahl),
|
|
)
|
|
except ValueError as exc:
|
|
self.pruefmanager.zeige_hinweis("Vorlage anlegen", str(exc))
|
|
return self._failed_result()
|
|
except Exception as exc:
|
|
self.pruefmanager.zeige_hinweis(
|
|
"Vorlage anlegen",
|
|
f"Die Vorlage konnte nicht angelegt werden: {exc}",
|
|
)
|
|
return self._failed_result()
|
|
|
|
# Nach erfolgreicher Layout-Erstellung: Atlas ggf. konfigurieren (nur falls aktiviert)
|
|
if kartenname_auswahl == KARTENNAME_BAUFREIGABE:
|
|
if atlas_erzeugen_bf:
|
|
self.configure_atlas_for_baufreigabe_layout()
|
|
else:
|
|
self.configure_atlas_for_baufreigabe_layout(
|
|
atlas_config={
|
|
"enabled": False,
|
|
}
|
|
)
|
|
|
|
return {"ok": True, "switch_to_tab_a": False, "atlas_seiten": 1}
|