Мене цікавить максимальна ширина багатокутника, наприклад озера, у напрямку схід-захід. Обмежувальні коробки допоможуть лише у простих багатокутниках, але не у складних увігнутих багатокутників.
Мене цікавить максимальна ширина багатокутника, наприклад озера, у напрямку схід-захід. Обмежувальні коробки допоможуть лише у простих багатокутниках, але не у складних увігнутих багатокутників.
Відповіді:
Це, ймовірно, вимагає певного сценарію в будь-якій GIS-платформі.
Найефективніший метод (асимптотично) - це вертикальна лінія зчитування: він вимагає сортування ребер за їх мінімальними y-координатами, а потім обробку країв знизу (мінімум y) до верху (максимум y), для O (e * log ( д)) алгоритм, коли задіяні e краї.
Процедура, хоча і проста, напрочуд складна у всіх випадках. Полігони можуть бути бридкими: вони можуть мати звисання, ковзання, отвори, бути від’єднаними, мати дублювані вершини, прогони вершин за прямими лініями та мати нерозв’язані межі між двома сусідніми компонентами. Ось приклад, який демонструє багато цих характеристик (і більше):
Ми будемо спеціально шукати горизонтальний відрізок максимальної довжини, що повністю лежить в межах закриття полігону. Наприклад, це виключає зависання між x = 20 і x = 40, що виходить з отвору між x = 10 і x = 25. Тоді прямо можна показати, що принаймні один з горизонтальних відрізків максимальної довжини перетинає щонайменше одну вершину. (Якщо є рішення не перетинаються вершин , вони будуть лежати всередині деякого паралелограма , обмеженого зверху і знизу рішеннями , які роблять перетинаються щонайменше , одну вершини. Це дає нам засіб , щоб знайти всі рішення.)
Відповідно, розміщення рядків повинно починатися з найнижчих вершин, а потім рухатися вгору (тобто у бік більш високих значень 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) - координати його верхньої кінцевої точки. На цьому рівні (у = 10) перший край перехоплюється всередині його внутрішнього, другий перехоплюється внизу тощо. Деякі краї, що закінчуються на цьому рівні, наприклад (від 10,0) до (10,10), не включаються до списку.
Щоб визначити, де розташовані внутрішні точки та зовнішні, подумайте, починаючи з крайньої лівої сторони, - звичайно, поза полігоном - і рухаючись горизонтально праворуч. Кожен раз, коли ми перетинаємо край, який не є горизонтальним , ми по черзі переходимо від зовнішнього до внутрішнього та заднього. (Це ще одна ключова ідея.) Однак усі точки в будь-якому горизонтальному краї визначаються як всередині багатокутника, незалежно від того. (Замикання багатокутника завжди включає його краї.)
Продовжуючи приклад, ось відсортований список x-координат, де не горизонтальні ребра починаються з або перетинають рядок 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, не перетинають жодних вершин: швидка перевірка на x-координати всіх вершин з y = 10 усуває такі інтервали.
Під час сканування ми можемо відстежувати довжину цих інтервалів, зберігаючи дані щодо максимально тривалого інтервалу, знайденого дотепер.
Зауважте деякі наслідки такого підходу. Вершина у формі "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 одиниці.
Єдині геометричні обчислення, що беруть участь, - це обчислення, де ребра перетинаються горизонтальними лініями: це проста лінійна інтерполяція. Також необхідні розрахунки, щоб визначити, які внутрішні сегменти містять вершини: це визначення між розмірами , які легко обчислюються з парою нерівностей. Ця простота робить алгоритм надійним і відповідним як для цілих, так і для координатних представлень з плаваючою точкою.
Якщо координати географічні , то горизонтальні лінії дійсно знаходяться на колах широти. Їх довжини не складно обчислити: просто помножте їх евклідові довжини на косинус їх широти (за сферичною моделлю). Тому цей алгоритм добре пристосовується до географічних координат. . крива, цей алгоритм правильно знайде максимальний горизонтальний відрізок.)
Далі йде 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% або більше, ніж максимальна величина схід-захід де-небудь в межах полігону.
Добре. У мене є ще одна (краща) ідея ( ідея №2 ). Але я вважаю, що краще реалізуватися як сценарій python, а не як SQL-querry. Знову ж таки, поширений випадок, а не лише EW.
Вам буде потрібно обмежувальне поле для багатокутника і азимут (А) як ваш напрямок вимірювання. Припустимо, що довжина ребер BBox дорівнює LA та LB. Максимально можливу відстань (MD) в межах багатокутника: MB = (LA^2 * LB^2)^(1/2)
так шукає значення (V) не більш , ніж МБ: V <= MB
.
Я не впевнений, що відповідь Fetzer - це те, що ви хочете зробити, але так st_box2d може виконати роботу.
Ідея SS_Rebelious № 1 працюватиме у багатьох випадках, але не для деяких увігнутих багатокутників.
Я думаю, що ви повинні створити штучні lw-лінії, точки яких слідують за ребрами, коли лінії, виконані вершинами, перетинають межі полігона, якщо є можливість лінії схід-захід.
Для цього ви можете спробувати скласти багатокутник із 4 вузлами, де довжина лінії велика, зробіть багатокутник P *, який є попереднім перекриттям з вами оригінальним багатокутником, і подивіться, чи залишають min (y1) та max (y2) деяку x-лінію можливість. (де y1 - сукупність точки між лівим верхнім корнетом та правою верхньою куткою, а y2 - у між між лівим і нижнім правою кутами 4-х вузлів багатокутника). Це не так просто, сподіваюся, ви знайдете інструменти psql, які допоможуть вам!
У мене є ідея №1 ( Редагувати: для загальних випадків не тільки напрям EW, а з деякими обмеженнями, які описані в коментарях). Я не надам код, просто поняття. "Х-напрямок" - це фактично азимут, який обчислюється ST_Azimuth. Запропоновані кроки:
Погляньте на моє запитання та відповідь із Злого Генія.
Сподіваємось, ваш озерний багатокутник має ряд точок, ви можете створювати лінії на цих точках за допомогою азимуту (аспект, гео напрям). Виберіть довжину рядків досить велику (частина ST_MakePoint), щоб ви могли обчислити найкоротшу лінію між двома найвіддаленішими лініями.
Ось приклад:
На прикладі показана максимальна ширина багатокутника. Я вибираю для цього підходу ST_ShortestLine (червона лінія). ST_MakeLine збільшив би значення (синя лінія), а кінцева точка лінії (внизу зліва) потрапила б у синю лінію багатокутника. Ви повинні обчислити відстань із центроїдами створених (довідкових) ліній.
Ідея неправильних або увігнутих багатокутників для цього підходу. Можливо, вам доведеться перетинати полігон з растром.