""" 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 import math 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 120mm Kartenschild.""" _FRAME_STROKE_WIDTH_MM = 0.5 _KARTENSCHILD_WIDTH_MM = 120.0 # Unterschied zu StandardLayout: 120mm statt 180mm _KARTENSCHILD_HEIGHT_MM = 92.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": (120.0, 100.0), # Breite angepasst auf 120mm Kartenschild "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": (120.0, 60.0), # Breite angepasst auf 120mm Kartenschild }, } _QUELLENANGABE_TEXT = ( "Quelle Geobasisdaten: GeoSN, " "dl-de/by-2-0
" "Quelle Fachdaten: Darstellung auf der Grundlage von Daten und mit Erlaubnis des " "Saechsischen Landesamtes fuer Umwelt, Landwirtschaft und Geologie
" "Basemap:" "© GeoBasis-DE / BKG ([%year($now)%]) " "CC BY 4.0 " "mit teilweise angepasster Signatur
" ) 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, ) -> 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: Ob das Extent an die Kartengroe angepasst wird 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 unterstuetzt. 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: 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_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 120mm Kartenschild statt 180mm, mit proportional skalierten Textfeldern. """ 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: 120mm Breite (angepasst), 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: 120mm 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: 120mm Breite (angepasst) 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, -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._KARTENSCHILD_WIDTH_MM, 0.0, stroke_width_mm=0.1, ) # Textfelder im Kartenschild: proportional skaliert auf 120mm # Skalierungsfaktor: 120/180 = 0.667 fuer horizontale Abstaende/Breiten scale_x = self._KARTENSCHILD_WIDTH_MM / 180.0 base_x = 16.0 * scale_x # ~10.7mm statt 16mm label_width_scaled = 101.0 * scale_x # ~67.3mm statt 101mm labels = [ ("[% @sn_amt %]", base_x, -84.0, label_width_scaled, 4.0, "Format_1", "Herausgeber"), ("[% @sn_bezeichnung %]", base_x, -75.0, label_width_scaled, 4.0, "Format_1", "Bezeichnung_Flurbereinigung"), ("[% @sn_name %]", base_x, -64.0, label_width_scaled, 5.0, "Format_2", "Name_Flurbereinigung"), ("[% @sn_gemeinden %]", base_x, -56.0, label_width_scaled, 4.0, "Format_1", "Stadt_Gemeinde"), ("[% @sn_landkreise_proj %]", base_x, -48.0, label_width_scaled, 4.0, "Format_1", "Landkreis"), ("[% @sn_kartenname %]", base_x, -32.0, label_width_scaled - 30, 12.0, "Format_3", "Kartenname"), ("Gefertigt: ", base_x, -23.0, 40.0 * scale_x, 4.0, "Format_1", "Gefertigt"), ("Geprueft: ", base_x + 40.0 * scale_x + 5.0, -23.0, 40.0 * scale_x, 4.0, "Format_1", "Geprueft"), ( "Massstab: 1 : [%round(map_get(item_variables('Hauptkarte'),'map_scale'),0)%]", base_x, -13.0, 40.0 * scale_x, 4.0, "Format_1", "Massstab", ), ("Ausgabedatum: [%format_date(now(), 'dd.MM.yyyy')%]", base_x + 40.0 * scale_x + 5.0, -13.0, 40.0 * scale_x, 4.0, "Format_1", "Ausgabedatum"), ("Aktenzeichen: ", base_x, -3.0, label_width_scaled, 4.0, "Format_1", "Aktenzeichen"), ] for text, rx, ry, w, h, fmt, obj_name in labels: 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, )