Нахождение сегментов ближайшей линии к точке, используя стройные?

17

Фон

Из известной точки мне требуется установить ближайший окружающий «видимый периметр» по таблице MultiLineStrings, как показано на диаграмме.

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

схема

  • Зеленый круг - это известная точка.
  • Черные линии - известные MultiLineStrings.
  • Серые линии указывают на радиальную развертку от известной точки.
  • Красные точки являются ближайшим пересечением радиальной развертки и MultiLineStrings.

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

параметры

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

Рассмотренная методология

  • Выполните радиальную развертку, построив ряд линий из известной точки (скажем, с шагом 1 градус).
  • Установите ближайшую точку пересечения каждой радиальной линии развертки с помощью MultiLineStrings.
  • Когда одна из линий радиальной развертки не пересекается ни с одной из строк MultiLineString, это указывает на зазор в периметре, который будет размещен в конструкции периметра 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

Я воспроизвел ваш пример с шейп-файлами.

Вы можете использовать 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 для создания радиусов (вращения линии от точки, см также Майк Toews ответ «s на 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) то же самое с оригинальными линиями (шейп-файл)

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 для ввода / вывода через шейп-файлы. Я добавил код к моему вопросу, который использует ваш ответ, и изменил его, чтобы уменьшить количество intersectionнеобходимых вычислений - спасибо.
Расти Магу