Finden Sie mit shapely die nächstgelegenen Liniensegmente für Point?

17

Hintergrund

Von einem bekannten Punkt aus muss der nächstgelegene "sichtbare Umfang" anhand einer Tabelle mit MultiLineStrings ermittelt werden, wie in der Abbildung dargestellt.

Ich habe diese Site mit einer Reihe von Begriffen durchsucht (z. B. Mindestkante, Mindestumfang, nächster Nachbar, Clip mit Polygon, Sichtbarkeit, Fang, Schnittknoten, Ray-Trace, Überflutungsfüllung, innere Begrenzung, Routing, konkaver Rumpf), aber Ich kann keine vorherige Frage finden, die diesem Szenario zu entsprechen scheint.

Diagramm

  • Der grüne Kreis ist der bekannte Punkt.
  • Die schwarzen Linien sind die bekannten MultiLineStrings.
  • Die grauen Linien sind ein Hinweis auf eine radiale Abweichung vom bekannten Punkt.
  • Die roten Punkte sind der nächstgelegene Schnittpunkt von Radial Sweep und MultiLineStrings.

Bildbeschreibung hier eingeben

Parameter

  • Der Punkt schneidet niemals die MultiLineStrings.
  • Der Punkt wird immer nominal in den MultiLineStrings zentriert.
  • Die MultiLineStrings werden den Punkt niemals vollständig einschließen, daher wird der Umfang eine MultiLineString sein.
  • Es wird eine Tabelle mit ungefähr 1.000 MultiLineStrings geben (normalerweise mit einer einzelnen Linie von ungefähr 100 Punkten).

Betrachtete Methodik

  • Führen Sie einen Radial-Sweep durch, indem Sie eine Reihe von Linien vom bekannten Punkt aus konstruieren (z. B. in Schritten von 1 Grad).
  • Stellen Sie mit den MultiLineStrings den nächstgelegenen Schnittpunkt jeder radialen Sweep-Linie fest.
  • Wenn eine der radialen Sweep-Linien keine der MultiLineStrings schneidet, weist dies auf eine Lücke im Umfang hin, die in der MultiLineString-Umfangskonstruktion Platz findet.

Zusammenfassung

Während diese Technik die nächsten Schnittpunkte findet, werden abhängig von der Auflösung des radialen Sweeps nicht unbedingt alle nächsten Umfangsknotenpunkte gefunden. Kann jemand eine alternative Methode empfehlen, um alle Umfangspunkte festzulegen oder die Radial-Sweep-Technik durch eine Art Pufferung, Sektorierung oder Versetzung zu ergänzen?

Software

Ich bevorzuge die Verwendung von SpatiaLite und / oder Shapely für die Lösung, würde jedoch alle Vorschläge begrüßen, die mit Open-Source-Software umgesetzt werden könnten.

Bearbeiten: Arbeitslösung (basierend auf der Antwort von @gene)

from shapely.geometry import Point, LineString, mapping, shape
from shapely.ops import cascaded_union
from shapely import affinity
import fiona

sweep_res = 10  # sweep resolution (degrees)
focal_pt = Point(0, 0)  # radial sweep centre point
sweep_radius = 100.0  # sweep radius

# create the radial sweep lines
line = LineString([(focal_pt.x,focal_pt.y), \
                   (focal_pt.x, focal_pt.y + sweep_radius)])

sweep_lines = [affinity.rotate(line, i, (focal_pt.x, focal_pt.y)) \
               for i in range(0, 360, sweep_res)]

radial_sweep = cascaded_union(sweep_lines)

# load the input lines and combine them into one geometry
input_lines = fiona.open("input_lines.shp")
input_shapes = [shape(f['geometry']) for f in input_lines]
all_input_lines = cascaded_union(input_shapes)

perimeter = []
# traverse each radial sweep line and check for intersection with input lines
for radial_line in radial_sweep:
    inter = radial_line.intersection(all_input_lines)

    if inter.type == "MultiPoint":
       # radial line intersects at multiple points
       inter_dict = {}
       for inter_pt in inter:
           inter_dict[focal_pt.distance(inter_pt)] = inter_pt
       # save the nearest intersected point to the sweep centre point
       perimeter.append(inter_dict[min(inter_dict.keys())])

    if inter.type == "Point":
       # radial line intersects at one point only
       perimeter.append(inter)

    if inter.type == "GeometryCollection":
       # radial line doesn't intersect, so skip
       pass

# combine the nearest perimeter points into one geometry
solution = cascaded_union(perimeter)

# save the perimeter geometry
schema = {'geometry': 'MultiPoint', 'properties': {'test': 'int'}}
with fiona.open('perimeter.shp', 'w', 'ESRI Shapefile', schema) as e:
     e.write({'geometry':mapping(solution), 'properties':{'test':1}})
Rusty Magoo
quelle
Normalerweise hat ein Radial-Sweep keine sinnvolle "Auflösung": Er tastet von einem "Ereignis" zum nächsten ab, wobei Ereignisse aus den ursprünglichen Knoten der Polylinien und ihren gegenseitigen Schnittpunkten bestehen (die dynamisch gefunden werden können, während Sie über das Original streichen Knoten). Die Ausgabe ist absolut genau.
whuber

Antworten:

17

Ich habe Ihr Beispiel mit Shapefiles reproduziert.

Sie können Shapely und Fiona verwenden , um Ihr Problem zu lösen.

1) Dein Problem (mit einem wohlgeformten Point):

Bildbeschreibung hier eingeben

2) beginnend mit einer beliebigen Zeile (mit einer angemessenen Länge):

from shapely.geometry import Point, LineString
line = LineString([(point.x,point.y),(final_pt.x,final_pt.y)])

Bildbeschreibung hier eingeben

3) Verwenden von shapely.affinity.rotate zum Erstellen der Radien (Drehen der Linie vom Punkt aus, siehe auch die Antwort von Mike Toews in Python, shapely library: Ist es möglich, eine affine Operation an einem Formpolygon durchzuführen? ):

from shapely import affinity
# Rotate i degrees CCW from origin at point (step 10°)
radii= [affinity.rotate(line, i, (point.x,point.y)) for i in range(0,360,10)]

Bildbeschreibung hier eingeben

4) jetzt, mit wohlgeformten: cascaded_union (oder wohlgeformt: unary_union ) erhalten eine Mehr:

from shapely.ops import cascaded_union
mergedradii = cascaded_union(radii)
print mergedradii.type
MultiLineString

5) das gleiche mit den ursprünglichen Linien (Shapefile)

import fiona
from shapely.geometry import shape
orlines = fiona.open("orlines.shp")
shapes = [shape(f['geometry']) for f in orlines]
mergedlines = cascaded_union(shapes)
print mergedlines.type
MultiLineString

6) Der Schnittpunkt zwischen den beiden Multigeometrien wird berechnet und das Ergebnis in einem Shapefile gespeichert:

 points =  mergedlines.intersection(mergedradii)
 print points.type
 MultiPoint
 from shapely.geometry import mapping
 schema = {'geometry': 'MultiPoint','properties': {'test': 'int'}}
 with fiona.open('intersect.shp','w','ESRI Shapefile', schema) as e:
      e.write({'geometry':mapping(points), 'properties':{'test':1}})

Ergebnis:

Bildbeschreibung hier eingeben

7) Aber Problem, wenn Sie einen längeren Radius verwenden, ist das Ergebnis anders:

Bildbeschreibung hier eingeben

8) Und wenn Sie Ihr Ergebnis erhalten möchten, müssen Sie nur den Punkt mit dem kürzesten Abstand vom ursprünglichen Punkt auf einem Radius auswählen:

points_ok = []
for line in mergeradii:
   if line.intersects(mergedlines):
       if line.intersection(mergedlines).type == "MultiPoint":
          # multiple points: select the point with the minimum distance
          a = {}
          for pt in line.intersection(merged):
              a[point.distance(pt)] = pt
          points_ok.append(a[min(a.keys())])
       if line.intersection(mergedlines).type == "Point":
          # ok, only one intersection
          points_ok.append(line.intersection(mergedlines))
solution = cascaded_union(points_ok)
schema = {'geometry': 'MultiPoint','properties': {'test': 'int'}}
with fiona.open('intersect3.shp','w','ESRI Shapefile', schema) as e:
     e.write({'geometry':mapping(solution), 'properties':{'test':1}})

Endergebnis:

Bildbeschreibung hier eingeben

Ich hoffe das ist was du willst.

Gen
quelle
1
Hervorragende Antwort - besonders informativ in Bezug auf die Verwendung von Fiona für die Eingabe / Ausgabe über Shapefiles. Ich habe meiner Frage einen Code angehängt, der Ihre Antwort verwendet und geändert hat, um die Anzahl der intersectionerforderlichen Berechnungen zu verringern. Vielen Dank.
Rusty Magoo