Geocoder Methods on Json Array in rails

171 Views Asked by At

I am calling some third party API to get locations

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

this is returning result in json like

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

With Active Record we can apply distance filter using Geocoder

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

But API response is json object ,How can I apply distance filter on json object using Geocoder.

1

There are 1 best solutions below

0
Christian Bruckmayer On

Unfortunately geocoder does only support ActiveRecord and MongoDB as storage backends but not JSON. With ActiveRecord / Mongo geocoder can also leverage the query language of the database (e.g. SQL) to efficiently find locations near a point. In your case you have now basically two options:

Store JSON data in ActiveRecord

You could store your JSON data in a database and use ActiveRecord to query the data. There is already a good documentation for this on the gem official GitHub page here https://github.com/alexreisner/geocoder#geospatial-database-queries.

Create ActiveRecord like object

If you don't need to store the data from your API request I would recommend to use an Active Record like volatile data storage.

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

We now 'import' your API JSON response and store it in an array as a class attribute in Location. This also has the advantage that you work with objects and not with hashes / JSON anymore.

Now we can create a near class method to select all locations close to one point.

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

The disadvantage here is that we need to iterate over all locations and calculate the distance as we don't have a query language for the JSON data. If performance is an issue, I would suggest to use an SQL database (see previous suggestion).

Full example

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)