Geocoder

28 Nov 2018

Geocoder is an awesome gem, it handles the complexity of finding a location from apis such as google, bing, maxmind etc. and it’s so easy to setup. Let’s say we have an api that requests a clients, latitude and longitude. Based on their country, we want to show different questions.

For information about how to setup geocoder, configure it to use google, please refer to their docs.

So let’s dive right in, we are reverse geocoding the location from the latitude and longitude given. Geocoder gem automatically fetches the location and stores it in the address attribute for example.

# app/models/place.rb
reverse_geocoded_by :latitude, :longitude,
  :address => :location
after_validation :reverse_geocode

When setting up geocoder, for speed, stability we set up a cache. Making continuous calls to a third party service is expensive. We can also set the TTL, in the cache to store items for a long time.

Geocoder.configure(cache: Redis.new)

When making a request for a location, Geocoder firstly checks the cache to see if the requested url exists (entire url is stored in the key), if so pull from cache, if not go ahead and make the request then store the entire response in the cache value, finally return the results.

# Search for location using co-ordinates
results = ::Geocoder.search(48.856614, 2.3522219)
results.first.country # => France
# url => http://maps.googleapis.com/maps/api/geocode/json?language=en&latlng=48.856614%2C2.3522219&sensor=false

Making calls to the google api is very expensive, it can cost a lot. Take a look at a few more different request, note that each request made fires off an api call to google, which is subsequently stored in the cache.

# Note the longitude has changed from 2.3522219 to 2.3522218
# which fires another response
results = ::Geocoder.search(48.856614, 2.3522218)
results.first.country # => France
# url => # url => http://maps.googleapis.com/maps/api/geocode/json?language=en&latlng=48.856614%2C2.3522218&sensor=false

# Take one digit off the latitude and longitude means another api request, still same country
results = ::Geocoder.search(48.85661, 2.352221)
results.first.country # => France
# url => # url => http://maps.googleapis.com/maps/api/geocode/json?language=en&latlng=48.85661%2C2.3522218&sensor=false

Our requirement is to only check clients country, how can we do this? Take off some decimal places right? How many?

# Client request can come in as any of the below and we will round to 1 decimal place
# client 1: 48.85661 to 2.3522218
# client 2: 48.85669 to 2.3522218
# client 3: 48.8566 to 2.3522211
# client 4: 48.85662 to 2.3522218
# client 5: 48.85660 to 2.3522212
coordinates = round_location_request(latitude, longitude) # => [48.8, 2.3]
results = ::Geocoder.search(coordinates)
results.first.country # => France

Now clients making a request, their latitude and longitude will be rounded to 1 decimal place. When we hit the cache for subsequent requests, users within the same city as original request, hits the cache. Money saved, cache hits will go up drastically and we can increase the TTL to something like 30 days.

We can decrease the decimal places to suits our requirements, to be extra cautious my code is getting the correct city I have opted for 1 decimal place. Here is a link to wiki article, showing how much rounding that can be knocked off the latitude/longitude, which I used as a reference.

https://en.wikipedia.org/wiki/Decimal_degrees#Accuracy