"""sn_verfahrensgebiet/functions/knickpunkt_pruefung.py – Knickpunktbasierte BROR-Prüfung. Vergleicht die Knickpunkte (Stützpunkte) des BauRaumOderBodenordnungsrecht- Polygons mit den Punktorten der geladenen ``AX_PunktortTA``-Objekte und identifiziert: - **Identische Punkte** (grün): An einem BROR-Knickpunkt liegt ein PunktortTA. - **Fehlende PunktortTA** (rot): Ein BROR-Knickpunkt hat keinen zugehörigen PunktortTA. - **Fehlende Knickpunkte** (orange): Ein PunktortTA liegt innerhalb des 3-cm-Puffers einer Verbindungslinie zwischen zwei aufeinanderfolgenden gematchten PunktortTA am BROR-Umring, hat aber keinen entsprechenden Knickpunkt im BROR. **Algorithmus:** 1. BROR-Polygon-Stützpunkte (Knickpunkte) extrahieren. 2. Spatial-Index auf PunktortTA aufbauen. 3. Für jeden Knickpunkt nächsten PunktortTA im Radius von :data:`KOORDINATEN_TOLERANZ_M` suchen → grün oder rot. Gematchte PunktortTA-Feature-IDs merken. 4. Entlang der BROR-Außengrenze aufeinanderfolgende gematchte PunktortTA ermitteln und für jedes konsekutive Paar eine gerade Verbindungslinie bilden; diese um :data:`VERBINDUNGSLINIE_PUFFER_M` puffern. 5. Nicht-gematchte PunktortTA innerhalb der Puffer → orange. Verbindungslinien- und Puffer-Layer werden dem QGIS-Projekt hinzugefügt. **Rückgabe:** ``(result_layer, identisch, kein_pta, fehlender_knick)`` """ from __future__ import annotations from typing import Dict, List, Optional, Set, Tuple from sn_basis.functions.qgiscore_wrapper import ( QgsVectorLayer, QgsFeature, QgsField, QgsGeometry, ) from sn_basis.functions.qt_wrapper import QVariant # --------------------------------------------------------------------------- # Konstanten # --------------------------------------------------------------------------- #: Koordinatentoleranz für die Knickpunkt ↔ PunktortTA-Übereinstimmung (m). KOORDINATEN_TOLERANZ_M: float = 0.01 #: Puffer um den BROR-Umring für die Suche nach fehlenden Knickpunkten (m). #: Wird aus Kompatibilitätsgründen behalten, wird aber nicht mehr verwendet. BOUNDARY_PUFFER_M: float = 0.5 #: Puffer um die Verbindungslinie zwischen zwei aufeinanderfolgenden PunktortTA (m). VERBINDUNGSLINIE_PUFFER_M: float = 0.03 #: Rundungsgenauigkeit für Vertex-Koordinaten im Flurstück-Set (Dezimalstellen). _VERTEX_ROUND = 3 ERGEBNIS_LAYER_NAME = "Knickpunktprüfung" FELD_TYP = "typ" WERT_IDENTISCH = "identisch" WERT_KEIN_PTA = "kein_pta" WERT_FEHLENDER_KNICK = "fehlender_knick" # --------------------------------------------------------------------------- # Interne Hilfsfunktionen # --------------------------------------------------------------------------- def _extrahiere_knickpunkte(bror_layer) -> List[object]: """Extrahiert alle Stützpunkte (Knickpunkte) des BROR-Polygons. Doppelte Start/End-Punkte eines Rings werden dedupliziert. Gibt eine flache Liste von ``QgsPointXY``-Objekten zurück. Parameters ---------- bror_layer : BauRaumOderBodenordnungsrecht-Polygon-Layer. Returns ------- List[QgsPointXY] Alle eindeutigen Stützpunkte; leer bei Fehler. """ try: from qgis.core import QgsPointXY # type: ignore[import] bror_feats = [ f for f in bror_layer.getFeatures() if f.geometry() and not f.geometry().isEmpty() ] if not bror_feats: return [] union_geom = QgsGeometry.unaryUnion([f.geometry() for f in bror_feats]) if union_geom is None or union_geom.isEmpty(): return [] punkte: List[object] = [] seen: Set[Tuple[float, float]] = set() def _add_ring(vertices): # Letzter Punkt = erster Punkt (Ring geschlossen) → überspringen ring_pts = list(vertices) n = len(ring_pts) if n >= 2: # Prüfen ob letzter Punkt identisch mit erstem last = n - 1 if ( abs(ring_pts[last].x() - ring_pts[0].x()) < 1e-9 and abs(ring_pts[last].y() - ring_pts[0].y()) < 1e-9 ): ring_pts = ring_pts[:-1] for pt in ring_pts: key = (round(pt.x(), _VERTEX_ROUND), round(pt.y(), _VERTEX_ROUND)) if key not in seen: seen.add(key) punkte.append(pt) geom_type = union_geom.wkbType() # Polygon oder MultiPolygon if union_geom.isMultipart(): for poly in union_geom.asMultiPolygon(): for ring in poly: _add_ring([QgsPointXY(x, y) for x, y in ring]) else: for ring in union_geom.asPolygon(): _add_ring([QgsPointXY(x, y) for x, y in ring]) return punkte except Exception: return [] def _erstelle_ergebnis_layer(crs_authid: str) -> object: """Erzeugt den leeren Punkt-Ergebnislayer mit Feld ``typ``. Parameters ---------- crs_authid : CRS-Kennung, z.B. ``"EPSG:25833"``. Returns ------- QgsVectorLayer Leerer Memory-Layer vom Typ Point. """ layer = QgsVectorLayer( f"Point?crs={crs_authid}", ERGEBNIS_LAYER_NAME, "memory" ) pr = layer.dataProvider() pr.addAttributes([QgsField(FELD_TYP, QVariant.String)]) layer.updateFields() return layer def _setze_kategorien_symbologie(layer) -> None: """Wendet Kategoriesymbologie an: grün/rot/orange Punkte. - ``identisch`` → grün - ``kein_pta`` → rot - ``fehlender_knick`` → orange Wird bei fehlendem QGIS-Kontext still ignoriert. """ try: from qgis.core import ( # type: ignore[import] QgsCategorizedSymbolRenderer, QgsRendererCategory, QgsMarkerSymbol, ) def _marker(color: str, size: str = "2") -> object: return QgsMarkerSymbol.createSimple( {"color": color, "size": size, "outline_style": "no"} ) categories = [ QgsRendererCategory( WERT_IDENTISCH, _marker("0,180,0,255"), "identisch (Knickpunkt mit PunktortTA)", ), QgsRendererCategory( WERT_KEIN_PTA, _marker("220,0,0,255"), "kein PunktortTA an Knickpunkt", ), QgsRendererCategory( WERT_FEHLENDER_KNICK, _marker("255,128,0,255"), "fehlender Knickpunkt (PunktortTA ohne BROR-Knickpunkt)", ), ] renderer = QgsCategorizedSymbolRenderer(FELD_TYP, categories) layer.setRenderer(renderer) except Exception: pass # Kein QGIS-Kontext (Tests) → Standarddarstellung behalten def _baue_pta_index(punktort_layer) -> Tuple[object, Dict[int, object]]: """Baut einen QgsSpatialIndex und eine ID→Geometrie-Map für PunktortTA. Parameters ---------- punktort_layer : Geladener ``AX_PunktortTA``-Layer. Returns ------- Tuple[QgsSpatialIndex, Dict[int, QgsGeometry]] Index und Feature-ID-zu-Geometrie-Dict. """ from qgis.core import QgsSpatialIndex # type: ignore[import] index = QgsSpatialIndex() geom_map: Dict[int, object] = {} for feat in punktort_layer.getFeatures(): geom = feat.geometry() if geom and not geom.isEmpty(): index.insertFeature(feat) geom_map[feat.id()] = geom return index, geom_map def _fehlende_knickpunkte_via_verbindungslinien( bror_union, pta_index, geom_map: Dict[int, object], matched_pta_ids: Set[int], punktort_layer, crs_authid: str, ) -> Tuple[int, List[object]]: """Ermittelt fehlende Knickpunkte via Verbindungslinien-Puffer. Bestimmt entlang der BROR-Außengrenze die Reihenfolge der gematchten ``AX_PunktortTA``. Für jedes konsekutive Paar wird eine gerade Verbindungslinie erzeugt und um :data:`VERBINDUNGSLINIE_PUFFER_M` gepuffert. Nicht-gematchte PunktortTA innerhalb dieses Puffers werden als fehlende Knickpunkte (orange) gewertet. Fügt dem QGIS-Projekt zwei Hilfs-Layer hinzu (falls ein Projekt-Kontext vorhanden ist): - *Knickpunktprüfung – Verbindungslinien* - *Knickpunktprüfung – Puffer* Parameters ---------- bror_union : Vereinigungsgeometrie des BROR-Polygons (``QgsGeometry``). pta_index : Spatial-Index der PunktortTA-Features (``QgsSpatialIndex``). geom_map : Feature-ID → ``QgsGeometry``-Mapping für PunktortTA. matched_pta_ids : IDs der bereits gematchten PunktortTA (identisch mit BROR-Knickpunkten). punktort_layer : Geladener ``AX_PunktortTA``-Layer (für zukünftige Erweiterungen reserviert; wird derzeit nicht direkt verwendet). crs_authid : CRS-Kennung des BROR-Layers, z.B. ``"EPSG:25833"``. Returns ------- Tuple[int, List[QgsFeature]] ``(fehlender_knick_count, orange_feats)`` – Anzahl gefundener fehlender Knickpunkte und Liste der zugehörigen ``QgsFeature``-Objekte. """ try: from qgis.core import QgsPointXY, QgsProject # type: ignore[import] except ImportError: return 0, [] if bror_union is None or bror_union.isEmpty(): return 0, [] # ------------------------------------------------------------------ # Alle Ringe extrahieren (Außen- und Innenringe) # ------------------------------------------------------------------ all_rings: List[List[object]] = [] if bror_union.isMultipart(): for poly in bror_union.asMultiPolygon(): for ring in poly: all_rings.append([QgsPointXY(x, y) for x, y in ring]) else: for ring in bror_union.asPolygon(): all_rings.append([QgsPointXY(x, y) for x, y in ring]) # Schlusspunkt (= Startpunkt, Ring geschlossen) entfernen cleaned_rings: List[List[object]] = [] for ring in all_rings: if len(ring) >= 2: if ( abs(ring[-1].x() - ring[0].x()) < 1e-9 and abs(ring[-1].y() - ring[0].y()) < 1e-9 ): ring = ring[:-1] if len(ring) >= 2: cleaned_rings.append(ring) line_geoms: List[object] = [] buffer_geoms: List[object] = [] orange_ids: Set[int] = set() # ------------------------------------------------------------------ # Pro Ring: gematchte PunktortTA in Reihenfolge, Paare bilden # ------------------------------------------------------------------ for ring_pts in cleaned_rings: # Gematchte PunktortTA auf diesem Ring lokalisieren pta_on_ring: List[Tuple[int, int, object]] = [] # (ring_idx, fid, pt) for fid in matched_pta_ids: pta_geom = geom_map.get(fid) if pta_geom is None: continue pta_pt = pta_geom.asPoint() for i, ring_pt in enumerate(ring_pts): dist_sq = ( (ring_pt.x() - pta_pt.x()) ** 2 + (ring_pt.y() - pta_pt.y()) ** 2 ) if dist_sq <= KOORDINATEN_TOLERANZ_M ** 2: pta_on_ring.append((i, fid, ring_pt)) break if len(pta_on_ring) < 2: continue pta_on_ring.sort(key=lambda t: t[0]) num_pta = len(pta_on_ring) for k in range(num_pta): _, _, pt1 = pta_on_ring[k] _, _, pt2 = pta_on_ring[(k + 1) % num_pta] # Gerade Verbindungslinie zwischen den zwei PunktortTA line_geom = QgsGeometry.fromPolylineXY([pt1, pt2]) if line_geom is None or line_geom.isEmpty(): continue line_geoms.append(line_geom) buffer_geom = line_geom.buffer(VERBINDUNGSLINIE_PUFFER_M, 8) if buffer_geom is None or buffer_geom.isEmpty(): continue buffer_geoms.append(buffer_geom) # Nicht-gematchte PunktortTA im Puffer suchen (via Spatial-Index) bb = buffer_geom.boundingBox() kandidaten = pta_index.intersects(bb) for fid in kandidaten: if fid in matched_pta_ids or fid in orange_ids: continue candidate_geom = geom_map.get(fid) if candidate_geom is None: continue try: if candidate_geom.within(buffer_geom): orange_ids.add(fid) except Exception: continue # ------------------------------------------------------------------ # Orange Features erstellen # ------------------------------------------------------------------ orange_feats: List[object] = [] for fid in orange_ids: pta_geom = geom_map.get(fid) if pta_geom is None: continue feat = QgsFeature() feat.setGeometry(pta_geom) feat.setAttributes([WERT_FEHLENDER_KNICK]) orange_feats.append(feat) # ------------------------------------------------------------------ # Hilfs-Layer erstellen und zum Projekt hinzufügen # ------------------------------------------------------------------ try: lines_layer = QgsVectorLayer( f"LineString?crs={crs_authid}", "Knickpunktprüfung – Verbindungslinien", "memory", ) lines_pr = lines_layer.dataProvider() line_feats_list = [] for lg in line_geoms: f = QgsFeature() f.setGeometry(lg) line_feats_list.append(f) lines_pr.addFeatures(line_feats_list) lines_layer.updateExtents() buffer_layer = QgsVectorLayer( f"Polygon?crs={crs_authid}", "Knickpunktprüfung – Puffer", "memory", ) buffer_pr = buffer_layer.dataProvider() buffer_feats_list = [] for bg in buffer_geoms: f = QgsFeature() f.setGeometry(bg) buffer_feats_list.append(f) buffer_pr.addFeatures(buffer_feats_list) buffer_layer.updateExtents() project = QgsProject.instance() if project is not None: project.addMapLayer(lines_layer) project.addMapLayer(buffer_layer) except Exception: pass # Kein QGIS-Kontext (Tests) → Layer-Erstellung überspringen return len(orange_feats), orange_feats # --------------------------------------------------------------------------- # Öffentliche API # --------------------------------------------------------------------------- def knickpunkt_pruefung( bror_layer, punktort_layer, flst_layer, progress=None, knickpunkte: Optional[List] = None, ) -> Tuple[Optional[object], int, int, int]: """Knickpunktbasierte BROR-Prüfung. Vergleicht BROR-Knickpunkte mit ``AX_PunktortTA``-Objekten und ermittelt fehlende Knickpunkte anhand von Flurstücksgrenzen. Parameters ---------- bror_layer : BauRaumOderBodenordnungsrecht-Polygon-Layer. punktort_layer : Geladener ``AX_PunktortTA``-Layer (Ergebnis aus ``grenzpunkt_alkis``). flst_layer : Nicht mehr verwendet. Wird aus Kompatibilitätsgründen weiterhin akzeptiert. progress : Optionaler ``ProgressDialog`` für Fortschrittsanzeige. knickpunkte : Vorberechnete Knickpunktliste (optional). Wenn ``None``, werden die Knickpunkte aus ``bror_layer`` extrahiert. Returns ------- Tuple[Optional[QgsVectorLayer], int, int, int] ``(result_layer, identisch_count, kein_pta_count, fehlender_knick_count)`` - ``result_layer``: Punkt-Layer mit grünen/roten/orangen Punkten, oder ``None`` bei Abbruch oder fehlgeschlagener Verarbeitung. - ``identisch_count``: Knickpunkte mit passendem PunktortTA (grün). - ``kein_pta_count``: Knickpunkte ohne passenden PunktortTA (rot). - ``fehlender_knick_count``: PunktortTA ohne BROR-Knickpunkt (orange). """ def _canceled() -> bool: return progress is not None and progress.is_canceled() def _schritt(label: str, wert: int) -> None: if progress is not None: progress.set_label(label) progress.set_value(wert) # ------------------------------------------------------------------ # Schritt 0: Knickpunkte ermitteln # ------------------------------------------------------------------ _schritt("Knickpunktprüfung: Knickpunkte extrahieren …", 0) if knickpunkte is None: knickpunkte = _extrahiere_knickpunkte(bror_layer) if not knickpunkte: return None, 0, 0, 0 n = len(knickpunkte) crs_authid = bror_layer.crs().authid() if _canceled(): return None, 0, 0, 0 # ------------------------------------------------------------------ # Schritt 1: Spatial-Index für PunktortTA aufbauen # ------------------------------------------------------------------ _schritt("Knickpunktprüfung: PunktortTA-Index aufbauen …", 1) if punktort_layer is None: return None, 0, 0, 0 try: pta_index, geom_map = _baue_pta_index(punktort_layer) except Exception: return None, 0, 0, 0 if _canceled(): return None, 0, 0, 0 # ------------------------------------------------------------------ # Schritte 2…n+1: Jeden Knickpunkt mit PunktortTA vergleichen # ------------------------------------------------------------------ try: from qgis.core import QgsRectangle # type: ignore[import] except ImportError: return None, 0, 0, 0 ergebnis_layer = _erstelle_ergebnis_layer(crs_authid) pr = ergebnis_layer.dataProvider() feats_to_add: List[object] = [] identisch_count = 0 kein_pta_count = 0 matched_pta_ids: Set[int] = set() for i, knick_pt in enumerate(knickpunkte): if _canceled(): return None, 0, 0, 0 _schritt(f"Knickpunktprüfung: Knickpunkt {i + 1}/{n} prüfen …", i + 2) x, y = knick_pt.x(), knick_pt.y() t = KOORDINATEN_TOLERANZ_M suchbox = QgsRectangle(x - t, y - t, x + t, y + t) kandidaten = pta_index.intersects(suchbox) gefunden = False for fid in kandidaten: pta_geom = geom_map.get(fid) if pta_geom is None: continue pta_pt = pta_geom.asPoint() dist = ((pta_pt.x() - x) ** 2 + (pta_pt.y() - y) ** 2) ** 0.5 if dist <= KOORDINATEN_TOLERANZ_M: gefunden = True matched_pta_ids.add(fid) break feat = QgsFeature() try: from qgis.core import QgsPointXY # type: ignore[import] feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y))) except Exception: continue feat.setAttributes([WERT_IDENTISCH if gefunden else WERT_KEIN_PTA]) feats_to_add.append(feat) if gefunden: identisch_count += 1 else: kein_pta_count += 1 if _canceled(): return None, 0, 0, 0 # ------------------------------------------------------------------ # Schritt n+2: Fehlende Knickpunkte (orange) via Verbindungslinien # ------------------------------------------------------------------ _schritt("Knickpunktprüfung: Fehlende Knickpunkte ermitteln …", n + 2) fehlender_knick_count = 0 try: bror_feats_union = [ f for f in bror_layer.getFeatures() if f.geometry() and not f.geometry().isEmpty() ] bror_union_geom = QgsGeometry.unaryUnion( [f.geometry() for f in bror_feats_union] ) except Exception: bror_union_geom = None if not _canceled(): fehlender_knick_count, orange_feats = _fehlende_knickpunkte_via_verbindungslinien( bror_union_geom, pta_index, geom_map, matched_pta_ids, punktort_layer, crs_authid, ) feats_to_add.extend(orange_feats) if _canceled(): return None, 0, 0, 0 # ------------------------------------------------------------------ # Schritt n+3: Ergebnislayer fertigstellen # ------------------------------------------------------------------ _schritt("Knickpunktprüfung: Ergebnislayer erstellen …", n + 3) pr.addFeatures(feats_to_add) ergebnis_layer.updateExtents() _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 Polygon-Teile mit allen Ringen (Außen- und Innenringe) # ------------------------------------------------------------------ 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_poly_parts = [poly for poly in bror_union.asMultiPolygon() if poly] else: poly = bror_union.asPolygon() all_poly_parts = [poly] if poly else [] except Exception: return None, None if not all_poly_parts: return None, None all_line_segments: List[Tuple[object, object]] = [] all_polygon_feats: List[object] = [] # ------------------------------------------------------------------ # Pro Polygon-Teil: alle Ringe (Außen + Innen) verarbeiten # ------------------------------------------------------------------ for poly_rings in all_poly_parts: corrected_qgs_rings: List[List[object]] = [] for raw_ring in poly_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 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 Ring 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: corrected_qgs_rings.append(ordered_pts + [ordered_pts[0]]) if not corrected_qgs_rings: continue # Erstes Element = Außenring, weitere = Innenringe (Löcher) polygon_geom = QgsGeometry.fromPolygonXY(corrected_qgs_rings) 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