Files
Plugin_SN_Plan41/ui/layout.py
T

1192 lines
43 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_plan41/ui/layout.py Aufbau von Drucklayouts für Plan41.
"""
from __future__ import annotations
import math
import importlib
from typing import Any
from sn_basis.functions.qt_wrapper import QFont
from sn_basis.functions.qgiscore_wrapper import (
QgsLayoutItem,
QgsLayoutItemLabel,
QgsLayoutItemLegend,
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."""
_TEXT_FORMAT_TEMPLATES = {
"Format_1": {
"font_family": "Arial",
"font_size": 11,
"font_style": "normal",
},
"Format_2": {
"font_family": "Arial",
"font_size": 13,
"font_style": "bold",
},
"Format_3": {
"font_family": "Arial",
"font_size": 16,
"font_style": "bold",
},
}
_OBJECT_TEMPLATES = {
"hauptkarte": {
"id": "Hauptkarte",
"object_name": "Hauptkarte",
},
"quellenangabe": {
"id": "Quellenangabe",
"object_name": "Quellenangabe",
"size_mm": (180.0, 100.0),
"format": "Format_1",
},
"seitenzahl": {
"id": "Seitenzahl",
"object_name": "Seitenzahl",
"size_mm": (60.0, 8.0),
"format": "Format_1",
},
"textfeld": {
"id": "Textfeld",
"object_name": "Textfeld",
"size_mm": (60.0, 8.0),
"format": "Format_1",
"html_mode": True,
"text": "",
},
"legende": {
"id": "Legende",
"object_name": "Legende",
"size_mm": (180.0, 60.0),
},
}
_QUELLENANGABE_TEXT = (
"Quelle Geobasisdaten: GeoSN, "
"<a href=\"https://www.govdata.de/dl-de/by-2-0\">dl-de/by-2-0</a><br>"
"Quelle Fachdaten: Darstellung auf der Grundlage von Daten und mit Erlaubnis des "
"Sächsischen Landesamtes für Umwelt, Landwirtschaft und Geologie<br>"
"Basemap:"
"© 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>"
)
_SEITENZAHL_TEXT = "Blatt [% attribute(@atlas_feature, 'Seitenzahl') %] von [% @atlas_totalfeatures %]"
def __init__(self, project: Any | None = None) -> None:
self.project = project or QgsProject.instance()
self._current_layout: Any | None = None
def _create_map_item(
self,
layout: Any,
template_name: str,
x_mm: float,
y_mm: float,
width_mm: float,
height_mm: float,
object_name: str | None = None,
) -> Any:
template = self._OBJECT_TEMPLATES[template_name]
map_item = QgsLayoutItemMap(layout)
map_item.setId(template["id"])
set_object_name = getattr(map_item, "setObjectName", None)
if callable(set_object_name):
set_object_name(object_name or template.get("object_name", template["id"]))
layout.addLayoutItem(map_item)
map_item.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM))
map_item.attemptResize(QgsLayoutSize(width_mm, height_mm, MM))
return map_item
def _set_item_frame_stroke_width(self, item: Any, stroke_width_mm: float) -> None:
set_frame_stroke_width = getattr(item, "setFrameStrokeWidth", None)
if not callable(set_frame_stroke_width):
return
try:
set_frame_stroke_width(stroke_width_mm)
return
except Exception:
pass
try:
qgis_core = importlib.import_module("qgis.core")
QgsLayoutMeasurement = getattr(qgis_core, "QgsLayoutMeasurement", None)
except Exception:
QgsLayoutMeasurement = None
if QgsLayoutMeasurement is not None:
try:
set_frame_stroke_width(QgsLayoutMeasurement(stroke_width_mm, MM))
return
except Exception:
pass
frame_stroke_width = getattr(item, "frameStrokeWidth", None)
if callable(frame_stroke_width):
try:
measurement = frame_stroke_width()
set_length = getattr(measurement, "setLength", None)
if callable(set_length):
set_length(stroke_width_mm)
set_frame_stroke_width(measurement)
except Exception:
pass
def add_text_label(
self,
text: str,
x_mm: float,
y_mm: float,
template_name: str = "textfeld",
text_format: str | None = None,
width_mm: float | None = None,
height_mm: float | None = None,
html_mode: bool = False,
expression_enabled: bool = False,
object_name: str | None = None,
) -> Any:
"""Einfache öffentliche Methode zum Hinzufügen eines Text-Labels.
Beispiel: add_text_label("Ausgabedatum: 2026-03-23", 100, 200, text_format="Format_1")
"""
if self._current_layout is None:
raise RuntimeError("Kein Layout aktiv. Bitte create_single_page_layout() oder create_atlas_layout() aufrufen.")
return self._create_label_item(
self._current_layout,
template_name,
text,
x_mm,
y_mm,
text_format=text_format,
width_mm=width_mm,
height_mm=height_mm,
html_mode=html_mode,
expression_enabled=expression_enabled,
object_name=object_name,
)
def _setup_layout_objects(
self,
kartenfenster_untere_rechte_ecke_mm: tuple,
hauptkarte: Any,
map_top_mm: float,
map_height_mm: float,
is_atlas: bool = False,
) -> None:
"""Zentrale Methode zum Hinzufügen aller Standard-Objekte ins Layout.
Diese Methode wird sowohl von create_single_page_layout() als auch von create_atlas_layout() aufgerufen.
Relatipunkt des Objekts ist die untere linke Ecke, berechnet von der unteren, rechten Ecke des Kartefensters
Args:
kartenfenster_untere_rechte_ecke_mm: Tupel (x_mm, y_mm) der unteren rechten Kartenecke
is_atlas: True für Atlas-Layout, False für Einzelblatt-Layout
"""
if self._current_layout is None:
raise RuntimeError("Kein Layout aktiv.")
def _coords_rel_untere_rechte_ecke(rel_x_mm: float, rel_y_mm: float) -> tuple[float, float]:
return (
kartenfenster_untere_rechte_ecke_mm[0] + rel_x_mm,
kartenfenster_untere_rechte_ecke_mm[1] + rel_y_mm,
)
mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None)
print(f"[Layout] QgsLayoutItemLabel.ModeHtml={mode_html!r}")
lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None)
print(f"[Layout] QgsLayoutItem.ReferencePoint.LowerLeft={lower_left!r}")
legende_x = kartenfenster_untere_rechte_ecke_mm[0] + 10.0
legende_height_mm = max(map_height_mm - 122.0, 20.0)
legende_y = map_top_mm
legende = self._create_legend_item(
self._current_layout,
"legende",
hauptkarte,
legende_x,
legende_y,
width_mm=180.0,
height_mm=legende_height_mm,
object_name="Legende",
)
legende_hoehe_mm = self._configure_legend_size(
legende,
width_limit_mm=180.0,
column_count=1,
equal_column_width=False,
)
quellenangabe_x, quellenangabe_y = _coords_rel_untere_rechte_ecke(10.0, -102.0)
quellenangabe_hoehe_mm = 20.0
quellenangabe_obere_y = quellenangabe_y - quellenangabe_hoehe_mm
legende_untere_y = legende_y + legende_hoehe_mm
if legende_untere_y > quellenangabe_obere_y:
legende_hoehe_mm = self._configure_legend_size(
legende,
width_limit_mm=180.0,
column_count=2,
equal_column_width=True,
)
print("[Layout] Legende überlappt Quellenangabe auf 2 Spalten umgestellt")
print("[Layout] Legende zum Layout hinzugefügt")
# Quellenangabe:
self.add_text_label(
self._QUELLENANGABE_TEXT,
quellenangabe_x,
quellenangabe_y,
width_mm=180.0,
height_mm=20.0,
text_format="Format_1",
html_mode=True,
expression_enabled=True,
object_name="Quellenangabe",
)
print("[Layout] Quellenangabe zum Layout hinzugefügt")
# Kartenschild-Box, -Linien und -Text werden hier ergänzt (TODO)
#Kartenschild-Box
kartenschild_x, kartenschild_y = _coords_rel_untere_rechte_ecke(10.0, 0.0)
self._create_rectangle_box_item(
self._current_layout,
"Kartenschild",
kartenschild_x,
kartenschild_y,
180.0,
92.0,
stroke_width_mm=1.0,
)
print("[Layout] Kartenschild-Box zum Layout hinzugefügt")
#Kartenschild-Linien
kartenschild_line_x, _ = _coords_rel_untere_rechte_ecke(10.0, 0.0)
kartenschild_line_offsets_y = (-10.0, -20.0, -30.0, -46.0, -62.0, -82.0)
for index, offset_y in enumerate(kartenschild_line_offsets_y, start=1):
_, line_y = _coords_rel_untere_rechte_ecke(0.0, offset_y)
self._create_rectangle_box_item(
self._current_layout,
f"Kartenschild_Linie_{index}",
kartenschild_line_x,
line_y,
180.0,
0.0,
stroke_width_mm=0.1,
)
print("[Layout] 6 Kartenschild-Linien zum Layout hinzugefügt")
#Kartenschild-Text
#Herausgeber
herausgeber_x, herausgeber_y = _coords_rel_untere_rechte_ecke(16.0, -84.0)
self.add_text_label(
"[% @sn_amt %]",
herausgeber_x,
herausgeber_y,
width_mm=171.0,
height_mm=4.0,
text_format="Format_1",
expression_enabled=True,
object_name="Herausgeber",
)
#Bezeichnung_Flurbereinigung
bezeichnung_flurbereinigung_x, bezeichnung_flurbereinigung_y = _coords_rel_untere_rechte_ecke(16.0, -75.0)
self.add_text_label(
"[% @sn_bezeichnung %]",
bezeichnung_flurbereinigung_x,
bezeichnung_flurbereinigung_y,
width_mm=171.0,
height_mm=4.0,
text_format="Format_1",
expression_enabled=True,
object_name="Bezeichnung_Flurbereinigung",
)
#Name Flurbereinigung
name_flurbereinigung_x, name_flurbereinigung_y = _coords_rel_untere_rechte_ecke(16.0, -64.0)
self.add_text_label(
"[% @sn_name %]",
name_flurbereinigung_x,
name_flurbereinigung_y,
width_mm=171.0,
height_mm=5.0,
text_format="Format_2",
expression_enabled=True,
object_name="Name_Flurbereinigung",
)
#Stadt_Gemeinde
stadt_gemeinde_x, stadt_gemeinde_y = _coords_rel_untere_rechte_ecke(16.0, -56.0)
self.add_text_label(
"[% @sn_gemeinden %]",
stadt_gemeinde_x,
stadt_gemeinde_y,
width_mm=171.0,
height_mm=4.0,
text_format="Format_1",
expression_enabled=True,
object_name="Stadt_Gemeinde",
)
#Landkreis
landkreis_x, landkreis_y = _coords_rel_untere_rechte_ecke(16.0, -48.0)
self.add_text_label(
"[% @sn_landkreise_proj %]",
landkreis_x,
landkreis_y,
width_mm=171.0,
height_mm=4.0,
text_format="Format_1",
expression_enabled=True,
object_name="Landkreis",
)
#Kartenname
kartenname_x, kartenname_y = _coords_rel_untere_rechte_ecke(16.0, -34.0)
self.add_text_label(
"[% @sn_kartenname %]",
kartenname_x,
kartenname_y,
width_mm=137.0,
height_mm=5.0,
text_format="Format_3",
expression_enabled=True,
object_name="Kartenname",
)
#Ergänzung(Seitenzahl)
#gefertigt
gefertigt_x, gefertigt_y = _coords_rel_untere_rechte_ecke(16.0, -23.0)
self.add_text_label(
"Gefertigt: ",
gefertigt_x,
gefertigt_y,
width_mm=81.0,
height_mm=4.0,
text_format="Format_1",
expression_enabled=True,
object_name="Gefertigt",
)
#geprüft
geprüft_x, geprüft_y = _coords_rel_untere_rechte_ecke(101.0, -23.0)
self.add_text_label(
"Geprüft: ",
geprüft_x,
geprüft_y,
width_mm=81.0,
height_mm=4.0,
text_format="Format_1",
expression_enabled=True,
object_name="Geprüft",
)
#Maßstab
maßstab_x, maßstab_y = _coords_rel_untere_rechte_ecke(16.0, -13.0)
self.add_text_label(
"Maßstab: 1 : [%round(map_get(item_variables('Hauptkarte'),'map_scale'),0)%]",
maßstab_x,
maßstab_y,
width_mm=81.0,
height_mm=4.0,
text_format="Format_1",
expression_enabled=True,
object_name="Maßstab",
)
#Ausgabedatum
ausgabedatum_x, ausgabedatum_y = _coords_rel_untere_rechte_ecke(101.0, -13.0)
self.add_text_label(
"Ausgabedatum: [%format_date(now(), 'dd.MM.yyyy')%]",
ausgabedatum_x,
ausgabedatum_y,
width_mm=81.0,
height_mm=4.0,
text_format="Format_1",
expression_enabled=True,
object_name="Ausgabedatum",
)
#Aktenzeichen
aktenzeichen_x, aktenzeichen_y = _coords_rel_untere_rechte_ecke(16.0, -3.0)
self.add_text_label(
"Aktenzeichen: ",
aktenzeichen_x,
aktenzeichen_y,
width_mm=171.0,
height_mm=4.0,
text_format="Format_1",
expression_enabled=True,
object_name="Aktenzeichen",
)
# Seitenzahl nur bei Atlas-Layout
if is_atlas:
seitenzahl_x, seitenzahl_y = _coords_rel_untere_rechte_ecke(166.0, -33.0)
self._create_label_item(
self._current_layout,
"seitenzahl",
self._SEITENZAHL_TEXT,
seitenzahl_x,
seitenzahl_y,
expression_enabled=True,
)
print("[Layout] Seitenzahl zum Layout hinzugefügt")
def _create_label_item(
self,
layout: Any,
template_name: str,
text: str,
x_mm: float,
y_mm: float,
*,
text_format: str | None = None,
font_name: str | None = None,
font_size: int | None = None,
font_style: str | None = None,
width_mm: float | None = None,
height_mm: float | None = None,
html_mode: bool = False,
expression_enabled: bool = False,
object_name: str | None = None,
) -> Any:
template = self._OBJECT_TEMPLATES[template_name]
label = QgsLayoutItemLabel(layout)
item_id = object_name or template["id"]
label.setId(item_id)
set_object_name = getattr(label, "setObjectName", None)
if callable(set_object_name):
set_object_name(item_id)
label.setText(text)
if html_mode:
set_mode = getattr(label, "setMode", None)
mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None)
if callable(set_mode) and mode_html is not None:
set_mode(mode_html)
if expression_enabled:
set_expr_enabled = getattr(label, "setExpressionEnabled", None)
if callable(set_expr_enabled):
try:
set_expr_enabled(True)
except Exception:
pass
format_name = text_format or template.get("format", "Format_1")
format_template = self._TEXT_FORMAT_TEMPLATES.get(format_name, self._TEXT_FORMAT_TEMPLATES["Format_1"])
resolved_font_name = font_name or format_template["font_family"]
resolved_font_size = font_size or format_template["font_size"]
resolved_font_style = font_style or format_template["font_style"]
font = QFont(resolved_font_name, resolved_font_size)
if hasattr(font, "setBold"):
font.setBold(resolved_font_style == "bold")
if hasattr(font, "setItalic"):
font.setItalic(resolved_font_style == "italic")
label.setFont(font)
set_reference_point = getattr(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)
template_width, template_height = template.get("size_mm", (0.0, 0.0))
label.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM))
label.attemptResize(
QgsLayoutSize(
width_mm if width_mm is not None else template_width,
height_mm if height_mm is not None else template_height,
MM,
)
)
set_locked = getattr(label, "setLocked", None)
if callable(set_locked):
try:
set_locked(True)
except Exception:
pass
layout.addLayoutItem(label)
return label
def _resolve_visible_map_layers(self, map_item: Any) -> list[Any]:
layers_method = getattr(map_item, "layers", None)
if callable(layers_method):
try:
resolved_layers = layers_method()
if isinstance(resolved_layers, (list, tuple)):
layers = [layer for layer in resolved_layers if layer is not None]
if layers:
return layers
except Exception:
pass
layer_tree_root_method = getattr(self.project, "layerTreeRoot", None)
if not callable(layer_tree_root_method):
return []
try:
root = layer_tree_root_method()
except Exception:
return []
find_layers = getattr(root, "findLayers", None)
if not callable(find_layers):
return []
visible_layers: list[Any] = []
try:
tree_layers = find_layers()
if not isinstance(tree_layers, (list, tuple)):
return []
for tree_layer in tree_layers:
is_visible = getattr(tree_layer, "itemVisibilityChecked", None)
if callable(is_visible) and not is_visible():
continue
layer = getattr(tree_layer, "layer", None)
resolved_layer = layer() if callable(layer) else None
if resolved_layer is not None:
visible_layers.append(resolved_layer)
except Exception:
return []
return visible_layers
def _create_legend_item(
self,
layout: Any,
template_name: str,
linked_map: Any,
x_mm: float,
y_mm: float,
*,
width_mm: float | None = None,
height_mm: float | None = None,
object_name: str | None = None,
) -> Any:
template = self._OBJECT_TEMPLATES[template_name]
legend = QgsLayoutItemLegend(layout)
item_id = object_name or template["id"]
legend.setId(item_id)
set_object_name = getattr(legend, "setObjectName", None)
if callable(set_object_name):
set_object_name(item_id)
set_linked_map = getattr(legend, "setLinkedMap", None)
if callable(set_linked_map):
try:
set_linked_map(linked_map)
except Exception:
pass
set_title = getattr(legend, "setTitle", None)
if callable(set_title):
try:
set_title("Legende")
except Exception:
pass
set_auto_update_model = getattr(legend, "setAutoUpdateModel", None)
if callable(set_auto_update_model):
try:
set_auto_update_model(False)
except Exception:
pass
set_filter_by_map = getattr(legend, "setLegendFilterByMapEnabled", None)
if callable(set_filter_by_map):
try:
set_filter_by_map(False)
except Exception:
pass
model_method = getattr(legend, "model", None)
if callable(model_method):
try:
model = model_method()
root_group_method = getattr(model, "rootGroup", None)
root_group = root_group_method() if callable(root_group_method) else None
clear = getattr(root_group, "clear", None)
if callable(clear):
clear()
add_layer = getattr(root_group, "addLayer", None)
if callable(add_layer):
for layer in self._resolve_visible_map_layers(linked_map):
add_layer(layer)
except Exception:
pass
set_reference_point = getattr(legend, "setReferencePoint", None)
upper_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "UpperLeft", None)
if callable(set_reference_point) and upper_left is not None:
set_reference_point(upper_left)
template_width, template_height = template.get("size_mm", (0.0, 0.0))
legend.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM))
legend.attemptResize(
QgsLayoutSize(
width_mm if width_mm is not None else template_width,
height_mm if height_mm is not None else template_height,
MM,
)
)
set_frame_enabled = getattr(legend, "setFrameEnabled", None)
if callable(set_frame_enabled):
try:
set_frame_enabled(True)
except Exception:
pass
self._set_item_frame_stroke_width(legend, 0.3)
set_background_enabled = getattr(legend, "setBackgroundEnabled", None)
if callable(set_background_enabled):
try:
set_background_enabled(False)
except Exception:
pass
set_locked = getattr(legend, "setLocked", None)
if callable(set_locked):
try:
set_locked(True)
except Exception:
pass
layout.addLayoutItem(legend)
refresh = getattr(legend, "refresh", None)
if callable(refresh):
try:
refresh()
except Exception:
pass
return legend
def _get_item_size_mm(self, item: Any) -> tuple[float | None, float | None]:
def _to_float(value: Any) -> float | None:
if isinstance(value, (int, float)):
return float(value)
if isinstance(value, str):
try:
return float(value)
except Exception:
return None
return None
size_with_units = getattr(item, "sizeWithUnits", None)
if callable(size_with_units):
try:
size = size_with_units()
get_width = getattr(size, "width", None)
get_height = getattr(size, "height", None)
width = _to_float(get_width()) if callable(get_width) else None
height = _to_float(get_height()) if callable(get_height) else None
if width is not None and height is not None:
return width, height
except Exception:
pass
rect = getattr(item, "rect", None)
if callable(rect):
try:
item_rect = rect()
get_width = getattr(item_rect, "width", None)
get_height = getattr(item_rect, "height", None)
width = _to_float(get_width()) if callable(get_width) else None
height = _to_float(get_height()) if callable(get_height) else None
return width, height
except Exception:
pass
return None, None
def _configure_legend_size(
self,
legend: Any,
*,
width_limit_mm: float,
column_count: int,
equal_column_width: bool,
) -> float:
set_locked = getattr(legend, "setLocked", None)
if callable(set_locked):
try:
set_locked(False)
except Exception:
pass
set_column_count = getattr(legend, "setColumnCount", None)
if callable(set_column_count):
try:
set_column_count(column_count)
except Exception:
pass
set_equal_column_width = getattr(legend, "setEqualColumnWidth", None)
if callable(set_equal_column_width):
try:
set_equal_column_width(equal_column_width)
except Exception:
pass
refresh = getattr(legend, "refresh", None)
if callable(refresh):
try:
refresh()
except Exception:
pass
adjust_box_size = getattr(legend, "adjustBoxSize", None)
if callable(adjust_box_size):
try:
adjust_box_size()
except Exception:
pass
width_mm, height_mm = self._get_item_size_mm(legend)
resolved_width = width_mm if width_mm is not None else width_limit_mm
resolved_height = height_mm if height_mm is not None else 20.0
final_width = min(resolved_width, width_limit_mm)
legend.attemptResize(QgsLayoutSize(final_width, resolved_height, MM))
if callable(refresh):
try:
refresh()
except Exception:
pass
_, updated_height_mm = self._get_item_size_mm(legend)
if callable(set_locked):
try:
set_locked(True)
except Exception:
pass
return updated_height_mm if updated_height_mm is not None else resolved_height
def _create_rectangle_box_item(
self,
layout: Any,
object_name: str,
x_mm: float,
y_mm: float,
width_mm: float,
height_mm: float,
stroke_width_mm: float = 0.3,
) -> Any:
box = QgsLayoutItemLabel(layout)
box.setId(object_name)
set_object_name = getattr(box, "setObjectName", None)
if callable(set_object_name):
set_object_name(object_name)
box.setText("")
set_reference_point = getattr(box, "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)
box.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM))
box.attemptResize(QgsLayoutSize(width_mm, height_mm, MM))
set_frame_enabled = getattr(box, "setFrameEnabled", None)
if callable(set_frame_enabled):
try:
set_frame_enabled(True)
except Exception:
pass
self._set_item_frame_stroke_width(box, stroke_width_mm)
set_background_enabled = getattr(box, "setBackgroundEnabled", None)
if callable(set_background_enabled):
try:
set_background_enabled(False)
except Exception:
pass
set_locked = getattr(box, "setLocked", None)
if callable(set_locked):
try:
set_locked(True)
except Exception:
pass
layout.addLayoutItem(box)
return box
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)
self._current_layout = layout
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
kartenfenster_untere_rechte_ecke_mm = (map_right_mm, map_bottom_mm)
print(
f"[Layout] Kartenbild-Kanten: rechts={map_right_mm:.1f} mm, unten={map_bottom_mm:.1f} mm"
)
hauptkarte = self._create_map_item(
layout,
"hauptkarte",
map_left_mm,
map_top_mm,
map_width_mm,
map_height_mm,
)
print(f"[Layout] QgsLayoutItemMap erstellt")
print("[Layout] Hauptkarte zum Layout hinzugefügt")
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:
self._set_item_frame_stroke_width(hauptkarte, 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")
self._setup_layout_objects(
kartenfenster_untere_rechte_ecke_mm,
hauptkarte,
map_top_mm,
map_height_mm,
is_atlas=False,
)
layout_manager.addLayout(layout)
print("[Layout] Layout zum LayoutManager hinzugefügt")
self._current_layout = None
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)
self._current_layout = layout
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
kartenfenster_untere_rechte_ecke_mm = (map_right_mm, map_bottom_mm)
hauptkarte = self._create_map_item(
layout,
"hauptkarte",
map_left_mm,
map_top_mm,
map_width_mm,
map_height_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:
self._set_item_frame_stroke_width(hauptkarte, 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")
self._setup_layout_objects(
kartenfenster_untere_rechte_ecke_mm,
hauptkarte,
map_top_mm,
map_height_mm,
is_atlas=True,
)
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)
print("[Layout] Layout zum LayoutManager hinzugefügt")
self._current_layout = None
open_layout_designer(layout)
print("[Layout] Layout Designer geöffnet")
return layout