Poligoni leviganti nella mappa dei contorni?


52

Ecco una mappa di contorno per la quale sono disponibili tutti i poligoni dei livelli.

Chiediamo come levigare i poligoni mantenendo tutti i vertici conservati nella loro posizione esatta?

In effetti il ​​contorno viene creato sopra i dati di una griglia, è possibile suggerire quindi di uniformare i dati della griglia e quindi il contorno risultante sarà più uniforme. Si noti che questo non funziona come desidero dal momento che la funzione di smoothing come il filtro gaussiano rimuoverà piccoli pacchetti di dati e cambierà l'intervallo della terza variabile, ad esempio l'altezza non consentita nella mia applicazione.

In realtà sto cercando un pezzo di codice (preferibilmente in Python ) che possa fare il livellamento di poligoni 2D (qualsiasi tipo: convesso, concavo, autointersecante ecc.) Ragionevolmente indolore (dimentica le pagine di codici) e preciso.

Cordiali saluti, c'è una funzione in ArcGIS che lo fa perfettamente, ma l'uso di applicazioni commerciali di terze parti non è la mia scelta per questa domanda.

inserisci qui la descrizione dell'immagine


1)

Scipy.interpolate:

inserisci qui la descrizione dell'immagine

Come vedi le spline risultanti (rosso) non sono soddisfacenti!

2)

Ecco il risultato usando il codice indicato qui . Non funziona bene!

inserisci qui la descrizione dell'immagine

3)

Per me la soluzione migliore dovrebbe essere qualcosa di simile alla figura seguente in cui un quadrato viene smussato gradualmente cambiando solo un valore. Spero in un concetto simile per levigare qualsiasi forma di poligoni.

inserisci qui la descrizione dell'immagine

Soddisfare la condizione che la spline superi i punti:

inserisci qui la descrizione dell'immagine

4)

Ecco la mia implementazione dell '"idea di whuber" riga per riga in Python sui suoi dati. Probabilmente ci sono alcuni bug poiché i risultati non sono buoni.

inserisci qui la descrizione dell'immagine

K = 2 è un disastro e quindi per k> = 4.

5)

Ho rimosso un punto nella posizione problematica e la spline risultante è identica a quella di whuber. Ma è ancora una domanda che perché il metodo non funziona in tutti i casi?

inserisci qui la descrizione dell'immagine

6)

Un buon livellamento per i dati di whuber può essere il seguente (disegnato da un software di grafica vettoriale) in cui è stato aggiunto senza problemi un punto in più (confrontare con l'aggiornamento

4):

inserisci qui la descrizione dell'immagine

7)

Guarda il risultato della versione Python del codice di Whuber per alcune forme iconiche:

inserisci qui la descrizione dell'immagine
Si noti che il metodo sembra non funzionare per le polilinee. Per la polilinea d'angolo (contorno) il verde è quello che voglio ma è diventato rosso. Questo deve essere affrontato poiché le mappe di contorno sono sempre polilinee sebbene le polilinee chiuse possano essere trattate come poligoni come nei miei esempi. Inoltre, il problema emerso nell'aggiornamento 4 non è stato ancora risolto.

8) [il mio ultimo]

Ecco la soluzione finale (non perfetta!):

inserisci qui la descrizione dell'immagine

Ricorda che dovrai fare qualcosa per l'area indicata dalle stelle. C'è forse un bug nel mio codice o il metodo proposto necessita di ulteriore sviluppo per considerare tutte le situazioni e fornire gli output desiderati.


come stai generando contorni "poligonali"? non sarebbero sempre linee, poiché un contorno che interseca il bordo di un DEM non si chiuderebbe mai su se stesso?
pistacchio

ho usato la funzione v.generalize in GRASS per eseguire il livellamento delle curve di livello con risultati decenti, anche se può richiedere del tempo per le mappe con contorni molto densi.
pistacchio

@pistachionut È possibile considerare che i livelli di contorno sono polilinea. Sto cercando codice puro nella prima fase. Se non disponibile, pacchetto leggero per Python.
Sviluppatore

Forse guarda scipy.org/Cookbook/Interpolazione perché sembra che tu voglia spline
PolyGeo

1
La curva di @Pablo Bezier nel tuo link funziona bene per le polilinee. whuber funziona quasi bene per i poligoni. Quindi insieme potrebbero rispondere alla domanda. Grazie mille per aver condiviso le tue conoscenze gratuitamente.
Sviluppatore

Risposte:


37

La maggior parte dei metodi per spline sequenze di numeri spline poligoni. Il trucco è rendere le spline "vicine" senza problemi agli endpoint. Per fare questo, "avvolgi" i vertici attorno alle estremità. Quindi spline le coordinate x e y separatamente.

Ecco un esempio funzionante in R. Utilizza la splineprocedura cubica predefinita disponibile nel pacchetto di statistiche di base. Per un maggiore controllo, sostituisci quasi ogni procedura che preferisci: assicurati solo che divida tra i numeri (cioè li interpoli) anziché semplicemente utilizzarli come "punti di controllo".

#
# Splining a polygon.
#
#   The rows of 'xy' give coordinates of the boundary vertices, in order.
#   'vertices' is the number of spline vertices to create.
#              (Not all are used: some are clipped from the ends.)
#   'k' is the number of points to wrap around the ends to obtain
#       a smooth periodic spline.
#
#   Returns an array of points. 
# 
spline.poly <- function(xy, vertices, k=3, ...) {
    # Assert: xy is an n by 2 matrix with n >= k.

    # Wrap k vertices around each end.
    n <- dim(xy)[1]
    if (k >= 1) {
        data <- rbind(xy[(n-k+1):n,], xy, xy[1:k, ])
    } else {
        data <- xy
    }

    # Spline the x and y coordinates.
    data.spline <- spline(1:(n+2*k), data[,1], n=vertices, ...)
    x <- data.spline$x
    x1 <- data.spline$y
    x2 <- spline(1:(n+2*k), data[,2], n=vertices, ...)$y

    # Retain only the middle part.
    cbind(x1, x2)[k < x & x <= n+k, ]
}

Per illustrarne l'uso, creiamo un piccolo poligono (ma complicato).

#
# Example polygon, randomly generated.
#
set.seed(17)
n.vertices <- 10
theta <- (runif(n.vertices) + 1:n.vertices - 1) * 2 * pi / n.vertices
r <- rgamma(n.vertices, shape=3)
xy <- cbind(cos(theta) * r, sin(theta) * r)

Spline usando il codice precedente. Per rendere la spline più liscia, aumentare il numero di vertici da 100; per renderlo meno uniforme, ridurre il numero di vertici.

s <- spline.poly(xy, 100, k=3)

Per vedere i risultati, tracciamo (a) il poligono originale in rosso tratteggiato, mostrando lo spazio tra il primo e l'ultimo vertice (cioè, non chiudendo la sua polilinea di confine); e (b) la spline in grigio, mostrando ancora una volta il suo spazio. (Poiché il divario è così piccolo, i suoi punti finali sono evidenziati con punti blu.)

plot(s, type="l", lwd=2, col="Gray")
lines(xy, col="Red", lty=2, lwd=2)
points(xy, col="Red", pch=19)
points(s, cex=0.8)
points(s[c(1,dim(s)[1]),], col="Blue", pch=19)

Poligono scanalato


5
Bella risposta. Esiste un modo per garantire che i contorni non finiscano per incrociarsi a causa del livellamento?
Kirk Kuykendall

Questa è una buona domanda, @Kirk. Non sono a conoscenza di alcun metodo per garantire il non attraversamento da questa forma di livellamento. (In effetti, non vedo nemmeno come garantire che la polilinea levigata non si intersechi da sola. Questo non è un grosso problema per la maggior parte dei contorni.) Per farlo, dovresti tornare all'originale DEM e invece usa un metodo migliore per calcolare i contorni in primo luogo. (Ci sono metodi migliori - sono stati conosciuti da molto tempo -. Ma per quanto ne so alcuni dei più popolari GISes non vengono utilizzati)
whuber

In primo luogo, sto ancora lavorando per implementare la tua risposta in Python, ma non ci riesco. Secondo, quale sarà il risultato se applichi il tuo metodo su un quadrato? Puoi fare riferimento a quelli che ho disegnato nella domanda.
Sviluppatore

1
Ho accettato questa come risposta poiché offre una buona soluzione. Anche se non è perfetto, ma mi ha dato alcune idee in giro, spero di trovare una soluzione che soddisfi i punti di cui sopra nella mia domanda e nei miei commenti. Puoi anche considerare i commenti di Whuber per la domanda [QC], ci sono buoni trucchi lì. Infine, dovrei dire che la traduzione in python è quasi semplice avendo installato un delizioso pacchetto Scipy. Considera anche il commento di Pablo in QC come possibile soluzione per le polilinee, ovvero le curve di Bezier. Buona fortuna a tutti.
Sviluppatore

1
vedendo le tue risposte, mi dispiace non aver curato bene la mia matematica !!!
Vinayan,

2

So che questo è un vecchio post, ma è apparso su Google per qualcosa che stavo cercando, quindi ho pensato di pubblicare la mia soluzione.

Non lo vedo come un esercizio di adattamento della curva 2D, ma piuttosto come un esercizio 3D. Considerando i dati come 3D possiamo garantire che le curve non si incrociano mai e possiamo usare le informazioni di altri contorni per migliorare la nostra stima per quella corrente.

Il seguente estratto di iPython utilizza l'interpolazione cubica fornita da SciPy. Nota che i valori z che ho tracciato non sono importanti, purché tutti i contorni siano equidistanti in altezza.

In [1]: %pylab inline
        pylab.rcParams['figure.figsize'] = (10, 10)
        Populating the interactive namespace from numpy and matplotlib

In [2]: import scipy.interpolate as si

        xs = np.array([0.0, 0.0, 4.5, 4.5,
                       0.3, 1.5, 2.3, 3.8, 3.7, 2.3,
                       1.5, 2.2, 2.8, 2.2,
                       2.1, 2.2, 2.3])
        ys = np.array([0.0, 3.0, 3.0, 0.0,
                       1.1, 2.3, 2.5, 2.3, 1.1, 0.5,
                       1.1, 2.1, 1.1, 0.8,
                       1.1, 1.3, 1.1])
        zs = np.array([0,   0,   0,   0,
                       1,   1,   1,   1,   1,   1,
                       2,   2,   2,   2,
                       3,   3,   3])
        pts = np.array([xs, ys]).transpose()

        # set up a grid for us to resample onto
        nx, ny = (100, 100)
        xrange = np.linspace(np.min(xs[zs!=0])-0.1, np.max(xs[zs!=0])+0.1, nx)
        yrange = np.linspace(np.min(ys[zs!=0])-0.1, np.max(ys[zs!=0])+0.1, ny)
        xv, yv = np.meshgrid(xrange, yrange)
        ptv = np.array([xv, yv]).transpose()

        # interpolate over the grid
        out = si.griddata(pts, zs, ptv, method='cubic').transpose()

        def close(vals):
            return np.concatenate((vals, [vals[0]]))

        # plot the results
        levels = [1, 2, 3]
        plt.plot(close(xs[zs==1]), close(ys[zs==1]))
        plt.plot(close(xs[zs==2]), close(ys[zs==2]))
        plt.plot(close(xs[zs==3]), close(ys[zs==3]))
        plt.contour(xrange, yrange, out, levels)
        plt.show()

Risultato interpolato cubico

I risultati qui non sembrano i migliori, ma con così pochi punti di controllo sono ancora perfettamente validi. Nota come viene estratta la linea verde per seguire il contorno blu più largo.


Le curve lisce montate devono rimanere il più vicino possibile al poligono / polilinea originali.
Sviluppatore

1

Ho scritto quasi esattamente il pacchetto che stai cercando ... ma era in Perl ed era più di un decennio fa: GD :: Polyline . Usava curve di Bezier cubiche 2D e avrebbe "smussato" un poligono o "polilinea" arbitrari (il mio nome allora per quello che ora viene comunemente chiamato "LineString").

L'algoritmo prevede due passaggi: dati i punti nel poligono, aggiungi due punti di controllo di Bezier tra ogni punto; quindi chiama un semplice algoritmo per effettuare un'approssimazione a tratti della spline.

La seconda parte è facile; la prima parte era un po 'd'arte. Qui c'era l'intuizione: si consideri un "segmento di controllo" un vertice N: vN. Il segmento di controllo era di tre punti di co-linear: [cNa, vN, cNb]. Il punto centrale era il vertice. La pendenza di questo controllo seg era uguale alla pendenza da Vertex N-1 a Vertex N + 1. La lunghezza della porzione sinistra di questo segmento era 1/3 della lunghezza dal vertice N-1 al vertice N, e la lunghezza della porzione destra di questo segmento era 1/3 della lunghezza dal vertice N al vertice N + 1.

Se la curva originale era quattro vertici: [v1, v2, v3, v4]quindi l'ogni vertice ora ottenere un segmento di controllo della forma: [c2a, v2, c2b]. Stringili insieme in questo modo: [v1, c1b, c2a, v2, c2b, c3a, v3, c3b, c4a, v4]e sgranocchia quattro alla volta mentre i quattro punti di Bezier:, [v1, c1b, c2a, v2]quindi [v2, c2b, c3a, v3], e così via. Poiché [c2a, v2, c2b]erano co-lineari, la curva risultante sarà liscia in corrispondenza di ciascun vertice.

Quindi, questo soddisfa anche il tuo requisito di parametrizzare la "tenuta" della curva: usa un valore inferiore a 1/3 per una curva "più stretta", una più grande per un adattamento "più circolare". In entrambi i casi, la curva risultante passa sempre attraverso i punti dati originali.

Ciò ha comportato una curva regolare che "ha circoscritto" il poligono originale. Ho anche avuto un modo per "inscrivere" una curva liscia ... ma non lo vedo nel codice CPAN.

Comunque, al momento non ho una versione disponibile in Python, né ho cifre. MA ... se / quando eseguo il porting su Python, sarò sicuro di postare qui.


Non è possibile valutare il codice Perl, aggiungere elementi grafici per dimostrare come funzionava, se possibile.
Sviluppatore
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.