首页 > 解决方案 > Rails 中 Json 数组上的地理编码器方法

问题描述

我正在调用一些第三方 API 来获取位置

https://localhost:3000/api/v1/locations 

这是在 json 中返回结果,例如

[{"location_id": 1,"name": "location1" ,"lat": "22.21","long": "-72.54"},{"location_id": 2,"name": "location2","lat": "45.21","long": "-74.21"}]

使用 Active Record,我们可以使用 Geocoder 应用距离过滤器

Location.near([lat, long], 100,units: :km)

但是 API 响应是 json 对象,如何使用 Geocoder 对 json 对象应用距离过滤器。

标签: ruby-on-railsruby-on-rails-6geocoderails-geocoder

解决方案


不幸的是,geocoder 只支持 ActiveRecord 和 MongoDB 作为存储后端,但不支持 JSON。使用 ActiveRecord / Mongo 地理编码器还可以利用数据库的查询语言(例如 SQL)来有效地查找点附近的位置。在您的情况下,您现在基本上有两种选择:

在 ActiveRecord 中存储 JSON 数据

您可以将 JSON 数据存储在数据库中并使用 ActiveRecord 查询数据。这里的 gem 官方 GitHub 页面上已经有一个很好的文档https://github.com/alexreisner/geocoder#geospatial-database-queries

创建类似 ActiveRecord 的对象

如果您不需要存储 API 请求中的数据,我建议您使用 Active Record,如易失性数据存储。

class Location
  attr_reader :id, :name, :lat, :long

  def initialize(id:, name:, lat:, long:)
    @id = id
    @name = name
    @lat = lat
    @long = long
  end

  def to_coordinates
    [lat.to_f, long.to_f]
  end

  class << self
    attr_accessor :locations

    def from_json(json)
      json.each do |location|
        locations << new(
          id: location[:location_id],
          name: location[:name],
          lat: location[:lat],
          long: location[:long]
        )
      end
    end

    def all
      locations
    end
  end

  self.locations = []
end

json = [
  {"location_id": 1, "name": "location1", "lat": "22.21", "long": "-72.54" },
  {"location_id": 2, "name": "location2", "lat": "45.21", "long": "-74.21" }
]

Location.from_json(json)
puts Location.all

我们现在“导入”您的 API JSON 响应并将其存储在一个数组中作为 Location 中的类属性。这还具有您使用对象而不是哈希/ JSON 的优点。

现在我们可以创建一个near类方法来选择靠近一个点的所有位置。

def near(lat, long, radius: 20, units: :km)
  all.select do |location|
    Geocoder::Calculations.distance_between(location, [lat, long], units: units) < radius
  end
end

这里的缺点是我们需要遍历所有位置并计算距离,因为我们没有 JSON 数据的查询语言。如果性能是一个问题,我建议使用 SQL 数据库(请参阅之前的建议)。

完整示例

require "geocoder"

class Location
  attr_reader :id, :name, :lat, :long

  def initialize(id:, name:, lat:, long:)
    @id = id
    @name = name
    @lat = lat
    @long = long
  end

  def to_coordinates
    [lat.to_f, long.to_f]
  end

  class << self
    attr_accessor :locations

    def from_json(json)
      json.each do |location|
        locations << new(
          id: location[:location_id],
          name: location[:name],
          lat: location[:lat],
          long: location[:long]
        )
      end
    end

    def all
      locations
    end

    def near(lat, long, radius: 20, units: :km)
      all.select do |location|
        Geocoder::Calculations.distance_between(location, [lat, long], units: units) < radius
      end
    end
  end

  self.locations = []
end

json = [
  {"location_id": 1, "name": "location1", "lat": "22.21", "long": "-72.54" },
  {"location_id": 2, "name": "location2", "lat": "45.21", "long": "-74.21" }
]

Location.from_json(json)

puts Location.near(45.21, -74.21)

推荐阅读