Verfahrensgebietsbildung mit korrigierten Knickpunkten
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user