1078 lines
38 KiB
Python
1078 lines
38 KiB
Python
"""
|
|
sn_basis/modules/print_layout.py - Aufbau von Einzelblatt-Drucklayouts.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import math
|
|
from typing import Any
|
|
|
|
from sn_basis.functions.qt_wrapper import QFont, Qt
|
|
from sn_basis.functions.qgiscore_wrapper import (
|
|
MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL,
|
|
MAP_GRID_ANNOTATION_DISPLAY_SHOW_X,
|
|
MAP_GRID_ANNOTATION_DISPLAY_SHOW_Y,
|
|
MAP_GRID_ANNOTATION_FORMAT_DECIMAL,
|
|
MAP_GRID_ANNOTATION_POSITION_INSIDE_FRAME,
|
|
MAP_GRID_BORDER_BOTTOM,
|
|
MAP_GRID_BORDER_LEFT,
|
|
MAP_GRID_BORDER_RIGHT,
|
|
MAP_GRID_BORDER_TOP,
|
|
MAP_GRID_FRAME_STYLE_INTERIOR_TICKS,
|
|
MAP_GRID_STYLE_FRAME_ANNOTATIONS,
|
|
MAP_GRID_UNIT_MAP_UNIT,
|
|
TEXT_BACKGROUND_SHAPE_RECTANGLE,
|
|
TEXT_BACKGROUND_SIZE_BUFFER,
|
|
TEXT_RENDER_UNIT_MILLIMETERS,
|
|
TEXT_RENDER_UNIT_POINTS,
|
|
QgsLayoutItem,
|
|
QgsLayoutItemLabel,
|
|
QgsLayoutItemLegend,
|
|
QgsLayoutItemMap,
|
|
QgsLayoutItemMapGrid,
|
|
QgsLayoutPoint,
|
|
QgsLayoutSize,
|
|
QgsPrintLayout,
|
|
QgsProject,
|
|
QgsTextBackgroundSettings,
|
|
QgsTextFormat,
|
|
QgsUnitTypes,
|
|
)
|
|
from sn_basis.functions.qgisui_wrapper import open_layout_designer
|
|
|
|
|
|
MM = QgsUnitTypes.LayoutMillimeters
|
|
|
|
|
|
class PrintLayout:
|
|
"""Erzeugt ein QGIS-Einzelblatt-Layout fuer den Druck."""
|
|
|
|
_FRAME_STROKE_WIDTH_MM = 0.5
|
|
_LEGEND_WIDTH_MM = 180.0
|
|
_GRID_INTERVAL_MAP_UNITS = 500.0
|
|
|
|
_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",
|
|
},
|
|
"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>"
|
|
)
|
|
|
|
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 _fit_extent_to_map_item(self, extent: Any, map_width_mm: float, map_height_mm: float) -> Any:
|
|
if extent is None:
|
|
return extent
|
|
if map_width_mm <= 0 or map_height_mm <= 0:
|
|
return extent
|
|
|
|
try:
|
|
extent_width = float(extent.width())
|
|
extent_height = float(extent.height())
|
|
except Exception:
|
|
return extent
|
|
|
|
if extent_width <= 0 or extent_height <= 0:
|
|
return extent
|
|
|
|
target_ratio = map_width_mm / map_height_mm
|
|
current_ratio = extent_width / extent_height
|
|
if not math.isfinite(target_ratio) or not math.isfinite(current_ratio) or target_ratio <= 0:
|
|
return extent
|
|
|
|
try:
|
|
center_x = (float(extent.xMinimum()) + float(extent.xMaximum())) / 2.0
|
|
center_y = (float(extent.yMinimum()) + float(extent.yMaximum())) / 2.0
|
|
except Exception:
|
|
return extent
|
|
|
|
fitted_width = extent_width
|
|
fitted_height = extent_height
|
|
if not math.isclose(current_ratio, target_ratio, rel_tol=1e-9, abs_tol=1e-9):
|
|
if current_ratio > target_ratio:
|
|
fitted_height = extent_width / target_ratio
|
|
else:
|
|
fitted_width = extent_height * target_ratio
|
|
|
|
safety_factor = 1.002
|
|
half_width = (fitted_width * safety_factor) / 2.0
|
|
half_height = (fitted_height * safety_factor) / 2.0
|
|
|
|
try:
|
|
qgis_core = importlib.import_module("qgis.core")
|
|
qgs_rectangle = getattr(qgis_core, "QgsRectangle", None)
|
|
except Exception:
|
|
qgs_rectangle = None
|
|
|
|
if qgs_rectangle is None:
|
|
return extent
|
|
|
|
try:
|
|
return qgs_rectangle(
|
|
center_x - half_width,
|
|
center_y - half_height,
|
|
center_x + half_width,
|
|
center_y + half_height,
|
|
)
|
|
except Exception:
|
|
return extent
|
|
|
|
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,
|
|
v_align: str = "top",
|
|
) -> Any:
|
|
if self._current_layout is None:
|
|
raise RuntimeError("Kein Layout aktiv. Bitte create_single_page_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,
|
|
v_align=v_align,
|
|
)
|
|
|
|
def _setup_layout_objects(
|
|
self,
|
|
kartenfenster_untere_rechte_ecke_mm: tuple,
|
|
hauptkarte: Any,
|
|
map_top_mm: float,
|
|
map_height_mm: float,
|
|
kartenname_auswahl: str = "",
|
|
) -> None:
|
|
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,
|
|
)
|
|
|
|
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=self._LEGEND_WIDTH_MM,
|
|
height_mm=legende_height_mm,
|
|
object_name="Legende",
|
|
)
|
|
|
|
legende_hoehe_mm = self._configure_legend_size(
|
|
legende,
|
|
width_limit_mm=self._LEGEND_WIDTH_MM,
|
|
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:
|
|
self._configure_legend_size(
|
|
legende,
|
|
width_limit_mm=self._LEGEND_WIDTH_MM,
|
|
column_count=2,
|
|
equal_column_width=True,
|
|
)
|
|
|
|
self.add_text_label(
|
|
self._QUELLENANGABE_TEXT,
|
|
quellenangabe_x,
|
|
quellenangabe_y,
|
|
width_mm=self._LEGEND_WIDTH_MM,
|
|
height_mm=20.0,
|
|
text_format="Format_1",
|
|
html_mode=True,
|
|
expression_enabled=True,
|
|
object_name="Quellenangabe",
|
|
)
|
|
|
|
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,
|
|
self._LEGEND_WIDTH_MM,
|
|
92.0,
|
|
stroke_width_mm=self._FRAME_STROKE_WIDTH_MM,
|
|
)
|
|
|
|
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,
|
|
self._LEGEND_WIDTH_MM,
|
|
0.0,
|
|
stroke_width_mm=0.1,
|
|
)
|
|
|
|
if kartenname_auswahl == "§38":
|
|
herausgeber_text = "[%@sn_landkreis_user%] [%@sn_behoerde%]"
|
|
else:
|
|
herausgeber_text = "Teilnehmergemeinschaft [%@sn_bezeichnung %] [%@sn_name %]"
|
|
|
|
labels = [
|
|
(herausgeber_text, 16.0, -84.0, 171.0, 4.0, "Format_1", "Herausgeber"),
|
|
("[% @sn_bezeichnung %]", 16.0, -75.0, 171.0, 4.0, "Format_1", "Bezeichnung_Flurbereinigung"),
|
|
("[% @sn_name %]", 16.0, -64.0, 171.0, 5.0, "Format_2", "Name_Flurbereinigung"),
|
|
("[% @sn_gemeinden %]", 16.0, -56.0, 171.0, 4.0, "Format_1", "Stadt_Gemeinde"),
|
|
("[% @sn_landkreise_proj %]", 16.0, -48.0, 171.0, 4.0, "Format_1", "Landkreis"),
|
|
("[% @sn_kartenname %]", 16.0, -32.0, 137.0, 12.0, "Format_3", "Kartenname"),
|
|
("Gefertigt: ", 16.0, -23.0, 81.0, 4.0, "Format_1", "Gefertigt"),
|
|
("Geprueft: ", 101.0, -23.0, 81.0, 4.0, "Format_1", "Geprüft"),
|
|
(
|
|
"Maßstab: 1 : [%round(map_get(item_variables('Hauptkarte'),'map_scale'),0)%]",
|
|
16.0,
|
|
-13.0,
|
|
81.0,
|
|
4.0,
|
|
"Format_1",
|
|
"Massstab",
|
|
),
|
|
("Ausgabedatum: [%format_date(now(), 'dd.MM.yyyy')%]", 101.0, -13.0, 81.0, 4.0, "Format_1", "Ausgabedatum"),
|
|
("Aktenzeichen: ", 16.0, -3.0, 171.0, 4.0, "Format_1", "Aktenzeichen"),
|
|
]
|
|
|
|
for entry in labels:
|
|
text, rx, ry, w, h, fmt, obj_name = entry[:7]
|
|
v_align = entry[7] if len(entry) > 7 else "top"
|
|
x, y = _coords_rel_untere_rechte_ecke(rx, ry)
|
|
self.add_text_label(
|
|
text,
|
|
x,
|
|
y,
|
|
width_mm=w,
|
|
height_mm=h,
|
|
text_format=fmt,
|
|
expression_enabled=True,
|
|
object_name=obj_name,
|
|
v_align=v_align,
|
|
)
|
|
|
|
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,
|
|
v_align: str = "top",
|
|
) -> 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)
|
|
|
|
align_map = {
|
|
"top": "AlignTop",
|
|
"center": "AlignVCenter",
|
|
"bottom": "AlignBottom",
|
|
}
|
|
align_attr = align_map.get(str(v_align).lower(), "AlignTop")
|
|
align_qt = getattr(Qt, align_attr, None)
|
|
if align_qt is None:
|
|
alignment_flag = getattr(Qt, "AlignmentFlag", None)
|
|
if alignment_flag is not None:
|
|
align_qt = getattr(alignment_flag, align_attr, None)
|
|
|
|
set_v_align = getattr(label, "setVAlign", None)
|
|
if callable(set_v_align) and align_qt is not None:
|
|
try:
|
|
set_v_align(align_qt)
|
|
except Exception:
|
|
pass
|
|
|
|
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(True)
|
|
except Exception:
|
|
pass
|
|
|
|
set_filter_by_map = getattr(legend, "setLegendFilterByMapEnabled", None)
|
|
if callable(set_filter_by_map):
|
|
try:
|
|
set_filter_by_map(True)
|
|
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, self._FRAME_STROKE_WIDTH_MM)
|
|
|
|
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
|
|
|
|
_, updated_height_mm = self._get_item_size_mm(legend)
|
|
if updated_height_mm is None:
|
|
updated_height_mm = 20.0
|
|
|
|
if callable(set_locked):
|
|
try:
|
|
set_locked(True)
|
|
except Exception:
|
|
pass
|
|
|
|
return updated_height_mm
|
|
|
|
def _configure_hauptkarte_grids(self, hauptkarte: Any) -> None:
|
|
grids_method = getattr(hauptkarte, "grids", None)
|
|
if not callable(grids_method):
|
|
return
|
|
|
|
try:
|
|
grid_stack = grids_method()
|
|
except Exception:
|
|
return
|
|
if grid_stack is None:
|
|
return
|
|
|
|
remove_grid = getattr(grid_stack, "removeGrid", None)
|
|
if callable(remove_grid):
|
|
for grid_id in ("SN_FrameAnnotationGrid", "SN_MarkerGrid"):
|
|
try:
|
|
remove_grid(grid_id)
|
|
except Exception:
|
|
pass
|
|
|
|
add_grid = getattr(grid_stack, "addGrid", None)
|
|
if not callable(add_grid):
|
|
return
|
|
|
|
if QgsLayoutItemMapGrid is object:
|
|
return
|
|
|
|
frame_annotation_grid = QgsLayoutItemMapGrid("SN_FrameAnnotationGrid", hauptkarte)
|
|
self._apply_grid_common_settings(frame_annotation_grid)
|
|
self._safe_grid_call(frame_annotation_grid, "setStyle", MAP_GRID_STYLE_FRAME_ANNOTATIONS)
|
|
self._safe_grid_call(frame_annotation_grid, "setFrameStyle", MAP_GRID_FRAME_STYLE_INTERIOR_TICKS)
|
|
self._safe_grid_call(frame_annotation_grid, "setAnnotationEnabled", True)
|
|
self._safe_grid_call(frame_annotation_grid, "setAnnotationFormat", MAP_GRID_ANNOTATION_FORMAT_DECIMAL)
|
|
self._safe_grid_call(frame_annotation_grid, "setAnnotationPrecision", 0)
|
|
self._safe_grid_call(
|
|
frame_annotation_grid,
|
|
"setAnnotationDisplay",
|
|
MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL,
|
|
MAP_GRID_BORDER_LEFT,
|
|
)
|
|
self._safe_grid_call(
|
|
frame_annotation_grid,
|
|
"setAnnotationDisplay",
|
|
MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL,
|
|
MAP_GRID_BORDER_TOP,
|
|
)
|
|
self._safe_grid_call(
|
|
frame_annotation_grid,
|
|
"setAnnotationDisplay",
|
|
MAP_GRID_ANNOTATION_DISPLAY_SHOW_Y,
|
|
MAP_GRID_BORDER_RIGHT,
|
|
)
|
|
self._safe_grid_call(
|
|
frame_annotation_grid,
|
|
"setAnnotationDisplay",
|
|
MAP_GRID_ANNOTATION_DISPLAY_SHOW_X,
|
|
MAP_GRID_BORDER_BOTTOM,
|
|
)
|
|
self._safe_grid_call(
|
|
frame_annotation_grid,
|
|
"setAnnotationPosition",
|
|
MAP_GRID_ANNOTATION_POSITION_INSIDE_FRAME,
|
|
MAP_GRID_BORDER_RIGHT,
|
|
)
|
|
self._safe_grid_call(
|
|
frame_annotation_grid,
|
|
"setAnnotationPosition",
|
|
MAP_GRID_ANNOTATION_POSITION_INSIDE_FRAME,
|
|
MAP_GRID_BORDER_BOTTOM,
|
|
)
|
|
self._apply_grid_annotation_text_style(frame_annotation_grid)
|
|
add_grid(frame_annotation_grid)
|
|
|
|
update_bounding_rect = getattr(hauptkarte, "updateBoundingRect", None)
|
|
if callable(update_bounding_rect):
|
|
try:
|
|
update_bounding_rect()
|
|
except Exception:
|
|
pass
|
|
|
|
refresh = getattr(hauptkarte, "refresh", None)
|
|
if callable(refresh):
|
|
try:
|
|
refresh()
|
|
except Exception:
|
|
pass
|
|
|
|
def _apply_grid_common_settings(self, grid: Any) -> None:
|
|
self._safe_grid_call(grid, "setEnabled", True)
|
|
self._safe_grid_call(grid, "setUnits", MAP_GRID_UNIT_MAP_UNIT)
|
|
self._safe_grid_call(grid, "setIntervalX", self._GRID_INTERVAL_MAP_UNITS)
|
|
self._safe_grid_call(grid, "setIntervalY", self._GRID_INTERVAL_MAP_UNITS)
|
|
|
|
def _safe_grid_call(self, grid: Any, method_name: str, *args: Any) -> None:
|
|
if any(arg is None for arg in args):
|
|
return
|
|
method = getattr(grid, method_name, None)
|
|
if not callable(method):
|
|
return
|
|
try:
|
|
method(*args)
|
|
except Exception:
|
|
pass
|
|
|
|
def _apply_grid_annotation_text_style(self, grid: Any) -> None:
|
|
if QgsTextFormat is object:
|
|
self._safe_grid_call(
|
|
grid,
|
|
"setAnnotationTextFormat",
|
|
{
|
|
"font_family": "Arial",
|
|
"font_size": 10,
|
|
"background": {
|
|
"enabled": True,
|
|
"shape": "rectangle",
|
|
"size_type": "buffer",
|
|
"buffer_mm": 1.0,
|
|
"fill_color": (255, 255, 255),
|
|
},
|
|
},
|
|
)
|
|
return
|
|
|
|
try:
|
|
text_format = QgsTextFormat()
|
|
text_format.setFont(QFont("Arial", 10))
|
|
|
|
if TEXT_RENDER_UNIT_POINTS is not None:
|
|
set_size_unit = getattr(text_format, "setSizeUnit", None)
|
|
if callable(set_size_unit):
|
|
set_size_unit(TEXT_RENDER_UNIT_POINTS)
|
|
set_size = getattr(text_format, "setSize", None)
|
|
if callable(set_size):
|
|
set_size(10)
|
|
|
|
if QgsTextBackgroundSettings is not object:
|
|
try:
|
|
qt_core = importlib.import_module("qgis.PyQt.QtCore")
|
|
qt_gui = importlib.import_module("qgis.PyQt.QtGui")
|
|
QSizeF = getattr(qt_core, "QSizeF", None)
|
|
QPointF = getattr(qt_core, "QPointF", None)
|
|
QColor = getattr(qt_gui, "QColor", None)
|
|
except Exception:
|
|
QSizeF = None
|
|
QPointF = None
|
|
QColor = None
|
|
|
|
background = QgsTextBackgroundSettings()
|
|
set_enabled = getattr(background, "setEnabled", None)
|
|
if callable(set_enabled):
|
|
set_enabled(True)
|
|
|
|
set_type = getattr(background, "setType", None)
|
|
if callable(set_type) and TEXT_BACKGROUND_SHAPE_RECTANGLE is not None:
|
|
set_type(TEXT_BACKGROUND_SHAPE_RECTANGLE)
|
|
|
|
set_size_type = getattr(background, "setSizeType", None)
|
|
if callable(set_size_type) and TEXT_BACKGROUND_SIZE_BUFFER is not None:
|
|
set_size_type(TEXT_BACKGROUND_SIZE_BUFFER)
|
|
|
|
#steuert die Größe des Hintergrunds, wenn die Größe auf "Puffer" eingestellt ist
|
|
set_size = getattr(background, "setSize", None)
|
|
if callable(set_size) and QSizeF is not None:
|
|
set_size(QSizeF(0.0, 0.0))
|
|
|
|
set_size_unit = getattr(background, "setSizeUnit", None)
|
|
if callable(set_size_unit) and TEXT_RENDER_UNIT_MILLIMETERS is not None:
|
|
set_size_unit(TEXT_RENDER_UNIT_MILLIMETERS)
|
|
|
|
set_offset = getattr(background, "setOffset", None)
|
|
if callable(set_offset):
|
|
try:
|
|
if QPointF is not None:
|
|
set_offset(QPointF(0.0, -1.0))
|
|
elif QSizeF is not None:
|
|
set_offset(QSizeF(0.0, -1.0))
|
|
else:
|
|
set_offset((0.0, -1.0))
|
|
except Exception:
|
|
set_offset_x = getattr(background, "setOffsetX", None)
|
|
set_offset_y = getattr(background, "setOffsetY", None)
|
|
if callable(set_offset_x) and callable(set_offset_y):
|
|
try:
|
|
set_offset_x(0.0)
|
|
set_offset_y(-1.0)
|
|
except Exception:
|
|
pass
|
|
|
|
set_offset_unit = getattr(background, "setOffsetUnit", None)
|
|
if callable(set_offset_unit) and TEXT_RENDER_UNIT_MILLIMETERS is not None:
|
|
set_offset_unit(TEXT_RENDER_UNIT_MILLIMETERS)
|
|
|
|
set_fill_color = getattr(background, "setFillColor", None)
|
|
if callable(set_fill_color) and QColor is not None:
|
|
set_fill_color(QColor(255, 255, 255))
|
|
|
|
set_background = getattr(text_format, "setBackground", None)
|
|
if callable(set_background):
|
|
set_background(background)
|
|
|
|
self._safe_grid_call(grid, "setAnnotationTextFormat", text_format)
|
|
except Exception:
|
|
pass
|
|
|
|
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,
|
|
kartenname_auswahl: str = "",
|
|
thema: str = "",
|
|
fit_extent_to_map: bool = True,
|
|
) -> Any:
|
|
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))
|
|
|
|
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,
|
|
)
|
|
|
|
fitted_extent = extent
|
|
if fit_extent_to_map:
|
|
fitted_extent = self._fit_extent_to_map_item(extent, map_width_mm, map_height_mm)
|
|
if fitted_extent is not None and hasattr(fitted_extent, "isNull") and callable(fitted_extent.isNull) and not fitted_extent.isNull():
|
|
try:
|
|
hauptkarte.setExtent(fitted_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, self._FRAME_STROKE_WIDTH_MM)
|
|
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_keep_layer_set = getattr(hauptkarte, "setKeepLayerSet", None)
|
|
if callable(set_keep_layer_set):
|
|
try:
|
|
set_keep_layer_set(True)
|
|
except Exception:
|
|
pass
|
|
|
|
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
|
|
)
|
|
try:
|
|
set_refresh_strategy(refresh_cache)
|
|
except Exception:
|
|
pass
|
|
|
|
self._configure_hauptkarte_grids(hauptkarte)
|
|
|
|
self._setup_layout_objects(
|
|
kartenfenster_untere_rechte_ecke_mm,
|
|
hauptkarte,
|
|
map_top_mm,
|
|
map_height_mm,
|
|
kartenname_auswahl,
|
|
)
|
|
|
|
layout_manager.addLayout(layout)
|
|
self._current_layout = None
|
|
open_layout_designer(layout)
|
|
return layout
|