Gemarkung (Name oder Nummer) und Flurstücksnummer eingeben, die Treffer werden im Layer „Flurstücke“ ausgewählt, angezoomt und kurz hervorgehoben. Verglichen wird das flurstueckskennzeichen, Gemarkungsnamen werden über den Layer „Gemarkungen“ aufgelöst. Ohne Layer „Flurstücke“ weist ein Dialog darauf hin, zuerst Verfahrensgebiet und Flurstücke zu laden. Erster Schritt von #56; Eigentümersuche und Anzeigefenster folgen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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))
|