Files
Plugin_SN_Verfahrensgebiet/functions/flurstueck_suche.py
T
DanielandClaude Opus 5.5 c8b380b5d3 Tab „Eigentümer“ mit Flurstückssuche (#56)
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>
2026-10-06 12:44:45 +02:00

176 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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))