PostGISでx方向(東西方向)のポリゴン内の最大距離を計算しますか?


13

湖などのポリゴンの東西方向の最大幅に興味があります。境界ボックスは、単純なポリゴンでのみ役立ちますが、複雑な凹面ポリゴンでは役立ちません。


3
私はpostgis機能に精通していません。ただし、境界ボックスツールがある場合があります。境界ボックスの幅は、EW方向の最大距離になります。
フェスター

4
@Fetzterは正しくありません。反例は、単純で複雑なポリゴンであっても、SWからNEに伸びる細い菱形です。その最大東西幅は、その境界ボックスの幅の任意の小さな部分になります。
whuber

1
私は、に基づいて、このタスクのためのユーティリティを作成しました。この、この提案。ポリゴンの最大幅または最小幅を計算できます。現在はshpファイルで動作しますが、PostGISで動作するように書き換えるか、PostGISでも動作するQGISプラグインに進化するまでしばらく待つことができます。詳細な説明とダウンロードリンクはこちら
SS_Rebelious

回答:


16

これには、GISプラットフォームでのスクリプト作成が必要になる可能性があります。

最も効率的な方法(漸近的に)は垂直ラインスイープです:最小y座標でエッジをソートし、O(e * log( e))eエッジが含まれる場合のアルゴリズム。

手順は単純ではありますが、すべての場合に正しく実行するには驚くほどトリッキーです。 ポリゴンは厄介なことがあります:ダングル、スライバー、穴、切断、頂点の複製、直線に沿った頂点の実行、隣接する2つのコンポーネント間の未解決の境界があります。これらの特性の多く(およびそれ以上)を示す例を次に示します。

ポリゴン

具体的には、ポリゴンの閉包内に完全に収まる最大長の水平セグメントを探します。 たとえば、これにより、x = 10とx = 25の間の穴から発散するx = 20とx = 40の間のダングルがなくなります。次に、最大長の水平セグメントの少なくとも1つが少なくとも1つの頂点と交差することを示すのは簡単です。(頂点と交差しないソリューションがある場合、それらは少なくとも1つの頂点と交差するソリューションによって上下を境界とする平行四辺形の内部にあります。これにより、すべてのソリューションを見つけることができます。)

したがって、ラインスイープは最も低い頂点から開始し、上方向に(つまり、より高いy値に向かって)各頂点で停止する必要があります。各停留所で、その標高から上方に向かって広がる新しいエッジを見つけます。その高さで下から終了するエッジを削除します(これは重要なアイデアの1つです。アルゴリズムを簡素化し、潜在的な処理の半分を削除します)。完全に一定の高さにあるすべてのエッジ(水平エッジ)を慎重に処理します。

たとえば、y = 10のレベルに達したときの状態を考えます。左から右に、次のエッジがあります。

      x.min x.max y.min y.max
 [1,]    10     0     0    30
 [2,]    10    24    10    20
 [3,]    20    24    10    20
 [4,]    20    40    10    10
 [5,]    40    20    10    10
 [6,]    60     0     5    30
 [7,]    60    60     5    30
 [8,]    60    70     5    20
 [9,]    60    70     5    15
[10,]    90   100    10    40

この表では、(x.min、y.min)はエッジの下端の座標であり、(x.max、y.max)は上端の座標です。このレベル(y = 10)では、最初のエッジはその内部でインターセプトされ、2番目のエッジはその下部でインターセプトされます。(10,0)から(10,10)など、このレベルで終了するエッジの一部はリストに含まれません

内側のポイントと外側のポイントの位置を判断するには、左端(もちろん、ポリゴンの外側)から開始し、水平に右に移動することを想像してください。水平ではないエッジを横切るたびに、外側から内側、そして後ろに交互に切り替わります。(これは別の重要なアイデアです。)ただし、水平エッジ内のすべてのポイントは、何であれポリゴンの内側にあると判断されます。(ポリゴンの閉包には、常にエッジが含まれます。)

例の続きとして、非水平エッジがy = 10の線で始まる、または交差するx座標のソートされたリストがあります。

x.array    6.7 10 20 48 60 63.3 65 90
interior     1  0  1  0  1    0  1  0

(x = 40がこのリストにないことに注意してください。)interior配列の値は、内部セグメントの左端をマークします。1は内部間隔、0は外部間隔を示します。したがって、最初の1は、x = 6.7からx = 10までの間隔がポリゴン内にあることを示します。次の0は、x = 10からx = 20までの間隔がポリゴンの外側にあることを示します。そのため、次のように進みます。配列は、ポリゴンの内部として4つの別々の間隔を識別します。

x = 60からx = 63.3までの間隔など、これらの間隔の一部は、頂点と交差しません。y= 10のすべての頂点のx座標に対するクイックチェックは、そのような間隔を削除します。

スキャン中に、これらの間隔の長さを監視し、これまでに見つかった最大長の間隔に関するデータを保持できます。

このアプローチの意味合いに注意してください。「v」字型の頂点は、検出されると、2つのエッジの原点になります。そのため、交差するときに2つのスイッチが発生します。これらのスイッチはキャンセルされます。逆さまの「v」は、左から右へのスキャンを開始する前に両方のエッジが除去されるため、処理されません。どちらの場合も、そのような頂点は水平セグメントをブロックしません。

3つ以上のエッジが頂点を共有できます:これは、(10,0)、(60,5)、(25、20)で示されており、(わかりにくいですが)(20,10)および(40)で示されています、10)。(これは、ダングルが(20,10)->(40,10)->(40,0)->(40、-50)->(40、10)->(20、 10)。(40,0)の頂点が別のエッジの内部にもあることに注意してください。

一番下に注意が必要な状況が示されています。非水平セグメントのx座標は

30, 50

これにより、x = 30の左側のすべてが外部、30〜50のすべてが内部、50以降がすべて外部と見なされます。このアルゴリズムでは、x = 40の頂点は考慮されません。

スキャンの終了時のポリゴンは次のとおりです。頂点を含むすべての内部間隔を濃い灰色で、最大長の間隔を赤で表示し、y座標に従って頂点に色を付けます。最大間隔は64単位です。

スキャン後

関係する唯一の幾何学的計算は、エッジが水平線と交差する場所を計算することです。これは単純な線形補間です。また、どの内部セグメントに頂点が含まれているかを判断するための計算も必要です。これらは、2つの不等式で簡単に計算される中間の判断です。この単純さにより、アルゴリズムは堅牢で、整数および浮動小数点の両方の座標表現に適しています。

座標が地理的な場合、水平線は実際には緯度の円上にあります。それらの長さを計算するのは難しくありません。ユークリッドの長さに緯度の余弦を掛けるだけです(球体モデルで)。したがって、このアルゴリズムは地理座標にうまく適合します。(+ -180子午線の回り込みをうまく処理するには、まず、ポリゴンを通過しない南極から北極への曲線を見つける必要があります。すべてのx座標を、それに対する水平変位として再表現した後曲線、このアルゴリズムは最大水平セグメントを正しく検出します。)


以下は、R計算を実行し、イラストを作成するために実装されたコードです。

#
# Plotting functions.
#
points.polygon <- function(p, ...) {
  points(p$v, ...)
}
plot.polygon <- function(p, ...) {
  apply(p$e, 1, function(e) lines(matrix(e[c("x.min", "x.max", "y.min", "y.max")], ncol=2), ...))
}
expand <- function(bb, e=1) {
  a <- matrix(c(e, 0, 0, e), ncol=2)
  origin <- apply(bb, 2, mean)
  delta <-  origin %*% a - origin
  t(apply(bb %*% a, 1, function(x) x - delta))
}
#
# Convert polygon to a better data structure.
#
# A polygon class has three attributes:
#   v is an array of vertex coordinates "x" and "y" sorted by increasing y;
#   e is an array of edges from (x.min, y.min) to (x.max, y.max) with y.max >= y.min, sorted by y.min;
#   bb is its rectangular extent (x0,y0), (x1,y1).
#
as.polygon <- function(p) {
  #
  # p is a list of linestrings, each represented as a sequence of 2-vectors 
  # with coordinates in columns "x" and "y". 
  #
  f <- function(p) {
    g <- function(i) {
      v <- p[(i-1):i, ]
      v[order(v[, "y"]), ]
    }
    sapply(2:nrow(p), g)
  }
  vertices <- do.call(rbind, p)
  edges <- t(do.call(cbind, lapply(p, f)))
  colnames(edges) <- c("x.min", "x.max", "y.min", "y.max")
  #
  # Sort by y.min.
  #
  vertices <- vertices[order(vertices[, "y"]), ]
  vertices <- vertices[!duplicated(vertices), ]
  edges <- edges[order(edges[, "y.min"]), ]

  # Maintaining an extent is useful.
  bb <- apply(vertices <- vertices[, c("x","y")], 2, function(z) c(min(z), max(z)))

  # Package the output.
  l <- list(v=vertices, e=edges, bb=bb); class(l) <- "polygon"
  l
}
#
# Compute the maximal horizontal interior segments of a polygon.
#
fetch.x <- function(p) {
  #
  # Update moves the line from the previous level to a new, higher level, changing the
  # state to represent all edges originating or strictly passing through level `y`.
  #
  update <- function(y) {
    if (y > state$level) {
      state$level <<- y
      #
      # Remove edges below the new level from state$current.
      #
      current <- state$current
      current <- current[current[, "y.max"] > y, ]
      #
      # Adjoin edges at this level.
      #
      i <- state$i
      while (i <= nrow(p$e) && p$e[i, "y.min"] <= y) {
        current <- rbind(current, p$e[i, ])
        i <- i+1
      }
      state$i <<- i
      #
      # Sort the current edges by x-coordinate.
      #
      x.coord <- function(e, y) {
        if (e["y.max"] > e["y.min"]) {
          ((y - e["y.min"]) * e["x.max"] + (e["y.max"] - y) * e["x.min"]) / (e["y.max"] - e["y.min"])
        } else {
          min(e["x.min"], e["x.max"])
        }
      }
      if (length(current) > 0) {
        x.array <- apply(current, 1, function(e) x.coord(e, y))
        i.x <- order(x.array)
        current <- current[i.x, ]
        x.array <- x.array[i.x]     
        #
        # Scan and mark each interval as interior or exterior.
        #
        status <- FALSE
        interior <- numeric(length(x.array))
        for (i in 1:length(x.array)) {
          if (current[i, "y.max"] == y) {
            interior[i] <- TRUE
          } else {
            status <- !status
            interior[i] <- status
          }
        }
        #
        # Simplify the data structure by retaining the last value of `interior`
        # within each group of common values of `x.array`.
        #
        interior <- sapply(split(interior, x.array), function(i) rev(i)[1])
        x.array <- sapply(split(x.array, x.array), function(i) i[1])

        print(y)
        print(current)
        print(rbind(x.array, interior))


        markers <- c(1, diff(interior))
        intervals <- x.array[markers != 0]
        #
        # Break into a list structure.
        #
        if (length(intervals) > 1) {
          if (length(intervals) %% 2 == 1) 
            intervals <- intervals[-length(intervals)]
          blocks <- 1:length(intervals) - 1
          blocks <- blocks - (blocks %% 2)
          intervals <- split(intervals, blocks)  
        } else {
          intervals <- list()
        }
      } else {
        intervals <- list()
      }
      #
      # Update the state.
      #
      state$current <<- current
    }
    list(y=y, x=intervals)
  } # Update()

  process <- function(intervals, x, y) {
    # intervals is a list of 2-vectors. Each represents the endpoints of
    # an interior interval of a polygon.
    # x is an array of x-coordinates of vertices.
    #
    # Retains only the intervals containing at least one vertex.
    between <- function(i) {
      1 == max(mapply(function(a,b) a && b, i[1] <= x, x <= i[2]))
    }
    is.good <- lapply(intervals$x, between)
    list(y=y, x=intervals$x[unlist(is.good)])
    #intervals
  }
  #
  # Group the vertices by common y-coordinate.
  #
  vertices.x <- split(p$v[, "x"], p$v[, "y"])
  vertices.y <- lapply(split(p$v[, "y"], p$v[, "y"]), max)
  #
  # The "state" is a collection of segments and an index into edges.
  # It will updated during the vertical line sweep.
  #
  state <- list(level=-Inf, current=c(), i=1, x=c(), interior=c())
  #
  # Sweep vertically from bottom to top, processing the intersection
  # as we go.
  #
  mapply(function(x,y) process(update(y), x, y), vertices.x, vertices.y)
}


scale <- 10
p.raw = list(scale * cbind(x=c(0:10,7,6,0), y=c(3,0,0,-1,-1,-1,0,-0.5,0.75,1,4,1.5,0.5,3)),
             scale *cbind(x=c(1,1,2.4,2,4,4,4,4,2,1), y=c(0,1,2,1,1,0,-0.5,1,1,0)),
             scale *cbind(x=c(6,7,6,6), y=c(.5,2,3,.5)))

#p.raw = list(cbind(x=c(0,2,1,1/2,0), y=c(0,0,2,1,0)))
#p.raw = list(cbind(x=c(0, 35, 100, 65, 0), y=c(0, 50, 100, 50, 0)))

p <- as.polygon(p.raw)

results <- fetch.x(p)
#
# Find the longest.
#
dx <- matrix(unlist(results["x", ]), nrow=2)
length.max <- max(dx[2,] - dx[1,])
#
# Draw pictures.
#
segment.plot <- function(s, length.max, colors,  ...) {
  lapply(s$x, function(x) {
      col <- ifelse (diff(x) >= length.max, colors[1], colors[2])
      lines(x, rep(s$y,2), col=col, ...)
    })
}
gray <- "#f0f0f0"
grayer <- "#d0d0d0"
plot(expand(p$bb, 1.1), type="n", xlab="x", ylab="y", main="After the Scan")
sapply(1:length(p.raw), function(i) polygon(p.raw[[i]], col=c(gray, "White", grayer)[i]))
apply(results, 2, function(s) segment.plot(s, length.max, colors=c("Red", "#b8b8a8"), lwd=4))
plot(p, col="Black", lty=3)
points(p, pch=19, col=round(2 + 2*p$v[, "y"]/scale, 0))
points(p, cex=1.25)

任意の方向の非凸多角形内部の最大長線がこの多角形の少なくとも1つの頂点と交差することを証明する定理はありますか?
SS_Rebelious

@SSはい、あります。証明のスケッチを次に示します。交差点がない場合、セグメントの端点は両方ともエッジの内側にあり、セグメントを少なくとも少し上下に移動できます。その長さは、変位量の線形関数です。したがって、移動時に長さが変わらない場合にのみ、最大長を持つことができます。これは、(a)最大長のセグメントで形成された平行四辺形の一部であり、(b)その平行四辺形の上端と下端の両方が頂点QEDと一致する必要があることを意味します。
whuber

そして、この定理の名前は何ですか?私はそれを見つけるのに苦労しています。ところで、頂点のない湾曲したエッジはどうですか(理論的なアプローチを意味します)。私が意味する図の例のスケッチ(ダンベル型のポリゴン): "C = D"。
SS_Rebelious

@SSエッジが湾曲している場合、定理は成り立たなくなります。微分幾何学の手法を適用して、有用な結果を得ることができます。Cheeger&Ebinの本、Riemannian GeometryのComparison Theoremsからこれらの方法を学びました。ただし、ほとんどのGISはとにかく詳細なポリラインで曲線を近似するため、問題は(実際問題として)意味がありません。
whuber

定理の名前(および可能であればページ)を指定できますか?私は本を​​手に入れましたが、必要な定理を見つけることができませんでした。
SS_Rebelious

9

これがラスターベースのソリューションです。 それは高速で(最初から最後まですべての作業を14分で完了しました)、スクリプトを作成する必要がなく、わずかな操作しか必要なく、かなり正確です。

ポリゴンのラスター表現から始めます。 これは、550行と1200列のグリッドを使用します。

ポリゴン

この表現では、灰色(内側)のセルの値は1で、他のすべてのセルはNoDataです。

ウェイトグリッドの単位セル値(「降雨量」)を使用して、西から東への流れの累積を計算します。

流れの蓄積

低蓄積は暗く、明るい黄色で最高の蓄積に増加します。

ゾーンの最大値(グリッドのポリゴンと値のフロー累積を使用)は、フローが最大のセルを識別します。これらを表示するには、右下にズームインする必要がありました。

最大

赤色のセルは、フローの最も高い累積の終わりを示します。これらは、ポリゴンの最大長の内部セグメントの右端の終点です。

これらのセグメントを見つけるには、すべての重量を赤いセルに配置し、逆方向にフローを実行します!

結果

下部近くの赤いストライプは、2行のセルを示しています。その中には、最大長の水平セグメントがあります。この表現をそのまま使用して、さらに分析するか、ポリライン(またはポリゴン)形状に変換します。

ラスター表現で行われたいくつかの離散化エラーがあります。計算時間をいくらか犠牲にして、解像度を上げることで減らすことができます。


このアプローチの非常に素晴らしい側面の1つは、パイプラインやサッカー場の設置、エコロジカルバッファーの作成など、何らかの目標を達成する必要がある大きなワークフローの一部として、物事の極端な価値を見つけることです。このプロセスにはトレードオフが伴います。したがって、非常に長い水平線は最適なソリューションの一部ではない可能性があります。代わりに、ほとんどの最も長い行がどこにあるかを知ることに注意するかもしれません。これは簡単です。ゾーン最大フローを選択するのではなく、ゾーン最大に近いすべてのセルを選択します。この例では、ゾーンの最大値は744(最長の内部セグメントがまたがる列の数)に等しくなります。代わりに、その最大値の5%以内のすべてのセルを選択しましょう。

最適に近いセルを選択

フローを東から西に実行すると、次の水平セグメントのコレクションが生成されます。

ほぼ最適なソリューション

これは、連続する東西範囲がポリゴン内の最大東西範囲よりも95%以上大きい場所のマップです。


3

OK。別の(より良い)アイデア(idea-№2)があります。しかし、SQLクエリとしてではなく、Pythonスクリプトとして実現する方が良いと思います。ここでも、EWだけでなく一般的なケースがあります。

ポリゴンのバウンディングボックスと、測定方向としての方位角(A)が必要です。BBoxのエッジの長さがLAとLBであると仮定します。ポリゴン内の最大可能距離(MD)はですMB = (LA^2 * LB^2)^(1/2)。したがって、シーク値(V)はMB:より大きくありませんV <= MB

  1. BBoxの任意の頂点から開始して、長さMBと方位Aの線(LL)を作成します。
  2. LLとポリゴンを交差させて交差線(IL)を取得します
  3. ILのジオメトリをチェックします-ILラインに2つのポイントしかない場合、その長さを計算します。4以上の場合-セグメントを計算し、最も長いセグメントの長さを取得します。ヌル(交差点なし)-無視。
  4. 開始点に到達しないまで、開始点のカウンターまたは時計回りからBBoxの端に向かって移動する別のLLラインを作成し続けます(ループ全体をBBoxで実行します)。
  5. ILの最大の長さの値を取得します(実際には、すべての長さを保存する必要はありません。ループ中に「これまでの」最大値を維持できます)-それが求めるものになります。

これは頂点の二重ループのように聞こえます。これは非効率であるため、回避する必要があります(非常に単純化されたポリゴンを除く)。
whuber

@whuber、ここには余分なループはありません。BBの2辺の意味のない処理がいくつかありますが、これはnullしか与えません。しかし、ここで削除された回答で提供したスクリプトでは、この処理は除外されました(現在はコメントのようですが、コメントとしては表示されません-削除された回答としてのみ)
SS_Rebelious

(1)質問に対する3番目のコメントです。(2)そのとおりです。説明を注意深く読むと、境界ボックスの(4つの)頂点とポリゴンの頂点の間の最も長いセグメントを見つけているように見えます。しかし、これがどのように質問に答えるかはわかりません。結果は間違いなくOPが求めたものではありません。
whuber

提案されたアルゴリズムである@whuberは、指定された方向を表す線とポリゴンの最も長い交点を見つけます。どうやら、結果は交差線間の距離-> 0であるか、すべての頂点を通過するかどうかを尋ねられたものです(湾曲した図形ではないため)。
SS_Rebelious

3

Fetzerの答えがあなたのやりたいことかどうかはわかりませんが、st_box2dが仕事をするかもしれません。

SS_RebeliousのアイデアN°1は多くの場合機能しますが、一部の凹面ポリゴンでは機能しません。

東西線の可能性がある場合、頂点で作られた線が多角形の境界を横切るときに、ポイントがエッジに従う人工的なlwラインを作成する必要があると思います。 動作しない例

このために、線の長さが長い4ノードのポリゴンを作成し、元のポリゴンとオーバーラップする前のポリゴンP *を作成し、min(y1)およびmax(y2)がx-lineを残すかどうかを確認できます。可能性。(ここで、y1は4つのノードポリゴンの左上のコルネットと右上隅の間のポイントのセット、y2は左下と右下隅の間のyのセットです)。これはそれほど簡単ではありません。psqlツールが役立つことを願っています。


これは正しい軌道に乗っています。最長のEWセグメントは、ポリゴンの頂点を通る水平線とポリゴンの内部との交点の中にあります。これには、頂点をループするコードが必要です。ポリゴンのラスター表現を横切る人工的な東西方向の流れに従うことで、代替の(しかし同等の)方法が利用できます。ポリゴンで見つかった最大の流れの長さ(「ゾーン統計」の1つ)が望ましい幅です。ラスターソリューションは3〜4ステップで取得でき、ループやスクリプトは必要ありません。
whuber

@Aname、誤解を避けるために「SS_Rebeliousのアイデア」に「№1」を追加してください。別の提案を追加しました。この編集は6文字未満なので、自分で回答を編集することはできません。
SS_Rebelious

1

私はアイデア-№1を持っています編集:一般的な場合、EW方向だけでなく、コメントで説明されているいくつかの制限付き)。コードを提供するのではなく、概念を提供します。「x方向」は実際には方位角であり、ST_Azimuthによって計算されます。提案される手順は次のとおりです。

  1. ポリゴンからすべての頂点をポイントとして抽出します。
  2. ポイントのすべてのペアの間に線を作成します。
  3. 元のポリゴン内にあるラインを選択します(lw-linesと呼びます)(ポリゴンの境界を越えるラインは必要ありません)。
  4. すべてのlwラインの距離と方位角を見つけます。
  5. 方位角が求められている方位角に等しいか、ある間隔にあるlwラインからの最長距離を選択します(求められている方位角に正確に等しい方位角はないかもしれません)。

これは、頂点(0,0)、(1000、1000)、および(501、499)の三角形など、一部の三角形でも機能しません。最大の東西幅は約2です。方位角はすべて約45度です。とにかく、頂点間の最短線分は東西幅の350倍以上です。
whuber

@whuber、あなたは正しい、三角形では失敗しますが、いくつかの自然の特徴を表すポリゴンでは有用かもしれません。
SS_Rebelious

1
正しい答えが得られることを期待して、単純な場合でも劇的に失敗する手順を推奨することは困難です!
whuber

@whuberなので、お勧めしないでください!;-)この質問に対する答えがなかったため、この回避策を提案しました。独自のより良い回答を投稿できることに注意してください。ところで、三角形のエッジにいくつかのポイントを配置する場合、私の提案は動作します
;

私はいくつかのアプローチを提案しました。ラスターはgis.stackexchange.com/questions/32552/…にあり、forums.esri.com / Thread.asp?c = 93f = 982t = 107703mc = 3で詳しく説明されています。もう1つは、あまり適切ではありませんが、興味深い用途がありますが、gis.stackexchange.com / questions / 23664 /…(ラドン変換)にあります。これはstats.stackexchange.com/a/33102で説明されています
whuber

1

悪の天才からの私の質問答えをご覧ください。

湖のポリゴンに多数のポイントがあれば、これらのポイントに方位角(アスペクト、地理方向)で線を作成できます。最も離れた2つの線の間の最短線を計算できるように、十分に長い線の長さ(ST_MakePoint部分)を選択します。

以下に例を示します。

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

この例は、ポリゴンの最大幅を示しています。このアプローチでは、ST_ShortestLine(赤い線)を選択します。ST_MakeLineは値を増やし(青い線)、線の終点(左下)はポリゴンの青い線に当たります。作成された(ヘルプ)ラインの重心で距離を計算する必要があります。

このアプローチのための不規則または凹面のポリゴンのアイデア。ポリゴンとラスタを交差させる必要があるかもしれません。

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