Considerando buchi / vincoli nella creazione del poligono Voronoi in QGIS?


12

Sto cercando di creare poligoni voronoi in QGIS che considererebbero "buchi" nel dominio generale. Un esempio potrebbe essere:

inserisci qui la descrizione dell'immagine

In realtà ho creato i Voronois in questa immagine usando QGIS tramite il comando GRASS, quindi usando lo strumento "Differenza" per creare i buchi. Un layer poligonale separato, che contiene l'estensione dei fori, è stato utilizzato come livello "Differenza". Un'applicazione di esempio sarebbe la creazione di poligoni attorno ai punti di campionamento che sono stati raccolti tra le strutture che dovrebbero essere esclusi dall'analisi.

Qui sorgono due problemi:

  1. La funzione "differenza" non sembra funzionare correttamente al 100%, con alcuni confini poligonali che si estendono nei "buchi". Questo può essere risolto trovando la riga nella tabella degli attributi che non ha un numero ID poligonale (o ID "0").

  2. Questo tipo di "punzonatura" dopo il fatto può provocare poligoni discontinui, come mostrato dalla freccia rossa nell'immagine.

La mia domanda è: esiste uno strumento o un plugin Voronoi che può considerare la presenza di "buchi" al centro del dominio, come un processo in una fase, ed eliminare anche la generazione di poligoni discontinui? Immagino che un tale strumento estenderebbe un confine poligonale all'intersezione più vicina con un altro confine, a meno che quell'altro confine non sbatta prima contro un confine di "buca".


Questo sarebbe simile ma l'opposto di (penso) usare una maschera di ambiente in ArcGIS . Ciò ti consentirebbe di vincolare i poligoni creati entro un determinato limite. Tuttavia, non sono a conoscenza di alcuno strumento che utilizzi confini / buchi complessi (anche se forse in ArcGIS la maschera potrebbe essere così complessa - non l'ho provato e potrei provarlo più tardi se avessi tempo).
Chris W,

Ho testato la teoria di ArcGIS e non funzionerà. Per la domanda collegata, è possibile vincolare i risultati a una forma esterna. Tuttavia, un buco nella forma viene ignorato dai poli risultanti. Inoltre, se quel foro contiene anche alcuni punti, lo strumento si spegne e non riesce a funzionare. Non riesco a spiegare il tuo primo problema con differenza, ma il secondo risultante in scaglie non è del tutto inaspettato - dopotutto, quell'area sarebbe comunque allocata allo stesso punto anche se il buco è presente. È possibile utilizzare quel metodo e quindi incorporare le scaglie nei loro vicini con un metodo di pulizia.
Chris W,

2
Potresti potenzialmente risolverlo andando su raster. Con una maschera raster, la distanza euclidea verrebbe fuori dai tuoi punti fino a quando non colpirebbe le cellule che escono da un altro punto o la tua maschera raster (la tua descrizione di sbattimento al contorno). Quindi esegui un po 'di pulizia zonale e vettorizzi il risultato per ottenere poligoni.
Chris W,

1
Vorrei assicurarmi che la geometria voronoi sia valida eseguendo v.clean, quindi controllare la geometria. Infine, esegui Difference per creare i buchi.
Klewis,

Cosa vuol dire Voronoi su questi buchi? Non vuoi fare i buchi in modo pulito? Perché un layer poligonale non dovrebbe farlo?
mdsumner,

Risposte:


3

Ciò potrebbe essere possibile utilizzando raster. Per prima cosa converti i tuoi punti e poligoni di confine in un raster ad alta risoluzione. Imposta una maschera per i tuoi confini usando r.mask. Quindi, esegui r.grow.distanceGRASS e usa il Value= output. Questo ti darà per ogni pixel, che è il punto più vicino. Converti questo in poligoni vettoriali. Potrebbero essere necessari ulteriori passaggi per eliminare i poligoni del nastro.


2

Questo è certamente possibile con i raster.

Speriamo che questo screenshot mostri il problema più chiaramente. La porzione B del voronoi è più vicina "in linea d'aria" al centro originale del voronoi, ma ciò non tiene conto del fatto che ci vorrebbe più tempo per camminare intorno all'edificio. La mia comprensione della domanda del PO è che il voronoi deve tenere conto di questa distanza extra per camminare intorno all'edificio.

inserisci qui la descrizione dell'immagine

Mi piace il suggerimento di @Guillaume. Tuttavia, quando l'ho provato ho avuto problemi r.grow.distancead onorare la maschera (vedi sotto. Le increspature non dovrebbero passare attraverso gli edifici).

La mia conoscenza di Grass non è così forte come potrebbe essere, quindi forse sto facendo qualcosa di stupido. Sicuramente, controlla prima quel suggerimento in quanto sarà molto meno lavoro del mio ;-)

inserisci qui la descrizione dell'immagine

Passaggio 1: creare una superficie di costo

Il primo passo è creare una superficie di costo. Questo deve essere fatto solo una volta.

  • creare un livello modificabile, buchi e tutto.
  • aggiungi un campo chiamato 'unit', impostalo su 1.
  • usando il poligono-raster sul tuo livello vettoriale "perforato" (quello che ha i buchi), usando il campo 'unit'. Ora hai una "maschera" di livello, dove 1 è spazio libero e 0 sta costruendo.
  • usa la calcolatrice raster per trasformarla in una superficie di costo. Imposterò "all'aperto" su 1 e "interno" su 9999. Ciò renderà proibitivo il passaggio attraverso gli edifici.

    (( "Maschera @ 1" = 1) * 1) + (( "maschera @ 1" = 0) * 9999)

È possibile ottenere risultati più "organici" aggiungendo un po 'di rumore alla superficie dei costi (ad esempio, utilizzare un numero casuale da 1 a 3, anziché solo 1 per i pixel esterni).

Passaggio 2. Creare raster dei costi cumulativi per ciascun centro voronoi

Ora possiamo eseguire (per una cella voronoi alla volta) l'algoritmo GRASS sul r.cost.coordinatesnostro livello di superficie di costo.

Per le coordinate iniziali, utilizzare il centro vornoi. Per la coordinata finale, scegli uno degli angoli della tua area. Suggerisco di usare 'Knights Tour' in quanto ciò dà risultati più fluidi.

Il risultato mostra linee di uguale tempo di viaggio da un centro voronoi. Nota come le bande avvolgono gli edifici.

inserisci qui la descrizione dell'immagine

Non sono sicuro del modo migliore per automatizzare questo. Forse l'elaborazione in modalità batch o eseguita in pyqgis.

Passaggio 3. Unire i raster

Questo probabilmente avrà bisogno di codice. L'algoritmo sarebbe

create a raster 'A' to match the size of your cumulative cost images
fill raster 'A' with a suitably high number e.g. 9999
create an array of the same size as the raster.
for each cumulative cost raster number 1..N
    for each cell in image
        if cell < value in raster 'A'
            set value in raster 'A' to cell value
            set corresponding cell in array to cum. cost image number
write out array as a raster

Tale approccio dovrebbe produrre un raster in cui ogni cellula è classificata dal centro voronoi a cui è più vicina, tenendo conto degli ostacoli.

È quindi possibile utilizzare raster-to-poligono. È quindi possibile utilizzare il plug-in Generalize per rimuovere gli artefatti dell'effetto "step" dal raster.

Ci scusiamo per la vaghezza nei passaggi 2 e 3 ... Spero che qualcuno entri in contatto con una soluzione più elegante :)


1
Grazie Steven, ho una raster GRASS funzionante, ma speravo in una soluzione più elegante, come indicato nella descrizione della taglia.
underdark

0

Nota n. 1 : non sono stato in grado di riprodurre il problema proposto perché lo strumento Differenza ha funzionato bene per me in diversi test che ho eseguito (forse era dovuto alla semplice geometria del problema o perché lo strumento è stato migliorato da quando la domanda era chiesto 1 anno fa).

Tuttavia, propongo una soluzione alternativa in PyQGIS per evitare l'uso dello strumento Differenza . Tutto si basa sull'intersezione locale tra due livelli di input (vedere la figura seguente):

  1. uno strato poligonale vettoriale che rappresenta i poligoni Voronoi;
  2. uno strato vettoriale poligonale che rappresenta i fori / vincoli che devono essere esclusi dall'analisi.

inserisci qui la descrizione dell'immagine

Nota n. 2 : dal momento che non desidero utilizzare lo strumento Differenza , non sono in grado di evitare la creazione di "scaglie" (vedi quindi), quindi ho dovuto eseguire lo v.cleanstrumento per eliminarle. Inoltre, come ha detto @Chris W,

[...] ma il secondo risultante in schegge non è del tutto inaspettato - dopotutto, quell'area verrebbe comunque allocata allo stesso punto anche se il foro è presente. È possibile utilizzare quel metodo e quindi incorporare le scaglie nei loro vicini con un metodo di pulizia .

Dopo queste premesse necessarie, inserisco il mio codice:

##Voronoi_Polygons=vector polygon
##Constraints=vector polygon
##Voronoi_Cleaned=output vector

from qgis.core import *

voronoi = processing.getObject(Voronoi_Polygons)
crs = voronoi.crs().toWkt()
ex = voronoi.extent()
extent = '%f,%f,%f,%f' % (ex.xMinimum(), ex.xMaximum(), ex.yMinimum(), ex.yMaximum())

constraints = processing.getObject(Constraints)

# Create the output layer
voronoi_mod = QgsVectorLayer('Polygon?crs='+ crs, 'voronoi' , 'memory')
prov = voronoi_mod.dataProvider()
fields = voronoi.pendingFields() # Fields from the input layer
prov.addAttributes(fields) # Add input layer fields to the outLayer
voronoi_mod.updateFields()

# Spatial index containing all the 'constraints'
index_builds = QgsSpatialIndex()
for feat in constraints.getFeatures():
    index_builds.insertFeature(feat)

final_geoms = {}
final_attrs = {}

for feat in voronoi.getFeatures():
    input_geom = feat.geometry()
    input_attrs = feat.attributes()
    final_geom = []
    multi_geom = input_geom.asPolygon()
    input_geoms = [] # edges of the input geometry
    for k in multi_geom:
        input_geoms.extend(k)
    final_geom.append(input_geoms)
    idsList = index_builds.intersects(input_geom.boundingBox())
    mid_geom = [] # edges of the holes/constraints
    if len(idsList) > 0:
        req = QgsFeatureRequest().setFilterFids(idsList)
        for ft in constraints.getFeatures(req):
            geom = ft.geometry()
            hole = []
            res = geom.intersection(input_geom)
            res_geom = res.asPolygon()
            for i in res_geom:
                hole.extend(i)
                mid_geom.append(hole)
        final_geom.extend(mid_geom)
    final_geoms[feat.id()] = final_geom
    final_attrs[feat.id()] = input_attrs

# Add the features to the output layer
outGeom = QgsFeature()
for key, value in final_geoms.iteritems():
    outGeom.setGeometry(QgsGeometry.fromPolygon(value))
    outGeom.setAttributes(final_attrs[key])
    prov.addFeatures([outGeom])

# Add 'voronoi_mod' to the Layers panel
QgsMapLayerRegistry.instance().addMapLayer(voronoi_mod)

# Run 'v.clean'
processing.runalg("grass7:v.clean",voronoi_mod, 2, 0.1, extent, -1, 0.0001, Voronoi_Cleaned, None)

# Remove 'voronoi_mod' to the Layers panel
QgsMapLayerRegistry.instance().removeMapLayer(voronoi_mod)

che porta a questo risultato:

inserisci qui la descrizione dell'immagine

Solo per chiarezza, questo sarebbe il risultato senza l'uso dello v.cleanstrumento:

inserisci qui la descrizione dell'immagine

La differenza con il risultato di @LeaningCactus è che, ormai, le geometrie non sono rotte e potrebbero essere "pulite" senza errori .


Fai delle buche più lunghe, ad esempio tagliando l'intera mappa, come un fiume, e vedrai il problema. L'aggiunta di scaglie ai vicini crea poligoni che sembrano molto diversi da un diagramma Voronoi vincolato. Ci ho provato.
underdark

Scusa, non capisco: hai trovato qualche errore nei risultati? Ho testato il codice solo per quei casi in cui i poligoni erano simili a quelli proposti nella domanda.
mgri,

Purtroppo non è possibile testare il codice adesso, ma potresti mostrare i risultati ottenuti con la modifica dei fori tracciata in i.stack.imgur.com/Jpfra.png ?
underdark

Se estendo il vincolo fino alla funzione sulla destra, ottengo questo . Invece, se sposto direttamente il vincolo, ottengo questo .
mgri

Il piccolo triangolo a cui punta la freccia rossa nel mio disegno è il problema. Non dovrebbe essere lì, ma è anche nei tuoi risultati. Sembra che questo approccio risolva il problema n. 1 della domanda ma lascia il n. 2 irrisolto.
underdark
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.