Landkreis-Auswahldialog bei fehlenden Projekteigenschaften, Style-Fix "Nur laden"
- Ist weder Verfahrensnummer noch Landkreis in den Projekteigenschaften gesetzt, wird jetzt per Dialog ein Landkreis abgefragt (inkl. "Alle Landkreise") statt direkt abzubrechen. - Verfahrensgebiet-spezifische ALKIS-Dialoge aus sn_basis hierher verschoben: neue Datei functions/verfahrensgebiet_dialoge.py (ask_bror_kombinationen, ask_landkreis_objekte, ask_landkreis_auswahl). - Bugfix: BROR-Layer bekommt seinen Style (BauRaumOderBodenordnungsrecht.qml) jetzt direkt beim Laden in verfahrensgebiet_alkis_komplett(), unabhängig vom weiteren Ablauf. working_tab.py überspringt bei "Nur Objekte laden" jetzt den gesamten VG-Nachbehandlungsblock (Style/Extent/Haken/Combobox/ Erfolgsmeldung), da dort kein echter VG-Layer entsteht. Detailprüfung/ Verifikation im echten QGIS (nicht nur per Unittest) steht noch aus. - Bugfix: Erfolgsmeldung "Layer wurde ... geladen (inkl. Flurstücksverschneidung)" erscheint nicht mehr bei "Nur Objekte laden". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
148d4dfa15
commit
5816b2af22
@@ -2,6 +2,11 @@
|
|||||||
- Fortschrittsanzeige beim Laden aus ALKIS in zwei Dialoge aufgeteilt (Suche / eigentlicher Ladevorgang), inkl. eigenem Suchschritt
|
- Fortschrittsanzeige beim Laden aus ALKIS in zwei Dialoge aufgeteilt (Suche / eigentlicher Ladevorgang), inkl. eigenem Suchschritt
|
||||||
- Mehrfachauswahl bei mehreren BauRaumOderBodenordnungsrecht-Treffern zur Verfahrensnummer (gemeinsames Laden/Verschmelzen mehrerer Objekte)
|
- Mehrfachauswahl bei mehreren BauRaumOderBodenordnungsrecht-Treffern zur Verfahrensnummer (gemeinsames Laden/Verschmelzen mehrerer Objekte)
|
||||||
- Neu: Laden aller/mehrerer Objekte eines ganzen Landkreises als Fallback, wenn keine Verfahrensnummer gesetzt ist (Gruppierung je Verfahren, optional "Nur laden" ohne weiteren Ablauf)
|
- Neu: Laden aller/mehrerer Objekte eines ganzen Landkreises als Fallback, wenn keine Verfahrensnummer gesetzt ist (Gruppierung je Verfahren, optional "Nur laden" ohne weiteren Ablauf)
|
||||||
|
- Neu: Ist weder Verfahrensnummer noch Landkreis in den Projekteigenschaften gesetzt, fragt ein Dialog den Landkreis ab (inkl. Option "Alle Landkreise"), statt direkt abzubrechen
|
||||||
|
- Landkreisweiter Objekt-Auswahldialog überarbeitet: Tabelle mit sortierbaren Spalten "Nummer"/"Name" (Standard: aufsteigend nach Nummer), Suchfeld, "Alle auswählen" jetzt korrekt (vorheriger Qt-Bug bei programmatischer Mehrfachauswahl behoben)
|
||||||
|
- Bugfix: BROR-Layer bekommt seinen ALKIS-Style jetzt direkt beim Laden (auch bei "Nur Objekte laden") statt später fälschlich den Verfahrensgebiet-Style zu erhalten – **Detailprüfung/Verifikation im echten QGIS-Test steht noch aus**
|
||||||
|
- Bugfix: Erfolgsmeldung "Layer wurde ... geladen (inkl. Flurstücksverschneidung)" erscheint nicht mehr fälschlich bei "Nur Objekte laden" (da dort keine Verschneidung stattfindet)
|
||||||
|
- Verfahrensgebiet-spezifische ALKIS-Dialoge aus sn_basis nach sn_verfahrensgebiet verschoben (functions/verfahrensgebiet_dialoge.py)
|
||||||
---
|
---
|
||||||
Version 26.7.1-unstable:
|
Version 26.7.1-unstable:
|
||||||
- Verfahrensgebiet als shape laden, inkl. Abgleich mit BROBOR-Objekt
|
- Verfahrensgebiet als shape laden, inkl. Abgleich mit BROBOR-Objekt
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ from sn_basis.functions import (
|
|||||||
from sn_basis.functions import QMessageBox
|
from sn_basis.functions import QMessageBox
|
||||||
from sn_basis.functions.qt_wrapper import QVariant
|
from sn_basis.functions.qt_wrapper import QVariant
|
||||||
from sn_basis.functions.variable_wrapper import get_variable
|
from sn_basis.functions.variable_wrapper import get_variable
|
||||||
from sn_basis.functions.dialog_wrapper import ask_bror_kombinationen, ask_landkreis_objekte
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_dialoge import ask_bror_kombinationen, ask_landkreis_objekte
|
||||||
|
from sn_basis.functions.landkreise import ALLE_LANDKREISE
|
||||||
|
|
||||||
alkis_sf_url = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs"
|
alkis_sf_url = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs"
|
||||||
typename = "adv:AX_BauRaumOderBodenordnungsrecht"
|
typename = "adv:AX_BauRaumOderBodenordnungsrecht"
|
||||||
@@ -219,10 +220,14 @@ def _suche_landkreis_kombinationen(tab_widget, landkreis_kennzahl: str, skip_exi
|
|||||||
if status == LoadStatus.KEEP:
|
if status == LoadStatus.KEEP:
|
||||||
return existing, status, {}, None
|
return existing, status, {}, None
|
||||||
|
|
||||||
|
# "Alle Landkreise" (Sentinel) -> kein Kennzahl-Präfix, LIKE '%' matcht alles.
|
||||||
|
praefix = "" if landkreis_kennzahl == ALLE_LANDKREISE else landkreis_kennzahl
|
||||||
|
landkreis_anzeige = "allen Landkreisen" if landkreis_kennzahl == ALLE_LANDKREISE else f"Landkreis {landkreis_kennzahl}"
|
||||||
|
|
||||||
wfs_uri_find = (
|
wfs_uri_find = (
|
||||||
f"url='{alkis_sf_url}' typename='{typename}' srsname='EPSG:25833' "
|
f"url='{alkis_sf_url}' typename='{typename}' srsname='EPSG:25833' "
|
||||||
f"sql=SELECT * FROM AX_BauRaumOderBodenordnungsrecht "
|
f"sql=SELECT * FROM AX_BauRaumOderBodenordnungsrecht "
|
||||||
f"WHERE bezeichnung LIKE '{landkreis_kennzahl}%'"
|
f"WHERE bezeichnung LIKE '{praefix}%'"
|
||||||
)
|
)
|
||||||
temp_layer = QgsVectorLayer(wfs_uri_find, "TempVerfahrensgebiet", "WFS")
|
temp_layer = QgsVectorLayer(wfs_uri_find, "TempVerfahrensgebiet", "WFS")
|
||||||
|
|
||||||
@@ -233,7 +238,7 @@ def _suche_landkreis_kombinationen(tab_widget, landkreis_kennzahl: str, skip_exi
|
|||||||
features = list(temp_layer.getFeatures())
|
features = list(temp_layer.getFeatures())
|
||||||
if not features:
|
if not features:
|
||||||
QMessageBox.critical(tab_widget, "Fehler",
|
QMessageBox.critical(tab_widget, "Fehler",
|
||||||
f"Für Landkreis {landkreis_kennzahl} wurden keine Objekte im WFS gefunden.")
|
f"Für {landkreis_anzeige} wurden keine Objekte im WFS gefunden.")
|
||||||
return None, LoadStatus.NONE, {}, None
|
return None, LoadStatus.NONE, {}, None
|
||||||
|
|
||||||
kombinationen: dict[tuple[str, str], list] = {}
|
kombinationen: dict[tuple[str, str], list] = {}
|
||||||
@@ -255,7 +260,7 @@ def _suche_landkreis_kombinationen(tab_widget, landkreis_kennzahl: str, skip_exi
|
|||||||
def _baue_layer_aus_landkreis_auswahl(tab_widget, kombinationen: dict, crs: str, status: "LoadStatus"):
|
def _baue_layer_aus_landkreis_auswahl(tab_widget, kombinationen: dict, crs: str, status: "LoadStatus"):
|
||||||
"""Auswahl + Bau für den Landkreis-Fallback.
|
"""Auswahl + Bau für den Landkreis-Fallback.
|
||||||
|
|
||||||
Zeigt :func:`~sn_basis.functions.dialog_wrapper.ask_landkreis_objekte`
|
Zeigt :func:`~sn_verfahrensgebiet.functions.verfahrensgebiet_dialoge.ask_landkreis_objekte`
|
||||||
(Mehrfachauswahl inkl. "Alle auswählen" und "Nur laden"-Checkbox). Die
|
(Mehrfachauswahl inkl. "Alle auswählen" und "Nur laden"-Checkbox). Die
|
||||||
ausgewählten Kombinationen werden anhand der ersten 5 Zeichen von
|
ausgewählten Kombinationen werden anhand der ersten 5 Zeichen von
|
||||||
``bezeichnung`` (= die eigentliche VKZ) gruppiert; je Gruppe wird EIN
|
``bezeichnung`` (= die eigentliche VKZ) gruppiert; je Gruppe wird EIN
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ from sn_basis.functions.dialog_wrapper import (
|
|||||||
show_info_dialog,
|
show_info_dialog,
|
||||||
ask_detailpruefung_oder_vg_laden,
|
ask_detailpruefung_oder_vg_laden,
|
||||||
)
|
)
|
||||||
|
from sn_basis.functions.landkreise import LANDKREISE, ALLE_LANDKREISE
|
||||||
|
from sn_basis.functions.ly_style_wrapper import apply_style
|
||||||
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_dialoge import ask_landkreis_auswahl
|
||||||
|
|
||||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import (
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import (
|
||||||
_suche_verfahrensgebiet_features,
|
_suche_verfahrensgebiet_features,
|
||||||
@@ -79,6 +82,8 @@ from sn_verfahrensgebiet.functions.knickpunkt_pruefung import (
|
|||||||
FELD_VKZ = "verfahrensnummer"
|
FELD_VKZ = "verfahrensnummer"
|
||||||
#: Anzeigename des BROR-Layers im Projekt
|
#: Anzeigename des BROR-Layers im Projekt
|
||||||
BROR_LAYER_NAME = "BauRaumOderBodenordnungsrecht (ALKIS)"
|
BROR_LAYER_NAME = "BauRaumOderBodenordnungsrecht (ALKIS)"
|
||||||
|
#: QML-Stildatei für den BROR-Layer (sn_verfahrensgebiet/styles/)
|
||||||
|
BROR_STYLE_QML = "BauRaumOderBodenordnungsrecht.qml"
|
||||||
#: Projektvariable für die Flächentoleranz (in m²); Default 0
|
#: Projektvariable für die Flächentoleranz (in m²); Default 0
|
||||||
FLAECHEN_TOLERANZ_VARIABLE = "flaechen_toleranz_m2"
|
FLAECHEN_TOLERANZ_VARIABLE = "flaechen_toleranz_m2"
|
||||||
#: Minimale implizite Flächentoleranz – Abweichungen unter diesem Wert
|
#: Minimale implizite Flächentoleranz – Abweichungen unter diesem Wert
|
||||||
@@ -309,13 +314,17 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
landkreis_kennzahl = get_variable("landkreis_kennzahl")
|
landkreis_kennzahl = get_variable("landkreis_kennzahl")
|
||||||
|
|
||||||
if not verfahrensnummer and not landkreis_kennzahl:
|
if not verfahrensnummer and not landkreis_kennzahl:
|
||||||
QMessageBox.critical(
|
# Keine Projekteigenschaften vorhanden – Landkreis per Dialog abfragen,
|
||||||
tab_widget,
|
# statt direkt abzubrechen.
|
||||||
"Fehler",
|
landkreis_kennzahl = ask_landkreis_auswahl(tab_widget, LANDKREISE)
|
||||||
"Weder eine Verfahrensnummer noch ein Landkreis ist in den "
|
if not landkreis_kennzahl:
|
||||||
"Projekteigenschaften eingetragen!",
|
QMessageBox.critical(
|
||||||
)
|
tab_widget,
|
||||||
return None, None, None, LoadStatus.NONE
|
"Fehler",
|
||||||
|
"Weder eine Verfahrensnummer noch ein Landkreis ist in den "
|
||||||
|
"Projekteigenschaften eingetragen!",
|
||||||
|
)
|
||||||
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
landkreis_modus = not verfahrensnummer
|
landkreis_modus = not verfahrensnummer
|
||||||
|
|
||||||
@@ -323,7 +332,10 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
# Dialog 1: Verfahrensgebiet(e) in ALKIS ermitteln/suchen
|
# Dialog 1: Verfahrensgebiet(e) in ALKIS ermitteln/suchen
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
if landkreis_modus:
|
if landkreis_modus:
|
||||||
such_label = f"Objekte in Landkreis {landkreis_kennzahl} ermitteln/suchen …"
|
if landkreis_kennzahl == ALLE_LANDKREISE:
|
||||||
|
such_label = "Objekte in allen Landkreisen ermitteln/suchen …"
|
||||||
|
else:
|
||||||
|
such_label = f"Objekte in Landkreis {landkreis_kennzahl} ermitteln/suchen …"
|
||||||
else:
|
else:
|
||||||
such_label = "Verfahrensgebiet(e) in ALKIS ermitteln/suchen …"
|
such_label = "Verfahrensgebiet(e) in ALKIS ermitteln/suchen …"
|
||||||
|
|
||||||
@@ -378,6 +390,9 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
if vg_status in (LoadStatus.FIRST, LoadStatus.RELOAD):
|
if vg_status in (LoadStatus.FIRST, LoadStatus.RELOAD):
|
||||||
project.addMapLayer(alkis_vg_layer)
|
project.addMapLayer(alkis_vg_layer)
|
||||||
bror_was_added_by_us = True
|
bror_was_added_by_us = True
|
||||||
|
# Style direkt nach dem Laden anwenden – unabhängig vom weiteren
|
||||||
|
# Ablauf, damit auch "Nur Objekte laden" den Style erhält.
|
||||||
|
apply_style(alkis_vg_layer, BROR_STYLE_QML)
|
||||||
|
|
||||||
if nur_laden:
|
if nur_laden:
|
||||||
# Nutzer hat "Nur laden" angehakt – kein Dialog 2, kein weiterer
|
# Nutzer hat "Nur laden" angehakt – kein Dialog 2, kein weiterer
|
||||||
|
|||||||
@@ -0,0 +1,299 @@
|
|||||||
|
"""sn_verfahrensgebiet/functions/verfahrensgebiet_dialoge.py – Dialoge zum
|
||||||
|
Laden eines Verfahrensgebiets aus ALKIS (Qt5/6/Mock-kompatibel).
|
||||||
|
"""
|
||||||
|
from typing import Any, Optional
|
||||||
|
from sn_basis.functions.qt_wrapper import (
|
||||||
|
QT_VERSION, exec_dialog, QInputDialog, QLineEdit,
|
||||||
|
QDialog, QDialogButtonBox, QListWidget, QListWidgetItem, QAbstractItemView,
|
||||||
|
QTableWidget, QTableWidgetItem, QTableWidgetSelectionRange,
|
||||||
|
QIcon, QVBoxLayout, QHBoxLayout, QLabel, QCheckBox,
|
||||||
|
DialogAccepted, ExtendedSelection, SelectRows, NoEditTriggers, AscendingOrder,
|
||||||
|
)
|
||||||
|
from sn_basis.functions.landkreise import ALLE_LANDKREISE
|
||||||
|
import os
|
||||||
|
|
||||||
|
# .../python/plugins (drei Ebenen über dieser Datei)
|
||||||
|
_PLUGINS_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
_WAPPEN_ICON = os.path.join(_PLUGINS_DIR, "sn_basis", "favicon.ico")
|
||||||
|
|
||||||
|
|
||||||
|
def ask_bror_kombinationen(
|
||||||
|
parent: Any,
|
||||||
|
kombinationen: list[tuple[str, str]],
|
||||||
|
) -> list[tuple[str, str]]:
|
||||||
|
"""Zeigt Dialog zur Auswahl einer oder mehrerer BROR-Kombinationen
|
||||||
|
(adv_name + bezeichnung).
|
||||||
|
|
||||||
|
Wird aufgerufen, wenn der WFS mehrere verschiedene Kombinationen aus
|
||||||
|
``adv_name`` und ``bezeichnung`` zurückliefert. Es können ein oder
|
||||||
|
mehrere Objekte gemeinsam ausgewählt und geladen (verschmolzen) werden.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
parent :
|
||||||
|
Eltern-Widget oder None.
|
||||||
|
kombinationen :
|
||||||
|
Sortierte Liste von ``(adv_name, bezeichnung)``-Tupeln.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[tuple[str, str]]
|
||||||
|
Die gewählten ``(adv_name, bezeichnung)``-Tupel. Leere Liste bei
|
||||||
|
Abbruch oder wenn nichts ausgewählt wurde.
|
||||||
|
"""
|
||||||
|
if not kombinationen:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if QT_VERSION == 0: # Mock-Modus
|
||||||
|
print(f"Mock-Modus: ask_bror_kombinationen → {kombinationen[:1]}")
|
||||||
|
return kombinationen[:1]
|
||||||
|
|
||||||
|
dialog = QDialog(parent)
|
||||||
|
dialog.setWindowTitle("Verfahrensgebiet aus ALKIS laden")
|
||||||
|
if os.path.exists(_WAPPEN_ICON):
|
||||||
|
dialog.setWindowIcon(QIcon(_WAPPEN_ICON))
|
||||||
|
layout = QVBoxLayout(dialog)
|
||||||
|
|
||||||
|
hinweis = QLabel(
|
||||||
|
f"Es wurden {len(kombinationen)} Objekte gefunden.\n\n"
|
||||||
|
"Bitte wählen Sie eines oder mehrere Objekte aus:"
|
||||||
|
)
|
||||||
|
hinweis.setWordWrap(True)
|
||||||
|
layout.addWidget(hinweis)
|
||||||
|
|
||||||
|
list_widget = QListWidget()
|
||||||
|
list_widget.setSelectionMode(ExtendedSelection)
|
||||||
|
for adv, bez in kombinationen:
|
||||||
|
item = QListWidgetItem(f"{adv} | {bez}")
|
||||||
|
list_widget.addItem(item)
|
||||||
|
list_widget.item(0).setSelected(True)
|
||||||
|
layout.addWidget(list_widget, 1) # Stretch-Faktor: wächst mit Fenstergröße
|
||||||
|
|
||||||
|
mehrfachauswahl_hinweis = QLabel("<small>Mehrfachauswahl mit Strg + Umschalt</small>")
|
||||||
|
layout.addWidget(mehrfachauswahl_hinweis)
|
||||||
|
|
||||||
|
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||||
|
buttons.accepted.connect(dialog.accept)
|
||||||
|
buttons.rejected.connect(dialog.reject)
|
||||||
|
layout.addWidget(buttons)
|
||||||
|
|
||||||
|
dialog.adjustSize()
|
||||||
|
breite = dialog.sizeHint().width()
|
||||||
|
|
||||||
|
# Liste auf max. 5 sichtbare Zeilen begrenzen, Rest per Scrollbar
|
||||||
|
sichtbare_zeilen = min(len(kombinationen), 5)
|
||||||
|
zeilenhoehe = list_widget.sizeHintForRow(0) if list_widget.count() else 20
|
||||||
|
# + Puffer für eine ggf. eingeblendete horizontale Scrollbar (bei langen
|
||||||
|
# Einträgen), damit diese nicht die letzte Zeile abschneidet.
|
||||||
|
scrollbar_puffer = list_widget.horizontalScrollBar().sizeHint().height()
|
||||||
|
list_hoehe = zeilenhoehe * sichtbare_zeilen + 2 * list_widget.frameWidth() + 4 + scrollbar_puffer
|
||||||
|
list_widget.setMinimumHeight(list_hoehe)
|
||||||
|
|
||||||
|
dialog.resize(breite, dialog.sizeHint().height())
|
||||||
|
|
||||||
|
if exec_dialog(dialog) != DialogAccepted:
|
||||||
|
return []
|
||||||
|
|
||||||
|
ausgewaehlte_indizes = sorted(list_widget.row(item) for item in list_widget.selectedItems())
|
||||||
|
return [kombinationen[i] for i in ausgewaehlte_indizes]
|
||||||
|
|
||||||
|
|
||||||
|
def ask_landkreis_objekte(
|
||||||
|
parent: Any,
|
||||||
|
kombinationen: list[tuple[str, str]],
|
||||||
|
) -> tuple[list[tuple[str, str]], bool]:
|
||||||
|
"""Zeigt Dialog zur Auswahl eines, mehrerer oder aller BROR-Objekte eines
|
||||||
|
Landkreises (Fallback, wenn keine Verfahrensnummer gesetzt ist).
|
||||||
|
|
||||||
|
Wie :func:`ask_bror_kombinationen`, zusätzlich mit einer "Alle
|
||||||
|
auswählen"-Checkbox (da hier potenziell deutlich mehr Treffer als bei der
|
||||||
|
VKZ-Suche auftreten) und einer "Nur laden"-Checkbox, mit der gesteuert
|
||||||
|
wird, ob nach dem Laden der weitere Workflow (Gemarkung/Flurstücke/
|
||||||
|
Flächenvergleich) fortgesetzt werden soll.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
parent :
|
||||||
|
Eltern-Widget oder None.
|
||||||
|
kombinationen :
|
||||||
|
Sortierte Liste von ``(adv_name, bezeichnung)``-Tupeln.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
tuple[list[tuple[str, str]], bool]
|
||||||
|
``(ausgewaehlte_kombinationen, nur_laden)``. ``([], False)`` bei
|
||||||
|
Abbruch oder wenn nichts ausgewählt wurde.
|
||||||
|
"""
|
||||||
|
if not kombinationen:
|
||||||
|
return [], False
|
||||||
|
|
||||||
|
if QT_VERSION == 0: # Mock-Modus
|
||||||
|
print(f"Mock-Modus: ask_landkreis_objekte → {kombinationen[:1]}")
|
||||||
|
return kombinationen[:1], False
|
||||||
|
|
||||||
|
dialog = QDialog(parent)
|
||||||
|
dialog.setWindowTitle("Verfahrensgebiete aus Landkreis laden")
|
||||||
|
if os.path.exists(_WAPPEN_ICON):
|
||||||
|
dialog.setWindowIcon(QIcon(_WAPPEN_ICON))
|
||||||
|
layout = QVBoxLayout(dialog)
|
||||||
|
|
||||||
|
hinweis = QLabel(
|
||||||
|
f"Es wurden {len(kombinationen)} Objekte gefunden.\n\n"
|
||||||
|
"Bitte wählen Sie eines oder mehrere Objekte aus:"
|
||||||
|
)
|
||||||
|
hinweis.setWordWrap(True)
|
||||||
|
layout.addWidget(hinweis)
|
||||||
|
|
||||||
|
suche_zeile = QHBoxLayout()
|
||||||
|
suche_zeile.addWidget(QLabel("Suche:"))
|
||||||
|
suche_feld = QLineEdit()
|
||||||
|
suche_feld.setPlaceholderText("Nummer oder Name …")
|
||||||
|
suche_zeile.addWidget(suche_feld)
|
||||||
|
layout.addLayout(suche_zeile)
|
||||||
|
|
||||||
|
alle_auswaehlen = QCheckBox("Alle auswählen")
|
||||||
|
layout.addWidget(alle_auswaehlen)
|
||||||
|
|
||||||
|
# Standardsortierung nach Bezeichnung; per Klick auf Spaltenkopf änderbar
|
||||||
|
# (setSortingEnabled aktiviert die eingebaute Tabellensortierung).
|
||||||
|
sortierte_kombinationen = sorted(kombinationen, key=lambda kb: kb[1])
|
||||||
|
|
||||||
|
table = QTableWidget(len(sortierte_kombinationen), 2)
|
||||||
|
table.setHorizontalHeaderLabels(["Nummer", "Name"])
|
||||||
|
table.setSelectionMode(ExtendedSelection)
|
||||||
|
table.setSelectionBehavior(SelectRows)
|
||||||
|
table.setEditTriggers(NoEditTriggers)
|
||||||
|
table.verticalHeader().setVisible(False)
|
||||||
|
table.verticalHeader().setDefaultSectionSize(22) # flachere Zeilen
|
||||||
|
table.horizontalHeader().setStretchLastSection(True)
|
||||||
|
table.setSortingEnabled(False)
|
||||||
|
for row, (adv, bez) in enumerate(sortierte_kombinationen):
|
||||||
|
table.setItem(row, 0, QTableWidgetItem(bez))
|
||||||
|
table.setItem(row, 1, QTableWidgetItem(adv))
|
||||||
|
table.setSortingEnabled(True)
|
||||||
|
table.sortByColumn(0, AscendingOrder) # Standard: aufsteigend nach Nummer
|
||||||
|
table.resizeColumnToContents(0)
|
||||||
|
table.setColumnWidth(0, max(110, table.columnWidth(0)))
|
||||||
|
table.selectRow(0)
|
||||||
|
layout.addWidget(table, 1) # Stretch-Faktor: wächst mit Fenstergröße
|
||||||
|
|
||||||
|
def _suche_geaendert(text):
|
||||||
|
text_lower = text.strip().lower()
|
||||||
|
for row in range(table.rowCount()):
|
||||||
|
bez_text = table.item(row, 0).text().lower()
|
||||||
|
adv_text = table.item(row, 1).text().lower()
|
||||||
|
treffer = not text_lower or text_lower in bez_text or text_lower in adv_text
|
||||||
|
table.setRowHidden(row, not treffer)
|
||||||
|
|
||||||
|
suche_feld.textChanged.connect(_suche_geaendert)
|
||||||
|
|
||||||
|
def _alle_auswaehlen_umschalten(checked):
|
||||||
|
table.clearSelection()
|
||||||
|
if checked:
|
||||||
|
letzte_spalte = table.columnCount() - 1
|
||||||
|
for row in range(table.rowCount()):
|
||||||
|
if not table.isRowHidden(row):
|
||||||
|
# setRangeSelected (statt selectRow) fügt der Selektion
|
||||||
|
# hinzu, ohne vorherige Zeilen wieder abzuwählen.
|
||||||
|
table.setRangeSelected(
|
||||||
|
QTableWidgetSelectionRange(row, 0, row, letzte_spalte), True
|
||||||
|
)
|
||||||
|
|
||||||
|
alle_auswaehlen.stateChanged.connect(_alle_auswaehlen_umschalten)
|
||||||
|
|
||||||
|
mehrfachauswahl_hinweis = QLabel("<small>Mehrfachauswahl mit Strg + Umschalt</small>")
|
||||||
|
layout.addWidget(mehrfachauswahl_hinweis)
|
||||||
|
|
||||||
|
nur_laden_checkbox = QCheckBox("Nur Objekte laden (ohne weitere Prüfungen)")
|
||||||
|
layout.addWidget(nur_laden_checkbox)
|
||||||
|
|
||||||
|
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||||
|
buttons.accepted.connect(dialog.accept)
|
||||||
|
buttons.rejected.connect(dialog.reject)
|
||||||
|
layout.addWidget(buttons)
|
||||||
|
|
||||||
|
dialog.adjustSize()
|
||||||
|
# Bezeichnungs-Spalte + großzügige Breite für den ADV-Namen (lange Namen
|
||||||
|
# sollen möglichst ohne Abschneiden sichtbar sein) + Rahmen/Scrollbar-Puffer.
|
||||||
|
breite = max(dialog.sizeHint().width(), table.columnWidth(0) + 480)
|
||||||
|
|
||||||
|
# Tabelle auf max. 15 sichtbare Zeilen begrenzen, Rest per Scrollbar – hier
|
||||||
|
# potenziell deutlich mehr Treffer als bei der VKZ-Suche, daher großzügiger
|
||||||
|
# bemessen als bei ask_bror_kombinationen (dort 5).
|
||||||
|
sichtbare_zeilen = min(len(sortierte_kombinationen), 15)
|
||||||
|
zeilenhoehe = table.verticalHeader().defaultSectionSize() if table.rowCount() else 20
|
||||||
|
kopfzeile_hoehe = table.horizontalHeader().height()
|
||||||
|
# + Puffer für eine ggf. eingeblendete horizontale Scrollbar (bei langen
|
||||||
|
# Einträgen), damit diese nicht die letzte Zeile abschneidet.
|
||||||
|
scrollbar_puffer = table.horizontalScrollBar().sizeHint().height()
|
||||||
|
table_hoehe = (
|
||||||
|
zeilenhoehe * sichtbare_zeilen + kopfzeile_hoehe
|
||||||
|
+ 2 * table.frameWidth() + 4 + scrollbar_puffer
|
||||||
|
)
|
||||||
|
table.setMinimumHeight(table_hoehe)
|
||||||
|
|
||||||
|
dialog.resize(breite, dialog.sizeHint().height())
|
||||||
|
|
||||||
|
if exec_dialog(dialog) != DialogAccepted:
|
||||||
|
return [], False
|
||||||
|
|
||||||
|
ausgewaehlte_zeilen = sorted(set(item.row() for item in table.selectedItems()))
|
||||||
|
ausgewaehlt = [(table.item(row, 1).text(), table.item(row, 0).text()) for row in ausgewaehlte_zeilen]
|
||||||
|
return ausgewaehlt, nur_laden_checkbox.isChecked()
|
||||||
|
|
||||||
|
|
||||||
|
def ask_landkreis_auswahl(
|
||||||
|
parent: Any,
|
||||||
|
landkreise: list[tuple[str, str]],
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""Fragt per Dropdown-Dialog einen Landkreis ab (Fallback, wenn weder
|
||||||
|
Verfahrensnummer noch Landkreis in den Projekteigenschaften gesetzt ist).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
parent :
|
||||||
|
Eltern-Widget oder None.
|
||||||
|
landkreise :
|
||||||
|
Liste von ``(kennzahl, name)``-Tupeln, z.B. ``LANDKREISE`` aus
|
||||||
|
:mod:`sn_basis.functions.landkreise`.
|
||||||
|
|
||||||
|
Die Auswahlliste beginnt mit einem leeren Platzhalter (der Nutzer muss
|
||||||
|
aktiv einen Eintrag wählen) und enthält zusätzlich "Alle Landkreise"
|
||||||
|
(Sentinel :data:`~sn_basis.functions.landkreise.ALLE_LANDKREISE`).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Optional[str]
|
||||||
|
Gewählte Landkreiskennzahl, :data:`ALLE_LANDKREISE` bei Auswahl aller
|
||||||
|
Landkreise, oder ``None`` bei Abbruch, leerem Platzhalter oder leerer
|
||||||
|
Landkreisliste.
|
||||||
|
"""
|
||||||
|
if not landkreise:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if QT_VERSION == 0: # Mock-Modus
|
||||||
|
print("Mock-Modus: ask_landkreis_auswahl → None (keine aktive Auswahl)")
|
||||||
|
return None
|
||||||
|
|
||||||
|
ALLE_LABEL = "Alle Landkreise"
|
||||||
|
namen = [""] + [ALLE_LABEL] + [name for _, name in landkreise]
|
||||||
|
|
||||||
|
dialog = QInputDialog(parent)
|
||||||
|
dialog.setWindowTitle("Landkreis auswählen")
|
||||||
|
if os.path.exists(_WAPPEN_ICON):
|
||||||
|
dialog.setWindowIcon(QIcon(_WAPPEN_ICON))
|
||||||
|
dialog.setLabelText("Landkreis:")
|
||||||
|
dialog.setComboBoxItems(namen)
|
||||||
|
dialog.setComboBoxEditable(False)
|
||||||
|
|
||||||
|
if exec_dialog(dialog) != DialogAccepted:
|
||||||
|
return None
|
||||||
|
|
||||||
|
name = dialog.textValue()
|
||||||
|
if not name:
|
||||||
|
return None
|
||||||
|
if name == ALLE_LABEL:
|
||||||
|
return ALLE_LANDKREISE
|
||||||
|
|
||||||
|
name_zu_kennzahl = {n: k for k, n in landkreise}
|
||||||
|
return name_zu_kennzahl.get(str(name))
|
||||||
@@ -148,6 +148,7 @@ _PATCH_KNICK = f"{_MOD}.knickpunkt_pruefung"
|
|||||||
_PATCH_EXTRAHIERE = f"{_MOD}._extrahiere_knickpunkte"
|
_PATCH_EXTRAHIERE = f"{_MOD}._extrahiere_knickpunkte"
|
||||||
_PATCH_LANDKREIS_SUCHE = f"{_MOD}._suche_landkreis_kombinationen"
|
_PATCH_LANDKREIS_SUCHE = f"{_MOD}._suche_landkreis_kombinationen"
|
||||||
_PATCH_LANDKREIS_BAU = f"{_MOD}._baue_layer_aus_landkreis_auswahl"
|
_PATCH_LANDKREIS_BAU = f"{_MOD}._baue_layer_aus_landkreis_auswahl"
|
||||||
|
_PATCH_ASK_LANDKREIS = f"{_MOD}.ask_landkreis_auswahl"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -678,9 +679,13 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
|
|
||||||
@patch(_PATCH_PROGRESS)
|
@patch(_PATCH_PROGRESS)
|
||||||
@patch(_PATCH_MSGBOX)
|
@patch(_PATCH_MSGBOX)
|
||||||
|
@patch(_PATCH_ASK_LANDKREIS, return_value=None)
|
||||||
@patch(_PATCH_GET_VAR, return_value="")
|
@patch(_PATCH_GET_VAR, return_value="")
|
||||||
def test_kein_vkz_kein_landkreis_zeigt_fehler(self, mock_get_var, mock_msgbox, mock_prog):
|
def test_kein_vkz_kein_landkreis_zeigt_fehler(
|
||||||
"""Weder Verfahrensnummer noch Landkreis gesetzt → Fehlermeldung, kein Layer."""
|
self, mock_get_var, mock_ask_landkreis, mock_msgbox, mock_prog
|
||||||
|
):
|
||||||
|
"""Weder Verfahrensnummer noch Landkreis gesetzt, Nutzer bricht den
|
||||||
|
Landkreis-Auswahldialog ab → Fehlermeldung, kein Layer."""
|
||||||
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
self.assertIsNone(enriched)
|
self.assertIsNone(enriched)
|
||||||
@@ -689,6 +694,33 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
self.assertEqual(status, LoadStatus.NONE)
|
self.assertEqual(status, LoadStatus.NONE)
|
||||||
mock_msgbox.critical.assert_called_once()
|
mock_msgbox.critical.assert_called_once()
|
||||||
|
|
||||||
|
@patch(_PATCH_PROGRESS)
|
||||||
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_LANDKREIS_BAU)
|
||||||
|
@patch(_PATCH_LANDKREIS_SUCHE)
|
||||||
|
@patch(_PATCH_ASK_LANDKREIS, return_value="22")
|
||||||
|
@patch(_PATCH_GET_VAR, return_value="")
|
||||||
|
def test_kein_vkz_kein_landkreis_dialog_liefert_landkreis(
|
||||||
|
self, mock_get_var, mock_ask_landkreis, mock_such, mock_bau, mock_proj, mock_prog
|
||||||
|
):
|
||||||
|
"""Weder Verfahrensnummer noch Landkreis gesetzt, Nutzer wählt einen
|
||||||
|
Landkreis im Dialog → Landkreis-Suchpfad wird mit dieser Kennzahl
|
||||||
|
verwendet (wie im Fall 'Landkreis aus Projekteigenschaften')."""
|
||||||
|
proj_inst = _DummyProject()
|
||||||
|
mock_proj.instance.return_value = proj_inst
|
||||||
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
|
mock_such.return_value = (None, LoadStatus.FIRST, {("A", "22015"): [MagicMock()]}, "EPSG:25833")
|
||||||
|
mock_bau.return_value = (vg_layer, LoadStatus.FIRST, True) # nur_laden=True
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
|
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
|
mock_ask_landkreis.assert_called_once()
|
||||||
|
mock_such.assert_called_once_with(None, "22")
|
||||||
|
mock_bau.assert_called_once()
|
||||||
|
self.assertIs(enriched, vg_layer)
|
||||||
|
self.assertEqual(status, LoadStatus.FIRST)
|
||||||
|
|
||||||
@patch(_PATCH_PROGRESS)
|
@patch(_PATCH_PROGRESS)
|
||||||
@patch(_PATCH_PROJECT)
|
@patch(_PATCH_PROJECT)
|
||||||
@patch(_PATCH_LANDKREIS_BAU)
|
@patch(_PATCH_LANDKREIS_BAU)
|
||||||
|
|||||||
+22
-15
@@ -21,7 +21,10 @@ from sn_verfahrensgebiet.functions.flurstueck_alkis import (
|
|||||||
ALKIS_SF_URL,
|
ALKIS_SF_URL,
|
||||||
FLST_LAYER_NAME,
|
FLST_LAYER_NAME,
|
||||||
)
|
)
|
||||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import verfahrensgebiet_alkis_komplett
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import (
|
||||||
|
verfahrensgebiet_alkis_komplett,
|
||||||
|
BROR_LAYER_NAME,
|
||||||
|
)
|
||||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett import verfahrensgebiet_shape_komplett
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett import verfahrensgebiet_shape_komplett
|
||||||
from sn_verfahrensgebiet.functions.grenzpunkt_alkis import grenzpunkte_gemarkungsweise, JOINED_LAYER_NAME
|
from sn_verfahrensgebiet.functions.grenzpunkt_alkis import grenzpunkte_gemarkungsweise, JOINED_LAYER_NAME
|
||||||
from sn_verfahrensgebiet.functions.gemarkung_alkis import (
|
from sn_verfahrensgebiet.functions.gemarkung_alkis import (
|
||||||
@@ -201,23 +204,27 @@ class WorkingTab(QWidget):
|
|||||||
apply_style(flst_layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
|
apply_style(flst_layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
|
||||||
self.setze_haken(self.haken_flurst, True)
|
self.setze_haken(self.haken_flurst, True)
|
||||||
|
|
||||||
# BROR-Layer: QML-Stil anwenden
|
# BROR-Layer bekommt seinen Style bereits direkt beim Laden in
|
||||||
if bror_layer and bror_layer.isValid():
|
# verfahrensgebiet_alkis_komplett() (auch im "Nur laden"-Fall).
|
||||||
apply_style(bror_layer, "BauRaumOderBodenordnungsrecht.qml")
|
|
||||||
|
|
||||||
# Enriched VG-Layer ins Projekt (ggf. bereits registriert, z.B. bei "als VG laden")
|
# Bei "Nur Objekte laden" (kein weiterer Ablauf) ist enriched_layer
|
||||||
if not project.mapLayer(enriched_layer.id()):
|
# tatsächlich nur der rohe BROR-Layer (erkennbar am Namen) – hier
|
||||||
project.addMapLayer(enriched_layer)
|
# fand keine Flurstücksverschneidung statt, es gibt also keinen
|
||||||
apply_style(enriched_layer, "verfahrensgebiet.qml")
|
# echten VG-Layer: VG-Style/-Kombobox/-Haken/-Meldung überspringen.
|
||||||
iface.mapCanvas().setExtent(enriched_layer.extent())
|
if enriched_layer.name() != BROR_LAYER_NAME:
|
||||||
iface.mapCanvas().refresh()
|
# Enriched VG-Layer ins Projekt (ggf. bereits registriert, z.B. bei "als VG laden")
|
||||||
self.setze_haken(self.haken_verf, True)
|
if not project.mapLayer(enriched_layer.id()):
|
||||||
|
project.addMapLayer(enriched_layer)
|
||||||
|
apply_style(enriched_layer, "verfahrensgebiet.qml")
|
||||||
|
iface.mapCanvas().setExtent(enriched_layer.extent())
|
||||||
|
iface.mapCanvas().refresh()
|
||||||
|
self.setze_haken(self.haken_verf, True)
|
||||||
|
|
||||||
self.verf_layer_combo.setLayer(enriched_layer)
|
self.verf_layer_combo.setLayer(enriched_layer)
|
||||||
self._on_verf_layer_changed(enriched_layer)
|
self._on_verf_layer_changed(enriched_layer)
|
||||||
|
|
||||||
msg = "erstmalig geladen" if status == LoadStatus.FIRST else "neu geladen"
|
msg = "erstmalig geladen" if status == LoadStatus.FIRST else "neu geladen"
|
||||||
success("Verfahrensgebiet", f"Layer wurde {msg} (inkl. Flurstücksverschneidung).", duration=5)
|
success("Verfahrensgebiet", f"Layer wurde {msg} (inkl. Flurstücksverschneidung).", duration=5)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._set_busy(self.btn_verf_alkis, False, "Aus ALKIS laden")
|
self._set_busy(self.btn_verf_alkis, False, "Aus ALKIS laden")
|
||||||
|
|||||||
Reference in New Issue
Block a user