Calcolo della larghezza media del poligono?


41

Sono interessato a esaminare la larghezza media di un poligono che rappresenta il manto stradale. Ho anche la linea centrale della strada come vettore (che a volte non è esattamente al centro). In questo esempio, la linea centrale della strada è in rosso e il poligono è blu:

inserisci qui la descrizione dell'immagine

Un approccio a forza bruta a cui ho pensato è quello di bufferizzare la linea con piccoli incrementi, intersecare il buffer con una griglia a rete, intersecare il poligono strada con una griglia a rete, calcolare l'area intersecata per entrambe le misure di intersezione e continuare a farlo fino a l'errore è piccolo. Questo è un approccio rozzo, però, e mi chiedo se esiste una soluzione più elegante. Inoltre, ciò nasconderebbe la larghezza di una strada grande e una strada piccola.

Sono interessato a una soluzione che utilizza il software ArcGIS 10, PostGIS 2.0 o QGIS. Ho visto questa domanda e scaricato lo strumento di Dan Patterson per ArcGIS10, ma non sono stato in grado di calcolare ciò che voglio con esso.

Ho appena scoperto lo strumento Geometria minima limite in ArcGIS 10 che mi consente di produrre i seguenti poligoni verdi:

inserisci qui la descrizione dell'immagine

Questa sembra una buona soluzione per le strade che seguono una griglia, ma non funzionerebbe diversamente, quindi sono ancora interessato ad altri suggerimenti.


Hai escluso possibili soluzioni sulla barra laterale? cioè gis.stackexchange.com/questions/2880/… apparentemente contrassegnato come una banale risposta a un post potenzialmente duplicato

@DanPatterson Non ho visto domande del genere (molte sono correlate, ovviamente). Intendevi che la mia domanda era contrassegnata? Non ho capito la tua seconda riga.
djq

Quella domanda correlata, @Dan, riguarda una diversa interpretazione di "larghezza" (beh, in realtà, l'interpretazione non è perfettamente chiara). Le risposte sembrano concentrarsi sulla ricerca della larghezza nel punto più largo, piuttosto che su una larghezza media.
whuber

Dato che @whuber vuole centralizzare qui le discussioni, chiudendo altre domande, suggerisco che la domanda venga modificata per capire " la stima della larghezza media di una striscia rettangolare "
Peter Krauss,

@Peter: Poiché una striscia rettangolare è a maggior ragione un poligono, il titolo più generale dovrebbe essere valido.
whuber

Risposte:


41

Parte del problema è trovare una definizione adatta di "larghezza media". Molti sono naturali ma differiranno, almeno leggermente. Per semplicità, considera le definizioni basate su proprietà facili da calcolare (che escluderà quelle basate sulla trasformazione dell'asse mediale o sulle sequenze di buffer, per esempio).

Ad esempio, si consideri che l'intuizione archetipica di un poligono con una "larghezza" definita è un piccolo buffer (diciamo del raggio r con estremità quadrate) attorno a una polilinea lunga, abbastanza diritta (diciamo della lunghezza L ). Pensiamo a 2r = w come alla sua larghezza. Così:

  • Il suo perimetro P è approssimativamente uguale a 2L + 2w;

  • La sua area A è approssimativamente uguale a w L.

La larghezza w e la lunghezza L possono quindi essere recuperate come radici del quadratico x ^ 2 - (P / 2) x + A; in particolare, possiamo stimare

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

Quando sei sicuro che il poligono sia davvero lungo e magro, come ulteriore approssimazione puoi prendere 2L + 2w per uguagliare 2L, da cui

  • w (rozzamente) = 2A / P.

L'errore relativo in questa approssimazione è proporzionale a w / L: più il poligono è magro, più w / L è vicino a zero e migliore è l'approssimazione.

Questo approccio non è solo estremamente semplice (basta dividere l'area per il perimetro e moltiplicare per 2), con entrambe le formule non importa come il poligono è orientato o dove si trova (perché tali movimenti euclidei non cambiano né l'area né il perimetro).

Potresti considerare di utilizzare una di queste formule per stimare la larghezza media di tutti i poligoni che rappresentano i segmenti di strada. L'errore commesso nella stima originale di w (con la formula quadratica) si verifica perché l'area A include anche dei piccoli cunei ad ogni curva della polilinea originale. Se la somma degli angoli di piega è t radianti (questa è la curvatura assoluta totale della polilinea), allora davvero

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

  • A = L w + Pi tw ^ 2.

Collegali alla soluzione precedente (formula quadratica) e semplifica. Quando il fumo scompare, il contributo del termine di curvatura t è scomparso! Ciò che originariamente sembrava un'approssimazione è perfettamente preciso per i buffer di polilinea non autointersecanti (con estremità quadrate). Per i poligoni a larghezza variabile, questa è quindi una definizione ragionevole di larghezza media.


Grazie @whuber è un'ottima risposta e mi ha aiutato a pensarci molto più chiaramente.
DJ

@whuber: sto scrivendo un documento e avrei bisogno di fornire un riferimento adeguato ("accademico") al metodo che descrivi qui. Hai un tale riferimento? Questa misura ha un nome? Altrimenti, posso nominarlo come te! Che dire della "misura della larghezza di Huber"?
luglio

@julien Non ho riferimenti. Questo formato potrebbe funzionare: MISC {20279, TITLE = {Calcolo della larghezza media del poligono?}, AUTORE = {whuber ( gis.stackexchange.com/users/664/whuber )}, HOWPUBLISHED = {GIS}, NOTE = {URL: gis.stackexchange.com/q/20279/664 (versione: 2013-08-13)}, EPRINT = { gis.stackexchange.com/q/20279 }, URL = { gis.stackexchange.com/q/20279 }}
whuber

1
Questa è una risposta favolosa e così semplice e ben spiegata. Grazie!
amball

18

Qui mostro poca ottimizzazione sulla soluzione @whuber, e sto mettendo in termini di "buffer buffer", perché è utile per integrare la soluzione di un problema più generale: esiste una funzione inversa st_buffer, che restituisce una stima della larghezza?

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;

Per questo problema, la questione @celenius sulla larghezza stradale , swla soluzione è

 sw = buffer_width(ST_Length(g1), g2)

dove si swtrova la "larghezza media", g1la linea centrale di g2e la strada g2è un POLIGONO . Ho usato solo la libreria standard OGC, testato con PostGIS e risolto altre serie applicazioni pratiche con la stessa funzione buffer_width.

DIMOSTRAZIONE

A2è l'area di g2, L1la lunghezza della linea centrale ( g1) di g2.

Supponendo che possiamo generare g2da g2=ST_Buffer(g1,w), e che g1è una scala, così g2è un rettangolo con lunghezza L1e larghezza 2*w, e

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

Non è la stessa formula di @whuber, perché qui wc'è metà della g2larghezza di rettangolo ( ). È un buon stimatore, ma come possiamo vedere dai test (sotto), non è esatto e la funzione lo usa come indizio, per ridurre l' g2area e come stimatore finale.

Qui non valutiamo i buffer con "endcap = square" o "endcap = round", che necessitano di una somma A2 di un'area di un buffer di punti con lo stesso w.

RIFERIMENTI: in un forum simile del 2005 , W. Huber spiega simili e altre soluzioni.

PROVE E MOTIVI

Per le linee rette i risultati, come previsto, sono esatti. Ma per altre geometrie i risultati possono essere deludenti. Il motivo principale è, forse, che tutto il modello è per rettangoli esatti o per geometrie che possono essere approssimate a un "rettangolo a strisce". Qui un "kit di test" per verificare i limiti di questa approssimazione (vedere wfactori risultati sopra).

 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;

RISULTATI:

CON RETTANGOLI (la linea centrale è una LINEA DIRITTA):

         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

CON ALTRE GEOMETRIE (linea centrale piegata):

         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.

Informazioni su btypevedi la guida ST_Buffer , con buone illustrazioni e le LINESTRING utilizzate qui.

CONCLUSIONI :

  • lo stimatore di w_estimè sempre migliore di w_near;
  • per g2geometrie "quasi rettangolari" , va bene, qualsiasiwfactor
  • per altre geometrie (vicino a "strisce rettangolari"), utilizzare il limite wfactor=~0.01per 1% di errore su w_estim. Fino a questo fattore, utilizzare un altro stimatore.

Attenzione e prevenzione

Perché si verifica l'errore di stima? Quando si utilizza ST_Buffer(g,w), ci si aspetta, dal "modello a strisce rettangolari", che la nuova area aggiunta dal buffer di larghezza wsia circa w*ST_Length(g)o w*ST_Perimeter(g)... Quando no, di solito per sovrapposizioni (vedere le linee piegate) o per "stile", è quando la stima wdell'errore medio . Questo è il messaggio principale dei test.

Per rilevare questo problema in qualsiasi re del buffer , controllare il comportamento della generazione del buffer:

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;

RISULTATI:

         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%

        mettere in guardia


13

Se è possibile unire i dati del poligono ai dati della linea centrale (in modo spaziale o tabellare), è sufficiente sommare le aree poligonali per ciascun allineamento della linea centrale e dividere per la lunghezza della linea centrale.


è vero! In questo caso, le mie linee centrali non hanno la stessa lunghezza, ma potrei sempre unirle come una sola e dividerle per poligono.
DJ

Se i tuoi dati sono in postgreSQL / postGIS e hai un campo id strada per linee centrali e poligoni, non è necessario unire / dividere e, utilizzando le funzioni di aggregazione, la tua risposta è solo una query. Sono lento a SQL, o vorrei pubblicare un esempio. Fammi sapere se è così che risolverai e ti aiuterò a risolverlo (se necessario)
Scro

Grazie Scro, non è attualmente in PostGIS, ma è abbastanza veloce da caricare. Penso che proverò prima l'approccio di @ whuber ma lo confronterò con i risultati di PostGIS (e grazie per l'offerta dell'aiuto SQL, ma io dovrebbe essere in grado di gestire). Principalmente cercando di chiarirmi prima l'approccio.
djq

+1 Questa è una bella soluzione semplice per le circostanze in cui è disponibile.
whuber

9

Ho sviluppato una formula per la larghezza media di un poligono e l'ho inserita in una funzione Python / ArcPy. La mia formula deriva (ma sostanzialmente si estende) dalla nozione più semplice di larghezza media che ho visto discusso altrove; cioè il diametro di un cerchio avente la stessa area del poligono. Tuttavia, nella domanda sopra e nel mio progetto, ero più interessato alla larghezza dell'asse più stretto. Inoltre, ero interessato alla larghezza media per forme potenzialmente complesse, non convesse.

La mia soluzione era:

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

Questo è:

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

La funzione è:

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

Ecco una mappa esportata con la larghezza media (e alcuni altri attributi di geometria come riferimento) attraverso una varietà di forme usando la funzione dall'alto:

inserisci qui la descrizione dell'immagine


4
Se semplifichi l'espressione, sarà giusto area / perimeter * 4.
culebrón,

Grazie, @ culebrón. Stavo cercando la chiarezza del concetto sulla semplicità della formula, e non ho mai nemmeno pensato di semplificare l'equazione. Questo dovrebbe risparmiarmi un po 'di tempo di elaborazione.
Tom,

0

Un'altra soluzione con asse mediale approssimativo:

  1. Calcola l'asse mediale approssimativo del poligono;
  2. Ottieni la lunghezza dell'asse mediale approssimativo;
  3. Ottieni la distanza da entrambe le estremità dell'asse al bordo del poligono;
  4. Somma la lunghezza dell'asse e le distanze dal passaggio 3: è la lunghezza approssimativa del poligono;
  5. Ora puoi dividere l'area del poligono per questa lunghezza e ottenere la larghezza media del poligono.

Il risultato sarà sicuramente sbagliato per quei poligoni in cui l'asse mediale approssimativo non è una singola linea continua, quindi puoi controllarlo prima del passaggio 1 e tornare NULLo qualcosa del genere.

esempi

Ecco un esempio della funzione PostgreSQL (nota: è necessario installare le estensioni postgis e postgis_sfcgal ):

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;

Svantaggio:

Questa soluzione non funzionerà con i casi in cui il poligono è quasi rettangolare e l'uomo può definire in modo intuitivo la sua lunghezza ma l'asse mediale approssimativo ha piccoli rami vicino al bordo e quindi l'algoritmo restituisce Nessuno.

Esempio:

esempio rotto

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.