Я б хотів створити орієнтований буфер для кожного багатокутника у моєму файлі форм за допомогою arcpy. Під орієнтуванням я маю на увазі, що у мене є два кути a1 і a2, які обмежують напрямок буфера. Це представлено на графіку нижче:
Будь-які ідеї?
Я б хотів створити орієнтований буфер для кожного багатокутника у моєму файлі форм за допомогою arcpy. Під орієнтуванням я маю на увазі, що у мене є два кути a1 і a2, які обмежують напрямок буфера. Це представлено на графіку нижче:
Будь-які ідеї?
Відповіді:
Ця відповідь ставить питання в більш широкий контекст, описує ефективний алгоритм, застосовний до представлення форм-файлів функцій (як "вектори" або "рядки" точок), показує деякі приклади його застосування та дає робочий код для використання або перенесення в середовище ГІС.
Це приклад морфологічного розширення. У повній загальності дилатація "поширює" точки регіону на їхні квартали; збір точок, куди вони звиваються, - це «дилатація». Застосування в ГІС безліч: моделювання поширення вогню, рух цивілізацій, поширення рослин та багато іншого.
Математично і в дуже великій (але корисній) загальності дилатація поширює набір точок у ріманівському багатоманітнику (такі як площина, сфера або еліпсоїд). Розкидання обумовлюється підмножиною дотичного пучка в цих точках. Це означає, що в кожній з точок задається набір векторів (напрямків і відстаней) (я називаю це «сусідством»); кожен з цих векторів описує геодезичний шлях, що починається в його базовій точці. Базова точка «розкидається» до кінців усіх цих шляхів. (Про значно більш обмежене визначення поняття "дилатація", яке звичайно використовується в обробці зображень, див . У статті Вікіпедії . Функція розповсюдження відома як експоненціальна карта в диференційній геометрії.)
"Буферизація" функції є одним з найпростіших прикладів такого розширення: навколо кожного пункту функції створюється (принаймні концептуально) диск постійного радіуса (радіус буфера). Об'єднання цих дисків є буфером.
Це питання вимагає обчислення дещо складнішого розширення, коли розкидання дозволяється відбуватися лише в заданому діапазоні кутів (тобто в межах кругового сектора). Це має сенс лише для особливостей, які не закривають помітно вигнуту поверхню (наприклад, невеликі риси на кулі або еліпсоїд або будь-які особливості в площині). Коли ми працюємо в площині, також має сенс орієнтувати всі сектори в одному напрямку. (Якби ми моделювали розповсюдження вогню вітром, ми хотіли б, щоб сектори були орієнтовані на вітер, і їх розміри можуть також змінюватися залежно від швидкості вітру: це одна мотивація загального визначення дилатації, яку я дав. ) (На криволінійних поверхнях, як еліпсоїд, взагалі неможливо орієнтувати всі сектори в "одному" напрямку.)
У наступних обставинах розширення порівняно легко обчислити:
Особливість знаходиться в площині (тобто ми розширюємо карту функції, і, сподіваємось, карта досить точна).
Розширення буде постійним : розповсюдження в кожній точці ознаки відбуватиметься в межах однакових орієнтацій околиць.
Це спільне сусідство опукло. Опуклість значно спрощує та пришвидшує обчислення.
Це питання підходить для таких спеціалізованих обставин: воно вимагає розширення довільних многокутників круговими секторами, походження яких (центри дисків, з яких вони прийшли) розташовані в базових точках. Якщо ці сектори не охоплюють більше 180 градусів, вони будуть опуклими. (Більш великі сектори завжди можна розділити навпіл на два опуклі сектори; об'єднання двох менших дилатацій дасть бажаний результат.)
Оскільки ми виконуємо евклідові обчислення - роблячи розтікання в площині, - ми можемо розширити точку лише шляхом перекладу дилатаційного сусідства до цієї точки. (Щоб мати змогу це зробити, сусідство потребує походженнящо відповідатиме базовій точці. Наприклад, походження секторів у цьому питанні - це центр кола, з якого вони утворені. Це походження лежить на межі сектора. У стандартній операції буферизації ГІС сусідство - це коло з початком у центрі; тепер походження лежить у внутрішній частині кола. Вибір походження не є великою справою в обчисленні, оскільки зміна походження просто зміщує всю дилатацію, але це може бути великою справою з точки зору моделювання природних явищ. sector
Функції в коді нижче показано , як може бути визначено походження.)
Розведення відрізка лінії може бути складним, але для опуклого сусідства ми можемо створити дилатацію як об'єднання дилатацій двох кінцевих точок разом із ретельно вибраним паралелограмом. (В інтересах простору я не зупиняюся на доведенні подібних математичних тверджень, але закликаю читачів намагатися довести свої власні докази, оскільки це прониклива вправа.) Ось ілюстрація із використанням трьох секторів (показаних рожевим кольором). Вони мають одиничні радіуси, і їх кути наведені в заголовках. Сам відрізок лінії має довжину 2, горизонтальний і зображений чорним кольором:
Паралелограми знаходять, розміщуючи рожеві точки, які знаходяться якнайдалі від відрізка лише у вертикальному напрямку . Це дає дві нижні точки та дві верхні точки вздовж прямих, паралельних відрізку. Треба просто з'єднати чотири точки в паралелограм (показаний синім кольором). Зверніть увагу, як це має сенс, навіть коли сам сектор є лише відрізком лінії (а не справжнім багатокутником): там кожна точка на відрізку була переведена у напрямку 171 градус на схід від півночі на відстань від 0 до 1. Набір цих кінцевих точок є показаним паралелограмом. Деталі цього обчислення відображаються у buffer
функції, визначеній dilate.edges
у наведеному нижче коді.
Щоб розширити поліліній , формуємо об’єднання дилатацій точок і відрізків, які його утворюють. Останні два рядки dilate.edges
виконайте цю петлю.
Розширювати багатокутник потрібно, включаючи внутрішню частину полігону разом з розширенням його межі. (Це твердження дає певні припущення щодо дилатаційного сусідства. Одне з них полягає в тому, що всі мікрорайони містять крапку (0,0), яка гарантує, що полігон включений в його розширення. Що стосується змінних мікрорайонів, він також передбачає, що дилатація будь-якого інтер'єру Точка багатокутника не буде виходити за межі розширення граничних точок. Це стосується постійних сусідств.)
Давайте розглянемо кілька прикладів того, як це працює, спочатку з нонагоном (обраним для розкриття деталей), а потім з колом (обраним для відповідності ілюстрації до питання). У прикладах надалі будуть використовувати ті самі три мікрорайони, але скоротилися до радіусу 1/3.
На цьому малюнку внутрішній полігон сірий, точкові дилатації (сектори) рожеві, а крайові дилатації (паралелограми) - сині.
"Коло" - це насправді лише 60-кутовий, але воно непогано наближає коло.
Коли основна ознака представлена N точками, а сусідство дилатації M точками, цей алгоритм вимагає зусиль O (N M) . Слід дотримуватися спрощення безладдя вершин і ребер у об'єднанні, що може вимагати зусиль O (N M log (N M)): це те, що потрібно попросити зробити ГІС; ми не повинні були це програмувати.
Обчислювальні зусилля можуть бути покращені до O (M + N) для опуклих базових ознак (адже ви можете розробити, як подорожувати по новій межі, відповідним чином об'єднавши списки вершин, що описують межі початкових двох фігур). Для цього також не знадобиться подальше очищення.
Коли дилатаційне середовище повільно змінює розмір та / або орієнтацію, коли ви просуваєтесь навколо основної ознаки, розширення краю може бути близько наближене до опуклого корпусу об'єднання дилатацій його кінцевих точок. Якщо в двох кварталах розширення є точки M1 і M2, це можна знайти з зусиллям O (M1 + M2), використовуючи алгоритм, описаний у Шамосі та Препарата, обчислювальна геометрія . Тому, якщо K = M1 + M2 + ... + M (N) є загальною кількістю вершин у N дилатаційних кварталах, ми можемо обчислити дилатацію за час O (K * log (K)).
Чому ми хотіли б вирішити таке узагальнення, якщо все, що ми хочемо, - це простий буфер? Для великих функцій на землі сусідство розширення (наприклад, диск), яке має постійний розмір у дійсності, може мати різний розмір на карті, де проводяться ці обчислення. Таким чином ми отримуємо спосіб зробити обчислення, які є точними для еліпсоїда , продовжуючи користуватися всіма перевагами евклідової геометрії.
Приклади були підготовлені за допомогою цього R
прототипу, який можна легко перенести на вашу улюблену мову (Python, C ++ тощо). За своєю структурою він паралельний аналізу, про який повідомляється у цій відповіді, тому не потребує окремого пояснення. Коментарі пояснюють деякі деталі.
(Можливо, буде цікаво відзначити, що тригонометричні обчислення використовуються лише для створення прикладних ознак - які є регулярними багатокутниками - та секторів. Жодна частина дилатаційних розрахунків не потребує тригонометрії.)
#
# Dilate the vertices of a polygon/polyline by a shape.
#
dilate.points <- function(p, q) {
# Translate a copy of `q` to each vertex of `p`, resulting in a list of polygons.
pieces <- apply(p, 1, function(x) list(t(t(q)+x)))
lapply(pieces, function(z) z[[1]]) # Convert to a list of matrices
}
#
# Dilate the edges of a polygon/polyline `p` by a shape `q`.
# `p` must have at least two rows.
#
dilate.edges <- function(p, q) {
i <- matrix(c(0,-1,1,0), 2, 2) # 90 degree rotation
e <- apply(rbind(p, p[1,]), 2, diff) # Direction vectors of the edges
# Dilate a single edge from `x` to `x+v` into a parallelogram
# bounded by parts of the dilation shape that are at extreme distances
# from the edge.
buffer <- function(x, v) {
y <- q %*% i %*% v # Signed distances orthogonal to the edge
k <- which.min(y) # Find smallest distance, then the largest *after* it
l <- (which.max(c(y[-(1:k)], y[1:k])) + k-1) %% length(y)[1] + 1
list(rbind(x+q[k,], x+v+q[k,], x+v+q[l,], x+q[l,])) # A parallelogram
}
# Apply `buffer` to every edge.
quads <- apply(cbind(p, e), 1, function(x) buffer(x[1:2], x[3:4]))
lapply(quads, function(z) z[[1]]) # Convert to a list of matrices
}
#----------------------- (This ends the dilation code.) --------------------------#
#
# Display a polygon and its point and edge dilations.
# NB: In practice we would submit the polygon, its point dilations, and edge
# dilations to the GIS to create and simplify their union, producing a single
# polygon. We keep the three parts separate here in order to illustrate how
# that polygon is constructed.
#
display <- function(p, d.points, d.edges, ...) {
# Create a plotting region covering the extent of the dilated figure.
x <- c(p[,1], unlist(lapply(c(d.points, d.edges), function(x) x[,1])))
y <- c(p[,2], unlist(lapply(c(d.points, d.edges), function(x) x[,2])))
plot(c(min(x),max(x)), c(min(y),max(y)), type="n", asp=1, xlab="x", ylab="y", ...)
# The polygon itself.
polygon(p, density=-1, col="#00000040")
# The dilated points and edges.
plot.list <- function(l, c) lapply(l, function(p)
polygon(p, density=-1, col=c, border="#00000040"))
plot.list(d.points, "#ff000020")
plot.list(d.edges, "#0000ff20")
invisible(NULL) # Doesn't return anything
}
#
# Create a sector of a circle.
# `n` is the number of vertices to use for approximating its outer arc.
#
sector <- function(radius, arg1, arg2, n=1, origin=c(0,0)) {
t(cbind(origin, radius*sapply(seq(arg1, arg2, length.out=n),
function(a) c(cos(a), sin(a)))))
}
#
# Create a polygon represented as an array of rows.
#
n.vertices <- 60 # Inscribes an `n.vertices`-gon in the unit circle.
angles <- seq(2*pi, 0, length.out=n.vertices+1)
angles <- angles[-(n.vertices+1)]
polygon.the <- cbind(cos(angles), sin(angles))
if (n.vertices==1) polygon.the <- rbind(polygon.the, polygon.the)
#
# Dilate the polygon in various ways to illustrate.
#
system.time({
radius <- 1/3
par(mfrow=c(1,3))
q <- sector(radius, pi/12, 2*pi/3, n=120)
d.points <- dilate.points(polygon.the, q)
d.edges <- dilate.edges(polygon.the, q)
display(polygon.the, d.points, d.edges, main="-30 to 75 degrees")
q <- sector(radius, pi/3, 4*pi/3, n=180)
d.points <- dilate.points(polygon.the, q)
d.edges <- dilate.edges(polygon.the, q)
display(polygon.the, d.points, d.edges, main="-150 to 30 degrees")
q <- sector(radius, -9/20*pi, -9/20*pi)
d.points <- dilate.points(polygon.the, q)
d.edges <- dilate.edges(polygon.the, q)
display(polygon.the, d.points, d.edges, main="171 degrees")
})
Час обчислення для цього прикладу (з останньої цифри), з N = 60 та M = 121 (зліва), M = 181 (середній) та M = 2 (праворуч), становило одну чверть секунди. Однак найбільше це було для показу. Зазвичай цей R
код обробляє близько N M = 1,5 мільйона в секунду (займає всього 0,002 секунди або близько того, щоб зробити всі наведені приклади розрахунків). Тим не менш, поява виробу M N передбачає розширення багатьох фігур або складних фігур через детальне сусідство може зайняти чимало часу, тому будьте уважні! Визначте терміни виникнення менших проблем, перш ніж вирішувати великі проблеми. За таких обставин можна звернутися до растрового рішення (що набагато простіше втілити, вимагаючи по суті лише один розрахунок сусідства.)
Це досить широко, але ви можете: