Files
Plugin_SN_Plan41/ui/layout.py

432 lines
19 KiB
Python
Raw Permalink 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_plan41/ui/layout.py Aufbau von Drucklayouts für Plan41.
"""
from __future__ import annotations
import math
from typing import Any
from sn_basis.functions.qt_wrapper import QFont
from sn_basis.functions.qgiscore_wrapper import (
QgsLayoutItem,
QgsLayoutItemLabel,
QgsLayoutItemMap,
QgsLayoutPoint,
QgsLayoutSize,
QgsPrintLayout,
QgsProject,
QgsUnitTypes,
)
from sn_basis.functions.qgisui_wrapper import open_layout_designer
MM = QgsUnitTypes.LayoutMillimeters
class Layout:
"""Erzeugt ein QGIS-Layout für den Druck."""
def __init__(self, project: Any | None = None) -> None:
self.project = project or QgsProject.instance()
def create_single_page_layout(
self,
name: str,
page_width_mm: float,
page_height_mm: float,
map_width_mm: float,
map_height_mm: float,
extent: Any,
plotmassstab: float,
thema: str = "",
) -> Any:
"""Erzeugt ein einseitiges Layout und öffnet es im Designer."""
print(f"[Layout] create_single_page_layout: name='{name}', "
f"page=({page_width_mm}x{page_height_mm}), map=({map_width_mm:.1f}x{map_height_mm:.1f}), "
f"massstab={plotmassstab}, thema='{thema}'")
layout_manager = self.project.layoutManager()
print(f"[Layout] layoutManager: {layout_manager!r}")
existing_layout = layout_manager.layoutByName(name)
if existing_layout is not None:
raise ValueError(f"Eine Vorlage mit der Bezeichnung '{name}' existiert bereits.")
layout = QgsPrintLayout(self.project)
layout.initializeDefaults()
layout.setName(name)
print(f"[Layout] QgsPrintLayout erstellt: {layout!r}")
page = layout.pageCollection().page(0)
page.setPageSize(QgsLayoutSize(page_width_mm, page_height_mm, MM))
print(f"[Layout] Seitengröße gesetzt: {page_width_mm}x{page_height_mm} mm")
map_left_mm = 10.0
map_top_mm = 10.0
map_right_mm = map_left_mm + map_width_mm
map_bottom_mm = map_top_mm + map_height_mm
print(
f"[Layout] Kartenbild-Kanten: rechts={map_right_mm:.1f} mm, unten={map_bottom_mm:.1f} mm"
)
hauptkarte = QgsLayoutItemMap(layout)
hauptkarte.setId("Hauptkarte")
print(f"[Layout] QgsLayoutItemMap erstellt")
# Zum Layout hinzufügen, BEVOR Eigenschaften gesetzt werden
layout.addLayoutItem(hauptkarte)
print("[Layout] Hauptkarte zum Layout hinzugefügt")
# Position und Größe setzen
hauptkarte.attemptMove(QgsLayoutPoint(map_left_mm, map_top_mm, MM))
hauptkarte.attemptResize(QgsLayoutSize(map_width_mm, map_height_mm, MM))
print(f"[Layout] Position und Größe gesetzt: pos=({map_left_mm}, {map_top_mm}), size=({map_width_mm:.1f}x{map_height_mm:.1f})")
# Extent und Maßstab setzen
x_min = getattr(extent, "xMinimum", lambda: float("nan"))()
y_min = getattr(extent, "yMinimum", lambda: float("nan"))()
x_max = getattr(extent, "xMaximum", lambda: float("nan"))()
y_max = getattr(extent, "yMaximum", lambda: float("nan"))()
print(f"[Layout] Extent input: xmin={x_min}, ymin={y_min}, xmax={x_max}, ymax={y_max}")
if extent is not None and hasattr(extent, "isNull") and callable(extent.isNull) and not extent.isNull():
try:
hauptkarte.setExtent(extent)
print(f"[Layout] setExtent() erfolgreich")
except Exception as exc:
print(f"[Layout] WARN: setExtent() fehlgeschlagen: {exc}")
else:
print(f"[Layout] WARN: Extent nicht gültig/callable, setExtent übersprungen")
# Maßstab setzen (NACH Extent)
if isinstance(plotmassstab, (int, float)) and math.isfinite(plotmassstab) and plotmassstab > 0:
try:
hauptkarte.setScale(plotmassstab)
print(f"[Layout] setScale({plotmassstab}) erfolgreich")
except Exception as exc:
print(f"[Layout] WARN: setScale({plotmassstab}) fehlgeschlagen: {exc}")
else:
print(f"[Layout] WARN: ungültiger plotmassstab={plotmassstab!r}, setScale übersprungen")
# Rahmen aktivieren
set_frame_enabled = getattr(hauptkarte, "setFrameEnabled", None)
if callable(set_frame_enabled):
try:
set_frame_enabled(True)
print(f"[Layout] Rahmen aktiviert")
except Exception as exc:
print(f"[Layout] WARN: setFrameEnabled fehlgeschlagen: {exc}")
set_frame_stroke_width = getattr(hauptkarte, "setFrameStrokeWidth", None)
if callable(set_frame_stroke_width):
try:
set_frame_stroke_width(0.5)
print(f"[Layout] Rahmenstrichbreite auf 0.5 mm gesetzt")
except Exception as exc:
print(f"[Layout] WARN: setFrameStrokeWidth(0.5) fehlgeschlagen: {exc}")
# Kartenthema setzen
if thema and thema != "aktuell":
follow_theme = getattr(hauptkarte, "setFollowVisibilityPreset", None)
set_theme_name = getattr(hauptkarte, "setFollowVisibilityPresetName", None)
if callable(follow_theme):
try:
follow_theme(True)
print(f"[Layout] setFollowVisibilityPreset(True)")
except Exception as exc:
print(f"[Layout] WARN: setFollowVisibilityPreset fehlgeschlagen: {exc}")
if callable(set_theme_name):
try:
set_theme_name(thema)
print(f"[Layout] Kartenthema auf '{thema}' gesetzt")
except Exception as exc:
print(f"[Layout] WARN: setFollowVisibilityPresetName('{thema}') fehlgeschlagen: {exc}")
else:
print(f"[Layout] Kartenthema nicht gesetzt (thema='{thema}')")
# Erst nach allen Properties: LayerSet einfrieren für Export
set_keep_layer_set = getattr(hauptkarte, "setKeepLayerSet", None)
if callable(set_keep_layer_set):
try:
set_keep_layer_set(True)
print("[Layout] setKeepLayerSet(True) Layerset für Export erhalten")
except Exception as exc:
print(f"[Layout] WARN: setKeepLayerSet fehlgeschlagen: {exc}")
# Refresh-Strategie (optional, nur wenn vorhanden)
set_refresh_strategy = getattr(hauptkarte, "setRefreshStrategy", None)
if callable(set_refresh_strategy):
refresh_cache = getattr(hauptkarte, "RefreshLaterOnly", None) or getattr(hauptkarte, "RefreshWhenRequested", None) or 0
if refresh_cache is not None:
try:
set_refresh_strategy(refresh_cache)
print(f"[Layout] setRefreshStrategy({refresh_cache}) gesetzt")
except Exception as exc:
print(f"[Layout] WARN: setRefreshStrategy fehlgeschlagen: {exc}")
print(f"[Layout] Hauptkarte vollständig konfiguriert")
quellenangabe = QgsLayoutItemLabel(layout)
quellenangabe.setId("Quellenangabe")
quellenangabe.setText(
"Quelle Geobasisdaten: GeoSN, "
"<a href=\"https://www.govdata.de/dl-de/by-2-0\">dl-de/by-2-0</a><br><br>"
"Quelle Fachdaten: Darstellung auf der Grundlage von Daten und mit Erlaubnis des "
"Sächsischen Landesamtes für Umwelt, Landwirtschaft und Geologie<br><br>"
"Basemap:<br><br>"
"© GeoBasis-DE / <a href=\"https://www.bkg.bund.de\">BKG</a> ([%year($now)%]) "
"<a href=\"https://creativecommons.org/licences/by/4.0/\">CC BY 4.0</a> "
"mit teilweise angepasster Signatur<br>"
)
set_mode = getattr(quellenangabe, "setMode", None)
mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None)
print(f"[Layout] QgsLayoutItemLabel.ModeHtml={mode_html!r}")
if callable(set_mode) and mode_html is not None:
set_mode(mode_html)
quellenangabe.setFont(QFont("Arial", 12))
set_reference_point = getattr(quellenangabe, "setReferencePoint", None)
lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None)
print(f"[Layout] QgsLayoutItem.ReferencePoint.LowerLeft={lower_left!r}")
if callable(set_reference_point) and lower_left is not None:
set_reference_point(lower_left)
quellenangabe_x_mm = map_right_mm + 20.0
quellenangabe_y_mm = map_bottom_mm - 120.0
quellenangabe.attemptMove(QgsLayoutPoint(quellenangabe_x_mm, quellenangabe_y_mm, MM))
quellenangabe.attemptResize(QgsLayoutSize(180.0, 100.0, MM))
layout.addLayoutItem(quellenangabe)
print("[Layout] Quellenangabe zum Layout hinzugefügt")
layout_manager.addLayout(layout)
print("[Layout] Layout zum LayoutManager hinzugefügt")
open_layout_designer(layout)
print("[Layout] Layout Designer geöffnet")
return layout
def create_atlas_layout(
self,
name: str,
page_width_mm: float,
page_height_mm: float,
map_width_mm: float,
map_height_mm: float,
extent: Any,
plotmassstab: float,
atlas_layer: Any,
thema: str = "",
) -> Any:
"""Erzeugt ein Atlas-Layout mit Coverage-Layer ``Atlasobjekte``."""
layout_manager = self.project.layoutManager()
existing_layout = layout_manager.layoutByName(name)
if existing_layout is not None:
raise ValueError(f"Eine Vorlage mit der Bezeichnung '{name}' existiert bereits.")
layout = QgsPrintLayout(self.project)
layout.initializeDefaults()
layout.setName(name)
page = layout.pageCollection().page(0)
page.setPageSize(QgsLayoutSize(page_width_mm, page_height_mm, MM))
# Verifiziere, dass QGIS die Größe akzeptiert hat
page_size = page.pageSize() if hasattr(page, 'pageSize') else None
if page_size is not None and hasattr(page_size, 'width'):
actual_w = float(page_size.width())
actual_h = float(page_size.height())
print(f"[Layout] Atlas Page gesetzt: x=0, y=0, width={page_width_mm:.1f}mm→{actual_w:.1f}mm, height={page_height_mm:.1f}mm→{actual_h:.1f}mm")
else:
print(f"[Layout] Atlas Page: x=0, y=0, width={page_width_mm:.1f}mm, height={page_height_mm:.1f}mm")
map_left_mm = 10.0
map_top_mm = 10.0
map_right_mm = map_left_mm + map_width_mm
map_bottom_mm = map_top_mm + map_height_mm
hauptkarte = QgsLayoutItemMap(layout)
hauptkarte.setId("Hauptkarte")
layout.addLayoutItem(hauptkarte)
hauptkarte.attemptMove(QgsLayoutPoint(map_left_mm, map_top_mm, MM))
hauptkarte.attemptResize(QgsLayoutSize(map_width_mm, map_height_mm, MM))
# Verifiziere mit Units-bewussten Methoden (rect() kann andere Units verwenden).
actual_w = None
actual_h = None
actual_x = None
actual_y = None
# Versuche zuerst, die Größe mit Unit-Methoden zu lesen
try:
if hasattr(hauptkarte, 'sizeWithUnits'):
size_item = hauptkarte.sizeWithUnits()
if hasattr(size_item, 'width') and hasattr(size_item, 'height'):
actual_w = float(size_item.width())
actual_h = float(size_item.height())
except Exception:
pass
try:
if hasattr(hauptkarte, 'positionWithUnits'):
pos_item = hauptkarte.positionWithUnits()
if hasattr(pos_item, 'x') and hasattr(pos_item, 'y'):
actual_x = float(pos_item.x())
actual_y = float(pos_item.y())
except Exception:
pass
# Fallback: nutze rect() und teile durch UnitFaktor, falls nötig
if actual_w is None or actual_h is None:
try:
actual_rect = hauptkarte.rect()
if actual_rect is not None:
actual_w = float(actual_rect.width())
actual_h = float(actual_rect.height())
actual_x = float(actual_rect.x())
actual_y = float(actual_rect.y())
except Exception:
pass
if actual_w is not None and actual_h is not None:
print(f"[Layout] Atlas Hauptkarte gesetzt: x={map_left_mm:.1f}mm→{actual_x:.1f}mm, y={map_top_mm:.1f}mm→{actual_y:.1f}mm, width={map_width_mm:.1f}mm→{actual_w:.1f}mm, height={map_height_mm:.1f}mm→{actual_h:.1f}mm")
else:
print(f"[Layout] Atlas Hauptkarte: x={map_left_mm:.1f}mm, y={map_top_mm:.1f}mm, width={map_width_mm:.1f}mm, height={map_height_mm:.1f}mm")
if extent is not None and hasattr(extent, "isNull") and callable(extent.isNull) and not extent.isNull():
try:
hauptkarte.setExtent(extent)
except Exception:
pass
if isinstance(plotmassstab, (int, float)) and math.isfinite(plotmassstab) and plotmassstab > 0:
try:
hauptkarte.setScale(plotmassstab)
except Exception:
pass
set_frame_enabled = getattr(hauptkarte, "setFrameEnabled", None)
if callable(set_frame_enabled):
try:
set_frame_enabled(True)
except Exception:
pass
set_frame_stroke_width = getattr(hauptkarte, "setFrameStrokeWidth", None)
if callable(set_frame_stroke_width):
try:
set_frame_stroke_width(0.5)
except Exception:
pass
if thema and thema != "aktuell":
follow_theme = getattr(hauptkarte, "setFollowVisibilityPreset", None)
set_theme_name = getattr(hauptkarte, "setFollowVisibilityPresetName", None)
if callable(follow_theme):
try:
follow_theme(True)
except Exception:
pass
if callable(set_theme_name):
try:
set_theme_name(thema)
except Exception:
pass
set_atlas_driven = getattr(hauptkarte, "setAtlasDriven", None)
if callable(set_atlas_driven):
try:
set_atlas_driven(True)
except Exception:
pass
# Fester Atlas-Maßstab: plotmassstab bleibt unverändert.
set_scaling_mode = getattr(hauptkarte, "setAtlasScalingMode", None)
if callable(set_scaling_mode):
fixed_mode = getattr(QgsLayoutItemMap, "Fixed", None)
if fixed_mode is not None:
try:
set_scaling_mode(fixed_mode)
except Exception:
pass
set_atlas_margin = getattr(hauptkarte, "setAtlasMargin", None)
if callable(set_atlas_margin):
try:
set_atlas_margin(0.0)
except Exception:
pass
set_keep_layer_set = getattr(hauptkarte, "setKeepLayerSet", None)
if callable(set_keep_layer_set):
try:
set_keep_layer_set(True)
except Exception:
pass
# Sicherheit: Größe nochmal nach Atlas-Konfiguration setzen, um sicherzustellen, dass sie nicht von der Atlas-Einstellung überschrieben wurde.
hauptkarte.attemptResize(QgsLayoutSize(map_width_mm, map_height_mm, MM))
print(f"[Layout] Atlas Hauptkarte Größe nach Atlas-Konfiguration erneut gesetzt: {map_width_mm:.1f}mm × {map_height_mm:.1f}mm")
quellenangabe = QgsLayoutItemLabel(layout)
quellenangabe.setId("Quellenangabe")
quellenangabe.setText(
"Quelle Geobasisdaten: GeoSN, "
"<a href=\"https://www.govdata.de/dl-de/by-2-0\">dl-de/by-2-0</a><br><br>"
"Quelle Fachdaten: Darstellung auf der Grundlage von Daten und mit Erlaubnis des "
"Sächsischen Landesamtes für Umwelt, Landwirtschaft und Geologie<br><br>"
"Basemap:<br><br>"
"© GeoBasis-DE / <a href=\"https://www.bkg.bund.de\">BKG</a> ([%year($now)%]) "
"<a href=\"https://creativecommons.org/licences/by/4.0/\">CC BY 4.0</a> "
"mit teilweise angepasster Signatur<br>"
)
set_mode = getattr(quellenangabe, "setMode", None)
mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None)
if callable(set_mode) and mode_html is not None:
set_mode(mode_html)
quellenangabe.setFont(QFont("Arial", 12))
set_reference_point = getattr(quellenangabe, "setReferencePoint", None)
lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None)
if callable(set_reference_point) and lower_left is not None:
set_reference_point(lower_left)
quellenangabe.attemptMove(QgsLayoutPoint(map_right_mm + 5.0, map_bottom_mm, MM))
quellenangabe.attemptResize(QgsLayoutSize(180.0, 100.0, MM))
layout.addLayoutItem(quellenangabe)
seitenzahl_label = QgsLayoutItemLabel(layout)
seitenzahl_label.setId("Seitenzahl")
seitenzahl_label.setFont(QFont("Arial", 12))
set_expr_enabled = getattr(seitenzahl_label, "setExpressionEnabled", None)
if callable(set_expr_enabled):
try:
set_expr_enabled(True)
except Exception:
pass
# Ausdrucksauswertung robust über [% ... %]-Platzhalter.
seitenzahl_label.setText(
"Seite [% attribute(@atlas_feature, 'Seitenzahl') %] von [% @atlas_totalfeatures %]"
)
set_reference_point = getattr(seitenzahl_label, "setReferencePoint", None)
lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None)
if callable(set_reference_point) and lower_left is not None:
set_reference_point(lower_left)
seitenzahl_label.attemptMove(QgsLayoutPoint(map_right_mm + 5.0, map_bottom_mm - 2.0, MM))
seitenzahl_label.attemptResize(QgsLayoutSize(60.0, 8.0, MM))
layout.addLayoutItem(seitenzahl_label)
atlas = layout.atlas()
if atlas is not None:
set_enabled = getattr(atlas, "setEnabled", None)
set_coverage = getattr(atlas, "setCoverageLayer", None)
set_hide_coverage = getattr(atlas, "setHideCoverage", None)
set_filter_features = getattr(atlas, "setFilterFeatures", None)
set_page_name = getattr(atlas, "setPageNameExpression", None)
if callable(set_enabled):
set_enabled(True)
if callable(set_coverage):
set_coverage(atlas_layer)
if callable(set_hide_coverage):
set_hide_coverage(True)
if callable(set_filter_features):
set_filter_features(False)
if callable(set_page_name):
set_page_name("attribute(@atlas_feature, 'Seitenzahl')")
layout_manager.addLayout(layout)
open_layout_designer(layout)
return layout