Polygone in der Konturkarte glätten?

52

Hier ist eine Konturkarte, für die alle Polygone von Ebenen verfügbar sind.

Fragen wir uns, wie die Polygone geglättet werden sollen, wobei alle Scheitelpunkte an ihren exakten Positionen erhalten bleiben.

In der Tat wird die Kontur über Rasterdaten erstellt. Sie können dann vorschlagen, die Rasterdaten zu glätten, und daher wird die resultierende Kontur glatter. Beachten Sie, dass dies nicht wie gewünscht funktioniert, da die Glättungsfunktion wie der Gauß-Filter kleine Datenpakete entfernt und den Bereich der dritten Variablen ändert, z. B. die Höhe, die in meiner Anwendung nicht zulässig ist.

Eigentlich bin ich auf der Suche nach einem Teil des Codes (vorzugsweise in Python ), der das Glätten von 2D-Polygonen (jeder Typ: konvex, konkav, sich selbst schneidend usw.) einigermaßen schmerzlos (Codeseiten vergessen) und genau kann.

Zu Ihrer Information, es gibt eine Funktion in ArcGIS , die dies perfekt macht. Die Verwendung von kommerziellen Anwendungen von Drittanbietern ist jedoch nicht meine Wahl für diese Frage.

Bildbeschreibung hier eingeben


1)

Scipy.interpolate:

Bildbeschreibung hier eingeben

Wie Sie sehen, sind die resultierenden Splines (rot) nicht zufriedenstellend!

2)

Hier ist das Ergebnis des Code in bestimmten Verwendung hier . Es funktioniert nicht gut!

Bildbeschreibung hier eingeben

3)

Für mich sollte die beste Lösung etwa die folgende Abbildung sein, in der ein Quadrat allmählich geglättet wird, indem nur ein Wert geändert wird. Ich hoffe auf ein ähnliches Konzept zum Glätten jeglicher Form von Polygonen.

Bildbeschreibung hier eingeben

Erfüllung der Bedingung, dass der Spline die Punkte überschreitet:

Bildbeschreibung hier eingeben

4)

Hier ist meine Umsetzung von "Whubers Idee" Zeile für Zeile in Python auf seine Daten. Möglicherweise gibt es einige Fehler, da die Ergebnisse nicht gut sind.

Bildbeschreibung hier eingeben

K = 2 ist eine Katastrophe und so für k> = 4.

5)

Ich habe einen Punkt an der problematischen Stelle entfernt und der resultierende Spline ist jetzt identisch mit dem von Whuber. Aber es ist immer noch eine Frage, warum die Methode nicht für alle Fälle funktioniert?

Bildbeschreibung hier eingeben

6)

Eine gute Glättung für Whubers Daten kann wie folgt aussehen (gezeichnet von einer Vektorgrafiksoftware), wobei ein zusätzlicher Punkt nahtlos hinzugefügt wurde (vergleiche mit dem Update)

4):

Bildbeschreibung hier eingeben

7)

Im Ergebnis der Python-Version von Whubers Code finden Sie einige ikonische Formen:

Bildbeschreibung hier eingeben
Beachten Sie, dass die Methode für Polylinien anscheinend nicht funktioniert. Für die Eckpolylinie (Kontur) ist Grün das, was ich will, aber rot. Dies muss behoben werden, da Konturkarten immer Polylinien sind, obwohl geschlossene Polylinien wie in meinen Beispielen als Polygone behandelt werden können. Auch nicht, dass das in Update 4 aufgetretene Problem noch nicht behoben wurde.

8) [mein letzter]

Hier ist die endgültige Lösung (nicht perfekt!):

Bildbeschreibung hier eingeben

Denken Sie daran, dass Sie etwas für den Bereich tun müssen, auf den die Sterne zeigen. Mein Code enthält möglicherweise einen Fehler, oder die vorgeschlagene Methode muss weiterentwickelt werden, um alle Situationen zu berücksichtigen und die gewünschten Ergebnisse zu erzielen.

Entwickler
quelle
Wie generieren Sie "Polygon" -Konturen? Wären sie nicht immer Linien, da sich eine Kontur, die den Rand eines DEM schneidet, niemals selbst schließen würde?
Pistazien
Ich habe die Funktion v.generalize in GRASS verwendet, um Konturlinien mit anständigen Ergebnissen zu glätten, obwohl es bei Karten mit sehr dichten Konturen eine Weile dauern kann.
Pistazien
@pistachionut Sie können davon ausgehen, dass die Konturebenen Mehrfachlinien sind. Ich suche in der ersten Phase nach reinem Code . Wenn nicht verfügbar, dann leichtes Paket für Python.
Entwickler
Vielleicht schauen Sie sich scipy.org/Cookbook/Interpolation an, weil es sich so anhört, als ob Sie
splinen
1
Die @Pablo Bezier-Kurve in Ihrem Link eignet sich gut für Polylinien. Whubers funktioniert fast gut für Polygone. So konnten sie sich gemeinsam mit der Frage befassen. Vielen Dank, dass Sie Ihr Wissen kostenlos weitergeben.
Entwickler

Antworten:

37

Die meisten Methoden zum Splinen von Folgen von Zahlen werden Polygone splinen. Der Trick besteht darin, die Splines an den Endpunkten reibungslos zu schließen. Um dies zu tun, "wickeln" Sie die Eckpunkte um die Enden. Dann spline die x- und y-Koordinaten getrennt.

Hier ist ein Arbeitsbeispiel in R. Es wird die Standardprozedur für kubische splineDaten verwendet, die im Basisstatistikpaket verfügbar ist. Ersetzen Sie für mehr Kontrolle fast jedes Verfahren, das Sie bevorzugen: Stellen Sie einfach sicher, dass es durch die Zahlen verläuft (dh sie interpoliert), anstatt sie lediglich als "Kontrollpunkte" zu verwenden.

#
# 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, ]
}

Um seine Verwendung zu veranschaulichen, erstellen wir ein kleines (aber kompliziertes) Polygon.

#
# 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 es mit dem vorhergehenden Code. Erhöhen Sie die Anzahl der Scheitelpunkte von 100, um den Spline glatter zu machen. Verringern Sie die Anzahl der Scheitelpunkte, um die Glättung zu verringern.

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

Um die Ergebnisse zu sehen, zeichnen wir (a) das ursprüngliche Polygon in rotem Strich und zeigen die Lücke zwischen dem ersten und dem letzten Scheitelpunkt (dh ohne die Grenzpolylinie zu schließen); und (b) der Spline ist grau und zeigt wieder seine Lücke. (Da die Lücke so klein ist, werden ihre Endpunkte mit blauen Punkten hervorgehoben.)

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)

Splined Polygon

whuber
quelle
5
Gute Antwort. Gibt es eine Möglichkeit, zu gewährleisten, dass sich Konturen durch Glätten nicht kreuzen?
Kirk Kuykendall
Das ist eine gute Frage, @Kirk. Mir ist keine Methode bekannt, um zu gewährleisten, dass diese Form der Glättung nicht überschritten wird. (Tatsächlich kann ich nicht einmal garantieren, dass sich die geglättete Polylinie nicht selbst schneidet. Dies ist jedoch für die meisten Konturen kein großes Problem.) Dazu müssten Sie zum Original zurückkehren Verwenden Sie stattdessen eine bessere Methode, um die Konturen zu berechnen. (Es gibt bessere Methoden - sie sind seit langem bekannt - aber AFAIK einige der beliebtesten GIS verwenden sie nicht.)
whuber
Erstens arbeite ich immer noch daran, Ihre Antwort in Python zu implementieren, aber nicht erfolgreich. Zweitens, was ist das Ergebnis, wenn Sie Ihre Methode auf ein Quadrat anwenden? Sie können sich auf die beziehen, die ich in der Frage gezeichnet habe.
Entwickler
1
Ich habe dies als Antwort akzeptiert, da es eine gute Lösung gibt. Auch wenn es nicht perfekt ist, aber mir einige Ideen gegeben haben, hoffe ich, dass ich eine Lösung finden werde, die die Punkte erfüllt, die ich oben in meiner Frage und meinen Kommentaren erwähnt habe. Sie können auch Whubers Kommentare zu der Frage [QC] berücksichtigen, da gibt es gute Tricks. Zuletzt sollte ich sagen, dass die Übersetzung in Python mit dem schönen Scipy-Paket fast unkompliziert ist. Berücksichtigen Sie auch Pablos Kommentar in QC als mögliche Lösung für Polylinien, dh Bezier-Kurven. Viel Glück allen.
Entwickler
1
Als ich Ihre Antworten sehe, bedaure ich, dass ich mich nicht gut um meine Mathematik gekümmert habe !!!
Vinayan
2

Ich weiß, dass dies ein alter Beitrag ist, aber er wurde bei Google für etwas angezeigt, nach dem ich gesucht habe, und deshalb dachte ich, ich würde meine Lösung veröffentlichen.

Ich sehe dies nicht als eine 2D-Kurvenanpassungsübung, sondern als eine 3D-Übung. Indem wir die Daten als 3D betrachten, können wir sicherstellen, dass sich die Kurven niemals kreuzen, und Informationen aus anderen Konturen verwenden, um unsere Schätzung für die aktuelle zu verbessern.

Der folgende iPython-Extrakt verwendet die von SciPy bereitgestellte kubische Interpolation. Beachten Sie, dass die von mir gezeichneten z-Werte nicht wichtig sind, solange alle Konturen einen gleichen Höhenabstand aufweisen.

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

Kubisch interpoliertes Ergebnis

Die Ergebnisse hier sehen nicht gut aus, aber mit so wenigen Kontrollpunkten sind sie immer noch perfekt gültig. Beachten Sie, wie die grüne angepasste Linie herausgezogen wird, um der breiteren blauen Kontur zu folgen.

Gilly
quelle
Die angepassten glatten Kurven sollten möglichst nahe am ursprünglichen Polygon / der ursprünglichen Polylinie bleiben.
Entwickler
1

Ich habe fast genau das Paket geschrieben, nach dem Sie suchen ... aber es war in Perl und vor über einem Jahrzehnt: GD :: Polyline . Es verwendete kubische Bezier-2D-Kurven und "glättete" ein beliebiges Polygon oder eine beliebige Polylinie (mein Name war damals das, was heute allgemein als "LineString" bezeichnet wird).

Der Algorithmus bestand aus zwei Schritten: Fügen Sie unter Berücksichtigung der Punkte im Polygon zwei Bezier-Steuerpunkte zwischen jedem Punkt hinzu. Rufen Sie dann einen einfachen Algorithmus auf, um den Spline stückweise zu approximieren.

Der zweite Teil ist einfach; Der erste Teil war ein bisschen Kunst. Hier war die Erkenntnis: betrachtet ein „Steuersegment“ einen Vertex N: vN. Das Steuersegment war drei kolineare Punkte [cNa, vN, cNb]. Der Mittelpunkt war der Scheitelpunkt. Die Steigung dieses Kontrollsegments war gleich der Steigung von Vertex N-1 bis Vertex N + 1. Die Länge des linken Abschnitts dieses Segments betrug 1/3 der Länge von Vertex N-1 bis Vertex N, und die Länge des rechten Abschnitts dieses Segments betrug 1/3 der Länge von Vertex N bis Vertex N + 1.

Wenn die ursprüngliche Kurve vier Eckpunkten war: [v1, v2, v3, v4]dann wird jeder der Scheitelpunkt nun ein Steuersegment des Formulars erhalten: [c2a, v2, c2b]. Ordnen Sie diese wie [v1, c1b, c2a, v2, c2b, c3a, v3, c3b, c4a, v4]folgt an : und kauen Sie sie zu viert als die vier Bezier-Punkte:, [v1, c1b, c2a, v2]dann [v2, c2b, c3a, v3]und so weiter. Da [c2a, v2, c2b]sie kolinear waren, ist die resultierende Kurve an jedem Scheitelpunkt glatt.

Dies entspricht also auch Ihrer Anforderung, die "Enge" der Kurve zu parametrisieren: Verwenden Sie einen kleineren Wert als 1/3 für eine "engere" Kurve, einen größeren für eine "lockerere" Kurve. In beiden Fällen durchläuft die resultierende Kurve immer die ursprünglich angegebenen Punkte.

Dies führte zu einer glatten Kurve, die das ursprüngliche Polygon "umschrieb". Ich hatte auch eine Möglichkeit, eine glatte Kurve "einzuschreiben" ... aber ich sehe das nicht im CPAN-Code.

Jedenfalls habe ich momentan keine Version in Python und auch keine Zahlen. ABER ... wenn ich dies nach Python portiere, werde ich es hier veröffentlichen.

Dan H
quelle
Perl-Code kann nicht ausgewertet werden. Fügen Sie nach Möglichkeit Grafiken hinzu, um die Funktionsweise zu veranschaulichen.
Entwickler