Как искать объекты в пределах указанного радиуса координаты XY (без драгоценных камней)? - PullRequest
2 голосов
/ 05 августа 2020

- ФОН -

Во-первых, к сожалению, поскольку программное обеспечение, с которым я работаю, имеет встроенную библиотеку Ruby, она не позволяет установку драгоценных камней. Я думаю, что геокодер выглядел многообещающим, увы ...

У меня есть X ссылок и узлов в 2D-пространстве, и я sh ищу ближайшую ссылку на заданную координату XY. По сути, я хочу создать свой собственный метод для этого, например:

def manual_search_from_point(x, y, distance_from_point_to_search, collection_of_objects)
...
end

, где «collection_of_objects» обычно является массивом группы объектов, которую я пытаюсь получить (ссылки).

Не уверен, есть ли способ непрерывного радиального поиска все дальше и дальше (увеличивая "distance_from_point_to_search") для поиска объектов без предварительного расчета расстояния между объектами. Имея это в виду, я подумал, что сбор всех координат XY ссылок (конечные точки и т.д. c.) И определение расстояния от этой точки до начальной точки XY может иметь смысл, используя:

def self.distance(x1, y1, x2, y2)
  Math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
end

... и иду оттуда.

- ТЕКУЩИЙ СТАТУС -

Сейчас я пытаюсь добавить координаты всех ссылок в ha sh в качестве значений и ссылки сам объект в качестве ключа, чтобы затем проработать значения XY каждой ссылки и определить расстояние, а также создать новый ha sh с объектом ссылки снова в качестве ключа и расстоянием до этого объекта в качестве значения. Я сделал первый ha sh, но не знаю, как циклически перебирать значения как отдельные XY. (Объекты одной ссылки могут иметь много точек XY)

points_hash = Hash[ array.map {|k| [k, k.bends]} ]

- ЖЕЛАТЕЛЬНЫЙ ВЫХОД -

От:

{#<Link:0x803a2e8>=>[394514.80, 421898.01, 394512.92, 421895.82]...} #(X1, Y1, X2, Y2)

до

{#<Link:0x803a2e8>=>[157],    #(distance in metres (value not real distance))
\#<Link:0x803a2e8>=>[196]...}  #(distance in metres (value not real distance))

Могу дать больше информации, если немного сложно получить то, что мне нужно. Также рад, что мне сказали, что я ошибаюсь!

Большое спасибо.

Ответы [ 3 ]

3 голосов
/ 06 августа 2020

Подход

Рассмотрим ссылку

[a, b]

, где a и b - двухэлементные массивы, которые соответствуют x-y координатам в евклидовом формате. пространство. Я предполагаю a != b.

В векторной терминологии каждая точка на линии, которая проходит через точки a и b, может быть выражена как:

αa + (1-α)b

для некоторых значение скаляра α. α удовлетворяет 0 <= α <= 1 для точек, попадающих на сегмент линии между a и b. Утверждается, что эти точки составляют выпуклую комбинацию из a и b. Я вычислю значение α (alpha), которое соответствует точке, которая находится как на линии, проходящей через a и b, так и на линии, перпендикулярной этой линии и проходящей через данную точку p.

Сначала вычислите наклон линии, проходящей между a и b. Пусть

a = [ax,ay]
b = [bx,by]

Точки c = [cx,cy] на линии, проходящей через a и b, выражаются:

cx = alpha*ax + (1-alpha)*bx
cy = alpha*ay + (1-alpha)*by

, которые мы можем упростить до:

cx = alpha*(ax-bx) + bx
cy = alpha*(ay-by) + by

Обратите внимание, что cx и cy равны bx и by, когда alpha равно нулю и равно ax и ay, когда alpha равно 1.

Предположим, теперь нам дана точка:

p = [px,py]

и wi sh, чтобы найти точку на линии (не обязательно отрезок линии), которая проходит через a и b, т.е. ближайшая точка к p. Мы можем сделать это следующим образом.

Сначала вычислите наклон линии, проходящей через a и b (предполагая bx != ax, что соответствует вертикальной линии):

slope = (by-ay)/(bx-ax)

Линии, перпендикулярные этой линии, имеют наклон, равный:

pslope = -1/slope

Давайте вычислим точку пересечения такой линии, которая проходит через точку p:

intercept + pslope*px = py

Итак

intercept = py - pslope*px
    

Теперь давайте посмотрим, где эта линия пересекает линию, проходящую через a и b с точки зрения значения alpha:

intercept + pslope(alpha*(ax-bx) + bx) = alpha*(ay-by) + by

Решение для альфа :

alpha = (by - pslope*bx - intercept)/(pslope*(ax-bx) - (ay-by))

Давайте попробуем это на примере. Рассмотрим всего две связи графика, как показано на профессионально подготовленном рисунке ниже. (Упс! Я вижу, что моя служба graphi c пренебрегла пометкой точки [2,3] как «A».) Сначала рассмотрите ссылку или сегмент линии A-B и точку P.

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

ax = 2
ay = 3

bx = 5
by = 7

px = 5
py = 2
slope = (by-ay).fdiv(bx-ax)
  #=> (7-3).fdiv(5-2) => 1.333.. 
pslope = -1.0/slope
  #=> -0.75 
intercept = py - pslope*px
  #=> 5.75 
alpha = (by - pslope*bx - intercept)/(pslope*(ax-bx) - (ay-by))
  #=> 0.8 
cx = alpha*(ax-bx) + bx
  #=> 2.6 
cy = alpha*(ay-by) + by
  #=> 3.8

Потому что 0 <= alpha (0.8) <= 1, (cx,cy) попадает в линейный сегмент A-B, поэтому эта точка является ближайшей точкой на линейном сегменте к P, а не только точка на линии, проходящей через A и B, ближайшая к P.

Квадрат расстояния от P до C оказывается равным

(cx-px)**2 + (cy-py)**2
  #=> (2.6-5.0)**2 + (3.8-2.0)**2 => 9

Если мы хотим включить все ссылки не дальше, чем, скажем, 3.5 от P, это эквивалентно включению всех ссылок, квадрат расстояния которых до P не превышает:

max_dist_sqrd = 3.5**2
  #=> 12.25

Как 9.0 <= max_dist_sqrd (12.25), эта ссылка будет включена.

Теперь рассмотрим ссылку D-E.

dx = 7
dy = 6

ex = 6
ey = 9

px = 5
py = 2
slope = (ey-dy).fdiv(ex-dx)
  #=> (9-6).fdiv(6-7) => -3.0 
pslope = -1.0/slope
  #=> 1.0/3.0 => 0.333
intercept = py - pslope*px
  #=> 2 - (0.333)*5 => 0.333..
alpha = (ey - pslope*ex - intercept)/(pslope*(dx-ex) - (dy-ey))
  #=> (9 - 0.333*6 - 0.333)/(0.333*(7-6) - (6-9)) #=> 2.0

Потому что alpha > 1.0 мы знайте, что точка F не находится на отрезке линии D-E. Давайте сделаем небольшой экскурс, чтобы увидеть, где находится точка:

fx = alpha*(dx-ex) + ex
  #=> 2.0(7-6) + 6 => 8.0
fy = alpha*(dy-ey) + ey
  #=> 2.0(6-9) + 9 => 3.0

Потому что alpha > 1.0 мы знаем, что ближайшая точка к P на отрезке линии D-E - D. Если бы alpha > 1.0, ближайшей точкой было бы E.

Таким образом, мы находим, что ближайший квадрат расстояния от точки P до отрезка линии D-E равен:

(dx-px)**2 + (dy-py)**2
  #=> (7-5)**2 + (6-2)**2 => 20

Начиная с 20.0 > max_dist_sqrd (12.25), эта ссылка не будет включена.

Код

def links_in_point_circle(links, point, max_dist)
  max_dist_sqrd = max_dist**2
  links.select { |link| sqrd_dist_to_link(link, point) <= max_dist_sqrd }
end
def sqrd_dist_to_link( ((xlow,ylow),(xhigh,yhigh)),(px,py) )
  return vert_link_dist(xlow,ylow,yhigh,px,py) if xlow == xhigh
  slope = -1.0/((yhigh-ylow).fdiv(xhigh-xlow))
  intercept = py - slope*px
  alpha = (yhigh - slope*xhigh - intercept)/(slope*(xlow-xhigh) - (ylow-yhigh))
  closest_x, closest_y =
    case alpha
    when -Float::INFINITY...0
      [xhigh, yhigh]
    when 0..1.0 
      [alpha*(xlow-xhigh) + xhigh, alpha*(ylow-yhigh) + yhigh]
    else
      [xlow, ylow]
    end
  (closest_x-px)**2 + (closest_y-py)**2  
end
def vert_link_dist(xlow, ylow, yhigh, px, py)
  return Float::INFINITY if px != xlow 
  case
  when py < ylow   then (ylow-py)**2
  when ylow..yhigh then 0
  else                  (py-yhigh)**2
  end
end

Пример

links    = [[[2,3],[5,7]], [[7,6], [6,9]]]
point    = [5,2]
max_dist = 3.5
links_in_point_circle(links, point, max_dist)
  #=> [[[2, 3], [5, 7]]]
1 голос
/ 19 августа 2020

Используя методы, предоставленные Cary Swoveland выше, ответ почти готов. Используя пример ниже:

class Link
  attr_accessor :bends
  def initialize(bends)
    @bends = bends
  end
end

link_1 = Link.new([1, 1, 2, 2, 3, 3, 4, 4])
link_2 = Link.new([5, 5, 6, 6, 7, 7, 8, 8])
link_3 = Link.new([11, 11, 14, 14, 17, 17, 22, 22, 25, 25])

valid_links = Array.new
links = [link_1, link_2, link_3]
links.each do |link|
  # Conditional goes here, to populate valid_links
  valid_links << link
end

point = [1.1, 1.1]

def link_coordinates(link)
  # To get the link XYs in desired format:
  # [[[x1, y1], [x2, y2]], [[x2, y2], [x3, y3]], [[x3, y3], [x4, y4]]]  (1)
  link = link.bends.each_slice(2).each_cons(2).to_a 
  #=> [[[1, 1], [2, 2]], [[2, 2], [3, 3]], [[3, 3], [4, 4]]]  (for first link)
end

closest_segment = valid_links.min_by { |link| sqrd_dist_to_link(link_coordinates(link), point)}

Вышеупомянутое приводит к ошибке в пределах sqrd_dist_to_link. Если раньше, когда «ссылки» использовали только значения и в формате:

links = [[[x1, y1], [x2, y2]], [[x2, y2], [x3, y3]], [[x3, y3], [x4, y4]]]  (2)
@closest_segment = links.min_by { |link| sqrd_dist_to_link(link, point)}

, удалось вернуть ближайшие координаты XY сегмента в формате: [[x1, y1], [x2 , y2]].

Учитывая, что «ссылки» с меткой «(1)» и ссылки с меткой «(2)» выводят один и тот же формат, я не уверен, как настроить параметры, чтобы valid_links.min_by { |link| sqrd_dist_to_link(link_coordinates(link), point)} получить объект ссылки ближайшего сегмента XY по желанию.

Примечание:

Я обновлю это, когда он будет работать правильно, чтобы он правильно квалифицировался как ответ .

1 голос
/ 06 августа 2020
  1. Создайте AABB -Tree: AABB-Tree - это пространственный индекс, то есть он ускоряет пространственные запросы. Вы можете использовать другую структуру данных индекса, такую ​​как kd-tree, uniform grid, quad-tree, R-tree и т. Д. c. Но я нашел AABB-дерево более простым и почти оптимальным для запросов диапазона (то, что вы хотите сделать, это запрос диапазона, например, например, ближайшие соседи )

    1.1) Сначала поместите каждую ссылку в AABB. Вычислите центроид каждого AABB.

    1.2) Поместите все ваши AABB в большой AABB, содержащий их все. Сделайте этот большой AABB деревом root узлом.

    1.3) Найдите самую большую сторону большого AABB (это может быть X или Y).

    1.4) Отсортируйте все AABB по координате центра тяжести (X или Y), соответствующей наибольшей стороне.

    1.5) Найдите середину отсортированного списка и разделите его на два списка.

    1.6) Создайте два дочерние узлы присоединяются к узлу root и назначают по одному списку каждому узлу.

    1.7) Вызов шага 1.2) рекурсивно для построения каждого дочернего узла в поддерево.

    1.8) Остановить рекурсию если размер списка <= N. Затем назначьте все AABB этому узлу. </p>

  2. Выполните запрос диапазона, чтобы найти ближайшую ссылку к точке (т. е. рекурсивно пройти по дереву) .

    2.1) Определите круг бесконечного радиуса с центром в точке, которую вы хотите запросить. Передайте круг в качестве параметра функции перемещения.

    2.2) Если AABB узла root пересекается с кругом, то вызовите рекурсивно функцию обхода с левым и правым потомками. Порядок важен, поэтому вы должны сначала выбрать дочерний элемент, расположенный ближе к центру круга.

    2.3) Если узел является листовым узлом (т. Е. Без дочерних узлов, он содержит список AABB ссылок), отметьте если круг пересекает AABB ссылок, если это так, вычисляется расстояние от точки до каждой ссылки и сохраняется кратчайшее расстояние. Пока вы вычисляете расстояние до каждого звена, сравните его с радиусом круга. Если расстояние меньше, чем радиус , обновите радиус круга до самого короткого расстояния и сохраните идентификатор ссылки с кратчайшим расстоянием.

  3. После прохождения функция fini sh, вы получите кратчайшее расстояние в логарифмах c время.

...