Cálculo da largura média do polígono?

41

Estou interessado em examinar a largura média de um polígono que representa a superfície da estrada. Eu também tenho a linha central da estrada como um vetor (que às vezes não está exatamente no centro). Neste exemplo, a linha central da estrada está em vermelho e o polígono é azul:

insira a descrição da imagem aqui

Uma abordagem de força bruta que eu pensei, é amortecer a linha em pequenos incrementos, interceptar o buffer com uma grade de rede de pesca, interceptar o polígono da estrada com uma grade de rede de pesca, calcular a área de interseção para ambas as medidas de interseção e continuar fazendo isso até o erro é pequeno. Essa é uma abordagem grosseira, e estou me perguntando se existe uma solução mais elegante. Além disso, isso ocultaria a largura de uma estrada grande e uma estrada pequena.

Estou interessado em uma solução que usa o software ArcGIS 10, PostGIS 2.0 ou QGIS. Vi essa pergunta e baixei a ferramenta de Dan Patterson para o ArcGIS10, mas não consegui calcular o que quero com ela.

Acabei de descobrir a ferramenta Minimum Bounding Geometry no ArcGIS 10, que me permite produzir os seguintes polígonos verdes:

insira a descrição da imagem aqui

Essa parece ser uma boa solução para estradas que seguem uma grade, mas não funcionariam de outra maneira, por isso ainda estou interessado em outras sugestões.

djq
fonte
Você descartou possíveis soluções na barra lateral? ie gis.stackexchange.com/questions/2880/... aparentemente marcada como uma resposta trivial para um posto potencialmente duplicado
@ DanPatterson Eu não vi nenhuma pergunta como essa (muitas estão relacionadas, é claro). Você quis dizer que minha pergunta foi sinalizada? Não entendi sua segunda linha.
DJQ
Essa pergunta relacionada, @Dan, refere-se a uma interpretação diferente de "largura" (bem, na verdade, a interpretação não é perfeitamente clara). As respostas parecem focar em encontrar a largura no ponto mais largo, em vez de uma largura média.
whuber
Como @whuber quer centralizar aqui discussões, fechando mais perguntas, eu sugiro que a questão é editado para as pessoas a entender " a estimativa de largura média de uma faixa retangular "
Peter Krauss
@ Peter: Como uma faixa retangular é um polígono a fortiori , o título mais geral deve permanecer.
whuber

Respostas:

41

Parte do problema é encontrar uma definição adequada de "largura média". Vários são naturais, mas diferem, pelo menos um pouco. Por uma questão de simplicidade, considere definições baseadas em propriedades fáceis de calcular (o que excluirá aquelas baseadas na transformação do eixo medial ou nas seqüências de buffers, por exemplo).

Como exemplo, considere que a intuição arquetípica de um polígono com uma "largura" definida é um pequeno buffer (digamos, raio r com extremidades quadradas) ao redor de uma polilinha longa e bastante reta (digamos comprimento L ). Pensamos em 2r = w como sua largura. Portanto:

  • Seu perímetro P é aproximadamente igual a 2L + 2w;

  • Sua área A é aproximadamente igual a w L.

A largura w e o comprimento L podem então ser recuperados como raízes do quadrático x ^ 2 - (P / 2) x + A; em particular, podemos estimar

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

Quando você tiver certeza de que o polígono é realmente longo e fino, como uma aproximação adicional, você pode levar 2L + 2w para igualar 2L, de onde

  • w (grosseiramente) = 2A / P.

O erro relativo nesta aproximação é proporcional a w / L: quanto mais fino o polígono, mais próximo w / L é de zero e melhor a aproximação fica.

Essa abordagem não é apenas extremamente simples (apenas divida a área pelo perímetro e multiplique por 2). Com qualquer uma das fórmulas, não importa como o polígono está orientado ou onde está localizado (porque esses movimentos euclidianos não alteram a área nem a área). perímetro).

Você pode considerar o uso de uma dessas fórmulas para estimar a largura média de qualquer polígono que represente segmentos de ruas. O erro que você comete na estimativa original de w (com a fórmula quadrática) ocorre porque a área A também inclui pequenas fatias em cada curva da polilinha original. Se a soma dos ângulos de dobra for t radianos (esta é a curvatura absoluta total da polilinha), então realmente

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

  • A = Lw + Pi tw ^ 2.

Conecte-os à solução anterior (fórmula quadrática) e simplifique. Quando a fumaça desaparece, a contribuição do termo de curvatura t desaparece! O que originalmente parecia uma aproximação é perfeitamente preciso para os buffers de polilinha sem auto-interseção (com extremidades ao quadrado). Para polígonos de largura variável, essa é, portanto, uma definição razoável de largura média.

whuber
fonte
Obrigado @whuber, que é uma ótima resposta e me ajudou a pensar muito mais claramente.
DJQ
@ whuber: estou escrevendo um artigo e precisaria dar uma referência ('acadêmica') adequada ao método que você descreve aqui. Você tem essa referência? Esta medida tem um nome? Se não, eu posso nomear depois de você! E a "medida de largura do Huber"?
julien
@ julien Não tenho nenhuma referência. Esse formato pode funcionar: MISC {20279, TITLE = {Calculando a largura média do polígono?}, AUTHOR = {whuber ( gis.stackexchange.com/users/664/whuber )}, HOWPUBLISHED = {GIS}, NOTE = {URL: gis.stackexchange.com/q/20279/664 (versão: 2013-08-13)}, EPRINT = { gis.stackexchange.com/q/20279 }, URL = { gis.stackexchange.com/q/20279 }}
whuber
1
Esta é uma resposta fabulosa e tão simples e bem explicada. Obrigado!
amball 16/11
18

Aqui mostro pouca otimização sobre a solução @whuber, e estou colocando em termos de "largura do buffer", porque é útil para integrar a solução de um problema mais geral: Existe uma função inversa st_buffer, que retorna uma estimativa de largura?

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;

Para este problema, a questão @celenius sobre largura da rua , swa solução é

 sw = buffer_width(ST_Length(g1), g2)

onde swé a "largura média", g1a linha central de g2e a rua g2é um polígono . Usei apenas a biblioteca padrão OGC, testei com PostGIS e resolvi outras aplicações práticas sérias com a mesma função buffer_width.

DEMONSTRAÇÃO

A2é a área de g2, L1o comprimento da linha central ( g1) de g2.

Supondo que possamos gerar g2por g2=ST_Buffer(g1,w), e que g1seja reto, g2um retângulo com comprimento L1e largura 2*w, e

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

Não é a mesma fórmula de @whuber, porque aqui westá metade da g2largura do retângulo ( ). É um bom estimador, mas, como podemos ver pelos testes (abaixo), não é exato, e a função o utiliza como uma pista para reduzir a g2área e como um estimador final.

Aqui, não avaliamos buffers com "endcap = square" ou "endcap = round", que precisam da soma A2 de uma área de um buffer de ponto com o mesmo w.

Referências: em um fórum semelhante de 2005 , W. Huber explica soluções semelhantes e outras.

ENSAIOS E RAZÕES

Para linhas retas, os resultados, como esperado, são exatos. Mas para outras geometrias, os resultados podem ser decepcionantes. A principal razão é que, talvez, todo o modelo seja para retângulos exatos ou para geometrias que possam ser aproximadas a um "retângulo de tira". Aqui está um "kit de teste" para verificar os limites dessa aproximação (veja wfactornos resultados acima).

 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;

RESULTADOS:

COM RETÂNGULOS (a linha central é uma LINHA RETO):

         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

COM OUTRAS GEOMETRIAS (linha central dobrada):

         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.

Sobre, btypeconsulte o guia ST_Buffer , com boas ilustrações e os LINESTRINGs usados ​​aqui.

CONCLUSÕES :

  • o estimador de w_estimé sempre melhor que w_near;
  • para g2geometrias "quase retangulares" , tudo bem, qualquerwfactor
  • para outras geometrias (próximo a "faixas retangulares"), use o limite wfactor=~0.01para 1% de erro em w_estim. Até esse fator, use outro estimador.

Cuidado e prevenção

Por que o erro de estimativa ocorre? Quando você usa ST_Buffer(g,w), espera, pelo "modelo de faixa retangular", que a nova área adicionada pelo buffer de largura wseja aproximadamente w*ST_Length(g)ou w*ST_Perimeter(g)... Quando não, geralmente por sobreposições (consulte linhas dobradas) ou "estilo", é quando a estimativa da wfalha média . Esta é a principal mensagem dos testes.

Para detectar esse problema em qualquer rei do buffer , verifique o comportamento da geração do 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;

RESULTADOS:

         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%

        alerta

Peter Krauss
fonte
13

Se você pode associar seus dados de polígono aos dados da linha de centro (por meios espaciais ou tabulares), basta somar as áreas de polígono para cada alinhamento da linha de centro e dividir pelo comprimento da linha de centro.

Scro
fonte
isso é verdade! Nesse caso, minhas linhas de centro não têm o mesmo comprimento, mas eu sempre posso uni-las como uma e dividi-las por polígono.
DJQ
Se seus dados estão no postgreSQL / postGIS e você possui um campo de identificação de rua para linhas de centro e polígonos, não há necessidade de mesclar / dividir e, usando funções agregadas, sua resposta está apenas a uma consulta. Eu sou lento no SQL, ou gostaria de postar um exemplo. Deixe-me saber se é assim que você vai resolver, e eu ajudarei a resolvê-lo (se necessário.) #
020
Obrigado Scro, atualmente não está no PostGIS, mas é muito rápido para carregar. Acho que tentarei a abordagem do @ whuber primeiro, mas compararei com os resultados do PostGIS (e obrigado pela oferta de ajuda do SQL, mas eu deve ser capaz de gerenciar). Principalmente tentando esclarecer primeiro a abordagem na minha cabeça.
DJQ
+1 Esta é uma solução simples e agradável para as circunstâncias em que está disponível.
whuber
9

Eu desenvolvi uma fórmula para a largura média de um polígono e a coloquei em uma função Python / ArcPy. Minha fórmula é derivada (mas amplia substancialmente) da noção mais direta de largura média que eu já vi discutida em outros lugares; isto é, o diâmetro de um círculo com a mesma área do seu polígono. No entanto, na pergunta acima e no meu projeto, eu estava mais interessado na largura do eixo mais estreito. Além disso, eu estava interessado na largura média de formas potencialmente complexas e não convexas.

Minha solução foi:

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

Isso é:

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

A função é:

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

Aqui está um mapa exportado com a largura média (e alguns outros atributos de geometria para referência) em uma variedade de formas usando a função acima:

insira a descrição da imagem aqui

Tom
fonte
4
Se você simplificar a expressão, será apenas area / perimeter * 4.
culebrón
Obrigado, @ culebrón. Eu buscava clareza de conceito em vez de simplicidade de fórmula, e nunca pensei em simplificar a equação. Isso deve me poupar algum tempo de processamento.
Tom
0

Outra solução com eixo medial aproximado:

  1. Calcular o eixo medial aproximado do polígono;
  2. Obter comprimento do eixo medial aproximado;
  3. Obter distância das duas extremidades do eixo até a borda do polígono;
  4. Soma o comprimento do eixo e as distâncias do passo 3 - é o comprimento aproximado do polígono;
  5. Agora você pode dividir a área do polígono por esse comprimento e obter a largura média do polígono.

O resultado certamente estará errado para os polígonos em que o eixo medial aproximado não é uma única linha contínua, para que você possa verificá-lo antes da etapa 1 e retornar NULLou algo assim.

exemplos

Aqui está um exemplo da função PostgreSQL (nota: você precisa instalar as extensões 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;

Desvantagem:

Essa solução não funcionará com casos em que o polígono é quase retangular e o ser humano pode intuitivamente definir seu comprimento, mas o eixo medial aproximado possui pequenos ramos próximos à borda e, portanto, o algoritmo retorna Nenhum.

Exemplo:

exemplo quebrado

Andrey Semakin
fonte