diff --git a/functions/knickpunkt_pruefung.py b/functions/knickpunkt_pruefung.py index 072c3ac..afdf23b 100644 --- a/functions/knickpunkt_pruefung.py +++ b/functions/knickpunkt_pruefung.py @@ -600,3 +600,207 @@ def knickpunkt_pruefung( _setze_kategorien_symbologie(ergebnis_layer) return ergebnis_layer, identisch_count, kein_pta_count, fehlender_knick_count + + +def erstelle_korrigiertes_polygon( + bror_layer, + result_layer, + crs_authid: str, +) -> Tuple[Optional[object], Optional[object]]: + """Erstellt korrigierte Verbindungslinien und ein Polygon aus identischen + und gefundenen fehlenden Knickpunkten. + + Kombiniert die gematchten PunktortTA (``identisch``, grün) mit den + gefundenen fehlenden Knickpunkten (``fehlender_knick``, orange) zu einem + korrigierten Verlauf entlang der BROR-Außengrenze. Orange Punkte werden + nach ihrer Projektion auf die Verbindungsstrecke zwischen ihren zwei + umgebenden identischen PunktortTA einsortiert. + + Parameters + ---------- + bror_layer : + BauRaumOderBodenordnungsrecht-Polygon-Layer (für die Ringreihenfolge). + result_layer : + Punkt-Ergebnislayer aus :func:`knickpunkt_pruefung` mit Feld ``typ``. + crs_authid : + CRS-Kennung, z.B. ``"EPSG:25833"``. + + Returns + ------- + Tuple[Optional[QgsVectorLayer], Optional[QgsVectorLayer]] + ``(linien_layer, polygon_layer)`` – Memory-Layer mit den korrigierten + Verbindungslinien und dem abgeleiteten Polygon, oder ``(None, None)`` + bei fehlgeschlagener Verarbeitung. + """ + try: + from qgis.core import QgsPointXY # type: ignore[import] + except ImportError: + return None, None + + # ------------------------------------------------------------------ + # Punkte aus Ergebnislayer extrahieren + # ------------------------------------------------------------------ + identisch_pts: List[object] = [] + orange_pts: List[object] = [] + + try: + for feat in result_layer.getFeatures(): + geom = feat.geometry() + if geom is None or geom.isEmpty(): + continue + pt = geom.asPoint() + typ = feat.attribute(FELD_TYP) + if typ == WERT_IDENTISCH: + identisch_pts.append(QgsPointXY(pt.x(), pt.y())) + elif typ == WERT_FEHLENDER_KNICK: + orange_pts.append(QgsPointXY(pt.x(), pt.y())) + except Exception: + return None, None + + if len(identisch_pts) < 3: + return None, None + + # ------------------------------------------------------------------ + # Alle BROR-Außenringe extrahieren (Multipolygon-sicher) + # ------------------------------------------------------------------ + try: + bror_feats = [ + f for f in bror_layer.getFeatures() + if f.geometry() and not f.geometry().isEmpty() + ] + bror_union = QgsGeometry.unaryUnion([f.geometry() for f in bror_feats]) + if bror_union is None or bror_union.isEmpty(): + return None, None + + if bror_union.isMultipart(): + all_raw_rings = [ + poly[0] for poly in bror_union.asMultiPolygon() if poly + ] + else: + poly = bror_union.asPolygon() + all_raw_rings = [poly[0]] if poly else [] + except Exception: + return None, None + + if not all_raw_rings: + return None, None + + all_line_segments: List[Tuple[object, object]] = [] + all_polygon_feats: List[object] = [] + + # ------------------------------------------------------------------ + # Pro Polygon-Teil: Identisch-Punkte verorten, Verlauf aufbauen + # ------------------------------------------------------------------ + for raw_ring in all_raw_rings: + ring_pts = [QgsPointXY(x, y) for x, y in raw_ring] + if len(ring_pts) >= 2 and ( + abs(ring_pts[-1].x() - ring_pts[0].x()) < 1e-9 + and abs(ring_pts[-1].y() - ring_pts[0].y()) < 1e-9 + ): + ring_pts = ring_pts[:-1] + + if len(ring_pts) < 3: + continue + + # Identisch-Punkte auf diesem Ring lokalisieren + on_ring: List[Tuple[int, object]] = [] + for pt in identisch_pts: + for i, rpt in enumerate(ring_pts): + dist_sq = (rpt.x() - pt.x()) ** 2 + (rpt.y() - pt.y()) ** 2 + if dist_sq <= KOORDINATEN_TOLERANZ_M ** 2: + on_ring.append((i, pt)) + break + + on_ring.sort(key=lambda t: t[0]) + + if len(on_ring) < 3: + # Fallback: Original-Ring dieses Teilgebiets verwenden + ordered_pts: List[object] = ring_pts + for j in range(len(ring_pts)): + all_line_segments.append( + (ring_pts[j], ring_pts[(j + 1) % len(ring_pts)]) + ) + else: + # Korrigierten Verlauf für diesen Teil aufbauen + ordered_pts = [] + n_part = len(on_ring) + + for k in range(n_part): + _, pt1 = on_ring[k] + _, pt2 = on_ring[(k + 1) % n_part] + + dx = pt2.x() - pt1.x() + dy = pt2.y() - pt1.y() + len_sq = dx * dx + dy * dy + + between: List[Tuple[float, object]] = [] + if len_sq > 0: + for opt in orange_pts: + t = ( + (opt.x() - pt1.x()) * dx + (opt.y() - pt1.y()) * dy + ) / len_sq + clamped_t = max(0.0, min(1.0, t)) + foot_x = pt1.x() + clamped_t * dx + foot_y = pt1.y() + clamped_t * dy + dist = ( + (opt.x() - foot_x) ** 2 + (opt.y() - foot_y) ** 2 + ) ** 0.5 + if dist <= VERBINDUNGSLINIE_PUFFER_M: + between.append((t, opt)) + + between.sort(key=lambda x: x[0]) + + segment_pts = [pt1] + [p for _, p in between] + ordered_pts.extend(segment_pts) + + all_pts_in_seg = segment_pts + [pt2] + for j in range(len(all_pts_in_seg) - 1): + all_line_segments.append( + (all_pts_in_seg[j], all_pts_in_seg[j + 1]) + ) + + if len(ordered_pts) < 3: + continue + + polygon_ring = ordered_pts + [ordered_pts[0]] + polygon_geom = QgsGeometry.fromPolygonXY([polygon_ring]) + if polygon_geom and not polygon_geom.isEmpty(): + pf = QgsFeature() + pf.setGeometry(polygon_geom) + all_polygon_feats.append(pf) + + if not all_polygon_feats: + return None, None + + # ------------------------------------------------------------------ + # Linien-Layer erstellen + # ------------------------------------------------------------------ + linien_layer = QgsVectorLayer( + f"LineString?crs={crs_authid}", + "Korrigierte Verbindungslinien (Detailprüfung)", + "memory", + ) + lines_pr = linien_layer.dataProvider() + line_feats_list = [] + for p1, p2 in all_line_segments: + lgeom = QgsGeometry.fromPolylineXY([p1, p2]) + if lgeom and not lgeom.isEmpty(): + f = QgsFeature() + f.setGeometry(lgeom) + line_feats_list.append(f) + lines_pr.addFeatures(line_feats_list) + linien_layer.updateExtents() + + # ------------------------------------------------------------------ + # Polygon-Layer erstellen (ein Feature pro Teilgebiet) + # ------------------------------------------------------------------ + polygon_layer = QgsVectorLayer( + f"Polygon?crs={crs_authid}", + "Korrigiertes Polygon (Detailprüfung)", + "memory", + ) + poly_pr = polygon_layer.dataProvider() + poly_pr.addFeatures(all_polygon_feats) + polygon_layer.updateExtents() + + return linien_layer, polygon_layer diff --git a/functions/verfahrensgebiet_alkis_komplett.py b/functions/verfahrensgebiet_alkis_komplett.py index efae825..f069368 100644 --- a/functions/verfahrensgebiet_alkis_komplett.py +++ b/functions/verfahrensgebiet_alkis_komplett.py @@ -22,7 +22,10 @@ optionaler Umringsprüfung: - Grenzpunkte (AX_PunktortTA) per BBox laden. - Knickpunkte des BROR mit PunktortTA vergleichen (grün/rot/orange). - - Ergebnislayer (Punkte) ins Projekt einfügen. + - Korrigiertes Polygon aus identischen + gefundenen PunktortTA ableiten. + - Flurstücke mit korrigiertem Polygon neu selektieren und enriched Layer + erzeugen. + - Ergebnislayer (Punkte), Verbindungslinien und Polygon ins Projekt einfügen. - Abschlussmeldung mit Zählung. Das BROR-Objekt (``AX_BauRaumOderBodenordnungsrecht``) bleibt bei Erfolg als @@ -66,6 +69,7 @@ from sn_verfahrensgebiet.functions.grenzpunkt_alkis import lade_punktort_ta from sn_verfahrensgebiet.functions.knickpunkt_pruefung import ( knickpunkt_pruefung, _extrahiere_knickpunkte, + erstelle_korrigiertes_polygon, ) # Feldname für das Verfahrenskennzeichen im enriched Layer @@ -523,6 +527,26 @@ def verfahrensgebiet_alkis_komplett( detail_progress.set_value(gesamt_schritte) detail_progress.close() + # ------------------------------------------------------------------ + # Korrigiertes Polygon aus identischen + fehlenden Knickpunkten + # ------------------------------------------------------------------ + if ergebnis_layer and ergebnis_layer.isValid(): + linien_layer_korr, polygon_layer_korr = erstelle_korrigiertes_polygon( + alkis_vg_layer, ergebnis_layer, alkis_vg_layer.crs().authid() + ) + if polygon_layer_korr is not None and polygon_layer_korr.isValid(): + anzahl_korr = _select_by_location(flst_layer, polygon_layer_korr) + if anzahl_korr > 0: + enriched_layer_neu, _, _ = erstelle_verfahrensgebiet_aus_auswahl( + flst_layer + ) + if enriched_layer_neu and enriched_layer_neu.isValid(): + _fuege_vkz_feld_hinzu(enriched_layer_neu, vkz) + enriched_layer = enriched_layer_neu + if linien_layer_korr is not None and linien_layer_korr.isValid(): + project.addMapLayer(linien_layer_korr) + project.addMapLayer(polygon_layer_korr) + if ergebnis_layer and ergebnis_layer.isValid(): project.addMapLayer(ergebnis_layer)