176 lines
6.3 KiB
Python
176 lines
6.3 KiB
Python
"""sn_verfahrensgebiet/functions/flurstueck_suche.py – Flurstückssuche im Layer „Flurstücke“ (#56).
|
||||
|
|
|
|||
|
|
Findet ein Flurstück über Gemarkung und Flurstücksnummer im Layer „Flurstücke“,
|
|||
|
|
den der Bearbeitungs-Tab aus ALKIS lädt. Verglichen wird das
|
|||
|
|
``flurstueckskennzeichen``, nicht die von QGIS abgeflachten Einzelfelder des
|
|||
|
|
WFS – deren Namen hängen vom Provider ab, das Kennzeichen nicht.
|
|||
|
|
|
|||
|
|
Aufbau des Kennzeichens (20 Zeichen, unbesetzte Teile füllt ALKIS mit ``_``)::
|
|||
|
|
|
|||
|
|
14 0268 ___ 00562 ____ 00
|
|||
|
|
│ │ │ │ │ └ Flurstücksfolge
|
|||
|
|
│ │ │ │ └ Nenner
|
|||
|
|
│ │ │ └ Zähler
|
|||
|
|
│ │ └ Flur (in Sachsen unbesetzt)
|
|||
|
|
│ └ Gemarkungsnummer
|
|||
|
|
└ Land
|
|||
|
|
|
|||
|
|
Die Gemarkung darf als Nummer oder als Name eingegeben werden. Namen werden
|
|||
|
|
über den Layer „Gemarkungen“ (vereinfachter ALKIS-Dienst, Feld ``ave_name``)
|
|||
|
|
in Nummern übersetzt. Das Modul zeigt keine Dialoge; Fehler kommen als
|
|||
|
|
:class:`FlurstueckSucheFehler` mit einer Meldung für den Nutzer zurück.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import re
|
|||
|
|
from dataclasses import dataclass, field
|
|||
|
|
from typing import List, Optional, Tuple
|
|||
|
|
|
|||
|
|
from sn_basis.functions import QgsProject
|
|||
|
|
|
|||
|
|
from sn_verfahrensgebiet.functions.flurstueck_alkis import (
|
|||
|
|
FELD_FLURSTUECKSKENNZEICHEN,
|
|||
|
|
FLST_LAYER_NAME,
|
|||
|
|
)
|
|||
|
|
from sn_verfahrensgebiet.functions.gemarkung_alkis import _extract_schluessel_und_name
|
|||
|
|
|
|||
|
|
GEMARKUNGEN_LAYER_NAME = "Gemarkungen"
|
|||
|
|
MELDUNG_KEIN_FLURSTUECKSLAYER = (
|
|||
|
|
"Zuerst Verfahrensgebiet und zugehörige Flurstücke aus ALKIS laden."
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
_NUMMER_MUSTER = re.compile(r"^\s*(\d+)\s*(?:/\s*([0-9A-Za-z]+)\s*)?$")
|
|||
|
|
|
|||
|
|
|
|||
|
|
class FlurstueckSucheFehler(Exception):
|
|||
|
|
"""Eingabe- oder Suchfehler; der Text ist für den Nutzer bestimmt."""
|
|||
|
|
|
|||
|
|
|
|||
|
|
class KeinFlurstueckslayer(FlurstueckSucheFehler):
|
|||
|
|
"""Im Projekt gibt es keinen Layer „Flurstücke“."""
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
super().__init__(MELDUNG_KEIN_FLURSTUECKSLAYER)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class SuchErgebnis:
|
|||
|
|
"""Treffer einer Flurstückssuche im Layer ``layer``."""
|
|||
|
|
|
|||
|
|
layer: object
|
|||
|
|
feature_ids: List[int] = field(default_factory=list)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _normiere_nenner(wert: str) -> str:
|
|||
|
|
return (wert or "").strip().strip("_").lstrip("0").lower()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def zerlege_flurstuecksnummer(text: str) -> Tuple[str, str]:
|
|||
|
|
"""Zerlegt ``"562"`` in ``("562", "")`` und ``"12/3"`` in ``("12", "3")``.
|
|||
|
|
|
|||
|
|
Führende Nullen fallen weg, damit ``"0562"`` und ``"562"`` gleich sind.
|
|||
|
|
"""
|
|||
|
|
treffer = _NUMMER_MUSTER.match(text or "")
|
|||
|
|
if not treffer:
|
|||
|
|
raise FlurstueckSucheFehler(
|
|||
|
|
"Die Flurstücksnummer bitte als Zahl oder als Zähler/Nenner eingeben, "
|
|||
|
|
"z. B. 562 oder 12/3."
|
|||
|
|
)
|
|||
|
|
zaehler = treffer.group(1).lstrip("0") or "0"
|
|||
|
|
return zaehler, _normiere_nenner(treffer.group(2) or "")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def zerlege_kennzeichen(kennzeichen) -> Optional[Tuple[str, str, str]]:
|
|||
|
|
"""Liefert ``(gemarkungsnummer, zaehler, nenner)`` eines Flurstückskennzeichens.
|
|||
|
|
|
|||
|
|
``None``, wenn das Kennzeichen zu kurz ist oder keine Gemarkung bzw. keinen
|
|||
|
|
Zähler enthält.
|
|||
|
|
"""
|
|||
|
|
kz = str(kennzeichen or "").strip()
|
|||
|
|
if len(kz) < 18:
|
|||
|
|
return None
|
|||
|
|
gemarkung = kz[2:6]
|
|||
|
|
zaehler = kz[9:14].strip("_").lstrip("0")
|
|||
|
|
if not gemarkung.isdigit() or not zaehler:
|
|||
|
|
return None
|
|||
|
|
return gemarkung, zaehler, _normiere_nenner(kz[14:18])
|
|||
|
|
|
|||
|
|
|
|||
|
|
def gemarkungsnummern(eingabe: str, gemarkungen_layer=None) -> List[str]:
|
|||
|
|
"""Übersetzt die Gemarkungseingabe in 4-stellige Gemarkungsnummern.
|
|||
|
|
|
|||
|
|
Ziffern gelten als Nummer: bis zu vier Stellen werden mit Nullen aufgefüllt,
|
|||
|
|
bei längeren Schlüsseln (z. B. ``140268``) zählen die letzten vier Stellen.
|
|||
|
|
Ein Name wird ohne Rücksicht auf Groß- und Kleinschreibung im Layer
|
|||
|
|
„Gemarkungen“ gesucht. Gleichnamige Gemarkungen liefern mehrere Nummern.
|
|||
|
|
"""
|
|||
|
|
text = (eingabe or "").strip()
|
|||
|
|
if not text:
|
|||
|
|
raise FlurstueckSucheFehler("Bitte eine Gemarkung eingeben (Name oder Nummer).")
|
|||
|
|
if text.isdigit():
|
|||
|
|
return [text[-4:].zfill(4)]
|
|||
|
|
|
|||
|
|
if gemarkungen_layer is None:
|
|||
|
|
raise FlurstueckSucheFehler(
|
|||
|
|
f"Den Gemarkungsnamen „{text}“ kann das Plugin nur mit dem Layer "
|
|||
|
|
f"„{GEMARKUNGEN_LAYER_NAME}“ auflösen. Bitte die Gemarkungsnummer "
|
|||
|
|
"eingeben oder die Gemarkungen laden."
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
gesucht = text.casefold()
|
|||
|
|
nummern = set()
|
|||
|
|
for feature in gemarkungen_layer.getFeatures():
|
|||
|
|
nummer, name = _extract_schluessel_und_name(feature)
|
|||
|
|
if nummer and name.casefold() == gesucht:
|
|||
|
|
nummern.add(nummer.zfill(4))
|
|||
|
|
if not nummern:
|
|||
|
|
raise FlurstueckSucheFehler(
|
|||
|
|
f"Im Layer „{GEMARKUNGEN_LAYER_NAME}“ gibt es keine Gemarkung „{text}“."
|
|||
|
|
)
|
|||
|
|
return sorted(nummern)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def finde_flurstuecke(layer, nummern: List[str], zaehler: str, nenner: str) -> List[int]:
|
|||
|
|
"""Feature-IDs der Flurstücke mit passender Gemarkung, Zähler und Nenner.
|
|||
|
|
|
|||
|
|
Ohne Nenner passen alle Flurstücke mit diesem Zähler (``562``, ``562/1`` …).
|
|||
|
|
"""
|
|||
|
|
if layer.fields().indexFromName(FELD_FLURSTUECKSKENNZEICHEN) < 0:
|
|||
|
|
raise FlurstueckSucheFehler(
|
|||
|
|
f"Der Layer „{FLST_LAYER_NAME}“ hat kein Feld „{FELD_FLURSTUECKSKENNZEICHEN}“."
|
|||
|
|
)
|
|||
|
|
gesucht = set(nummern)
|
|||
|
|
ids: List[int] = []
|
|||
|
|
for feature in layer.getFeatures():
|
|||
|
|
teile = zerlege_kennzeichen(feature[FELD_FLURSTUECKSKENNZEICHEN])
|
|||
|
|
if teile is None:
|
|||
|
|
continue
|
|||
|
|
gemarkung, f_zaehler, f_nenner = teile
|
|||
|
|
if gemarkung in gesucht and f_zaehler == zaehler and (not nenner or f_nenner == nenner):
|
|||
|
|
ids.append(feature.id())
|
|||
|
|
return ids
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _hole_layer(name: str):
|
|||
|
|
treffer = QgsProject.instance().mapLayersByName(name)
|
|||
|
|
return treffer[0] if treffer else None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def flurstueck_suchen(gemarkung_text: str, nummer_text: str) -> SuchErgebnis:
|
|||
|
|
"""Sucht das Flurstück im Layer „Flurstücke“ des aktuellen Projekts.
|
|||
|
|
|
|||
|
|
Raises
|
|||
|
|
------
|
|||
|
|
KeinFlurstueckslayer
|
|||
|
|
Der Layer „Flurstücke“ fehlt.
|
|||
|
|
FlurstueckSucheFehler
|
|||
|
|
Die Eingabe ist unvollständig oder ungültig.
|
|||
|
|
"""
|
|||
|
|
flst_layer = _hole_layer(FLST_LAYER_NAME)
|
|||
|
|
if flst_layer is None:
|
|||
|
|
raise KeinFlurstueckslayer()
|
|||
|
|
zaehler, nenner = zerlege_flurstuecksnummer(nummer_text)
|
|||
|
|
nummern = gemarkungsnummern(gemarkung_text, _hole_layer(GEMARKUNGEN_LAYER_NAME))
|
|||
|
|
return SuchErgebnis(flst_layer, finde_flurstuecke(flst_layer, nummern, zaehler, nenner))
|