Altlasfunktion eingebaut (wenn plotgröße größer als Zielformat)

This commit is contained in:
2026-03-20 22:32:30 +01:00
parent 284f2a2a03
commit a54d4fbe3c
3 changed files with 577 additions and 0 deletions

View File

@@ -3,8 +3,20 @@ 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
@@ -53,6 +65,140 @@ class TabBLogic:
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
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, "")
@@ -178,9 +324,20 @@ class TabBLogic:
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"
@@ -247,7 +404,154 @@ class TabBLogic:
"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}