ポリゴンの平均幅を計算していますか?


41

路面を表すポリゴンの平均幅を調べることに興味があります。また、道路の中心線をベクトルとして持っています(正確には中心にない場合があります)。この例では、道路の中心線は赤で、ポリゴンは青です。

ここに画像の説明を入力してください

私が考えていたブルートフォースアプローチの1つは、ラインを少しずつバッファリングし、バッファをフィッシュネットグリッドと交差させ、道路ポリゴンとフィッシュネットグリッドを交差させ、両方の交差測定の交差面積を計算し、これをエラーはわずかです。しかし、これは大雑把なアプローチであり、よりエレガントなソリューションがあるかどうか疑問に思っています。さらに、これにより、大きな道路と小さな道路の幅が隠されます。

ArcGIS 10、PostGIS 2.0、またはQGISソフトウェアを使用するソリューションに興味があります。この質問を見て、ArcGIS10用のDan Pattersonのツールをダウンロードしましたが、必要なものを計算できませんでした。

ArcGIS 10で最小境界ジオメトリツールを発見したところ、次の緑のポリゴンを作成できます。

ここに画像の説明を入力してください

これは、グリッドに沿った道路に適したソリューションのように見えますが、それ以外では機能しません。そのため、他の提案にも興味があります。


サイドバーで可能な解決策を除外しましたか?すなわちgis.stackexchange.com/questions/2880/… 重複する可能性のある投稿に対する些細な回答としてフラグが立てられているようです

@DanPattersonこのような質問はありませんでした(もちろん多くは関連しています)。私の質問にフラグが立てられたということですか?あなたのセカンドラインがわかりませんでした。
djq

その関連する質問@Danは、「幅」の異なる解釈に関するものです(実際、解釈は完全には明確ではありません)。返信は、平均的な幅はなく、最も広いポイントで見つけることに焦点を当てているようです。
whuber

@whuberがここに集中したいような議論は、別の質問を閉じて、私は質問が人々に編集した「理解していることを示唆している短冊の平均幅のた推定を
ピーター・クラウス

@Peter:短冊があるのでなおさら Aポリゴン、より一般的なタイトルが立つべきです。
whuber

回答:


41

問題の一部は、「平均幅」の適切な定義を見つけることです。いくつかは自然ですが、少なくともわずかに異なります。簡単にするために、計算が簡単なプロパティに基づいた定義を検討します(たとえば、中間軸の変換やバッファーのシーケンスに基づいた定義は除外されます)。

例として、明確な「幅」を持つポリゴンの典型的な直観は、長くてかなりまっすぐなポリライン(長さLなど)の周りの小さなバッファー(正方形の端を持つ半径r など)であると考えてください。2r = wをその幅と考えます。したがって:

  • 境界線Pは2L + 2wにほぼ等しい。

  • その面積Aは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倍するだけです)、どちらの式でも、ポリゴンの向きや場所は関係ありません(そのようなユークリッドの動きはエリアも境界)。

これらの式のいずれかを使用して、道路セグメントを表すポリゴンの平均幅を推定することを検討できます。面積Aには元のポリラインの各曲げの小さなくさびも含まれるため、wの元の推定(2次式を使用)でエラーが発生します。曲げ角度の合計がtラジアンの場合(これはポリラインの絶対曲率の合計です)、

  • P = 2L + 2w + 2 Pi twおよび

  • A = L w + Pi tw ^ 2。

これらを以前の(2次式)ソリューションにプラグインし、単純化します。煙が消えると、曲率項tからの寄与は消えました! もともと近似のように見えたものは、自己交差しないポリラインバッファー(正方形の端を持つ)に対して完全に正確です。 したがって、可変幅ポリゴンの場合、これは平均幅の合理的な定義です。


@whuberに感謝します。これは素晴らしい答えであり、それについてもっと明確に考えるのに役立ちました。
djq

@whuber:私は論文を書いているので、ここで説明する方法への適切な(「アカデミック」)参照を与える必要があります。そのような参照はありますか?このメジャーには名前がありますか?そうでない場合は、あなたにちなんで名前を付けることができます!「Huberの幅測定」はどうですか?
ジュリアン

@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

1
これは素晴らしい答えであり、とてもシンプルで説明が行き届いています。ありがとうございました!
アンボール

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、および通りg2POLYGONです。OGC標準ライブラリのみを使用し、PostGISでテストし、同じbuffer_width関数で他の深刻な実用的なアプリケーションを解決しました。

デモンストレーション

A2領域でありg2L1(中心線の長さ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年の同様のフォーラムで、W。フーバーはそのようなソリューションやその他のソリューションについて説明しています。

テストと理由

直線の場合、予想どおり、結果は正確です。しかし、他のジオメトリの場合、結果はがっかりする可能性があります。主な理由は、おそらく、すべてのモデルが正確な長方形、または「ストリップ長方形」に近似できるジオメトリのためです。ここに、この近似の限界をチェックするための「テストキット」があります(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ジオメトリの場合は、OKwfactor
  • 別のジオメトリ(「長方形のストリップ」に近い)の場合、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

空間データまたは表形式の手段でポリゴンデータを中心線データに結合できる場合は、中心線の配置ごとにポリゴン領域を合計し、中心線の長さで除算します。


それは本当だ!この場合、中心線は同じ長さではありませんが、それらを常に1つに結合し、ポリゴンごとに分割することができます。
djq

データがpostgreSQL / postGISにあり、中心線とポリゴンのストリートIDフィールドがある場合、マージ/分割の必要はなく、集計関数を使用すると、答えはクエリだけです。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。数式の単純さよりも概念の明確さを追求していましたが、方程式を単純化することさえ考えませんでした。これにより、処理時間が節約されます。
トム

0

おおよその内側軸を持つ別のソリューション:

  1. 多角形のおおよその中心軸を計算します。
  2. おおよその内側軸の長さを取得します。
  3. 軸の両端からポリゴンの境界までの距離を取得します。
  4. 軸の長さとステップ3からの距離を合計します。これはおおよそのポリゴンの長さです。
  5. これで、ポリゴンの領域をこの長さで分割し、ポリゴンの平均幅を取得できます。

おおよその中心軸が単一の連続線ではないポリゴンの場合、結果は確実に間違っているので、ステップ1の前にチェックして戻りNULLます。

例

ここではPostgreSQLの機能(:あなたがインストールする必要がノートの例でのPostGISpostgis_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を返します。

例:

壊れた例

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.