680 lines
29 KiB
Python
680 lines
29 KiB
Python
"""
|
||
sn_plan41/ui/tab_b_logic.py – Fachlogik für Tab B (Druck)
|
||
"""
|
||
from __future__ import annotations
|
||
import math
|
||
from typing import Any
|
||
|
||
from sn_basis.functions.qt_wrapper import QVariant
|
||
from sn_basis.functions.variable_wrapper import set_variable, get_variable
|
||
from sn_basis.functions.qgiscore_wrapper import get_layer_extent
|
||
from sn_basis.functions.qgiscore_wrapper import (
|
||
QgsProject,
|
||
QgsVectorLayer,
|
||
QgsGeometry,
|
||
QgsFeature,
|
||
QgsField,
|
||
QgsVectorFileWriter,
|
||
)
|
||
from sn_basis.functions.sys_wrapper import get_plugin_root, join_path, file_exists
|
||
from sn_basis.modules.Pruefmanager import Pruefmanager
|
||
from sn_basis.modules.layerpruefer import Layerpruefer
|
||
from sn_plan41.ui.layout import Layout
|
||
|
||
|
||
KARTENNAME_VAR = "sn_kartenname"
|
||
PLOTMASSSTAB_VAR = "sn_plotmassstab"
|
||
VIEW_VAR = "sn_view"
|
||
ZIELGROESSE_VAR = "sn_zielgroesse"
|
||
FORMFAKTOR_VAR = "sn_formfaktor"
|
||
KARTENNAME_38 = "§38"
|
||
KARTENNAME_41 = "§41"
|
||
MASSSTAB_WIE_KARTENFENSTER = "Wie Kartenfenster"
|
||
THEMA_WIE_KARTENFENSTER = "wie kartenfenster"
|
||
|
||
KARTENNAME_BY_AUSWAHL = {
|
||
KARTENNAME_38: "Planungsübersicht §38 FlurbG",
|
||
KARTENNAME_41: "Karte zum Plan über die gemeinschaftlichen und öffentlichen Anlagen (§ 41 FlurbG)",
|
||
}
|
||
|
||
PLOTMASSSTAB_BY_AUSWAHL = {
|
||
"1:5.000": "5000",
|
||
"1:10.000": "10000",
|
||
"1:15.000": "15000",
|
||
"1:20.000": "20000",
|
||
"1:25.000": "25000",
|
||
"1:50.000": "50000",
|
||
"1:100.000": "100000",
|
||
}
|
||
|
||
# Breite x Höhe in mm (Hochformat, DIN-Standard)
|
||
DIN_GROESSEN: dict[str, tuple[int, int]] = {
|
||
"DIN A0": (841, 1189),
|
||
"DIN A1": (594, 841),
|
||
"DIN A2": (420, 594),
|
||
"DIN A3": (297, 420),
|
||
"DIN A4": (210, 297),
|
||
}
|
||
DIN_STANDARD = "DIN A0"
|
||
|
||
class TabBLogic:
|
||
"""
|
||
Kapselt die Fachlogik von Tab B.
|
||
"""
|
||
|
||
def __init__(self, pruefmanager: Pruefmanager) -> None:
|
||
self.pruefmanager = pruefmanager
|
||
|
||
@staticmethod
|
||
def _wkt_rect(x_min: float, y_min: float, x_max: float, y_max: float) -> str:
|
||
return (
|
||
f"POLYGON(({x_min} {y_min}, {x_max} {y_min}, {x_max} {y_max}, "
|
||
f"{x_min} {y_max}, {x_min} {y_min}))"
|
||
)
|
||
|
||
@staticmethod
|
||
def _set_topological_editing_enabled() -> None:
|
||
project = QgsProject.instance()
|
||
set_topological = getattr(project, "setTopologicalEditing", None)
|
||
if callable(set_topological):
|
||
try:
|
||
set_topological(True)
|
||
except Exception:
|
||
pass
|
||
|
||
@staticmethod
|
||
def _apply_atlas_style(layer: Any) -> None:
|
||
style_path = join_path(get_plugin_root(), "sn_plan41", "assets", "atlasobjekte.qml")
|
||
if not file_exists(style_path):
|
||
return
|
||
try:
|
||
ok, _ = layer.loadNamedStyle(str(style_path))
|
||
if ok:
|
||
getattr(layer, "triggerRepaint", lambda: None)()
|
||
except Exception:
|
||
pass
|
||
|
||
@staticmethod
|
||
def _find_tile_grid_for_roll_atlas(
|
||
kartenbild_w_mm: float,
|
||
kartenbild_h_mm: float,
|
||
din_dims: tuple[int, int],
|
||
respect_max_sheet_size: bool = False,
|
||
) -> tuple[int, int, float, float]:
|
||
"""Bestimmt ein Atlas-Raster für Endlosrolle, das Kacheln statt Streifen erzeugt.
|
||
|
||
Ziel: Kachel-Seitenverhältnis möglichst nah am Einzelblatt-Kartenfenster
|
||
(inkl. Rändern) bei zugleich moderater Seitenanzahl.
|
||
"""
|
||
if kartenbild_w_mm <= 0 or kartenbild_h_mm <= 0:
|
||
return 1, 1, max(1.0, kartenbild_w_mm), max(1.0, kartenbild_h_mm)
|
||
|
||
# Einzelblatt-Kartenfenster für beide Orientierungen
|
||
dim_w, dim_h = float(din_dims[0]), float(din_dims[1])
|
||
orientation_candidates: list[tuple[float, float]] = [
|
||
(dim_w - 210.0, dim_h - 20.0),
|
||
(dim_h - 210.0, dim_w - 20.0),
|
||
]
|
||
|
||
best_score = math.inf
|
||
best_result: tuple[int, int, float, float] | None = None
|
||
|
||
for target_w, target_h in orientation_candidates:
|
||
if target_w <= 0 or target_h <= 0:
|
||
continue
|
||
|
||
target_aspect = target_w / target_h
|
||
px0 = max(1, int(round(kartenbild_w_mm / target_w)))
|
||
py0 = max(1, int(round(kartenbild_h_mm / target_h)))
|
||
|
||
for pages_x in range(max(1, px0 - 3), px0 + 4):
|
||
for pages_y in range(max(1, py0 - 3), py0 + 4):
|
||
tile_w = kartenbild_w_mm / pages_x
|
||
tile_h = kartenbild_h_mm / pages_y
|
||
if tile_w <= 0 or tile_h <= 0:
|
||
continue
|
||
|
||
# Im Blatt-Modus darf die resultierende Atlasseite die
|
||
# gewählte Zielgröße (inkl. Orientierung) nicht überschreiten.
|
||
if respect_max_sheet_size and (tile_w > target_w or tile_h > target_h):
|
||
continue
|
||
|
||
tile_aspect = tile_w / tile_h
|
||
# 0 bei perfekter Übereinstimmung; symmetrisch für >1/<1
|
||
aspect_error = abs(math.log(tile_aspect / target_aspect))
|
||
|
||
# Streifen bestrafen
|
||
strip_penalty = 0.0
|
||
if tile_aspect < 0.5:
|
||
strip_penalty = abs(math.log(tile_aspect / 0.5))
|
||
elif tile_aspect > 2.0:
|
||
strip_penalty = abs(math.log(tile_aspect / 2.0))
|
||
|
||
page_count = pages_x * pages_y
|
||
|
||
# Gewichtung: zuerst Formatnähe, dann Streifenvermeidung,
|
||
# danach Seitenzahl minimieren.
|
||
score = (aspect_error * 12.0) + (strip_penalty * 6.0) + (page_count * 0.20)
|
||
|
||
if score < best_score:
|
||
best_score = score
|
||
best_result = (pages_x, pages_y, tile_w, tile_h)
|
||
|
||
if best_result is None:
|
||
if respect_max_sheet_size:
|
||
fallback_candidates: list[tuple[int, int, float, float, int]] = []
|
||
for target_w, target_h in orientation_candidates:
|
||
if target_w <= 0 or target_h <= 0:
|
||
continue
|
||
pages_x = max(1, math.ceil(kartenbild_w_mm / target_w))
|
||
pages_y = max(1, math.ceil(kartenbild_h_mm / target_h))
|
||
tile_w = kartenbild_w_mm / pages_x
|
||
tile_h = kartenbild_h_mm / pages_y
|
||
fallback_candidates.append((pages_x, pages_y, tile_w, tile_h, pages_x * pages_y))
|
||
|
||
if fallback_candidates:
|
||
fallback_candidates.sort(key=lambda entry: (entry[4], abs(math.log((entry[2] / entry[3]) if entry[3] > 0 else 1.0))))
|
||
fx, fy, fw, fh, _ = fallback_candidates[0]
|
||
return fx, fy, fw, fh
|
||
|
||
return 1, 1, kartenbild_w_mm, kartenbild_h_mm
|
||
|
||
return best_result
|
||
|
||
def _create_atlasobjekte_layer(
|
||
self,
|
||
layer: Any,
|
||
extent: Any,
|
||
pages_x: int,
|
||
pages_y: int,
|
||
seite_karte_w: float,
|
||
seite_karte_h: float,
|
||
massstab_zahl: float,
|
||
) -> Any | None:
|
||
layer_crs = layer.crs() if hasattr(layer, "crs") else None
|
||
crs_authid = layer_crs.authid() if layer_crs is not None and hasattr(layer_crs, "authid") else "EPSG:25832"
|
||
atlas_layer = QgsVectorLayer(f"Polygon?crs={crs_authid}", "Atlasobjekte", "memory")
|
||
if not atlas_layer or not atlas_layer.isValid():
|
||
return None
|
||
|
||
provider = atlas_layer.dataProvider()
|
||
provider.addAttributes([
|
||
QgsField("Seitenzahl", QVariant.Int),
|
||
])
|
||
atlas_layer.updateFields()
|
||
|
||
tile_w_m = seite_karte_w * massstab_zahl / 1000.0
|
||
tile_h_m = seite_karte_h * massstab_zahl / 1000.0
|
||
|
||
x_min = extent.xMinimum()
|
||
y_min = extent.yMinimum()
|
||
x_max = extent.xMaximum()
|
||
y_max = extent.yMaximum()
|
||
|
||
seitenzahl = 1
|
||
features = []
|
||
for row_idx in range(pages_y):
|
||
tile_y_max = y_max - row_idx * tile_h_m
|
||
tile_y_min = tile_y_max - tile_h_m
|
||
|
||
for col_idx in range(pages_x):
|
||
tile_x_min = x_min + col_idx * tile_w_m
|
||
tile_x_max = tile_x_min + tile_w_m
|
||
|
||
tile_geom = QgsGeometry.fromWkt(
|
||
self._wkt_rect(tile_x_min, tile_y_min, tile_x_max, tile_y_max)
|
||
)
|
||
|
||
feat = QgsFeature(atlas_layer.fields())
|
||
feat.setGeometry(tile_geom)
|
||
feat["Seitenzahl"] = seitenzahl
|
||
features.append(feat)
|
||
seitenzahl += 1
|
||
|
||
if not features:
|
||
return None
|
||
|
||
provider.addFeatures(features)
|
||
atlas_layer.updateExtents()
|
||
self._set_topological_editing_enabled()
|
||
|
||
verfahrens_db = get_variable("verfahrens_db", scope="project") or ""
|
||
print(f"[TabBLogic] Atlasobjekte: verfahrens_db='{verfahrens_db}'")
|
||
if not verfahrens_db:
|
||
QgsProject.instance().addMapLayer(atlas_layer)
|
||
self._apply_atlas_style(atlas_layer)
|
||
print("[TabBLogic] Atlasobjekte temporär ins Projekt geladen")
|
||
return atlas_layer
|
||
|
||
opts = QgsVectorFileWriter.SaveVectorOptions()
|
||
opts.driverName = "GPKG"
|
||
opts.fileEncoding = "UTF-8"
|
||
opts.layerName = "Atlasobjekte"
|
||
if file_exists(verfahrens_db):
|
||
opts.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer
|
||
else:
|
||
opts.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteFile
|
||
|
||
err_result = QgsVectorFileWriter.writeAsVectorFormatV3(
|
||
atlas_layer,
|
||
verfahrens_db,
|
||
QgsProject.instance().transformContext(),
|
||
opts,
|
||
)
|
||
|
||
# QGIS-Versionen liefern hier entweder nur WriterError
|
||
# oder ein Tupel (WriterError, msg, newPath, layerName).
|
||
err_code = err_result[0] if isinstance(err_result, tuple) else err_result
|
||
if err_code != QgsVectorFileWriter.NoError:
|
||
print(f"[TabBLogic] Atlasobjekte schreiben fehlgeschlagen: err={err_result}")
|
||
return None
|
||
|
||
for existing in QgsProject.instance().mapLayersByName("Atlasobjekte"):
|
||
try:
|
||
QgsProject.instance().removeMapLayer(existing.id())
|
||
except Exception:
|
||
pass
|
||
|
||
uri = f"{verfahrens_db}|layername=Atlasobjekte"
|
||
loaded_layer = QgsVectorLayer(uri, "Atlasobjekte", "ogr")
|
||
if not loaded_layer or not loaded_layer.isValid():
|
||
print(f"[TabBLogic] Atlasobjekte laden aus GPKG fehlgeschlagen: uri='{uri}'")
|
||
return None
|
||
|
||
QgsProject.instance().addMapLayer(loaded_layer)
|
||
self._apply_atlas_style(loaded_layer)
|
||
print("[TabBLogic] Atlasobjekte aus Verfahrens-DB geladen und gestylt")
|
||
return loaded_layer
|
||
|
||
def set_kartenname_for_auswahl(self, auswahl: str) -> None:
|
||
"""Setzt die Projektvariable ``sn_kartenname`` anhand der Kartennamen-Auswahl."""
|
||
kartenname = KARTENNAME_BY_AUSWAHL.get(auswahl, "")
|
||
set_variable(KARTENNAME_VAR, kartenname, scope="project")
|
||
|
||
def set_plotmassstab_for_auswahl(self, auswahl: str, aktueller_massstab: float | None = None) -> None:
|
||
"""Setzt die Projektvariable ``sn_plotmassstab`` anhand der Maßstabsauswahl."""
|
||
if auswahl == MASSSTAB_WIE_KARTENFENSTER:
|
||
if aktueller_massstab and aktueller_massstab > 0:
|
||
set_variable(PLOTMASSSTAB_VAR, str(int(round(aktueller_massstab))), scope="project")
|
||
else:
|
||
set_variable(PLOTMASSSTAB_VAR, "", scope="project")
|
||
return
|
||
|
||
value = PLOTMASSSTAB_BY_AUSWAHL.get(auswahl, "")
|
||
set_variable(PLOTMASSSTAB_VAR, value, scope="project")
|
||
|
||
def set_view_for_auswahl(self, auswahl: str) -> None:
|
||
"""Setzt ``sn_view`` auf ``aktuell`` oder den Namen des gewählten Layerthemas."""
|
||
if auswahl == THEMA_WIE_KARTENFENSTER:
|
||
set_variable(VIEW_VAR, "aktuell", scope="project")
|
||
return
|
||
|
||
set_variable(VIEW_VAR, auswahl or "", scope="project")
|
||
|
||
def set_zielgroesse_for_auswahl(self, auswahl: str) -> None:
|
||
"""Setzt ``sn_zielgroesse`` auf den gewählten DIN-Namen."""
|
||
set_variable(ZIELGROESSE_VAR, auswahl if auswahl in DIN_GROESSEN else DIN_STANDARD, scope="project")
|
||
|
||
def set_formfaktor(self, endlosrolle: bool) -> None:
|
||
"""Setzt ``sn_formfaktor`` auf ``Endlosrolle`` oder ``Blatt``."""
|
||
set_variable(FORMFAKTOR_VAR, "Endlosrolle" if endlosrolle else "Blatt", scope="project")
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────
|
||
# Pipeline: Druckvorlage_anlegen
|
||
# ─────────────────────────────────────────────────────────────────────────
|
||
|
||
def druckvorlage_anlegen(
|
||
self,
|
||
layer: object,
|
||
kartenname_auswahl: str,
|
||
massstab_auswahl: str,
|
||
zielgroesse: str,
|
||
formfaktor: bool,
|
||
) -> dict:
|
||
"""Pipeline 'Druckvorlage_anlegen'.
|
||
|
||
Prüft Parameter, berechnet Plotgröße und stellt bei Bedarf Atlas-Rückfrage.
|
||
|
||
Parameters
|
||
----------
|
||
layer:
|
||
Aktuell gewählter Verfahrensgebiet-Layer aus Tab A.
|
||
|
||
Returns
|
||
-------
|
||
dict
|
||
``ok`` (bool): Ob die Pipeline erfolgreich durchlaufen werden soll.
|
||
``switch_to_tab_a`` (bool): Ob Tab A aktiviert werden soll.
|
||
``atlas_seiten`` (int): Anzahl benötigter Seiten (1 = kein Atlas).
|
||
"""
|
||
# ─── 1. Verfahrensgebiet-Layer prüfen ─────────────────────────────
|
||
lp = Layerpruefer(layer=layer)
|
||
ergebnis = lp.pruefe()
|
||
if not ergebnis.ok:
|
||
self.pruefmanager.zeige_hinweis(
|
||
"Verfahrensgebiets-Layer angeben",
|
||
"Verfahrensgebiets-Layer angeben",
|
||
)
|
||
return {"ok": False, "switch_to_tab_a": True, "atlas_seiten": 0}
|
||
|
||
layer_id = getattr(layer, "id", lambda: "")() or ""
|
||
set_variable("sn_verfahrensgebietslayer", layer_id, scope="project")
|
||
|
||
# ─── 2. Kartenname prüfen ─────────────────────────────────────────
|
||
if kartenname_auswahl not in (KARTENNAME_38, KARTENNAME_41):
|
||
self.pruefmanager.zeige_hinweis(
|
||
"Kartennamen wählen",
|
||
"Kartennamen wählen",
|
||
)
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||
|
||
# ─── 3. Maßstab ermitteln ─────────────────────────────────────────
|
||
if massstab_auswahl == MASSSTAB_WIE_KARTENFENSTER:
|
||
massstab_str = get_variable(PLOTMASSSTAB_VAR, scope="project") or ""
|
||
try:
|
||
massstab_zahl = float(massstab_str)
|
||
except (ValueError, TypeError):
|
||
massstab_zahl = 0.0
|
||
else:
|
||
massstab_zahl = float(PLOTMASSSTAB_BY_AUSWAHL.get(massstab_auswahl, 0))
|
||
|
||
if massstab_zahl <= 0:
|
||
self.pruefmanager.zeige_hinweis(
|
||
"Maßstab fehlt",
|
||
"Kein gültiger Maßstab angegeben.",
|
||
)
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||
|
||
# ─── 4. Kartenbild berechnen ──────────────────────────────────────
|
||
# Der Layer wird als metrisch projiziert (Einheit: m) vorausgesetzt,
|
||
# wie es für deutsche Planungslagen (z.B. EPSG:25832) üblich ist.
|
||
extent = get_layer_extent(layer)
|
||
if extent is None:
|
||
self.pruefmanager.zeige_hinweis(
|
||
"Fehler",
|
||
"Layer-Ausdehnung konnte nicht ermittelt werden.",
|
||
)
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||
|
||
# Naturgröße (m) → Papiergröße (mm): mm = m * 1000 / massstab
|
||
kartenbild_w = extent.width() * 1000.0 / massstab_zahl
|
||
kartenbild_h = extent.height() * 1000.0 / massstab_zahl
|
||
|
||
# ─── 5. Plotgröße = Kartenbild + Randabstand (x+210 mm, y+20 mm) ──
|
||
plotgroesse_w = kartenbild_w + 210.0
|
||
plotgroesse_h = kartenbild_h + 20.0
|
||
|
||
# ─── 6. Zielgröße bestimmen ───────────────────────────────────────
|
||
din_dims = DIN_GROESSEN.get(zielgroesse, DIN_GROESSEN[DIN_STANDARD])
|
||
if formfaktor: # Endlosrolle: X-Richtung entspricht der Plotgröße
|
||
ziel_w, ziel_h = plotgroesse_w, float(min(din_dims))
|
||
else:
|
||
ziel_w, ziel_h = float(din_dims[0]), float(din_dims[1])
|
||
|
||
if ziel_w < DIN_GROESSEN["DIN A4"][0] or ziel_h < DIN_GROESSEN["DIN A4"][1]:
|
||
self.pruefmanager.zeige_hinweis(
|
||
"Blattgröße zu klein",
|
||
"Die Zielgröße darf nicht kleiner als DIN A4 sein.",
|
||
)
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||
|
||
# ─── 7. Passt auf ein Blatt? -> Layout erzeugen ───────────────────
|
||
print(f"[TabBLogic] plotgroesse=({plotgroesse_w:.1f}x{plotgroesse_h:.1f}), "
|
||
f"zielgroesse=({ziel_w:.1f}x{ziel_h:.1f}), passt={plotgroesse_w <= ziel_w and plotgroesse_h <= ziel_h}")
|
||
if plotgroesse_w > ziel_w or plotgroesse_h > ziel_h:
|
||
if plotgroesse_w <= ziel_h and plotgroesse_h <= ziel_w:
|
||
ziel_w, ziel_h = ziel_h, ziel_w
|
||
|
||
if plotgroesse_w <= ziel_w and plotgroesse_h <= ziel_h:
|
||
kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get(
|
||
kartenname_auswahl, "Vorlage"
|
||
)
|
||
thema = get_variable(VIEW_VAR, scope="project") or ""
|
||
print(f"[TabBLogic] frage_text aufrufen, default='{kartenname}', thema='{thema}'")
|
||
vorlage_name, bestaetigt = self.pruefmanager.frage_text(
|
||
"Neue Vorlage anlegen",
|
||
"Bezeichnung der Vorlage:",
|
||
default_text=kartenname,
|
||
)
|
||
print(f"[TabBLogic] frage_text Ergebnis: name='{vorlage_name}', bestaetigt={bestaetigt}")
|
||
if not bestaetigt:
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||
|
||
vorlage_name = (vorlage_name or "").strip() or kartenname
|
||
|
||
print(f"[TabBLogic] Rufe Layout().create_single_page_layout auf: name='{vorlage_name}', "
|
||
f"page=({ziel_w}x{ziel_h}), map=({kartenbild_w:.1f}x{kartenbild_h:.1f}), "
|
||
f"massstab={massstab_zahl}, thema='{thema}'")
|
||
try:
|
||
Layout().create_single_page_layout(
|
||
name=vorlage_name,
|
||
page_width_mm=ziel_w,
|
||
page_height_mm=ziel_h,
|
||
map_width_mm=kartenbild_w,
|
||
map_height_mm=kartenbild_h,
|
||
extent=extent,
|
||
plotmassstab=massstab_zahl,
|
||
thema=thema,
|
||
)
|
||
print("[TabBLogic] create_single_page_layout erfolgreich abgeschlossen")
|
||
except ValueError as exc:
|
||
print(f"[TabBLogic] ValueError: {exc}")
|
||
self.pruefmanager.zeige_hinweis("Vorlage anlegen", str(exc))
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||
except Exception as exc:
|
||
print(f"[TabBLogic] Exception: {exc!r}")
|
||
self.pruefmanager.zeige_hinweis("Vorlage anlegen", f"Die Vorlage konnte nicht angelegt werden: {exc}")
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||
|
||
return {"ok": True, "switch_to_tab_a": False, "atlas_seiten": 1}
|
||
|
||
# ─── 8. Atlas: Anzahl Seiten berechnen ────────────────────────────
|
||
# Nutzbarer Kartenbereich pro Atlasseite (abzüglich gleichem Randabstand)
|
||
seite_karte_w = ziel_w - 210.0
|
||
seite_karte_h = ziel_h - 20.0
|
||
|
||
if seite_karte_w <= 0 or seite_karte_h <= 0:
|
||
self.pruefmanager.zeige_hinweis(
|
||
"Blattgröße zu klein",
|
||
"Die gewählte Zielgröße ist kleiner als der Mindest-Randabstand. "
|
||
"Bitte eine größere Blattgröße wählen.",
|
||
)
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||
|
||
pages_x = math.ceil(kartenbild_w / seite_karte_w)
|
||
pages_y = math.ceil(kartenbild_h / seite_karte_h)
|
||
|
||
# Für Atlas in beiden Modi (Endlosrolle + Blatt): Seitenraster so wählen,
|
||
# dass Atlasobjekte näher am Einzelblattformat liegen und keine Streifen entstehen.
|
||
opt_pages_x, opt_pages_y, opt_tile_w_mm, opt_tile_h_mm = self._find_tile_grid_for_roll_atlas(
|
||
kartenbild_w_mm=kartenbild_w,
|
||
kartenbild_h_mm=kartenbild_h,
|
||
din_dims=din_dims,
|
||
respect_max_sheet_size=(not formfaktor),
|
||
)
|
||
|
||
# Endlosrolle: Nur die Breite darf wachsen, die Höhe muss innerhalb
|
||
# der gewählten Zielhöhe bleiben.
|
||
if formfaktor:
|
||
max_tile_h_mm = max(1.0, ziel_h - 20.0)
|
||
if opt_tile_h_mm > max_tile_h_mm:
|
||
required_pages_y = max(1, math.ceil(kartenbild_h / max_tile_h_mm))
|
||
opt_pages_y = max(opt_pages_y, required_pages_y)
|
||
opt_tile_h_mm = kartenbild_h / opt_pages_y
|
||
|
||
pages_x = max(1, opt_pages_x)
|
||
pages_y = max(1, opt_pages_y)
|
||
seite_karte_w = max(1.0, opt_tile_w_mm)
|
||
seite_karte_h = max(1.0, opt_tile_h_mm)
|
||
modus = "Endlosrolle" if formfaktor else "Blatt"
|
||
print(
|
||
f"[TabBLogic] {modus} Rasteroptimierung: pages_x={pages_x}, pages_y={pages_y}, "
|
||
f"tile_mm=({seite_karte_w:.1f}x{seite_karte_h:.1f}), "
|
||
f"tile_aspect={seite_karte_w / seite_karte_h:.3f}"
|
||
)
|
||
if formfaktor:
|
||
max_tile_h_mm = max(1.0, ziel_h - 20.0)
|
||
print(
|
||
f"[TabBLogic] Endlosrolle Höhenlimit: tile_h={seite_karte_h:.1f}mm, "
|
||
f"max_tile_h={max_tile_h_mm:.1f}mm, within_limit={seite_karte_h <= max_tile_h_mm}"
|
||
)
|
||
|
||
anzahl_seiten = pages_x * pages_y
|
||
|
||
ja = self.pruefmanager.frage_ja_nein(
|
||
"Ausdruck als Atlas anlegen?",
|
||
f"Für die ausgewählten Parameter sind {anzahl_seiten} Einzelseiten erforderlich.\n"
|
||
"Ausdruck als Atlas anlegen?",
|
||
default=True,
|
||
)
|
||
print(f"[TabBLogic] Atlas-Rückfrage: ja={ja}, geplante_seiten={anzahl_seiten}")
|
||
if not ja:
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": anzahl_seiten}
|
||
|
||
atlas_layer = self._create_atlasobjekte_layer(
|
||
layer=layer,
|
||
extent=extent,
|
||
pages_x=pages_x,
|
||
pages_y=pages_y,
|
||
seite_karte_w=seite_karte_w,
|
||
seite_karte_h=seite_karte_h,
|
||
massstab_zahl=massstab_zahl,
|
||
)
|
||
if atlas_layer is None:
|
||
self.pruefmanager.zeige_hinweis(
|
||
"Atlasobjekte",
|
||
"Atlasobjekte-Layer konnte nicht erzeugt werden.",
|
||
)
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||
print("[TabBLogic] Atlasobjekte-Layer erfolgreich erzeugt")
|
||
|
||
try:
|
||
anzahl_seiten = int(atlas_layer.featureCount())
|
||
except Exception:
|
||
pass
|
||
|
||
# Berechne die Kachelgröße aus den Atlasobjekten.
|
||
# Für die Layout-Kartengröße muss die größte Atlas-Kachel passen,
|
||
# sonst werden größere Atlasobjekte abgeschnitten.
|
||
total_w_m = 0.0
|
||
total_h_m = 0.0
|
||
min_w_m = math.inf
|
||
min_h_m = math.inf
|
||
max_w_m = 0.0
|
||
max_h_m = 0.0
|
||
feature_count = 0
|
||
get_features = getattr(atlas_layer, "getFeatures", None)
|
||
if callable(get_features):
|
||
try:
|
||
for feat in get_features():
|
||
geom = feat.geometry() if hasattr(feat, "geometry") else None
|
||
if geom is None or geom.isEmpty():
|
||
continue
|
||
bbox = geom.boundingBox() if hasattr(geom, "boundingBox") else None
|
||
if bbox is None:
|
||
continue
|
||
feat_w_m = float(bbox.width())
|
||
feat_h_m = float(bbox.height())
|
||
total_w_m += feat_w_m
|
||
total_h_m += feat_h_m
|
||
min_w_m = min(min_w_m, feat_w_m)
|
||
min_h_m = min(min_h_m, feat_h_m)
|
||
max_w_m = max(max_w_m, feat_w_m)
|
||
max_h_m = max(max_h_m, feat_h_m)
|
||
feature_count += 1
|
||
except Exception:
|
||
pass
|
||
|
||
if feature_count > 0:
|
||
avg_tile_w_m = total_w_m / feature_count
|
||
avg_tile_h_m = total_h_m / feature_count
|
||
if not math.isfinite(min_w_m):
|
||
min_w_m = avg_tile_w_m
|
||
if not math.isfinite(min_h_m):
|
||
min_h_m = avg_tile_h_m
|
||
target_tile_w_m = max_w_m
|
||
target_tile_h_m = max_h_m
|
||
else:
|
||
avg_tile_w_m = seite_karte_w * massstab_zahl / 1000.0
|
||
avg_tile_h_m = seite_karte_h * massstab_zahl / 1000.0
|
||
min_w_m = avg_tile_w_m
|
||
min_h_m = avg_tile_h_m
|
||
max_w_m = avg_tile_w_m
|
||
max_h_m = avg_tile_h_m
|
||
target_tile_w_m = avg_tile_w_m
|
||
target_tile_h_m = avg_tile_h_m
|
||
|
||
# Konvertiere Kachelgrößen zu mm
|
||
avg_tile_w_mm = avg_tile_w_m * 1000.0 / massstab_zahl
|
||
avg_tile_h_mm = avg_tile_h_m * 1000.0 / massstab_zahl
|
||
min_tile_w_mm = min_w_m * 1000.0 / massstab_zahl
|
||
min_tile_h_mm = min_h_m * 1000.0 / massstab_zahl
|
||
max_tile_w_mm = max_w_m * 1000.0 / massstab_zahl
|
||
max_tile_h_mm = max_h_m * 1000.0 / massstab_zahl
|
||
target_tile_w_mm = target_tile_w_m * 1000.0 / massstab_zahl
|
||
target_tile_h_mm = target_tile_h_m * 1000.0 / massstab_zahl
|
||
|
||
# Layout-Kartengröße = größte Kachelgröße
|
||
atlas_map_w = max(1.0, target_tile_w_mm)
|
||
atlas_map_h = max(1.0, target_tile_h_mm)
|
||
atlas_page_w = atlas_map_w + 210.0
|
||
atlas_page_h = atlas_map_h + 20.0
|
||
|
||
# Debug: Atlasobjekte Geometrien
|
||
print(
|
||
f"[TabBLogic] Atlasobjekte Geometrien (Meter): "
|
||
f"total_w={total_w_m:.1f}m, total_h={total_h_m:.1f}m, "
|
||
f"min_w={min_w_m:.1f}m, min_h={min_h_m:.1f}m, "
|
||
f"avg_w={avg_tile_w_m:.1f}m, avg_h={avg_tile_h_m:.1f}m, "
|
||
f"max_w={max_w_m:.1f}m, max_h={max_h_m:.1f}m, features={feature_count}"
|
||
)
|
||
print(
|
||
f"[TabBLogic] Atlas layout Größen: "
|
||
f"page=(x=10, y=10, w={atlas_page_w:.1f}mm, h={atlas_page_h:.1f}mm), "
|
||
f"map=(x=10, y=10, w={atlas_map_w:.1f}mm, h={atlas_map_h:.1f}mm), "
|
||
f"kachel_min_mm=({min_tile_w_mm:.1f}x{min_tile_h_mm:.1f}), "
|
||
f"kachel_avg_mm=({avg_tile_w_mm:.1f}x{avg_tile_h_mm:.1f}), "
|
||
f"kachel_max_mm=({max_tile_w_mm:.1f}x{max_tile_h_mm:.1f})"
|
||
)
|
||
|
||
kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get(
|
||
kartenname_auswahl, "Atlas"
|
||
)
|
||
thema = get_variable(VIEW_VAR, scope="project") or ""
|
||
vorlage_name, bestaetigt = self.pruefmanager.frage_text(
|
||
"Neue Atlasvorlage anlegen",
|
||
"Bezeichnung der Vorlage:",
|
||
default_text=f"{kartenname} Atlas",
|
||
)
|
||
print(f"[TabBLogic] Atlas frage_text Ergebnis: name='{vorlage_name}', bestaetigt={bestaetigt}")
|
||
if not bestaetigt:
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": anzahl_seiten}
|
||
|
||
vorlage_name = (vorlage_name or "").strip() or f"{kartenname} Atlas"
|
||
|
||
try:
|
||
print(
|
||
f"[TabBLogic] Rufe create_atlas_layout auf: name='{vorlage_name}', "
|
||
f"page=({atlas_page_w:.1f}x{atlas_page_h:.1f}), "
|
||
f"map=({atlas_map_w:.1f}x{atlas_map_h:.1f}), massstab={massstab_zahl}, thema='{thema}'"
|
||
)
|
||
Layout().create_atlas_layout(
|
||
name=vorlage_name,
|
||
page_width_mm=atlas_page_w,
|
||
page_height_mm=atlas_page_h,
|
||
map_width_mm=atlas_map_w,
|
||
map_height_mm=atlas_map_h,
|
||
extent=extent,
|
||
plotmassstab=massstab_zahl,
|
||
atlas_layer=atlas_layer,
|
||
thema=thema,
|
||
)
|
||
print("[TabBLogic] create_atlas_layout erfolgreich abgeschlossen")
|
||
except ValueError as exc:
|
||
self.pruefmanager.zeige_hinweis("Atlasvorlage anlegen", str(exc))
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||
except Exception as exc:
|
||
self.pruefmanager.zeige_hinweis("Atlasvorlage anlegen", f"Die Atlasvorlage konnte nicht angelegt werden: {exc}")
|
||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||
|
||
return {"ok": True, "switch_to_tab_a": False, "atlas_seiten": anzahl_seiten} |