计算PostGIS中x方向(东西方向)上多边形内的最大距离?


13

我对多边形(例如湖泊)沿东西方向的最大宽度感兴趣。边界框仅对简单的多边形有用,而对复杂的凹形多边形无效。


3
我对postgis功能不熟悉。但是,可能有一个边界框工具。边界框的宽度将是EW方向上的最大距离。
Fezter

4
@Fetzter是不正确的:一个反例,即使是对于简单的复杂多边形,也是从SW延伸到NE的细菱形。它的最大东西方向宽度可以是其边界框宽度的任意一小部分。
ub

1
我已经创建了一个基于此任务的工具这个这个提议。能够计算多边形的最大或最小宽度。当前,它可用于shp文件,但是您可以将其重写以与PostGIS一起使用,或者等待一段时间,直到它演变为也可与PostGIS一起使用的QGIS插件。详细说明和下载链接在这里
SS_Rebelious 2012年

Answers:


16

这可能需要在任何GIS平台中编写一些脚本。

最有效的方法(渐近)是垂直线扫掠:它需要根据边缘的最小y坐标对边缘进行排序,然后处理从底部(最小y)到顶部(最大y)的边缘,得出O(e * log( e))涉及e边的算法。

该过程虽然很简单,但是在所有情况下都很难正确完成。 多边形可能是令人讨厌的:它们可以具有悬挂,条状,孔状,断开连接,具有重复的顶点,沿直线的顶点行以及在两个相邻组件之间具有未溶解的边界。这是一个展示了许多(甚至更多)特征的示例:

多边形

我们将专门寻求完全位于多边形闭合区域内的最大长度水平线段。 例如,这消除了从x = 10和x = 25之间的孔发出的x = 20和x = 40之间的悬空。然后可以直接看出,至少一个最大长度的水平线段与至少一个顶点相交。(如果有解决方案不相交的顶点,他们将位于一些平行四边形通过其解决方案在顶部和底部为界的内部相交至少一个顶点。这让我们找到一种手段所有的解决方案。)

因此,线扫描必须从最低的顶点开始,然后向上移动(即,朝较高的y值),以在每个顶点处停止。在每个停靠点,我们发现从该高程向上发出的任何新边;消除在该高度处从下方终止的任何边缘(这是关键思想之一:简化了算法并消除了一半的潜在处理);并仔细处理完全处于恒定高度的所有边缘(水平边缘)。

例如,考虑达到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),第一个边缘在其内部被拦截,第二个边缘在其内部被拦截,依此类推。在此级别终止的某些边沿,例如(10,0)至(10,10),包括在列表中。

要确定内部点和外部点在哪里,请想象从最左边开始(当然是在多边形外部),然后水平向右移动。每次我们越过不是水平的边缘时,就会交替从外部切换到内部,再切换回内部。(这是另一个关键思想。)但是,无论水平如何,都将确定任何水平边缘内的所有点都在多边形内。(多边形的闭合始终包括其边缘。)

继续示例,这是x坐标的排序列表,其中非水平边缘从y = 10线开始或穿过y = 10线:

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的间隔在多边形之外。因此,它继续进行:数组将四个单独的间隔标识为多边形内部。

这些间隔中的某些间隔(例如从x = 60到x = 63.3的间隔)不与任何顶点相交:快速检查所有y = 10的顶点的x坐标可消除此类间隔。

在扫描过程中,我们可以监视这些间隔的长度,并保留有关到目前为止找到的最大长度间隔的数据。

注意这种方法的一些含义。遇到“ V”形顶点时,它是两个边的起点。因此,穿越时会发生两个开关。这些开关会抵消。甚至没有处理任何颠倒的“ v”,因为在开始从左到右扫描之前,两个边缘都被消除了。在这两种情况下,此类顶点均不会遮挡水平线段。

可以有两个以上的边共享一个顶点:在(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个单位长。

扫描后

唯一涉及的几何计算是计算边缘与水平线的交点:这是一个简单的线性插值。还需要进行计算以确定哪些内部线段包含顶点:这些是中间性确定,很容易通过几个不等式进行计算。这种简单性使该算法既健壮又适合整数和浮点坐标表示。

如果坐标是地理坐标,则水平线实际上在纬度圆上。它们的长度并不难计算:只需将其欧几里得长度乘以它们的纬度的余弦即可(在球形模型中)。因此,该算法很好地适应了地理坐标。(要处理围绕+ -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)

是否有一个定理证明在任何给定方向上非凸多边形内部的最大长度线与该多边形的至少一个顶点相交?
SS_Rebelious

@SS是的,有。这是一个证明的草图:如果没有相交,则线段的端点都位于边缘的内部,并且线段可以至少上下移动一点。它的长度是位移量的线性函数。因此,只有在移动时长度不变的情况下,它才能具有最大长度。这意味着(a)它是由最大长度段形成的平行四边形的一部分,并且(b)该平行四边形的顶部和底部边缘都必须满足顶点QED。
ub

这个定理的名称是什么?我正在努力寻找它。顺便说一句,没有顶点的弯曲边缘呢(我是从理论上讲)?我所指的数字示例示例(哑铃形多边形):“ C = D”。
SS_Rebelious 2012年

@SS边缘弯曲时,该定理不再成立。可以应用微分几何技术来获得有用的结果。我从Cheeger&Ebin的书《黎曼几何中的比较定理》中学到了这些方法。但是,大多数GIS无论如何都将通过详细的折线来近似曲线,因此这个问题(作为实际问题)尚无定论。
ub

您可以指定定理的名称(如果可能的话,还可以指定页面)?我已经有了书,但找不到所需的定理。
SS_Rebelious 2012年

9

这是基于栅格的解决方案。 它速度快(从头到尾我在14分钟内完成了所有工作),无需编写脚本,只需执行少量操作,并且相当准确。

从多边形的栅格表示开始。 这使用550行和1200列的网格:

多边形

在此表示形式中,灰色(内部)单元格的值为1,其他所有单元格均为NoData。

使用权重网格的单位像元值(“雨量”),计算从西向东的流量累积量:

流量累积

低累积量是暗的,以亮黄色增加到最高累积量。

区域最大值(对网格使用多边形,对值使用流量累积)标识流量最大的单元格。为了显示这些,我不得不向右下方放大:

最大值

红细胞标记了最高流量积累的末端:它们是多边形最大长度的内部片段的最右边端点。

要找到这些段,请将所有权重放在红色单元格上,并使流向后运行!

结果

底部附近的红色条纹标记了两行单元格:其中包含最大长度的水平线段。可以按原样使用此表示形式以进行进一步分析,或将其转换为折线(或多边形)形状。

栅格表示存在一些离散化错误。可以通过增加分辨率来降低它,而这会花费一些计算时间。


这种方法的一个非常好的方面是,通常在较大的工作流程中,我们需要找到事物的极高价值,在其中需要实现一些目标:安置管道或足球场,创建生态缓冲区等。该过程涉及权衡。因此,最长的水平线可能不是最佳解决方案的一部分。相反,我们可能想知道几乎最长的线在哪里。这很简单:不要选择最大区域流量,而是选择接近最大区域的所有像元。在此示例中,区域最大值等于744(最长的内部段所跨越的列数)。相反,让我们选择该最大值的5%以内的所有单元格:

选定的近优单元

从东向西流动产生以下水平段集合:

接近最佳的解决方案

这是一个位置地图,其中不间断的东西方范围比多边形内任何地方的最大东西方范围都大95%或更大。


3

好。我还有一个更好的主意(idea-№2)。但是我想最好将其实现为python脚本,而不是SQL查询。同样,这是常见的情况,不仅是电子战。

您将需要一个多边形的边界框和一个方位角(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线中只有两个点,则计算其长度。如果大于或等于4,请计算细分并获取最长的细分的长度。空(根本没有交集)-忽略。
  4. 继续创建从起始点计数器或顺时针方向向BBox边缘移动的另一条LL线,直到您不会在起始点结束为止(您将在BBox上进行整个循环)。
  5. 选择最大的IL长度值(实际上,您不必存储所有长度,循环时可以仅保留“到目前为止”的最大值)-这将是您所追求的。

这听起来像是顶点上的双重循环:效率很低,应避免使用(非常简化的多边形除外)。
whuber

@whuber,我在这里看不到任何多余的循环。BB的2个面只有一些无意义的处理,只会产生空值。但是,此处理未包含在我在此处删除的答案中提供的脚本中(似乎现在是注释,但我不认为它是注释-只是删除的答案)
SS_Rebelious

(1)这是对该问题的第三条评论。(2)您是对的:在非常仔细地阅读了说明之后,在我看来,您正在寻找边界框的(四个)顶点与多边形的顶点之间的最长分段。不过,我看不出这是如何回答问题的:结果肯定不是OP寻求的结果。
Whuber

@whuber,提出的算法找到多边形与代表给定方向的线的最长交点。显然,结果是询问相交线之间的距离-> 0还是它通过所有顶点(对于非弯曲图形)的问题。
SS_Rebelious

3

我不确定Fetzer的答案是您要执行的操作,但是st_box2d可以完成此工作。

SS_Rebelious的想法N°1在许多情况下都适用,但不适用于某些凹面多边形。

我认为,如果存在东西线的可能性,则必须创建人造lw线,当顶点制作的线穿过多边形的边界时,其点将跟随边。 一个不起作用的例子

为此,您可以尝试制作一个线长较高的4个节点的多边形,将先前与您的原始多边形重叠的多边形P *设置为原始多边形,然后查看min(y1)和max(y2)是否留有一些x线可能性。(其中y1是您的4个节点多边形的左上角短号和右上角之间的点集,y2是y上四个点多边形的左下角和右下角之间的点集)。这不是那么容易,我希望您会找到psql工具来帮助您!


这是正确的。在与多边形内部相交的水平线穿过多边形顶点的交点中,可以找到最长的EW段。这需要代码在顶点上循环。通过沿着多边形的栅格表示进行人工的东西向流动,可以使用另一种(但等效的)方法:在多边形中找到的最大流动长度(这是其“区域统计”之一)是所需的宽度。光栅解决方案仅需3到4步即可获得,并且不需要循环或编写脚本。
ub

@Aname,请在“ SS_Rebelious的主意”中添加“№1”,以免造成误解:我添加了另一个建议。我自己无法编辑您的答案,因为此编辑少于6个字符。
SS_Rebelious

1

我有一个主意№1编辑:对于一般情况,不仅是电子战方向,而且在注释中有一些限制)。我不会提供代码,只是一个概念。“ x方向”实际上是一个方位角,由ST_Azimuth计算得出。建议的步骤是:

  1. 从多边形中提取所有顶点作为点。
  2. 在每对点之间创建线。
  3. 选择原始多边形内的线(我们称其为lw线)(我们不需要会越过多边形边界的线)。
  4. 查找每条lw线的距离和方位角。
  5. 从lw线中选择最长距离,在该lw线中,方位角等于寻找的方位角或位于某个间隔内(可能是没有方位角会完全等于寻找的方位角)。

即使对于某些三角形,例如在顶点(0,0),(1000、1000)和(501、499)的三角形,这也将无法工作。它的最大东西方向宽度大约为2;方位角均为45度左右;无论如何,顶点之间的最短线段是东西方向宽度的350倍以上。
ub

@whuber,您是对的,对于三角形而言,它将失败,但是对于表示某些自然特征的多边形而言,它可能会很有用。
SS_Rebelious

1
很难推荐一个即使在简单的情况下也会严重失败的过程,希望它有时可能会得到正确的答案!
ub

@whuber,所以不推荐!;-)我提出了此替代方法,因为此问题没有答案。请注意,您可以发布自己的更好答案。顺便说一句,如果您将一些点放在三角形的边缘,我的建议将起作用;-)
SS_Rebelious 2012年

我已经提出了几种方法。一个栅格在gis.stackexchange.com/questions/32552/…上,并在forums.esri.com/Thread.asp?c=93&f=982&t=107703&mc=3上进行了详细说明。另一个工具-不太适用,但是有有趣的用途-在gis.stackexchange.com/questions/23664/…(ra变换)上。stats.stackexchange.com/a/33102中对此进行了说明。
whuber

1

看看我的问题和Evil Genius 的回答

希望您的湖泊多边形具有多个点,您可以在这些点上使用方位角(长宽比,地理方向)创建线。选择足够大的线的长度(ST_MakePoint部分),以便可以计算两条最远的线之间的最短线。

这是一个例子:

在此处输入图片说明

该示例显示了多边形的最大宽度。我为这种方法选择了ST_ShortestLine(红线)。ST_MakeLine将增加值(蓝线),并且线的端点(左下)将触及多边形的蓝线。您必须使用创建的(帮助)线的质心来计算距离。

用于此方法的不规则或凹面多边形的想法。可能是您必须将多边形与栅格相交。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.