Обчислення середньої ширини багатокутника? [зачинено]


40

Мені цікаво вивчити середню ширину багатокутника, який представляє дорожнє покриття. У мене також є центральна лінія дороги як вектор (що іноді не зовсім в центрі). У цьому прикладі лінія дороги - червоного кольору, а полігон - синього кольору:

введіть тут опис зображення

Один із підходів, що я думав про грубу силу, - це буферизація лінії невеликими кроками, пересікання буфера з сіткою риб'ячої мережі, пересічення дороги-багатокутника з сіткою рибальської мережі, обчислення ділянки перетину для обох заходів перетину та продовження цього помилка невелика. Це хоч і є грубим підходом, і мені цікаво, чи є більш елегантне рішення. Крім того, це приховувало б ширину великої дороги та малої дороги.

Мене цікавить рішення, яке використовує програмне забезпечення ArcGIS 10, PostGIS 2.0 або QGIS. Я бачив це запитання і завантажив інструмент Дена Паттерсона для ArcGIS10, але мені не вдалося розрахувати, що я хочу з ним.

Щойно я відкрив інструмент геометрії мінімальної граничної межі в ArcGIS 10, який дозволяє мені створювати такі зелені багатокутники:

введіть тут опис зображення

Це здається гарним рішенням для доріг, які слідують за сіткою, але не працюватимуть інакше, тому мене все ще цікавлять будь-які інші пропозиції.


Ви виключали можливі рішення на бічній панелі? тобто gis.stackexchange.com/questions/2880/…, мабуть, позначено як тривіальна відповідь на потенційно повторюваний пост

@DanPatterson я не бачив подібного питання (багато, звичайно, пов'язані). Ви мали на увазі, що моє запитання позначено прапором? Я не зрозумів вашого другого рядка.
djq

Це пов'язане питання @Dan стосується іншої інтерпретації "ширини" (ну, насправді, інтерпретація не зовсім зрозуміла). Здається, відповіді там зосереджуються на пошуку ширини в найширшій точці, а не на середній ширині.
whuber

Оскільки @whuber хочуть централізувати тут обговорення, закриваючи ще одне запитання, я пропоную редагувати це питання, щоб люди зрозуміли " оцінку середньої ширини прямокутної смуги "
Пітер Краус

@ Петер: Оскільки прямокутна смуга є фортіорі багатокутником, то загальна назва повинна стояти.
whuber

Відповіді:


40

Частина проблеми полягає у пошуку відповідного визначення поняття «середня ширина». Кілька природних, але будуть відрізнятися, принаймні трохи. Для простоти розглянемо визначення, засновані на властивостях, які легко обчислити (що виключить ті, які базуються, наприклад, на перетворенні медіальної осі або послідовності буферів).

Як приклад, розглянемо, що архетипна інтуїція багатокутника з певною «шириною» є невеликим буфером (скажімо, радіусом r з квадратними кінцями) навколо довгої досить прямолінійної лінії (скажімо, довжини L ). Ми вважаємо 2r = w як його ширину. Таким чином:

  • Його периметр P приблизно дорівнює 2L + 2w;

  • Його площа А приблизно дорівнює w L.

Ширина w і довжина L можуть бути відновлені як корені квадратичного x ^ 2 - (P / 2) x + A; зокрема, ми можемо оцінити

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

Якщо ви впевнені, що багатокутник дійсно довгий і худий, для подальшого наближення ви можете взяти 2L + 2w до рівня 2L, звідки

  • w (грубо) = 2A / P.

Відносна похибка в цьому наближенні пропорційна w / L: чим стрункіший багатокутник, тим ближче w / L до нуля, і тим краще наближення.

Цей підхід не тільки надзвичайно простий (просто розділіть площу на периметр і помножте на 2), а будь-яка формула не має значення, як орієнтований багатокутник чи де він знаходиться (тому що такі евклідові рухи не змінюють ні площу, ні периметр).

Ви можете скористатися будь-якою з цих формул для оцінки середньої ширини для будь-яких багатокутників, які представляють вуличні сегменти. Помилка, яку ви робите в початковій оцінці w (з квадратичною формулою), виникає через те, що область A також включає крихітні клини на кожному вигині початкової полілінії. Якщо сума кутів вигину дорівнює т радіанам (це сумарна абсолютна кривизна полілінії), то дійсно

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

  • A = L w + Pi tw ^ 2.

Підключіть їх до попереднього (квадратичної формули) рішення та спростіть. Коли дим очищається, внесок від терміну кривизни t зник! Те, що спочатку виглядало як наближення, є абсолютно точним для полілінійних буферів, що не перетинаються (з квадратними кінцями). Таким чином, для полігонів змінної ширини це розумне визначення середньої ширини.


Дякую @whuber, що це чудова відповідь, і це допомогло мені думати про це набагато чіткіше.
djq

@whuber: Я пишу статтю, і мені потрібно дати належну ("академічну") посилання на метод, який ви описали тут. У вас є така довідка? Чи має цей захід назву? Якщо ні, я можу назвати це за вами! А як щодо "міри ширини Губера"?
липень

@julien У мене немає посилань. Цей формат може працювати: MISC {20279, TITLE = {Обчислення середньої ширини багатокутника?}, AUTHOR = {whuber ( gis.stackexchange.com/users/664/whuber )}, HOWPUBLISHED = {GIS}, NOTE = {URL: gis.stackexchange.com/q/20279/664 (версія: 2013-08-13)}, EPRINT = { gis.stackexchange.com/q/20279 }, URL = { gis.stackexchange.com/q/20279 }}
whuber

18

Тут я демонструю невелику оптимізацію щодо рішення @whuber, і я вказую на "ширину буфера", оскільки це корисно для інтеграції рішення більш загальної проблеми: Чи є зворотна функція st_buffer, яка повертає оцінку ширини?

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;

Для цієї проблеми, @celenius питання про ширині вулиці , sw, рішення

 sw = buffer_width(ST_Length(g1), g2)

де sw"середня ширина", g1центральна лінія g2, а вулиця g2- ПОЛІГОН . Я використовував лише стандартну бібліотеку OGC, протестовану з PostGIS , і вирішував інші серйозні практичні програми з тією ж функцією buffer_width.

ДЕМОНСТРАЦІЯ

A2- площа g2, L1довжина центральної лінії ( g1) g2.

Припустимо, що ми можемо генерувати g2за допомогою g2=ST_Buffer(g1,w), а g1це пряма, так g2це прямокутник з довжиною L1і шириною 2*w, і

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

Це не та сама формула @whuber, тому що тут wполовина g2ширини прямокутника ( ). Це хороший оцінювач, але, як ми бачимо тестами (нижче), не є точним, і функція використовує його як підказку, для зменшення g2площі та як остаточний оцінювач.

Тут ми не оцінюємо буфери з "endcap = square" або "endcap = round", яким потрібна сума до A2 області точкового буфера з однаковою w.

ДОВІДКИ: на подібному форумі 2005 року В. Хубер пояснює подібні та інші рішення.

ТЕСТИ ТА ПРИЧИНИ

Для прямих ліній результати, як очікувалося, є точними. Але для інших геометрій результати можуть бути невтішними. Основна причина - мабуть, вся модель - для точних прямокутників, або для геометрій, які можна наблизити до «смугового прямокутника». Тут "тестовий комплект" для перевірки меж цього наближення (див. wfactorУ результатах вище).

 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;

РЕЗУЛЬТАТИ:

З ПРАВИЛЬНИМИ (центральна лінія - ПРАВА ЛІНІЯ):

         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

З ІНШИМИ ГЕОМЕТРІЯми (у центрі складена):

         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.

Про btypeдивіться посібник ST_Buffer , з хорошими ілюстрастратинами та LINESTRING, які використовуються тут.

ВИСНОВКИ :

  • оцінка w_estimзавжди краща, ніж w_near;
  • для "близької до прямокутної" g2геометрії, її нормально, будь-якаwfactor
  • для іншої геометрії (поблизу "прямокутних смуг") використовуйте обмеження wfactor=~0.01на 1% помилки на w_estim. До цього wfactor використовуйте інший оцінювач.

Обережність і профілактика

Чому виникає помилка оцінки? Коли ви використовуєте ST_Buffer(g,w), ви «від прямокутної моделі смужок» очікуєте, що нова область, додана буфером ширини, wстановить приблизно w*ST_Length(g)або w*ST_Perimeter(g)... Коли ні, зазвичай це накладення (див. Складені лінії) або «стилізація» - це коли оцінка середньої wнесправності . Це головне повідомлення тестів.

Щоб виявити цю проблему в будь-якому короткі буфері , перевірте поведінку генерації буфера:

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;

РЕЗУЛЬТАТИ:

         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%

        оповіщення


13

Якщо ви можете приєднати дані своїх полігонів до даних вашої центральної лінії (або просторовими, або табличними способами), тоді просто підсумовуйте області полігону для кожного вирівнювання середньої лінії та діліть на довжину центральної лінії.


це правда! У цьому випадку мої центральні лінії не однакової довжини, але я завжди можу з'єднати їх як одну і розділити їх на багатокутник.
djq

Якщо ваші дані перебувають у postgreSQL / postGIS і у вас є поле ідентифікатора вулиці для центральних ліній та полігонів, злиття / розбиття не потребує, а використовуючи сукупні функції, ваша відповідь - це лише запит. Я повільний у SQL, або я б розмістив приклад. Повідомте мене, якщо ви збираєтеся вирішити це, і я допоможу розібратися (якщо потрібно.)
Scro

Спасибі Scro, наразі він не знаходиться в PostGIS, але він досить швидко завантажується. Я думаю, спершу спробую підхід @ whuber, але порівняю його з результатами PostGIS (і дякую за пропозицію допомоги SQL, але я повинен вміти керувати). В основному намагаюся спочатку зрозуміти підхід у голові.
djq

+1 Це приємне просте рішення для обставин, коли це доступно.
whuber

9

Я розробив формулу середньої ширини багатокутника і ввів його у функцію Python / ArcPy. Моя формула випливає з (але суттєво розширюється) найбільш прямого поняття середньої ширини, яке я бачив, обговорюваного в інших місцях; тобто діаметр кола, що має ту саму площу, як і ваш багатокутник. Однак у питанні вище та в моєму проекті мене більше цікавила ширина найвужчої осі. Крім того, мене зацікавила середня ширина для потенційно складних, не опуклих фігур.

Моє рішення було:

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

Це:

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

Функція:

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

Ось експортована карта із середньою шириною (та деякими іншими атрибутами геометрії для довідки) для різних форм, використовуючи функцію зверху:

введіть тут опис зображення


4
Якщо ви спростите вираз, це буде просто area / perimeter * 4.
culebrón

Спасибі, @ culebrón. Я збирався зрозуміти концепцію щодо простоти формули, і навіть не думав про спрощення рівняння. Це повинно заощадити трохи часу на обробку.
Том

0

Ще одне рішення з приблизною медіальною віссю:

  1. Обчисліть приблизну медіальну вісь багатокутника;
  2. Отримати довжину приблизної медіальної осі;
  3. Отримайте відстань від обох кінців осі до межі полігону;
  4. Сума довжини осі та відстані від кроку 3 - це приблизна довжина багатокутника;
  5. Тепер ви можете розділити площу полігону на цю довжину і отримати середню ширину багатокутника.

Результат, безумовно, буде неправильним для тих багатокутників, де наближена медіальна вісь не є єдиною суцільною лінією, тому ви можете перевірити її перед кроком 1 і повернутися NULLчи щось.

приклади

Ось приклад функції PostgreSQL (зверніть увагу: вам потрібно встановити розширення postgis та 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;

Недолік:

Це рішення не працюватиме у випадках, коли багатокутник майже прямокутний і людина може інтуїтивно визначити його довжину, але наближена медіальна вісь має невеликі гілки біля краю, і таким чином алгоритм не повертає None.

Приклад:

ламаний приклад

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.