403 lines
15 KiB
Python
403 lines
15 KiB
Python
"""
|
|
sn_basis/modules/print_layout_baufreigabe.py - Layout-Erstellung fuer Baufreigabe-Vorlagen.
|
|
|
|
Baufreigabe-Layouts verwenden immer feste DIN A3/A4 Seitengroessen im Querformat,
|
|
ein 120 mm breites Kartenschild (statt 180 mm) und erbenerben die Standard-Logik
|
|
fuer Hauptkarte, Quellenangabe und Legende vom PrintLayout.
|
|
|
|
Atlas-Integration erfolgt nach Layouterstellung durch externe Konfiguration
|
|
in der PrintLogic.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
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
|
|
from sn_basis.modules.print_layout import PrintLayout
|
|
|
|
|
|
MM = QgsUnitTypes.LayoutMillimeters
|
|
|
|
|
|
class PrintLayoutBaufreigabe(PrintLayout):
|
|
"""Erzeugt QGIS-Einzelblatt-Layouts mit fester DIN A3/A4 Querformat-Groesse und 80mm Kartenschild."""
|
|
|
|
_FRAME_STROKE_WIDTH_MM = 0.5
|
|
_KARTENSCHILD_WIDTH_MM = 80.0
|
|
_KARTENSCHILD_HEIGHT_MM = 58.0
|
|
_ATLAS_FRAME_MARGIN_MM = 10.0
|
|
_GRID_INTERVAL_MAP_UNITS = 500.0
|
|
|
|
# DIN-Formate: nur A3 und A4 im Querformat
|
|
_DIN_A3_LANDSCAPE = (420, 297) # Breite x Hoehe (Querformat)
|
|
_DIN_A4_LANDSCAPE = (297, 210) # Querformat
|
|
|
|
_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": (80.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": (80.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:
|
|
super().__init__(project)
|
|
|
|
def create_baufreigabe_layout(
|
|
self,
|
|
name: str,
|
|
din_format: str,
|
|
map_width_mm: float,
|
|
map_height_mm: float,
|
|
extent: Any,
|
|
plotmassstab: float,
|
|
thema: str = "",
|
|
fit_extent_to_map: bool = True,
|
|
atlas_driven: bool = True,
|
|
) -> Any:
|
|
"""Erstellt ein Baufreigabe-Layout mit fester DIN A3/A4 Querformat-Groesse.
|
|
|
|
Args:
|
|
name: Name des Layouts
|
|
din_format: "DIN A3" oder "DIN A4"
|
|
map_width_mm: Breite der Hauptkarte
|
|
map_height_mm: Hoehe der Hauptkarte
|
|
extent: QgsRectangle der Kartenausdehnung
|
|
plotmassstab: Plotmassstab (z.B. 5000 fuer 1:5000)
|
|
thema: Name des Visibility-Presets (optional)
|
|
fit_extent_to_map: Extent bei Bedarf auf das Karten-Seitenverhaeltnis erweitern
|
|
|
|
Returns:
|
|
QgsPrintLayout objektInstance (wird im Layout-Dialog geoeffnet)
|
|
|
|
Raises:
|
|
ValueError: Falls Name bereits existiert oder Format ungueltig ist
|
|
"""
|
|
# Seitenformat auswaehlen (nur A3/A4 im Querformat)
|
|
if din_format == "DIN A3":
|
|
page_width_mm, page_height_mm = self._DIN_A3_LANDSCAPE
|
|
elif din_format == "DIN A4":
|
|
page_width_mm, page_height_mm = self._DIN_A4_LANDSCAPE
|
|
else:
|
|
raise ValueError(f"Baufreigabe: Format {din_format} ist nicht unterstützt. Nur DIN A3/A4 Querformat.")
|
|
|
|
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:
|
|
set_extent = getattr(hauptkarte, "setExtent", None)
|
|
if callable(set_extent):
|
|
set_extent(fitted_extent)
|
|
else:
|
|
hauptkarte.zoomToExtent(fitted_extent)
|
|
except Exception:
|
|
pass
|
|
|
|
if isinstance(plotmassstab, (int, float)) and plotmassstab > 0:
|
|
try:
|
|
hauptkarte.setScale(plotmassstab)
|
|
except Exception:
|
|
pass
|
|
|
|
# Atlas-Steuerung nur aktivieren, wenn Atlas tatsaechlich genutzt wird.
|
|
# Andernfalls bleiben Extent/Scale unveraendert (z.B. Canvas-Ausschnitt).
|
|
if atlas_driven:
|
|
set_atlas_driven_fn = getattr(hauptkarte, "setAtlasDriven", None)
|
|
if callable(set_atlas_driven_fn):
|
|
try:
|
|
set_atlas_driven_fn(True)
|
|
except Exception:
|
|
pass
|
|
|
|
set_atlas_scaling_mode = getattr(hauptkarte, "setAtlasScalingMode", None)
|
|
atlas_mode_auto = getattr(getattr(QgsLayoutItemMap, "AtlasScalingMode", object), "Auto", None)
|
|
if callable(set_atlas_scaling_mode) and atlas_mode_auto is not None:
|
|
try:
|
|
set_atlas_scaling_mode(atlas_mode_auto)
|
|
except Exception:
|
|
pass
|
|
|
|
set_atlas_margin = getattr(hauptkarte, "setAtlasMargin", None)
|
|
if callable(set_atlas_margin):
|
|
try:
|
|
set_atlas_margin(self._calc_atlas_margin_percent(map_width_mm, map_height_mm))
|
|
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_baufreigabe(
|
|
kartenfenster_untere_rechte_ecke_mm,
|
|
hauptkarte,
|
|
map_top_mm,
|
|
map_height_mm,
|
|
)
|
|
|
|
layout_manager.addLayout(layout)
|
|
self._current_layout = None
|
|
open_layout_designer(layout)
|
|
return layout
|
|
|
|
def _setup_layout_objects_baufreigabe(
|
|
self,
|
|
kartenfenster_untere_rechte_ecke_mm: tuple,
|
|
hauptkarte: Any,
|
|
map_top_mm: float,
|
|
map_height_mm: float,
|
|
) -> None:
|
|
"""Erstellt Legende, Quellenangabe und Kartenschild fuer Baufreigabe-Layout.
|
|
|
|
Angepasst fuer 80mm Kartenschild statt 180mm.
|
|
"""
|
|
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: 80mm Breite, hoehe adaptive wie Standard
|
|
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._KARTENSCHILD_WIDTH_MM,
|
|
height_mm=legende_height_mm,
|
|
object_name="Legende",
|
|
)
|
|
|
|
legende_hoehe_mm = self._configure_legend_size(
|
|
legende,
|
|
width_limit_mm=self._KARTENSCHILD_WIDTH_MM,
|
|
column_count=1,
|
|
equal_column_width=False,
|
|
)
|
|
|
|
# Quellenangabe: 80mm Breite
|
|
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._KARTENSCHILD_WIDTH_MM,
|
|
column_count=2,
|
|
equal_column_width=True,
|
|
)
|
|
|
|
self.add_text_label(
|
|
self._QUELLENANGABE_TEXT,
|
|
quellenangabe_x,
|
|
quellenangabe_y,
|
|
width_mm=self._KARTENSCHILD_WIDTH_MM,
|
|
height_mm=20.0,
|
|
text_format="Format_1",
|
|
html_mode=True,
|
|
expression_enabled=True,
|
|
object_name="Quellenangabe",
|
|
)
|
|
|
|
# Kartenschild: 80mm Breite
|
|
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._KARTENSCHILD_WIDTH_MM,
|
|
self._KARTENSCHILD_HEIGHT_MM,
|
|
stroke_width_mm=self._FRAME_STROKE_WIDTH_MM,
|
|
)
|
|
|
|
# Kartenschild-Linien: 6 Trennlinien (Positionen gleich)
|
|
kartenschild_line_x, _ = _coords_rel_untere_rechte_ecke(10.0, 0.0)
|
|
kartenschild_line_offsets_y = (-10.0, -20.0, -30.0, -46.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._KARTENSCHILD_WIDTH_MM,
|
|
0.0,
|
|
stroke_width_mm=0.1,
|
|
)
|
|
|
|
# Textfelder im Kartenschild ohne Skalierungslogik.
|
|
# Positionen werden bewusst als feste Werte gesetzt und bei Bedarf manuell angepasst.
|
|
|
|
labels = [
|
|
("Teilnehmergemeinschaft [%@sn_bezeichnung %] [%@sn_name %]", 14.0, -48.0, 74.0, 8.0, "Format_1", "Herausgeber","Bottom"),
|
|
("[% @sn_bezeichnung %]", 14.0, -41.0, 74.0, 4.0, "Format_1", "Bezeichnung_Flurbereinigung"),
|
|
("[% @sn_name %]", 14.0, -32.0, 74.0, 5.0, "Format_2", "Name_Flurbereinigung"),
|
|
("[% @sn_kartenname %]", 14.0, -23.0, 74.0, 6.0, "Format_3", "Kartenname"),
|
|
("Ausgabedatum: [%format_date(now(), 'dd.MM.yyyy')%]", 14.0, -13.0, 74.0, 4.0, "Format_1", "Ausgabedatum"),
|
|
(
|
|
"Maßstab: 1 : [%round(map_get(item_variables('Hauptkarte'),'map_scale'),0)%]",
|
|
14.0,
|
|
-3.0,
|
|
74.0,
|
|
4.0,
|
|
"Format_1",
|
|
"Massstab",
|
|
),
|
|
|
|
]
|
|
|
|
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 _calc_atlas_margin_percent(self, map_width_mm: float, map_height_mm: float) -> float:
|
|
"""Berechnet Atlas-Margin in Prozent, sodass etwa 10 mm Rand sichtbar bleiben."""
|
|
margin_mm = self._ATLAS_FRAME_MARGIN_MM
|
|
if map_width_mm <= 2 * margin_mm or map_height_mm <= 2 * margin_mm:
|
|
return 5.0
|
|
|
|
p_width = (100.0 * margin_mm) / (map_width_mm - 2.0 * margin_mm)
|
|
p_height = (100.0 * margin_mm) / (map_height_mm - 2.0 * margin_mm)
|
|
return max(p_width, p_height)
|