Запрос на поиск всех местоположений в радиусе для каждого местоположения по другой координате

Мы используем драгоценный камень geokit-rails, чтобы найти все локации на определенном расстоянии от другой локации.

class CreateLocations < ActiveRecord::Migration[7.2]
  def change
    create_table :locations do |t|
      t.decimal :lat
      t.decimal :lng
      t.float   :radius
    end
  end
end

class Location < ApplicationRecord
  acts_as_mappable
end

Следующее работает отлично и возвращает все местоположения в пределах 5 миль от (37,-122):

Location.within(5, origin: [37,-122])

Затем нам нужно найти все местоположения на расстоянии, хранящемся в столбце радиуса в разделе «Местоположение». Следующий запрос не работает.

Location.within('locations.radius', origin: [37,-122])

Может ли кто-нибудь помочь мне понять, что я делаю неправильно?

[Обновление] Решение @max и @engineersmnky, приведенное ниже, сработало отлично. Я также обнаружил функцию «слияния», которая позволяет использовать .within в объединяемой таблице. Например, если в каком-либо месте много продуктов, вы можете с помощью этого поиска найти все продукты, продаваемые в определенных местах в радиусе их доставки. Надеюсь, это сэкономит кому-то еще много времени.

Product.joins(:location).merge(Location.within(Location.arel_table[:radius], origin: [37, -122]))

Я не уверен, что этот метод вообще предназначен для такой работы, но вы можете попробовать Location.within(Location.arel_table[:radius], origin: [37,-122]). Вот здесь и заканчивается спор github.com/geokit/geokit-rails/blob/…

max 27.08.2024 11:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
55
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Просто используйте простой объект местоположения с радиусом

Location.flat_map do |location|
  Location.within(location.radius, origin: [37,-122]).ids
end

Нам нужно найти все местоположения, находящиеся в пределах расстояния доставки (радиуса) для каждого местоположения, а не только в пределах одного радиуса. У нас есть жестко запрограммированный следующий запрос, и он работает, но мы надеялись использовать драгоценный камень geokit, поскольку мы все равно его используем:

vince 26.08.2024 21:15

Location.where(Location.distance_sql, {широта: 37, длина: -122})

vince 26.08.2024 21:17

class Location < ApplicationRecord def self.distance_sql h = "POWER(SIN((RADIANS(locations.lat - :lat))/2.0),2) + " + "COS(RADIANS(:lat)) * COS(RADIANS(locations) .lat)) * " + "POWER(SIN((RADIANS(locations.lng - :lng))/2.0),2)" r = 3956 # Радиус Земли в милях "2 * #{r} * ASIN(SQRT( #{h})) <location.radius" end end

vince 26.08.2024 21:17
Ответ принят как подходящий

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

Location.within( Location.arel_table[:radius], origin: [37,-122])

В результате получится <=, но это намного чище, чем альтернативы.

Однако формула отличается от вашей.

(ACOS(least(1,COS(#{lat})*COS(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*COS(RADIANS(#{qualified_lng_column_name}))+
COS(#{lat})*SIN(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*SIN(RADIANS(#{qualified_lng_column_name}))+
SIN(#{lat})*SIN(RADIANS(#{qualified_lat_column_name}))))*#{multiplier})

В качестве альтернативы мы можем использовать вашу текущую концепцию (показанную в комментарии ), но использовать реализацию [geokit-rails]:

origin = Geokit::LatLng(37,-122)
Location.where(Location.distance_sql(origin).lt(Location.arel_table[:radius]))

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

# Including Math because otherwise I would need to create new instances of 
# Addition, Division, and Multiplication for each operation  

Arel::Nodes::NamedFunction.include(Arel::Math) 

class Location < ApplicationRecord
  scope :inside_radius, ->(lat: , lng:) {
    location_table = Arel::Table.new('locations')
    q = Arel::Nodes::NamedFunction.new('ASIN',[
      Arel::Nodes::NamedFunction.new('SQRT',[ 
        Arel::Nodes::NamedFunction.new('POWER',
          [
            Arel::Nodes::NamedFunction.new("SIN",
             [ Arel::Nodes::NamedFunction.new("RADIANS",[location_table[:lat] - lat])]
            ) / Arel.sql('2.0'),
            2
          ]
        ) + 
        Arel::Nodes::NamedFunction.new('COS',[Arel::Nodes::NamedFunction.new('RADIANS',[lat])]) *
        Arel::Nodes::NamedFunction.new('COS',[Arel::Nodes::NamedFunction.new('RADIANS',[location_table[:lat]])]) * 
        Arel::Nodes::NamedFunction.new('POWER',
          [
            Arel::Nodes::NamedFunction.new("SIN",
             [ Arel::Nodes::NamedFunction.new("RADIANS",[location_table[:lng] - lng])]
            ) / Arel.sql('2.0'),
            2
          ]
        )
      ])
    ]).*(2).*(3956).lt(location_table[:radius])
    where(q)
  }
end 

Использование:

Location.inside_radius(lat: 37, lng: -122)

Производит

SELECT locations.*
FROM 
  locations 
WHERE 
ASIN(SQRT((POWER(SIN(RADIANS(([locations].[lat] - 37))) / 2.0, 2) + COS(RADIANS(37)) * COS(RADIANS([locations].[lat])) * POWER(SIN(RADIANS(([locations].[lng] - -122))) / 2.0, 2)))) * 2 * 3956 < [locations].[radius]

Другие вопросы по теме