Generare poligoni di uguali dimensioni in linea con PyQGIS?


42

Vorrei creare poligoni lungo una linea per usarli per AtlasCreator nel prossimo passaggio.

ArcMap ha uno strumento chiamato Funzionalità dell'indice delle mappe delle strisce .

Con questo strumento posso scegliere l'altezza e la larghezza dei miei poligoni (diciamo 8 km x 4 km) e produrli / ruotarli automaticamente lungo la linea.

Uno degli attributi generati di ciascun poligono è l'angolo di rotazione di cui ho bisogno per ruotare le mie frecce nord in Atlas Generator in seguito.

inserisci qui la descrizione dell'immagine

Qualcuno ha idea di come risolvere questo compito in QGIS / con pyQGIS? Andrebbero bene anche gli algoritmi Grass o SAGA o un modello di cassetta degli attrezzi che potrebbe essere utilizzato all'interno di un plug-in personalizzato;) Edit1: Non ho bisogno solo delle estensioni di stampa ma anche dei poligoni stessi, perché voglio stampare una mappa con tutti i poligoni / estensioni come una sorta di mappa panoramica.

Edit2: sto offrendo una ricompensa mentre sto ancora cercando una soluzione PyQGIS che può essere utilizzata in un plug-in QGIS senza la necessità di installare un software a parte QGIS (nessun RDBMS come PostGIS / Oracle)


4
Sembra un'idea divertente per un plugin.
alphabetasoup,

1
Come pensiero selvaggio, penso che qualcosa basato sulla generalizzazione di Peucker-Douglas potrebbe funzionare
plablo09

1
forse v.split.length, quindi tracciare una linea retta tra l'inizio e il punto finale dei segmenti e quindi v.buffer con l'opzione "Non creare tappi alle estremità delle polilinee"
Thomas B

1
Mi piacerebbe iniziare una taglia su questa domanda ma non ho ancora abbastanza reputazione; (
Berlinmapper

2
Potrebbe esserci del codice riutilizzabile nelle implementazioni "etichetta-follow line". I tuoi rettangoli sono come impronte di glifi di un carattere a spaziatura fissa.
user30184

Risposte:


29

Domanda interessante! È qualcosa che volevo provare da solo, quindi ho provato.

Puoi farlo in PostGRES / POSTGIS con una funzione che genera un insieme di poligoni.

Nel mio caso, ho una tabella con una caratteristica (una MULTILINESTRING) che rappresenta una linea ferroviaria. Deve usare un CRS in metri, sto usando osgb (27700). Ho fatto "pagine" di 4 km x 2 km.

Qui puoi vedere il risultato ... la sostanza verde è la rete stradale, agganciata a un buffer di 1 km attorno alla ferrovia, che corrisponde bene all'altezza dei poligoni.

cartina a strisce generata da postgis

Ecco la funzione ...

CREATE OR REPLACE FUNCTION getAllPages(wid float, hite float, srid integer, overlap float) RETURNS SETOF geometry AS
$BODY$
DECLARE
    page geometry; -- holds each page as it is generated
    myline geometry; -- holds the line geometry
    startpoint geometry;
    endpoint geometry;
    azimuth float; -- angle of rotation
    curs float := 0.0 ; -- how far along line left edge is
    step float;
    stepnudge float;
    currpoly geometry; -- used to make pages
    currline geometry;
    currangle float;
    numpages float;
BEGIN
    -- drop ST_LineMerge call if using LineString 
    -- replace this with your table.
    SELECT ST_LineMerge(geom) INTO myline from traced_osgb; 
    numpages := ST_Length(myline)/wid;

    step := 1.0/numpages;
    stepnudge := (1.0-overlap) * step; 
    FOR r in 1..cast (numpages as integer)
    LOOP
        -- work out current line segment

        startpoint :=  ST_SetSRID(ST_Line_Interpolate_Point(myline,curs),srid);
        endpoint :=  ST_SetSRID(ST_Line_Interpolate_Point(myline,curs+step),srid);
        currline := ST_SetSRID(ST_MakeLine(startpoint,endpoint),srid);

        -- make a polygon of appropriate size at origin of CRS
        currpoly := ST_SetSRID(ST_Extent(ST_MakeLine(ST_MakePoint(0.0,0.0),ST_MakePoint(wid,hite))),srid);

        -- then nudge downwards so the midline matches the current line segment
        currpoly := ST_Translate(currpoly,0.0,-hite/2.0);

        -- Rotate to match angle
        -- I have absolutely no idea how this bit works. 
        currangle := -ST_Azimuth(startpoint,endpoint) - (PI()/2.0) + PI();
        currpoly := ST_Rotate(currpoly, currangle);

        -- then move to start of current segment
        currpoly := ST_Translate(currpoly,ST_X(startpoint),ST_Y(startpoint));

        page := currpoly;

        RETURN NEXT page as geom; -- yield next result
        curs := curs + stepnudge;
    END LOOP;
    RETURN;
END
$BODY$
LANGUAGE 'plpgsql' ;

Usando questa funzione

Ecco un esempio; Pagine da 4 km x 2 km, epsg: 27700 e sovrapposizione del 10%

select st_asEwkt(getallpages) from getAllPages(4000.0, 2000.0, 27700, 0.1);

Dopo averlo eseguito, è possibile esportare da PgAdminIII in un file CSV. Puoi importarlo in QGIS, ma potresti aver bisogno di impostare manualmente il CRS per il layer - QGIS non usa SRID in EWKT per impostare il layer CRS per te: /

Aggiunta dell'attributo cuscinetto

Questo è probabilmente più facile con Postgis, può essere fatto con espressioni QGIS ma dovrai scrivere del codice. Qualcosa come questo...

create table pages as (
    select getallpages from getAllPages(4000.0, 2000.0, 27700, 0.1)
);

alter table pages add column bearing float;

update pages set bearing=ST_Azimuth(ST_PointN(getallpages,1),ST_PointN(getallpages,2));

Avvertenze

È un po 'compromesso e ha avuto solo la possibilità di testare su un set di dati.

Non sono sicuro al 100% di quali due vertici dovrai scegliere sull'aggiornamento dell'attributo di rilevamento query.. potrebbe essere necessario sperimentare.

Devo confessare che non ho idea del motivo per cui devo fare una formula così contorta per ruotare il poligono in modo che corrisponda al segmento di linea corrente. Pensavo di poter usare l'output di ST_Azimuth () in ST_Rotate (), ma apparentemente no.


la tua risposta è davvero grandiosa e qualcosa che proverò di sicuro. Una limitazione per me è che non posso usare Postgres per il progetto su cui sto lavorando e ho bisogno di qualcosa che non dipende dal lato server. Ma forse posso usare il tuo ottima logica per riprodurre qualcosa del genere con pyQGIS.
Berlinmapper,

2
in tal caso, dai un'occhiata alla classe QgsGeometry . Ha un sottoinsieme delle operazioni geometriche di PostGIS e sarà un buon punto di partenza se si desidera seguire il percorso pyQGIS. L'algoritmo dovrebbe essere portabile su pyQGIS ..
Steven Kay il

3
Penso che per Postgis un approccio che utilizza ST_Simplify per generare linee di riferimento e spezzare la linea in segmenti e quindi usare ST_Buffer e ST_Envelope sarebbe più breve ed efficiente.
Matthias Kuhn,

@Matthias Kuhn: se spezzassi la linea in segmenti avrei potuto ottenere linee di uguali dimensioni ma non necessariamente anche poligoni di uguali dimensioni. per esempio se la linea è piuttosto "sinuosa" il poligono sarebbe probabilmente più corto, no?
Berlinmapper,

2
Ho testato la tua soluzione e la versione PyQGIS del tuo script. Qualche idea su come risolvere alcuni problemi minori rimasti: bit.ly/1KL7JHn ?
Berlinmapper,

12

Esistono diverse soluzioni. E questo può funzionare con polilinea semplice e più entità selezionate

diagramma a blocchi:

  1. parametri

    1. seleziona l'orientamento per la generazione e leggi l'indice (da sinistra a destra, da nord a sud ...)
    2. imposta la dimensione dell'oggetto

    shape = (4000,8000) # (<width>,<length>)
    1. definire la sovrapposizione coef (10% di default?)
  2. dentro
    1. Ordinare la polilinea (confrontare il punto iniziale e quello finale) L'ordinamento dipende dalla scelta dell'orientamento> creare un vertice per ordinare i nomi degli ordini
  3. loop su OrderNodes

    1. crea il tuo primo punto come ancora

    2. per ogni vertice aggiungilo su dict x, y, id e calcola un vettore

    3. genera poligono (oltre la lunghezza e l'orientamento del vettore) riducendo la sovrapposizione (10% / 2)> 5% poligono sinistro 5% poligono destro con lo stesso punto di ancoraggio
    4. Fermati quando un punto di vertice precedente è fuori dal poligono o se len vettore è> per modellare la lunghezza
    5. Generare il poligono con una buona soluzione precedente e impostare il punto di ancoraggio con l'ultima buona posizione
    6. Esegui nuovo loop e reimposta dict x, y, id per generare il successivo oggetto poligono.

Puoi modificare questa proposta se non è davvero chiara o commentata.


sembra sofisticato ma devo ammettere che non so ancora come usarlo per il modellatore o PyQGIS. a proposito: cos'è un coefficiente di sovrapposizione ?.
Berlinmapper,

@Berlinmapper che fa parte del poligono con sovrapposizione 8000 x 10% in questo caso. Puoi sceglierne un altro o creare una supperposizione di distanza fissa tra poligono. Puoi vederlo in tutti gli atlanti per indicare la pagina successiva delle tessere nell'angolo
GeoStoneMarten,

la tua soluzione è pensata per essere utilizzata con pyQGIS o con la toolbox di elaborazione? suona benissimo ma non so ancora come procedere
Berlinmapper

1
@Berlinmapper Penso che sia necessario utilizzare pyQGIS per creare lo script di processo e impostare i parametri di input e output nella toolbox di elaborazione o nel plugin QGIS. Come arcgistoolbox. In realtà non ho tempo per farlo e testarlo.
GeoStoneMarten,

12

Steven Kays risponde in pyqgis. Basta selezionare le linee nel livello prima di eseguire lo script. Lo script non supporta la fusione di riga, quindi non può funzionare su layer con multilinestring

#!python
# coding: utf-8

# https://gis.stackexchange.com/questions/173127/generating-equal-sized-polygons-along-line-with-pyqgis
from qgis.core import QgsMapLayerRegistry, QgsGeometry, QgsField, QgsFeature, QgsPoint
from PyQt4.QtCore import QVariant


def getAllPages(layer, width, height, srid, overlap):
    for feature in layer.selectedFeatures():
        geom = feature.geometry()
        if geom.type() <> QGis.Line:
            print "Geometry type should be a LineString"
            return 2
        pages = QgsVectorLayer("Polygon?crs=epsg:"+str(srid), 
                      layer.name()+'_id_'+str(feature.id())+'_pages', 
                      "memory")
        fid = QgsField("fid", QVariant.Int, "int")
        angle = QgsField("angle", QVariant.Double, "double")
        attributes = [fid, angle]
        pages.startEditing()
        pagesProvider = pages.dataProvider()
        pagesProvider.addAttributes(attributes)
        curs = 0
        numpages = geom.length()/(width)
        step = 1.0/numpages
        stepnudge = (1.0-overlap) * step
        pageFeatures = []
        r = 1
        currangle = 0
        while curs <= 1:
            # print 'r =' + str(r)
            # print 'curs = ' + str(curs)
            startpoint =  geom.interpolate(curs*geom.length())
            endpoint = geom.interpolate((curs+step)*geom.length())
            x_start = startpoint.asPoint().x()
            y_start = startpoint.asPoint().y()
            x_end = endpoint.asPoint().x()
            y_end = endpoint.asPoint().y()
            # print 'x_start :' + str(x_start)
            # print 'y_start :' + str(y_start)
            currline = QgsGeometry().fromWkt('LINESTRING({} {}, {} {})'.format(x_start, y_start, x_end, y_end))
            currpoly = QgsGeometry().fromWkt(
                'POLYGON((0 0, 0 {height},{width} {height}, {width} 0, 0 0))'.format(height=height, width=width))
            currpoly.translate(0,-height/2)
            azimuth = startpoint.asPoint().azimuth(endpoint.asPoint())
            currangle = (startpoint.asPoint().azimuth(endpoint.asPoint())+270)%360
            # print 'azimuth :' + str(azimuth)
            # print 'currangle : ' +  str(currangle)

            currpoly.rotate(currangle, QgsPoint(0,0))
            currpoly.translate(x_start, y_start)
            currpoly.asPolygon()
            page = currpoly
            curs = curs + stepnudge
            feat = QgsFeature()
            feat.setAttributes([r, currangle])
            feat.setGeometry(page)
            pageFeatures.append(feat)
            r = r + 1

        pagesProvider.addFeatures(pageFeatures)
        pages.commitChanges()
        QgsMapLayerRegistry.instance().addMapLayer(pages)
    return 0

layer = iface.activeLayer()
getAllPages(layer, 500, 200, 2154, 0.4)

1
Grande. Ho provato la soluzione. Qualche idea su come risolvere questi problemi che la soluzione ha ancora: bit.ly/1KL7JHn ?
Berlinmapper,


grazie grande risorsa per capire come funziona lo strumento ArcMap. sfortunatamente non sono abituato a VB ma forse qualcun altro può usarlo per pubblicare una risposta / commento;)
Berlinmapper

4

Le due risposte (al momento della pubblicazione) sono ingegnose e ben spiegate. Tuttavia, esiste anche una soluzione MOLTO semplice ma efficace per questo (supponendo che accetti tutte le tue mappe allineate a nord in modo tradizionale, piuttosto che una direzione nord casuale basata sul fiume). Se vuoi rotazioni, è possibile ma un po 'più complesso (vedi in basso).

Per prima cosa dai un'occhiata al mio post qui . Questo ti dà una guida per la creazione di coperture di mappe per Atlas. Il metodo che desideri è un adattamento di "Workflow 2" nel tutorial. Dividi la tua funzione lineare per vertici o lunghezza e bufferizza le funzionalità per qualsiasi importo. L'importo del buffer determina in parte la sovrapposizione (ma vedi sotto) ma, cosa più importante, crea una funzione con un'area. È possibile utilizzare un numero qualsiasi di plugin per dividere le linee ma GRASS v.split.length e v.split.vert sono buone opzioni (disponibili in Processing Toolbox).

Dopo aver abilitato Atlas Generation in Map Composer e selezionato il layer bufferizzato, tornare alla scheda degli elementi e selezionare l'oggetto della mappa. Seleziona "Controllato da Atlas" e, nel tuo caso d'uso, opterei per Margin around feature. Questo controllerà la tua sovrapposizione tra le mappe (in alternativa potresti preferire la scala fissa).

Puoi visualizzare l'anteprima di Atlas usando il pulsante Anteprima Atlas nella barra degli strumenti in alto del compositore e vedere quante pagine produrrà. Nota che puoi scegliere di esportare tutte le pagine in un singolo PDF o come file separati.

Per far ruotare la mappa lungo la linea, c'è un campo di rotazione nelle proprietà dell'elemento Compositore mappe. Dovrai impostare un'espressione (usa il piccolo pulsante a destra della casella di rotazione). Seleziona la variabile come opzione e poi Modifica. Verrà visualizzato un generatore di espressioni e qui potrai accedere alla geometria o ai campi delle funzioni dell'atlante. È quindi possibile creare un espresso per ruotare la mappa in base alla rotazione delle funzioni (è possibile calcolare il rilevamento utilizzando i punti iniziale e finale di ciascun segmento di linea e un po 'di trigantino). Ripeti la stessa procedura per ruotare la freccia del nord (usando la stessa espressione o variabile pre-calcolata).


grazie per questa soluzione. ma penso che in questo modo non otterrei poligoni delle singole estensioni che voglio stampare. ho bisogno che producano anche una "mappa panoramica" con tutte le estensioni di stampa.
Berlinmapper,
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.