我对多边形(例如湖泊)沿东西方向的最大宽度感兴趣。边界框仅对简单的多边形有用,而对复杂的凹形多边形无效。
我对多边形(例如湖泊)沿东西方向的最大宽度感兴趣。边界框仅对简单的多边形有用,而对复杂的凹形多边形无效。
Answers:
这可能需要在任何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)
这是基于栅格的解决方案。 它速度快(从头到尾我在14分钟内完成了所有工作),无需编写脚本,只需执行少量操作,并且相当准确。
从多边形的栅格表示开始。 这使用550行和1200列的网格:
在此表示形式中,灰色(内部)单元格的值为1,其他所有单元格均为NoData。
使用权重网格的单位像元值(“雨量”),计算从西向东的流量累积量:
低累积量是暗的,以亮黄色增加到最高累积量。
区域最大值(对网格使用多边形,对值使用流量累积)标识流量最大的单元格。为了显示这些,我不得不向右下方放大:
红细胞标记了最高流量积累的末端:它们是多边形最大长度的内部片段的最右边端点。
要找到这些段,请将所有权重放在红色单元格上,并使流向后运行!
底部附近的红色条纹标记了两行单元格:其中包含最大长度的水平线段。可以按原样使用此表示形式以进行进一步分析,或将其转换为折线(或多边形)形状。
栅格表示存在一些离散化错误。可以通过增加分辨率来降低它,而这会花费一些计算时间。
这种方法的一个非常好的方面是,通常在较大的工作流程中,我们需要找到事物的极高价值,在其中需要实现一些目标:安置管道或足球场,创建生态缓冲区等。该过程涉及权衡。因此,最长的水平线可能不是最佳解决方案的一部分。相反,我们可能想知道几乎最长的线在哪里。这很简单:不要选择最大区域流量,而是选择接近最大区域的所有像元。在此示例中,区域最大值等于744(最长的内部段所跨越的列数)。相反,让我们选择该最大值的5%以内的所有单元格:
从东向西流动产生以下水平段集合:
这是一个位置地图,其中不间断的东西方范围比多边形内任何地方的最大东西方范围都大95%或更大。
好。我还有一个更好的主意(idea-№2)。但是我想最好将其实现为python脚本,而不是SQL查询。同样,这是常见的情况,不仅是电子战。
您将需要一个多边形的边界框和一个方位角(A)作为您的测量方向。假设BBox边缘的长度为LA和LB。的多边形内最大可能距离(MD)是:MB = (LA^2 * LB^2)^(1/2)
,所以求值(V)不小于MB更大:V <= MB
。
我不确定Fetzer的答案是您要执行的操作,但是st_box2d可以完成此工作。
SS_Rebelious的想法N°1在许多情况下都适用,但不适用于某些凹面多边形。
我认为,如果存在东西线的可能性,则必须创建人造lw线,当顶点制作的线穿过多边形的边界时,其点将跟随边。
为此,您可以尝试制作一个线长较高的4个节点的多边形,将先前与您的原始多边形重叠的多边形P *设置为原始多边形,然后查看min(y1)和max(y2)是否留有一些x线可能性。(其中y1是您的4个节点多边形的左上角短号和右上角之间的点集,y2是y上四个点多边形的左下角和右下角之间的点集)。这不是那么容易,我希望您会找到psql工具来帮助您!
我有一个主意№1(编辑:对于一般情况,不仅是电子战方向,而且在注释中有一些限制)。我不会提供代码,只是一个概念。“ x方向”实际上是一个方位角,由ST_Azimuth计算得出。建议的步骤是: