Як створити орієнтований буфер за допомогою arcpy?


9

Я б хотів створити орієнтований буфер для кожного багатокутника у моєму файлі форм за допомогою arcpy. Під орієнтуванням я маю на увазі, що у мене є два кути a1 і a2, які обмежують напрямок буфера. Це представлено на графіку нижче: введіть тут опис зображення

Будь-які ідеї?


3
Потрібно б більше інформації про кути. З якої осі ви вимірюєте кути? CW чи CCW? Як ви знаходите кожен кут на багатокутнику? З якими видами багатокутників ми маємо справу? (Коло - це не багатокутник.)
Павло

1
+1 до @Paul, але я подумав, що коло - це багатокутник, поки я не прочитав це .
PolyGeo

+1 теж! Я використав коло, щоб легко проілюструвати проблему. Полігони - це результати сегментації в електронному пізнанні з наступною класифікацією для ідентифікації класу. Кути a1 і a2 походять від кута азимуту освітлення сегментованого супутникового зображення. У прикладі кут азимута дорівнював би 0, a1 і a2, рівний 0 +/- 15 ° (довільно фіксований до 15 °).
WAF

2
@PolyGeo "Полігон" в ГІС використовується дещо інакше, ніж у математиці. Тут йдеться про цифрове зображення (двовимірного) регіону або його закриття . Регіони, як правило, (але не завжди) представлені полігональними наближеннями, але - оскільки ми знаємо, що наші комп’ютерні уявлення є лише наближеннями - ми опускаємо «наближення» і просто використовуємо «багатокутник».
whuber

Відповіді:


20

Підсумок

Ця відповідь ставить питання в більш широкий контекст, описує ефективний алгоритм, застосовний до представлення форм-файлів функцій (як "вектори" або "рядки" точок), показує деякі приклади його застосування та дає робочий код для використання або перенесення в середовище ГІС.

Фон

Це приклад морфологічного розширення. У повній загальності дилатація "поширює" точки регіону на їхні квартали; збір точок, куди вони звиваються, - це «дилатація». Застосування в ГІС безліч: моделювання поширення вогню, рух цивілізацій, поширення рослин та багато іншого.

Математично і в дуже великій (але корисній) загальності дилатація поширює набір точок у ріманівському багатоманітнику (такі як площина, сфера або еліпсоїд). Розкидання обумовлюється підмножиною дотичного пучка в цих точках. Це означає, що в кожній з точок задається набір векторів (напрямків і відстаней) (я називаю це «сусідством»); кожен з цих векторів описує геодезичний шлях, що починається в його базовій точці. Базова точка «розкидається» до кінців усіх цих шляхів. (Про значно більш обмежене визначення поняття "дилатація", яке звичайно використовується в обробці зображень, див . У статті Вікіпедії . Функція розповсюдження відома як експоненціальна карта в диференційній геометрії.)

"Буферизація" функції є одним з найпростіших прикладів такого розширення: навколо кожного пункту функції створюється (принаймні концептуально) диск постійного радіуса (радіус буфера). Об'єднання цих дисків є буфером.

Це питання вимагає обчислення дещо складнішого розширення, коли розкидання дозволяється відбуватися лише в заданому діапазоні кутів (тобто в межах кругового сектора). Це має сенс лише для особливостей, які не закривають помітно вигнуту поверхню (наприклад, невеликі риси на кулі або еліпсоїд або будь-які особливості в площині). Коли ми працюємо в площині, також має сенс орієнтувати всі сектори в одному напрямку. (Якби ми моделювали розповсюдження вогню вітром, ми хотіли б, щоб сектори були орієнтовані на вітер, і їх розміри можуть також змінюватися залежно від швидкості вітру: це одна мотивація загального визначення дилатації, яку я дав. ) (На криволінійних поверхнях, як еліпсоїд, взагалі неможливо орієнтувати всі сектори в "одному" напрямку.)

У наступних обставинах розширення порівняно легко обчислити:

  • Особливість знаходиться в площині (тобто ми розширюємо карту функції, і, сподіваємось, карта досить точна).

  • Розширення буде постійним : розповсюдження в кожній точці ознаки відбуватиметься в межах однакових орієнтацій околиць.

  • Це спільне сусідство опукло. Опуклість значно спрощує та пришвидшує обчислення.

Це питання підходить для таких спеціалізованих обставин: воно вимагає розширення довільних многокутників круговими секторами, походження яких (центри дисків, з яких вони прийшли) розташовані в базових точках. Якщо ці сектори не охоплюють більше 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 передбачає розширення багатьох фігур або складних фігур через детальне сусідство може зайняти чимало часу, тому будьте уважні! Визначте терміни виникнення менших проблем, перш ніж вирішувати великі проблеми. За таких обставин можна звернутися до растрового рішення (що набагато простіше втілити, вимагаючи по суті лише один розрахунок сусідства.)


Нічого собі, це дуже докладно і захоплююче. Я нічого не очікував.
Павло

1

Це досить широко, але ви можете:

  1. буфер початкового багатокутника
  2. знайти точку початку "орієнтованих" променів, що створюються на межі полігону (якась точка дотику?)
  3. створити / розширити рядок з цієї точки на відстань поза буфером, використовуючи кут, обговорений в коментарях до питання.
  4. перетинають цю лінію з буфером і початковим багатокутником. Це, ймовірно, можна зробити одночасно з 3) за допомогою відповідних аргументів для розширення.
  5. витягнути новий багатокутник «орієнтований буфер» з отриманого набору багатокутників

Я вважаю, що ОП означає "орієнтований буфер" у сенсі морфологічного розширення кожної форми сектором кола. (Цей опис одразу дає растровий розв'язок, але оскільки
файли форм

Сподіваємось, ОП уточнить цей момент. Я ввійшов у свою думку, грунтуючись на графіці, яка не завжди є найбезпечнішою. У будь-якому випадку, хоча можна розмістити неправильну форму комірок відносно обчисленого місця (я це робив у gridedit ... я відчуваю себе старим!), Я б подумав, що векторне рішення стане більш чистим / використовуючи векторні функції дуги краще . Загальний метод, ймовірно, аналогічний, незалежно від моделі даних. Можливо, трохи більше кодування для користувача на растровій стороні.
Roland

Насправді, на растровій стороні кодування не потрібно :-). Це можна зробити кількома способами, включаючи фокусні статистичні дані з відповідним чином визначеним сусідством. Я згоден, що тут переважніше векторне рішення: більш чисте та точне. Для надзвичайно великих або складних наборів даних він може затухати, хоча растрове рішення буде швидким, тому завжди варто знати, як це робити обома способами.
whuber

Ми думали про фокусні фокуси , але не були впевнені, чи форма + кут роботи ОП буде складно поєднати в єдине сусідство .
Роланд

Тільки сектор повинен бути описаний мікрорайоном, що легко зробити .
whuber
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.