Знаходження найближчих відрізків рядків до точки за допомогою точного?


17

Фон

З відомої точки я вимагаю встановити найближчий оточуючий "видимий периметр" проти таблиці MultiLineStrings, як показано на схемі.

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

Діаграма

  • Зелене коло - відома Точка.
  • Чорні лінії - це відомі MultiLineStrings.
  • Сірі лінії є вказівкою на радіальну розгортку з відомої точки.
  • Червоні точки є найближчим перетином променевої розгортки та MultiLineStrings.

введіть тут опис зображення

Параметри

  • Точка ніколи не перетинатиме MultiLineStrings.
  • Точка завжди буде номінально зосереджена в MultiLineStrings.
  • MultiLineStrings ніколи не повністю закриє точку, тому периметр буде MultiLineString.
  • Буде таблиця, що містить приблизно 1000 MultiLineStrings (як правило, містить один рядок приблизно 100 балів).

Розглянута методика

  • Проведіть радіальну розгортку, побудувавши ряд відомих точок (з, скажімо, з кроком 1 ступеня).
  • Встановіть найближчу точку перетину кожної радіальної лінії розгортки за допомогою MultiLineStrings.
  • Коли одна з радіальних зачисток не перетинається з жодною з MultiLineStrings, це вказуватиме на розрив у периметрі, який би розміщувався в периметрі конструкції MultiLineString.

Підсумок

Хоча ця методика знайде найближчі перехрестя, вона не обов'язково знайде всі найближчі точки вузла периметра, залежні від роздільної здатності радіального розгортки. Чи може хтось порекомендувати альтернативний метод встановлення всіх точок периметра або доповнити техніку радіального зачитування якоюсь формою буферизації, секціонування чи зміщення?

Програмне забезпечення

Я вважаю за краще використовувати SpatiaLite та / або Shapely для рішення, але вітаю будь-які пропозиції, які можна реалізувати за допомогою програмного забезпечення з відкритим кодом.

Редагувати: робоче рішення (засноване на відповіді @gene)

from shapely.geometry import Point, LineString, mapping, shape
from shapely.ops import cascaded_union
from shapely import affinity
import fiona

sweep_res = 10  # sweep resolution (degrees)
focal_pt = Point(0, 0)  # radial sweep centre point
sweep_radius = 100.0  # sweep radius

# create the radial sweep lines
line = LineString([(focal_pt.x,focal_pt.y), \
                   (focal_pt.x, focal_pt.y + sweep_radius)])

sweep_lines = [affinity.rotate(line, i, (focal_pt.x, focal_pt.y)) \
               for i in range(0, 360, sweep_res)]

radial_sweep = cascaded_union(sweep_lines)

# load the input lines and combine them into one geometry
input_lines = fiona.open("input_lines.shp")
input_shapes = [shape(f['geometry']) for f in input_lines]
all_input_lines = cascaded_union(input_shapes)

perimeter = []
# traverse each radial sweep line and check for intersection with input lines
for radial_line in radial_sweep:
    inter = radial_line.intersection(all_input_lines)

    if inter.type == "MultiPoint":
       # radial line intersects at multiple points
       inter_dict = {}
       for inter_pt in inter:
           inter_dict[focal_pt.distance(inter_pt)] = inter_pt
       # save the nearest intersected point to the sweep centre point
       perimeter.append(inter_dict[min(inter_dict.keys())])

    if inter.type == "Point":
       # radial line intersects at one point only
       perimeter.append(inter)

    if inter.type == "GeometryCollection":
       # radial line doesn't intersect, so skip
       pass

# combine the nearest perimeter points into one geometry
solution = cascaded_union(perimeter)

# save the perimeter geometry
schema = {'geometry': 'MultiPoint', 'properties': {'test': 'int'}}
with fiona.open('perimeter.shp', 'w', 'ESRI Shapefile', schema) as e:
     e.write({'geometry':mapping(solution), 'properties':{'test':1}})

Зазвичай радіальний зйомок не має значущої "роздільної здатності": він сканує від однієї "події" до наступної в порядку, коли події складаються з оригінальних вузлів поліліній та їх взаємних перетинів (які можна динамічно знайти під час переміщення навколо оригіналу вузли). Його вихід буде абсолютно точним.
whuber

Відповіді:


17

Я відтворив ваш приклад за допомогою shapefiles.

Ви можете використовувати Shapely та Fiona для вирішення своєї проблеми.

1) Ваша проблема (зі стрункою Point):

введіть тут опис зображення

2) починаючи з довільної лінії (з адекватною довжиною):

from shapely.geometry import Point, LineString
line = LineString([(point.x,point.y),(final_pt.x,final_pt.y)])

введіть тут опис зображення

3) використовуючи shapely.affinity.rotate для створення радіусів (обертаючи лінію від точки, дивіться також відповідь Майка Тоувса на Python, вишукана бібліотека: чи можна зробити афінну операцію на полігоні форми? ):

from shapely import affinity
# Rotate i degrees CCW from origin at point (step 10°)
radii= [affinity.rotate(line, i, (point.x,point.y)) for i in range(0,360,10)]

введіть тут опис зображення

4) тепер, використовуючи shapely : cascaded_union (або shapely: unary_union ), щоб отримати MultiLineString:

from shapely.ops import cascaded_union
mergedradii = cascaded_union(radii)
print mergedradii.type
MultiLineString

5) те ж саме з оригінальними рядками (shapefile)

import fiona
from shapely.geometry import shape
orlines = fiona.open("orlines.shp")
shapes = [shape(f['geometry']) for f in orlines]
mergedlines = cascaded_union(shapes)
print mergedlines.type
MultiLineString

6) обчислюється перетин між двома мультигеометриями, і результат зберігається у файл форми:

 points =  mergedlines.intersection(mergedradii)
 print points.type
 MultiPoint
 from shapely.geometry import mapping
 schema = {'geometry': 'MultiPoint','properties': {'test': 'int'}}
 with fiona.open('intersect.shp','w','ESRI Shapefile', schema) as e:
      e.write({'geometry':mapping(points), 'properties':{'test':1}})

Результат:

введіть тут опис зображення

7) але проблема, якщо ви користуєтеся більшим радіусом, результат відрізняється:

введіть тут опис зображення

8) А якщо ви хочете отримати результат, вам потрібно вибрати лише радіус, який має найменшу відстань від початкової точки на радіусі:

points_ok = []
for line in mergeradii:
   if line.intersects(mergedlines):
       if line.intersection(mergedlines).type == "MultiPoint":
          # multiple points: select the point with the minimum distance
          a = {}
          for pt in line.intersection(merged):
              a[point.distance(pt)] = pt
          points_ok.append(a[min(a.keys())])
       if line.intersection(mergedlines).type == "Point":
          # ok, only one intersection
          points_ok.append(line.intersection(mergedlines))
solution = cascaded_union(points_ok)
schema = {'geometry': 'MultiPoint','properties': {'test': 'int'}}
with fiona.open('intersect3.shp','w','ESRI Shapefile', schema) as e:
     e.write({'geometry':mapping(solution), 'properties':{'test':1}})

Кінцевий результат:

введіть тут опис зображення

Я сподіваюсь, що ви цього хочете.


1
Відмінна відповідь - особливо інформативна стосовно використання Fiona для введення / виводу через shapefiles. Я додав до свого запитання якийсь код, який використовує вашу відповідь і змінив його, щоб зменшити кількість intersectionнеобхідних розрахунків - дякую.
Rusty Magoo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.