CSV-Export: Nachbarschaftserkennung ueber Bounding-Box-Ueberschneidung (STRtree/shapely)
Neue Spalte "Nachbarn" (kommaseparierte TeileId-Liste) in export_csv.py: zwei Elemente gelten als benachbart, wenn ihre Bounding-Boxes (x/y-Ebene) sich ueberschneiden. Toleranz konfigurierbar in mm ueber cfg/export.cfg [Nachbarschaft] -> toleranz_mm, siehe lib/export_neighbors.py. Nur EXPORTCSV betroffen, EXPORTSIVAS bleibt unveraendert. shapely als neue Abhaengigkeit in requirements.txt ergaenzt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
export_neighbors.py - Ermittelt benachbarte Elemente ueber die Bounding-Box (STRtree).
|
||||
|
||||
Zwei Elemente gelten als benachbart, wenn ihre Bounding-Boxes sich in der
|
||||
x/y-Ebene (Grundriss, Z/Hoehe wird ignoriert) ueberschneiden. Um auch nah
|
||||
beieinander liegende, aber nicht direkt beruehrende Elemente (z.B. Kreisel)
|
||||
als benachbart zu erkennen, wird jede Bounding-Box vor der Pruefung um die
|
||||
Haelfte der konfigurierten Toleranz nach allen Seiten erweitert - zwei
|
||||
Elemente mit einem Abstand <= toleranz_mm gelten dann ebenfalls als
|
||||
benachbart. Die Toleranz wird aus cfg/export.cfg ([Nachbarschaft] ->
|
||||
toleranz_mm) gelesen.
|
||||
|
||||
Nur von export_csv.py genutzt (EXPORTCSV), nicht von export_sivas.py.
|
||||
"""
|
||||
|
||||
import configparser
|
||||
|
||||
from shapely.geometry import box
|
||||
from shapely.strtree import STRtree
|
||||
|
||||
from export_blockpatterns import cfg_path_from_env
|
||||
|
||||
|
||||
def load_neighbor_tolerance_mm(cfg_path=None):
|
||||
"""Liest die Nachbarschafts-Toleranz (mm) aus export.cfg. Default: 0."""
|
||||
if cfg_path is None:
|
||||
cfg_path = cfg_path_from_env()
|
||||
parser = configparser.ConfigParser(inline_comment_prefixes=("#",))
|
||||
parser.read(cfg_path, encoding="utf-8")
|
||||
return parser.getfloat("Nachbarschaft", "toleranz_mm", fallback=0.0)
|
||||
|
||||
|
||||
def compute_neighbor_ids(items, tolerance_mm):
|
||||
"""Ermittelt je Item die IDs (item["teileid"]) ueberschneidender Nachbarn.
|
||||
|
||||
items = Liste von dict mit "teileid" und optional "_bbox"
|
||||
({"cx","cy","dx","dy",...}, siehe export.lsp csv:get-bbox / export_csv.py
|
||||
bbox_columns). Items ohne _bbox (z.B. die synthetische Omniflo-Sum-Zeile)
|
||||
haben keine Nachbarn.
|
||||
|
||||
Rueckgabe: Liste von kommaseparierten Nachbar-ID-Strings, positionsgleich
|
||||
zu items (nicht ueber die ID dedupliziert, da TeileId nicht zwingend
|
||||
eindeutig ist).
|
||||
"""
|
||||
idx_with_bbox = [i for i, it in enumerate(items) if it.get("_bbox")]
|
||||
result = [""] * len(items)
|
||||
if len(idx_with_bbox) < 2:
|
||||
return result
|
||||
|
||||
half_tol = tolerance_mm / 2.0
|
||||
geoms = []
|
||||
for i in idx_with_bbox:
|
||||
bb = items[i]["_bbox"]
|
||||
minx = bb["cx"] - bb["dx"] / 2.0 - half_tol
|
||||
maxx = bb["cx"] + bb["dx"] / 2.0 + half_tol
|
||||
miny = bb["cy"] - bb["dy"] / 2.0 - half_tol
|
||||
maxy = bb["cy"] + bb["dy"] / 2.0 + half_tol
|
||||
geoms.append(box(minx, miny, maxx, maxy))
|
||||
|
||||
tree = STRtree(geoms)
|
||||
for local_i, global_i in enumerate(idx_with_bbox):
|
||||
hits = tree.query(geoms[local_i], predicate="intersects")
|
||||
neighbor_ids = [
|
||||
items[idx_with_bbox[int(local_j)]]["teileid"]
|
||||
for local_j in hits
|
||||
if int(local_j) != local_i
|
||||
]
|
||||
result[global_i] = ", ".join(neighbor_ids)
|
||||
|
||||
return result
|
||||
Reference in New Issue
Block a user