Berechnung der durchschnittlichen Breite des Polygons? [geschlossen]

40

Ich möchte die durchschnittliche Breite eines Polygons untersuchen, das die Straßenoberfläche darstellt. Ich habe auch die Straßenmittellinie als Vektor (der manchmal nicht genau in der Mitte ist). In diesem Beispiel ist die Straßenmittellinie rot und das Polygon blau:

Bildbeschreibung hier eingeben

Ein Brute-Force-Ansatz, den ich mir überlegt habe, besteht darin, die Linie in kleinen Schritten zu puffern, den Puffer mit einem Fischnetzgitter zu schneiden, das Straßenpolygon mit einem Fischnetzgitter zu schneiden, die Schnittfläche für beide Schnittmaße zu berechnen und dies bis zu tun der fehler ist klein. Dies ist jedoch eine grobe Herangehensweise, und ich frage mich, ob es eine elegantere Lösung gibt. Außerdem würde dies die Breite einer großen Straße und einer kleinen Straße verbergen.

Ich interessiere mich für eine Lösung, die ArcGIS 10, PostGIS 2.0 oder QGIS-Software verwendet. Ich habe diese Frage gesehen und Dan Pattersons Tool für ArcGIS10 heruntergeladen, konnte aber nicht berechnen, was ich damit möchte.

Ich habe gerade das Werkzeug " Minimale Begrenzungsgeometrie" in ArcGIS 10 entdeckt, mit dem ich die folgenden grünen Polygone erstellen kann:

Bildbeschreibung hier eingeben

Dies scheint eine gute Lösung für Straßen zu sein, die einem Raster folgen, aber ansonsten nicht funktionieren würden. Daher bin ich immer noch an anderen Vorschlägen interessiert.

djq
quelle
Haben Sie mögliche Lösungen in der Seitenleiste ausgeschlossen? Das heißt, gis.stackexchange.com/questions/2880/… wurde anscheinend als triviale Antwort auf einen potenziell duplizierten Beitrag
@DanPatterson Ich habe keine solche Frage gesehen (viele sind natürlich verwandt). Meinten Sie, dass meine Frage markiert wurde? Ich habe deine zweite Zeile nicht verstanden.
Djq
Diese verwandte Frage, @Dan, betrifft eine andere Interpretation von "width" (na ja, eigentlich ist die Interpretation nicht ganz klar). Die Antworten scheinen sich darauf zu konzentrieren, die Breite an der breitesten Stelle und nicht auf eine durchschnittliche Breite zu finden.
Whuber
Da @whuber hier Diskussionen zentralisieren und weitere Fragen schließen möchte, schlage ich vor, dass die Frage bearbeitet wird, um zu verstehen, dass " die durchschnittliche Breite eines rechteckigen Streifens geschätzt wird "
Peter Krauss,
@ Peter: Da ein rechteckiger Streifen erst recht ein Polygon ist, sollte der allgemeinere Titel stehen.
whuber

Antworten:

40

Ein Teil des Problems besteht darin, eine geeignete Definition der "durchschnittlichen Breite" zu finden. Einige sind natürlich, unterscheiden sich aber zumindest geringfügig. Betrachten Sie der Einfachheit halber Definitionen, die auf einfach zu berechnenden Eigenschaften basieren (was beispielsweise diejenigen ausschließt, die auf der Transformation der Mittelachse oder auf Sequenzen von Puffern basieren).

Als Beispiel sei angenommen, dass die archetypische Intuition eines Polygons mit einer bestimmten "Breite" ein kleiner Puffer (etwa mit dem Radius r mit quadratischen Enden) um eine lange, ziemlich gerade Polylinie (etwa mit der Länge L ) ist. Wir denken an 2r = w als seine Breite. Somit:

  • Sein Umfang P ist ungefähr gleich 2L + 2w;

  • Seine Fläche A ist ungefähr gleich wL.

Die Breite w und Länge L können dann als Wurzeln des quadratischen x ^ 2 - (P / 2) x + A gewonnen werden; Insbesondere können wir schätzen

  • w = (P - Sqrt (P ^ 2 - 16A)) / 4 .

Wenn Sie sicher sind, dass das Polygon wirklich lang und dünn ist, können Sie als weitere Annäherung 2L + 2w nehmen, um 2L zu entsprechen, von wo aus

  • w (grob) = 2A / P.

Der relative Fehler in dieser Approximation ist proportional zu w / L: Je dünner das Polygon ist, desto näher ist w / L an Null und desto besser wird die Approximation.

Dieser Ansatz ist nicht nur extrem einfach (man teile die Fläche durch den Umfang und multipliziere mit 2), bei beiden Formeln spielt es keine Rolle, wie das Polygon ausgerichtet ist oder wo es sich befindet (weil solche euklidischen Bewegungen weder die Fläche noch die Fläche ändern) Umfang).

Sie können eine dieser Formeln verwenden, um die durchschnittliche Breite für alle Polygone zu schätzen , die Straßensegmente darstellen. Der Fehler, den Sie bei der ursprünglichen Schätzung von w (mit der quadratischen Formel) machen, tritt auf, weil der Bereich A auch winzige Keile an jeder Biegung der ursprünglichen Polylinie enthält. Wenn die Summe der Biegewinkel t Bogenmaß ist (dies ist die absolute Gesamtkrümmung der Polylinie), dann wirklich

  • P = 2L + 2w + 2 Pi tw und

  • A = L w + Pi tw ^ 2.

Stecken Sie diese in die vorherige (quadratische Formel) Lösung und vereinfachen Sie. Wenn der Rauch verschwindet , ist der Beitrag des Krümmungsterms t verschwunden! Was ursprünglich wie eine Annäherung aussah, ist für nicht sich selbst schneidende Polylinienpuffer (mit quadratischen Enden) vollkommen genau. Für Polygone mit variabler Breite ist dies daher eine vernünftige Definition der durchschnittlichen Breite.

whuber
quelle
Danke @whuber, das ist eine großartige Antwort, und es hat mir geholfen, klarer darüber nachzudenken.
DJQ
@whuber: Ich schreibe eine Arbeit und müsste einen ordentlichen ('akademischen') Verweis auf die Methode geben, die Sie hier beschreiben. Haben Sie eine solche Referenz? Hat diese Maßnahme einen Namen? Wenn nicht, kann ich es nach dir benennen! Was ist mit dem "Huber-Breitenmaß"?
Julien
@ Julien Ich habe keine Referenzen. Dieses Format könnte funktionieren: MISC {20279, TITLE = {Berechnung der durchschnittlichen Breite eines Polygons?}, AUTHOR = {whuber ( gis.stackexchange.com/users/664/whuber )}, HOWPUBLISHED = {GIS}, NOTE = {URL: gis.stackexchange.com/q/20279/664 (Version: 2013-08-13)}, EPRINT = { gis.stackexchange.com/q/20279 }, URL = { gis.stackexchange.com/q/20279 }}
whuber
18

Hier zeige ich nur eine geringe Optimierung der @ whuber-Lösung und gebe die "Pufferbreite" an, da dies nützlich ist, um die Lösung eines allgemeineren Problems zu integrieren: Gibt es eine st_buffer-Umkehrfunktion, die eine Breitenschätzung zurückgibt?

CREATE FUNCTION buffer_width(
        -- rectangular strip mean width estimator
    p_len float,   -- len of the central line of g
    p_geom geometry, -- g
    p_btype varchar DEFAULT 'endcap=flat' -- st_buffer() parameter
) RETURNS float AS $f$
  DECLARE
    w_half float;
    w float;    
  BEGIN
         w_half := 0.25*ST_Area(p_geom)/p_len;
         w      := 0.50*ST_Area( ST_Buffer(p_geom,-w_half,p_btype) )/(p_len-2.0*w_half);
     RETURN w_half+w;
  END
$f$ LANGUAGE plpgsql IMMUTABLE;

Für dieses Problem der @celenius Frage nach Breiter Straße , swist die Lösung

 sw = buffer_width(ST_Length(g1), g2)

wo swist die "durchschnittliche Breite", g1die Mittellinie von g2und die Straße g2ist ein POLYGON . Ich habe nur die mit PostGIS getestete OGC-Standardbibliothek verwendet und andere ernsthafte praktische Anwendungen mit derselben buffer_width-Funktion gelöst.

DEMONSTRATION

A2ist die Fläche von g2, L1die Länge der Mittellinie ( g1) von g2.

Angenommen , dass wir erzeugen kann g2durch g2=ST_Buffer(g1,w), und das g1ist eine gerade, so g2ist ein Rechteck mit Länge L1und Breite 2*w, und

    A2 = L1*(2*w)   -->  w = 0.5*A2/L1

Es ist nicht die gleiche Formel von @whuber, da hier die wHälfte der g2Breite von rectangle ( ) angegeben ist. Es ist ein guter Schätzer, aber wie wir anhand der Tests (unten) sehen können, ist er nicht genau und die Funktion verwendet ihn als Anhaltspunkt, um die g2Fläche zu verkleinern und als endgültigen Schätzer.

Hier werden keine Puffer mit "endcap = square" oder "endcap = round" ausgewertet, die eine Summe zu A2 einer Fläche eines Punktpuffers mit derselben benötigen w.

LITERATUR: In einem ähnlichen Forum von 2005 erklärt W. Huber ähnliche und andere Lösungen.

PRÜFUNGEN UND GRÜNDE

Bei geraden Linien sind die Ergebnisse erwartungsgemäß genau. Bei anderen Geometrien können die Ergebnisse jedoch enttäuschend sein. Der Hauptgrund ist möglicherweise, dass das gesamte Modell für exakte Rechtecke oder für Geometrien bestimmt ist, die sich einem "Streifenrechteck" annähern lassen. Hier ein "Testkit" zur Überprüfung der Grenzen dieser Näherung (siehe wfactorErgebnisse oben).

 SELECT *, round(100.0*(w_estim-w)/w,1) as estim_perc_error
    FROM (
        SELECT btype, round(len,1) AS len, w, round(w/len,3) AS wfactor,
               round(  buffer_width(len, gbase, btype)  ,2) as w_estim ,
               round(  0.5*ST_Area(gbase)/len       ,2) as w_near
        FROM (
         SELECT
            *, st_length(g) AS len, ST_Buffer(g, w, btype) AS gbase
         FROM (
               -- SELECT ST_GeomFromText('LINESTRING(50 50,150 150)') AS g, -- straight
               SELECT ST_GeomFromText('LINESTRING(50 50,150 150,150 50,250 250)') AS g,
            unnest(array[1.0,10.0,20.0,50.0]) AS w
              ) AS t, 
             (SELECT unnest(array['endcap=flat','endcap=flat join=bevel']) AS btype
             ) AS t2
        ) as t3
    ) as t4;

ERGEBNISSE:

MIT RECHTECKEN (Mittellinie ist eine GERADE LINIE):

         btype          |  len  |  w   | wfactor | w_estim | w_near | estim_perc_error 
------------------------+-------+------+---------+---------+--------+------------------
 endcap=flat            | 141.4 |  1.0 |   0.007 |       1 |      1 |                0
 endcap=flat join=bevel | 141.4 |  1.0 |   0.007 |       1 |      1 |                0
 endcap=flat            | 141.4 | 10.0 |   0.071 |      10 |     10 |                0
 endcap=flat join=bevel | 141.4 | 10.0 |   0.071 |      10 |     10 |                0
 endcap=flat            | 141.4 | 20.0 |   0.141 |      20 |     20 |                0
 endcap=flat join=bevel | 141.4 | 20.0 |   0.141 |      20 |     20 |                0
 endcap=flat            | 141.4 | 50.0 |   0.354 |      50 |     50 |                0
 endcap=flat join=bevel | 141.4 | 50.0 |   0.354 |      50 |     50 |                0

MIT ANDEREN GEOMETRIEN (Mittellinie gefaltet):

         btype          | len |  w   | wfactor | w_estim | w_near | estim_perc_error 
 -----------------------+-----+------+---------+---------+--------+------------------
 endcap=flat            | 465 |  1.0 |   0.002 |       1 |      1 |                0
 endcap=flat join=bevel | 465 |  1.0 |   0.002 |       1 |   0.99 |                0
 endcap=flat            | 465 | 10.0 |   0.022 |    9.98 |   9.55 |             -0.2
 endcap=flat join=bevel | 465 | 10.0 |   0.022 |    9.88 |   9.35 |             -1.2
 endcap=flat            | 465 | 20.0 |   0.043 |   19.83 |  18.22 |             -0.9
 endcap=flat join=bevel | 465 | 20.0 |   0.043 |   19.33 |  17.39 |             -3.4
 endcap=flat            | 465 | 50.0 |   0.108 |   46.29 |  40.47 |             -7.4
 endcap=flat join=bevel | 465 | 50.0 |   0.108 |   41.76 |  36.65 |            -16.5

 wfactor= w/len
 w_near = 0.5*area/len
 w_estim is the proposed estimator, the buffer_width function.

Über btypesiehe ST_Buffer Führung , mit guten ilustratins und den Linienfolgen verwendet hier.

SCHLUSSFOLGERUNGEN :

  • der Schätzer von w_estimist immer besser als w_near;
  • Für "fast rechteckige" g2Geometrien ist es in Ordnungwfactor
  • Verwenden Sie für andere Geometrien (in der Nähe von "rechteckigen Streifen") die Grenze wfactor=~0.01für 1% des Fehlers auf w_estim. Verwenden Sie bis zu diesem Faktor einen anderen Schätzer.

Vorsicht und Vorbeugung

Warum tritt der Schätzfehler auf? Wenn Sie verwenden ST_Buffer(g,w), erwarten Sie vom "rechteckigen Streifenmodell", dass der neue Bereich, der durch den Puffer mit der Breite hinzugefügt wwird, ungefähr w*ST_Length(g)oder w*ST_Perimeter(g)... Wenn nicht, normalerweise durch Überlagerungen (siehe gefaltete Linien) oder durch "Stylen", wann die Schätzung des durchschnittlichen wFehlers . Dies ist die Hauptbotschaft der Tests.

Überprüfen Sie das Verhalten der Puffergenerierung , um dieses Problem bei jedem Pufferkönig zu erkennen :

SELECT btype, w, round(100.0*(a1-len1*2.0*w)/a1)::varchar||'%' AS straight_error,  
                 round(100.0*(a2-len2*2.0*w)/a2)::varchar||'%' AS curve2_error,
                 round(100.0*(a3-len3*2.0*w)/a3)::varchar||'%' AS curve3_error
FROM (
 SELECT
    *, st_length(g1) AS len1, ST_Area(ST_Buffer(g1, w, btype)) AS a1,
    st_length(g2) AS len2, ST_Area(ST_Buffer(g2, w, btype)) AS a2,
    st_length(g3) AS len3, ST_Area(ST_Buffer(g3, w, btype)) AS a3
 FROM (
       SELECT ST_GeomFromText('LINESTRING(50 50,150 150)') AS g1, -- straight
              ST_GeomFromText('LINESTRING(50 50,150 150,150 50)') AS g2,
              ST_GeomFromText('LINESTRING(50 50,150 150,150 50,250 250)') AS g3,
              unnest(array[1.0,20.0,50.0]) AS w
      ) AS t, 
     (SELECT unnest(array['endcap=flat','endcap=flat join=bevel']) AS btype
     ) AS t2
) as t3;

ERGEBNISSE:

         btype          |  w   | straight_error | curve2_error | curve3_error 
------------------------+------+----------------+--------------+--------------
 endcap=flat            |  1.0 | 0%             | -0%          | -0%
 endcap=flat join=bevel |  1.0 | 0%             | -0%          | -1%
 endcap=flat            | 20.0 | 0%             | -5%          | -10%
 endcap=flat join=bevel | 20.0 | 0%             | -9%          | -15%
 endcap=flat            | 50.0 | 0%             | -14%         | -24%
 endcap=flat join=bevel | 50.0 | 0%             | -26%         | -36%

        warnen

Peter Krauss
quelle
13

Wenn Sie Ihre Polygondaten mit Ihren Mittelliniendaten verbinden können (entweder räumlich oder tabellarisch), addieren Sie einfach die Polygonflächen für jede Mittellinienausrichtung und dividieren Sie durch die Länge der Mittellinie.

Scro
quelle
das ist richtig! In diesem Fall sind meine Mittellinien nicht gleich lang, aber ich könnte sie immer als eine verbinden und sie pro Polygon teilen.
Djq
Wenn sich Ihre Daten in postgreSQL / postGIS befinden und Sie ein Straßen-ID-Feld für Mittellinien und Polygone haben, ist das Zusammenführen / Teilen nicht erforderlich, und bei Verwendung von Aggregatfunktionen ist Ihre Antwort nur eine Abfrage entfernt. Ich bin langsam in SQL, oder ich würde ein Beispiel posten. Lassen Sie mich wissen, ob Sie das
Problem auf
Danke Scro, es ist derzeit nicht in PostGIS, aber es ist ziemlich schnell zu laden. Ich denke, ich werde zuerst den Ansatz von @ whuber ausprobieren, aber ich werde ihn mit den Ergebnissen von PostGIS vergleichen (und danke für das Angebot von SQL-Hilfe, aber ich in der Lage sein sollte, zu verwalten). Hauptsächlich versuche ich, die Annäherung zuerst in meinem Kopf zu verdeutlichen.
14.
+1 Dies ist eine schöne einfache Lösung für Situationen, in denen es verfügbar ist.
Whuber
9

Ich habe eine Formel für die durchschnittliche Breite eines Polygons entwickelt und in eine Python / ArcPy-Funktion eingefügt. Meine Formel leitet sich aus dem einfachsten Begriff der Durchschnittsbreite ab (erweitert ihn jedoch erheblich), den ich an anderer Stelle erörtert habe. Das heißt, der Durchmesser eines Kreises mit der gleichen Fläche wie Ihr Polygon. In der obigen Frage und in meinem Projekt interessierte mich jedoch mehr die Breite der engsten Achse. Außerdem interessierte mich die durchschnittliche Breite für möglicherweise komplexe, nicht konvexe Formen.

Meine Lösung war:

(perimeter / pi) * area / (perimeter**2 / (4*pi))
= 4 * area / perimeter

Das ist:

(Diameter of a circle with the same perimeter as the polygon) * Area / (Area of a circle with the same perimeter as the polygon)

Die Funktion ist:

def add_average_width(featureClass, averageWidthField='Width'):
    '''
    (str, [str]) -> str

    Calculate the average width of each feature in the feature class. The width
        is reported in units of the feature class' projected coordinate systems'
        linear unit.

    Returns the name of the field that is populated with the feature widths.
    '''
    import arcpy
    from math import pi

    # Add the width field, if necessary
    fns = [i.name.lower() for i in arcpy.ListFields(featureClass)]
    if averageWidthField.lower() not in fns:
        arcpy.AddField_management(featureClass, averageWidthField, 'DOUBLE')

    fnsCur = ['SHAPE@LENGTH', 'SHAPE@AREA', averageWidthField]
    with arcpy.da.UpdateCursor(featureClass, fnsCur) as cur:
        for row in cur:
            perim, area, width = row
            row[-1] = ((perim/pi) * area) / (perim**2 / (4 * pi))
            cur.updateRow(row)

    return averageWidthField

Hier ist eine exportierte Karte mit der durchschnittlichen Breite (und einigen anderen Geometrieattributen als Referenz) für eine Vielzahl von Formen unter Verwendung der Funktion von oben:

Bildbeschreibung hier eingeben

Tom
quelle
4
Wenn Sie den Ausdruck vereinfachen, wird es gerecht area / perimeter * 4.
Culebrón
Vielen Dank, @ culebrón. Ich strebte nach Klarheit des Konzepts gegenüber der Einfachheit der Formel, und ich habe nie daran gedacht, die Gleichung zu vereinfachen. Dies sollte mir einige Bearbeitungszeit sparen.
Tom
0

Eine andere Lösung mit ungefährer Mittelachse:

  1. Berechnen Sie die ungefähre Mittelachse des Polygons.
  2. Länge der ungefähren Mittelachse ermitteln;
  3. Abstand von beiden Enden der Achse zum Rand des Polygons ermitteln;
  4. Länge der Summenachse und Abstände von Schritt 3 - Dies ist die ungefähre Länge des Polygons.
  5. Jetzt können Sie die Fläche des Polygons durch diese Länge teilen und die durchschnittliche Breite des Polygons ermitteln.

Bei Polygonen, bei denen die ungefähre Mittelachse keine durchgehende Linie ist, ist das Ergebnis sicherlich falsch. Sie können sie also vor Schritt 1 überprüfen und zurückkehren NULLoder so.

Beispiele

Hier ist ein Beispiel für die PostgreSQL-Funktion (Hinweis: Sie müssen die Erweiterungen postgis und postgis_sfcgal installieren ):

CREATE FUNCTION ApproximatePolygonLength(geom geometry)
RETURNS float AS $$
    SELECT
        CASE
            /* in case when approximate medial axis is empty or simple line
             * return axis length
             */
            WHEN (ST_GeometryType(axis.axis) = 'ST_LineString' OR ST_IsEmpty(axis.axis))
                THEN axis_length.axis_length
                    + start_point_distance.start_point_distance
                    + end_point_distance.end_point_distance
            /* else geometry is too complex to define length */
            ELSE NULL
        END AS length
    FROM
        LATERAL (
            SELECT
                ST_MakeValid(geom) AS valid_geom
        ) AS valid_geom,
        LATERAL (
            SELECT
                /* `ST_LineMerge` returns:
                 *  - `GEOMETRYCOLLECTION EMPTY`, if `ST_ApproximateMedialAxis` is an empty line (i.e. for square);
                 *  - `LINESTRING ...`, if `ST_ApproximateMedialAxis` is a simple line;
                 *  - `MULTILINESTRING ...`, if `ST_ApproximateMedialAxis` is a complex line
                 *     that can not be merged to simple line. In this case we should return `NULL`.
                 */
                ST_LineMerge(
                    ST_ApproximateMedialAxis(
                        valid_geom.valid_geom
                    )
                ) AS axis
        ) AS axis,
        LATERAL (
            SELECT
                ST_Boundary(valid_geom.valid_geom) AS border
        ) AS border,
        LATERAL (
            SELECT
                ST_Length(axis.axis) AS axis_length
        ) AS axis_length,
        LATERAL (
            SELECT
                ST_IsClosed(axis.axis) AS axis_is_closed
        ) AS axis_is_closed,
        LATERAL (
            SELECT
                CASE WHEN axis_is_closed.axis_is_closed THEN 0
                ELSE
                    ST_Distance(
                        border.border,
                        CASE ST_GeometryType(axis.axis)
                            WHEN 'ST_LineString' THEN ST_StartPoint(axis.axis)
                            /* if approximate medial axis is empty (i.e. for square),
                             * get centroid of geometry
                             */
                            ELSE ST_Centroid(valid_geom.valid_geom)
                        END
                    )
                END AS start_point_distance
        ) AS start_point_distance,
        LATERAL (
            SELECT
                CASE WHEN axis_is_closed.axis_is_closed THEN 0
                ELSE
                    ST_Distance(
                        border.border,
                        CASE ST_GeometryType(axis.axis)
                            WHEN 'ST_LineString' THEN ST_EndPoint(axis.axis)
                            /* if approximate medial axis is empty (i.e. for square),
                             * get centroid of geometry
                             */
                            ELSE ST_Centroid(valid_geom.valid_geom)
                        END
                    )
                END AS end_point_distance
        ) AS end_point_distance;
$$ LANGUAGE SQL;

CREATE FUNCTION ApproximatePolygonWidth(geom geometry)
RETURNS float AS $$
    SELECT
        CASE
            WHEN length IS NULL THEN NULL
            ELSE area.area / length.length
        END AS width
    FROM
        (
            SELECT ApproximatePolygonLength(geom) AS length
        ) AS length,
        (
            SELECT
                ST_Area(
                    ST_MakeValid(geom)
                ) AS area
        ) AS area;
$$ LANGUAGE SQL;

Nachteil:

Diese Lösung funktioniert nicht in Fällen, in denen das Polygon fast rechteckig ist und der Mensch seine Länge intuitiv definieren kann, die ungefähre Mittelachse jedoch nahe der Kante kleine Verzweigungen aufweist und der Algorithmus daher None zurückgibt.

Beispiel:

gebrochenes Beispiel

Andrey Semakin
quelle