Мы используем драгоценный камень 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.flat_map do |location|
Location.within(location.radius, origin: [37,-122]).ids
end
Нам нужно найти все местоположения, находящиеся в пределах расстояния доставки (радиуса) для каждого местоположения, а не только в пределах одного радиуса. У нас есть жестко запрограммированный следующий запрос, и он работает, но мы надеялись использовать драгоценный камень geokit, поскольку мы все равно его используем:
Location.where(Location.distance_sql, {широта: 37, длина: -122})
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
Немного пробежав исходный код, как указал @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]
Я не уверен, что этот метод вообще предназначен для такой работы, но вы можете попробовать
Location.within(Location.arel_table[:radius], origin: [37,-122])
. Вот здесь и заканчивается спор github.com/geokit/geokit-rails/blob/…