DEV Community

Cover image for gem Geocoder - calculate coordinates, distances, search nearby
Yaroslav Shmarov
Yaroslav Shmarov

Posted on • Originally published at blog.corsego.com on

gem Geocoder - calculate coordinates, distances, search nearby

Geocoder gem allows you to preform different operations with coordinates.

Useful usage examples:

  • find latitude and longitude coordinates by address,
  • address by lat-lon coordinates,
  • find all Locations within a square (within_bounding_box)
  • find the distance between two Locations (distance_from)
  • find all Locations around X coordinates (near)
  • find all Locations around X location (nearbys)

Afterwards, when you have the coordinates of a location, you can use a separate Maps API to display them on a map.

Basic geocoder usage

Geocoder gem does a search via a Places API, and returns coordinates. The more detailed address you search for, the more precise the coordinates you will receive.

bundle add geocoder

# search
ua = Geocoder.search('Kyiv')
ua.first.coordinates
# => [50.4500336, 30.5241361] # latitude and longitude
ua.first.country
# => 'Ukraine'

fr = Geocoder.search('Notre dame cathedral paris')
fr.first.city
# => 'Paris'

# geographic_center
Geocoder::Calculations.geographic_center([ua.first.coordinates, fr.first.coordinates])
# => [50.51223060957045, 16.201193185230583]
Enter fullscreen mode Exit fullscreen mode

Debug current HTTP request

You can get the current web requests country/ip/etc.

You can use it, for example, to geoblock countries like Ruzzia

# controller or view
request.location
request.location
request.location.try(:country)
# request.ip
Enter fullscreen mode Exit fullscreen mode

Geocode a Rails model

Storing the address as a single string looks like a simple straightforward solution, however storing each address detail separately gives you more power.

A usual address on Google Maps has the sequence street, city, state, country, zip.

Scaffold your location model:

rails g scaffold Location latitude:float:index longitude:float:index street city state country zip
rails g scaffold Location latitude:float:index longitude:float:index address
rails db:migrate
Enter fullscreen mode Exit fullscreen mode

Geocoder will automatically perform a search and find the latitude and longitude of your location.

To save compute power and API thresholds, it makes sence to geocode only if the address has changed.

Basic (one address field):

# app/models/location.rb
  geocoded_by :address   
  after_validation :geocode, if: :address_changed?
  # after_validation :geocode, if: ->(obj){ obj.address.present? and obj.address_changed? }
Enter fullscreen mode Exit fullscreen mode

Advanced (multiple address fields):

# app/models/location.rb
  geocoded_by :address   
  after_validation :geocode, if: :address_changed? 

  def address 
    [street, city, state, country, zip].compact.join(', ')
  end

  private

  def address_changed?
    country_changed? ||
      state_changed? 
      city_changed? ||
      street_changed? ||
      zip_changed? ||
  end 
Enter fullscreen mode Exit fullscreen mode

DEMO DATA : seeds with a few real hotels in France

# db/seeds.rb
name = "Hôtel Martinez - The Unbound Collection by Hyatt"
address = "73 Bd de la Croisette, 06400 Cannes"
Location.create(name:, address:)

name = "Exclusive Hotel Belle Plage"
address = "2 Rue Brougham, 06400 Cannes"
Location.create(name:, address:)

name = "Best Western Premier Le Patio des Artistes - Cannes"
address = "6 Rue de Bône, 06400 Cannes"
Location.create(name:, address:)

name = "Le Negresco"
address = "37 Prom. des Anglais, 06000 Nice"
Location.create(name:, address:)

name = "Caesars Palace"
address = "3570 S Las Vegas Blvd, Las Vegas, NV 89109, United States"
Location.create(name:, address:)
Enter fullscreen mode Exit fullscreen mode

Now you can find call geocoder methods on the model.

# geocode a single record:
address = Address.first
address.geocode
address.save

# geocode all:
Location.all.each { |location| location.geocode && location.save }

Location.geocoded
# => return objects with coordinates
Location.not_geocoded
# => return objects without coordinates

Location.first.to_coordinates
# => [51.51436195, 31.31593525714063]

Location.first.nearbys(20)
Location.first.nearbys(20, units: :km)
# => array of locations within 20 km of coordinates, excluding selected location
# ! useful to show "similar" or "nearby" feature

Location.near(Location.first, 20, units: :km, order: :distance)
Location.near('Omaha, NE, US', 20)
# => all locations within 20 km of coordinates
# ! useful for "find all next to...." feature

Location.first.distance_from(Location.second)
Location.first.distance_to(Location.second) # same as above
Location.first.distance_form([40.714,-100.234])
# => 1.8493403104012456

# all locations within square
sw_corner = [40.71, 100.23] 
ne_corner = [36.12, 88.65]
Location.within_bounding_box(sw_corner, ne_corner)
Enter fullscreen mode Exit fullscreen mode

Search locations near

Having a search for for place and distance, you can find relevant Locations. This can be a vital feature when building a website like AirBnB or Booking.com.

Example query in human words: Find all locations within 10km distance from Chernihiv, Ukraine

# app/controllers/locations_controller.rb
class LocationsController < ApplicationController
  def index
    if params[:place].present?
      @locations = Location.near(params[:place], params[:distance] || 10, order: :distance)
    else
      @locations = Location.all
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

add a search form within a view:

<%= form_with url: locations_path, method: :get do |form| %>
  <%= form.label :place, "City, Country" %>
  <%= form.text_field :place, value: params[:place] %>

  <%= label :distance, "Distance, km" %>
  <%= text_field_tag :distance, [10, 20, 30], params[:distance] %>

  <%= form.submit "Search" %>
<% end %>
Enter fullscreen mode Exit fullscreen mode

Display coordinates on static map

To display a market on a static image map, you would need to connect a places API.

There are a few options for using Places API:

From the above, I’ve tried only Mapbox. As long as you receive a Mapbox API key, you can display the map with an image_tag:

<%= image_tag "https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/pin-s+ff2700(#{location.longitude},#{location.latitude})/#{location.longitude},#{location.latitude},13,0/300x200?access_token=#{Rails.application.credentials.dig(:mapbox_key)}" %>
Enter fullscreen mode Exit fullscreen mode

There’s much more that we can do with coordinates. In the future I hope to explore:

  • gem Mapkick for displaying multiple Locations on a responsive map
  • different Geocoding API adapters (like Amazon Location API)
  • get browser location with Javascript

That’s it for now.

Top comments (0)