Trovare i segmenti di linea più vicini a Point usando ben fatto?


17

sfondo

Da un punto noto, ho bisogno di stabilire il "perimetro visibile" circostante più vicino su una tabella di MultiLineStrings, come mostrato nel diagramma.

Ho cercato questo sito con una serie di termini (ad es. Bordo minimo, perimetro minimo, vicino più vicino, clip, contenente poligono, visibilità, snap, nodi di taglio, ray-trace, riempimento di alluvione, bordo interno, percorso, scafo concavo) ma non riesco a trovare alcuna domanda precedente che sembra corrispondere a questo scenario.

Diagramma

  • Il cerchio verde è il punto noto.
  • Le linee nere sono note MultiLineStrings.
  • Le linee grigie sono un'indicazione di uno sweep radiale dal punto noto.
  • I punti rossi sono l'intersezione più vicina dello sweep radiale e MultiLineStrings.

inserisci qui la descrizione dell'immagine

parametri

  • Il punto non intersecherà mai MultiLineStrings.
  • Il punto sarà sempre centrato nominalmente all'interno di MultiLineStrings.
  • Le MultiLineString non racchiuderanno mai completamente il Punto, pertanto il perimetro sarà una MultiLineString.
  • Ci sarà una tabella contenente circa 1.000 MultiLineStrings (normalmente contenente una sola riga di circa 100 punti).

Metodologia considerata

  • Intraprendi una radiale costruendo una serie di linee dal Punto noto (ad esempio, con incrementi di 1 grado).
  • Stabilire il punto di intersezione più vicino di ciascuna linea di sweep radiale con MultiLineStrings.
  • Quando una delle linee di sweep radiali non si interseca con nessuna delle MultiLineString, ciò indicherebbe uno spazio nel perimetro che verrebbe sistemato nella costruzione del MultiLineString perimetrale.

Sommario

Mentre questa tecnica troverà le intersezioni più vicine, non troverà necessariamente tutti i punti del nodo perimetrale più vicini, a seconda della risoluzione dello sweep radiale. Qualcuno può raccomandare un metodo alternativo per stabilire tutti i punti perimetrali o integrare la tecnica di sweep radiale con una qualche forma di buffering, settaggio o compensazione?

Software

La mia preferenza è quella di utilizzare SpatiaLite e / o Shapely per la soluzione, ma gradirei qualsiasi suggerimento che possa essere implementato utilizzando software open source.

Modifica: soluzione di lavoro (basata sulla risposta di @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}})

Normalmente uno sweep radiale non ha una "risoluzione" significativa: scansiona da un "evento" all'altro in ordine, in cui gli eventi consistono dei nodi originali delle polilinee e delle loro intersezioni reciproche (che possono essere trovati dinamicamente mentre si spostano attorno all'originale nodi). Il suo output sarà perfettamente accurato.
whuber

Risposte:


17

Ho riprodotto il tuo esempio con shapefile.

Puoi usare Shapely e Fiona per risolvere il tuo problema.

1) Il tuo problema (con un formoso Point):

inserisci qui la descrizione dell'immagine

2) a partire da una linea arbitraria (con una lunghezza adeguata):

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

inserisci qui la descrizione dell'immagine

3) usare shapely.affinity.rotate per creare i raggi (ruotando la linea dal punto, guarda anche la risposta di Mike Toews su Python, libreria formosa: è possibile eseguire un'operazione affine sul poligono forma? ):

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)]

inserisci qui la descrizione dell'immagine

4) ora, usando shapely: cascaded_union (o shapely: unary_union ) per ottenere un MultiLineString:

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

5) lo stesso con le linee originali (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) viene calcolata l'intersezione tra le due multigeometrie e il risultato viene salvato in un file di forma:

 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}})

Risultato:

inserisci qui la descrizione dell'immagine

7) ma, problema, se usi un raggio più lungo, il risultato è diverso:

inserisci qui la descrizione dell'immagine

8) E se vuoi ottenere il tuo risultato, devi selezionare solo un punto con la distanza più breve dal punto originale su un raggio:

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}})

Risultato finale:

inserisci qui la descrizione dell'immagine

Spero che sia quello che vuoi.


1
Risposta eccellente - particolarmente istruttiva per quanto riguarda l'utilizzo di Fiona per input / output tramite shapefile. Ho aggiunto del codice alla mia domanda che utilizza la tua risposta e l'ho modificato per ridurre la quantità di intersectioncalcoli richiesti. Grazie.
Rusty Magoo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.