Differences

This shows you the differences between two versions of the page.

Link to this comparison view

geocoder_complete_ruby_geocoding_solution_with_rails [2017/09/05 12:18] (current)
Line 1: Line 1:
 +====== Geocoder Complete Ruby geocoding solution with Rails ======
  
 +If you need to work with geographic data, Geocoder is an excellent gem for converting addresses and coordinates,​ finding nearby locations, determining distances, and more!
 +
 +Let's get a simple app to use as base to our Geocoder, so let's clone
 +
 +<sxh bash>
 +git clone https://​github.com/​douglasqsantos/​dqs-rails-base.git
 +</​sxh>​
 +
 +Now we need to access the base of the application.
 +<sxh bash>
 +cd dqs-rails-base
 +</​sxh>​
 +
 +Now we need to create a scaffold to use as base to our example
 +<sxh bash>
 +rails g scaffold location address:​string latitude:​float longitude:​float
 +</​sxh>​
 +
 +Now let's run the migration
 +<sxh bash>
 +rake db:migrate
 +</​sxh>​
 +
 +Now let's run the bundle
 +<sxh bash>
 +bundle install
 +</​sxh>​
 +
 +Now we can launch the server.
 +<sxh bash>
 +rails server
 +</​sxh>​
 +
 +Now we can access the locations at: http://​localhost:​3000/​locations
 +
 +Now let's add the geocoder gem into the Gemfile
 +<sxh bash>
 +vim Gemfile
 +[...]
 +gem '​geocoder'​
 +</​sxh>​
 +
 +Now let's get the geocoder gem
 +<sxh bash>
 +bundle install
 +</​sxh>​
 +
 +Now we need to configure the model to use the geocode
 +<sxh ruby>
 +vim app/​models/​location.rb
 +class Location < ActiveRecord::​Base
 +
 + # Getting the address coordinates after validation
 + # and store in latitude and longitude attributes
 + ​geocoded_by :address
 + ​after_validation :geocode, :if => :​address_changed?​
 +
 +end 
 +</​sxh>​
 +
 +Now let's test the geocode at: http://​localhost:​3000/​locations/​new
 +
 +Now let's add some locations
 +  - Statue of Liberty, NY
 +  - Empire State Building, New York
 +  - Golden Gate Bridge, San Francisco
 +  - Time Square, New York City, NY
 +
 +After create the Statue of Liberty, NY we will get something like:
 +
 +{{:​geocoderails-01.png?​800}}
 +
 +
 +Now let's use an built method of geocode that is used to get the nearby location.
 +
 +Let's add it into show.html.erb
 +<sxh xml>
 +vim app/​views/​locations/​show.html.erb
 +<p id="​notice"><​%= notice %></​p>​
 +
 +<p>
 +  <​strong>​Address:</​strong>​
 +  <%= @location.address %>
 +</p>
 +
 +<p>
 +  <​strong>​Latitude:</​strong>​
 +  <%= @location.latitude %>
 +</p>
 +
 +<p>
 +  <​strong>​Longitude:</​strong>​
 +  <%= @location.longitude %>
 +</p>
 +
 +<​h3>​Nearby locations</​h3>​
 +
 +<ul>
 +<% for location in @location.nearbys(10) %>
 +        <​li><​%= link_to location.address,​ location %> (<%= location.distance.round(2) %> miles) (<%= ((location.distance).to_f / 0.62137).round(2) %> kms) </li>
 +<% end %>
 +</ul>
 +
 +<%= link_to '​Edit',​ edit_location_path(@location) %> |
 +<%= link_to '​Back',​ locations_path %>
 +</​sxh>​
 +
 +Now let's take a look at show of  Empire State Building, New York we'll get something like:
 +
 +{{:​geocoderails-02.png?​800}}
 +
 +Now let's add an image of the location we are showing into the show.html.erb
 +<sxh xml>
 +vim app/​views/​locations/​show.html.erb ​
 +<p id="​notice"><​%= notice %></​p>​
 +
 +<p>
 +  <​strong>​Address:</​strong>​
 +  <%= @location.address %>
 +</p>
 +
 +<p>
 +  <​strong>​Latitude:</​strong>​
 +  <%= @location.latitude %>
 +</p>
 +
 +<p>
 +  <​strong>​Longitude:</​strong>​
 +  <%= @location.longitude %>
 +</p>
 +
 +<%= image_tag "​http://​maps.google.com/​maps/​api/​staticmap?​size=450x300&​sensor=false&​zoom=16&​markers=#​{@location.latitude}%2C#​{@location.longitude}"​ %>
 +
 +<​h3>​Nearby locations</​h3>​
 +
 +<ul>
 +<% for location in @location.nearbys(10) %>
 +        <​li><​%= link_to location.address,​ location %> (<%= location.distance.round(2) %> miles) (<%= ((location.distance).to_f / 0.62137).round(2) %> kms) </li>
 +<% end %>
 +</ul>
 +
 +<%= link_to '​Edit',​ edit_location_path(@location) %> |
 +<%= link_to '​Back',​ locations_path %>
 +</​sxh>​
 +
 +Now let's take a look at: Empire State Building, New York into show, we'll get something like
 +
 +{{:​geocoderails-03.png?​800}}
 +
 +Now if you need another kind of map you need to take a look at: https://​developers.google.com/​maps/​documentation/​
 +
 +If you want to use an hybrid map we can use as:
 +<sxh xml>
 +vim app/​views/​locations/​show.html.erb ​
 +<p id="​notice"><​%= notice %></​p>​
 +
 +<p>
 +  <​strong>​Address:</​strong>​
 +  <%= @location.address %>
 +</p>
 +
 +<p>
 +  <​strong>​Latitude:</​strong>​
 +  <%= @location.latitude %>
 +</p>
 +
 +<p>
 +  <​strong>​Longitude:</​strong>​
 +  <%= @location.longitude %>
 +</p>
 +
 +<%= image_tag "​https://​maps.googleapis.com/​maps/​api/​staticmap?​maptype=hybrid&​center=#​{@location.latitude},#​{@location.longitude}&​zoom=16&​size=450x300&​markers=color:​blue%7Clabel:​S%7C#​{@location.latitude},#​{@location.longitude}&​key=PUT_YOUR_KEY_HERE"​ %>
 +
 +<​h3>​Nearby locations</​h3>​
 +
 +<ul>
 +<% for location in @location.nearbys(10) %>
 +        <​li><​%= link_to location.address,​ location %> (<%= location.distance.round(2) %> miles) (<%= ((location.distance).to_f / 0.62137).round(2) %> kms) </li>
 +<% end %>
 +</ul>
 +
 +<%= link_to '​Edit',​ edit_location_path(@location) %> |
 +<%= link_to '​Back',​ locations_path %>
 +</​sxh>​
 +
 +We shall get something like: 
 +
 +{{:​geocoderails-04.png?​800}}
 +
 +Now let's create a search box into index.html.erb to search for a specific address and get the nearby locations of that.
 +<sxh xml>
 +vim app/​views/​locations/​index.html.erb
 +<p id="​notice"><​%= notice %></​p>​
 +
 +<%= form_tag locations_path,​ :method => :get do %>
 +  <p>
 +    <%= text_field_tag :search, params[:​search] %>
 +    <%= submit_tag "​Search Near", :name => nil %>
 +  </p>
 +<% end %>
 +
 +<​h1>​Listing Locations</​h1>​
 +
 +<​table>​
 +  <​thead>​
 +    <tr>
 +      <​th>​Address</​th>​
 +      <​th>​Latitude</​th>​
 +      <​th>​Longitude</​th>​
 +      <th colspan="​3"></​th>​
 +    </tr>
 +  </​thead>​
 +
 +  <​tbody>​
 +    <% @locations.each do |location| %>
 +      <tr>
 +        <​td><​%= location.address %></​td>​
 +        <​td><​%= location.latitude %></​td>​
 +        <​td><​%= location.longitude %></​td>​
 +        <​td><​%= link_to '​Show',​ location %></​td>​
 +        <​td><​%= link_to '​Edit',​ edit_location_path(location) %></​td>​
 +        <​td><​%= link_to '​Destroy',​ location, method: :delete, data: { confirm: 'Are you sure?' } %></​td>​
 +      </tr>
 +    <% end %>
 +  </​tbody>​
 +</​table>​
 +
 +<br>
 +
 +<%= link_to 'New Location',​ new_location_path %>
 +</​sxh>​
 +
 +Now we need to configure the controller to get the information sent by the search box and process it and return the results.
 +<sxh xml>
 +vim app/​controllers/​locations_controller.rb
 +class LocationsController < ApplicationController
 +  before_action :​set_location,​ only: [:show, :edit, :update, :destroy]
 +
 +  # GET /locations
 +  # GET /​locations.json
 +  def index
 +    if params[:​search].present?​
 +      # Get the nearby locations, here we are searching for nearby locations
 +      # close 50 miles away.
 +      @locations = Location.near(params[:​search],​ 50, :order => "​distance"​)
 +    else
 +      @locations = Location.all
 +    end
 +  end
 +</​sxh>​
 +
 +Now if we make a search about: Central Park, NY we will get something like:
 +
 +{{:​geocoderails-05.png?​800}}
 +
 +Now let's improve the layout of the locations.
 +
 +Now let's remove the scaffolds.scss
 +<sxh bash>
 +rm -rf app/​assets/​stylesheets/​scaffolds.scss
 +</​sxh>​
 +
 +Now let's reconfigure the index.html.erb
 +<sxh xml>
 +vim app/​views/​locations/​index.html.erb
 +<ol class="​breadcrumb">​
 +  <​li><​%= link_to('​Locations',​ {:​controller => '​locations',​ :action => '​index'​}) %></​li>​
 +  <li class="​active">​Index</​li>​
 +</ol>
 +
 +
 +<%= form_tag locations_path,​ :class => '​form-inline',​ :method => :get do %>
 +  <div class="​form-group">​
 +    <%= text_field_tag :search, params[:​search],​ :class => '​form-control'​ %>
 +    <%= submit_tag('​Search Near', :class => 'btn btn-primary'​) %>
 +    <%= link_to("​Show All", {:​controller => '​locations',​ :action => '​index'​},​ :class => 'btn btn-primary'​) %>
 +  </​div>​
 +<% end %>
 +
 +<div class="​table-responsive">​
 +<​h2>​Listing Locations</​h2>​
 +
 +  <%= link_to("​Add New Location",​ {:action => '​new'​},​ :class => 'btn btn-primary'​) %>
 +  <br>
 +  <br>
 +
 +  <​div><​%= pluralize(@locations.size,​ '​Locatio'​) %> found </​div>​
 +  <table class="​table table-striped table-hover table-bordered"​ summary="​Admin User list">​
 +    <thead class="​tables-header">​
 +    <tr>
 +      <​th>​Address</​th>​
 +      <​th>​Latitude</​th>​
 +      <​th>​Longitude</​th>​
 +      <th colspan="​3"​ class="​text-center">​Actions</​th>​
 +    </tr>
 +  </​thead>​
 +
 +  <​tbody>​
 +    <% @locations.each do |location| %>
 +      <tr>
 +        <​td><​%= location.address %></​td>​
 +        <​td><​%= location.latitude %></​td>​
 +        <​td><​%= location.longitude %></​td>​
 +        <​td><​%= link_to '',​ location, :class => '​glyphicon glyphicon-eye-open'​ %></​td>​
 +        <​td><​%= link_to '',​ edit_location_path(location),:​class => '​glyphicon glyphicon-pencil'​ %></​td>​
 +        <​td><​%= link_to '',​ location,:​class => '​glyphicon glyphicon-trash'​ , method: :delete, data: { confirm: 'Are you sure?' } %></​td>​
 +      </tr>
 +    <% end %>
 +  </​tbody>​
 +</​table>​
 +</​sxh>​
 +
 +We'll something like this into index:
 +
 +{{:​geocoderails-06.png?​800}}
 +
 +Now let's reconfigure the show.html.erb
 +<sxh xml>
 +vim app/​views/​locations/​show.html.erb
 +<ol class="​breadcrumb">​
 +  <​li><​%= link_to('​Locations',​ {:​controller => '​locations',​ :action => '​index'​}) %></​li>​
 +  <li class="​active">​Show</​li>​
 +</ol>
 +<​h2>​Show Location</​h2>​
 +<div class="​row">​
 +  <div class="​col-md-6 col-md-offset-3">​
 +    <table class="​table table-striped table-hover table-bordered">​
 +      <tr>
 +        <th class="​tables-header">​Address</​th>​
 +        <​td><​%= @location.address %></​td>​
 +      </tr>
 +      <tr>
 +        <th class="​tables-header">​Latitude</​th>​
 +        <​td><​%= @location.latitude %></​td>​
 +      </tr>
 +      <tr>
 +        <th class="​tables-header">​Longitude</​th>​
 +        <​td><​%= @location.longitude %></​td>​
 +      </tr>
 +    </​table>​
 +
 +    <!-- Image of the location -->
 +    <%= image_tag "​http://​maps.google.com/​maps/​api/​staticmap?​size=450x300&​sensor=false&​zoom=16&​markers=#​{@location.latitude}%2C#​{@location.longitude}"​ %>
 +
 +  <!-- Nearby Locations ​ -->
 +  <​h3>​Nearby locations</​h3>​
 +
 +  <ul>
 +  <% for location in @location.nearbys(10) %>
 +    <​li><​%= link_to location.address,​ location %> (<%= location.distance.round(2) %> miles) (<%= ((location.distance).to_f / 0.62137).round(2) %> kms) </li>
 +  <% end %>
 +  </ul>
 +
 +  <!-- Buttons -->
 +  <%= link_to '​Back',​ locations_path ,:class => 'btn btn-primary'​ %> 
 +  <%= link_to '​Edit',​ edit_location_path(@location),:​class => 'btn btn-primary'​ %> 
 +
 +  </​div>​
 +</​div>​
 +</​sxh>​
 +
 +We'll something like this into show:
 +
 +{{:​geocoderails-07.png?​800}}
 +
 +Now let's change the partial form
 +<sxh xml>
 +vim app/​views/​locations/​_form.html.erb ​
 +<%= form_for(@location) do |f| %>
 +  <% if @location.errors.any?​ %>
 +    <div id="​error_explanation">​
 +      <​h2><​%= pluralize(@location.errors.count,​ "​error"​) %> prohibited this location from being saved:</​h2>​
 +
 +      <ul>
 +      <% @location.errors.full_messages.each do |message| %>
 +        <​li><​%= message %></​li>​
 +      <% end %>
 +      </ul>
 +    </​div>​
 +  <% end %>
 +  <div class="​row">​
 +  <div class="​col-md-6 col-md-offset-3">​
 +
 +    <div class="​form-group">​
 +    <%= f.label :address %><​br>​
 +    <%= f.text_field :address, :class => '​form-control'​ %>
 +  </​div>​
 +    <div class="​form-group">​
 +    <%= f.label :latitude %><​br>​
 +    <%= f.text_field :latitude, :class => '​form-control'​ %>
 +  </​div>​
 +    <div class="​form-group">​
 +    <%= f.label :longitude %><​br>​
 +    <%= f.text_field :longitude , :class => '​form-control'​ %>
 +  </​div>​
 +    <div class="​form-buttons">​
 +    <%= f.submit :class => 'btn btn-primary'​ %>
 +  </​div>​
 +<% end %>
 +</​sxh>​
 +
 +Now let's configure the new.html.erb
 +<sxh xml>
 +vim app/​views/​locations/​new.html.erb ​
 +<ol class="​breadcrumb">​
 +  <​li><​%= link_to('​Locations',​ {:​controller => '​locations',​ :action => '​index'​}) %></​li>​
 +  <li class="​active">​New</​li>​
 +</ol>
 +
 +<​h2>​New Location</​h2>​
 +
 +<%= render '​form'​ %>
 +<%= link_to '​Back',​ locations_path ,:class => 'btn btn-primary'​ %> 
 +
 +  <!-- Close the divs from the form -->
 +  </​div>​
 +</​div>​
 +
 +</​sxh>​
 +
 +Now let's change the last one edit.html.erb
 +<sxh xml>
 +vim app/​views/​locations/​edit.html.erb ​
 +<ol class="​breadcrumb">​
 +  <​li><​%= link_to('​Locations',​ {:​controller => '​locations',​ :action => '​index'​}) %></​li>​
 +  <li class="​active">​Edit</​li>​
 +</ol>
 +
 +<​h2>​Editing Location</​h2>​
 +
 +<%= render '​form'​ %>
 +
 +  <!-- Buttons -->
 +  <%= link_to '​Back',​ locations_path ,:class => 'btn btn-primary'​ %> 
 +  <%= link_to '​Show',​ @location,:​class => 'btn btn-primary'​ %> 
 +
 +<!-- Close the divs from the form -->
 +  </​div>​
 +</​div>​
 +</​sxh>​
 +
 +Now let's take a look at new location:
 +
 +{{:​geocoderails-08.png?​800}}
 +
 +Now let's take a look at edit location:
 +
 +{{:​geocoderails-09.png?​800}}
 +
 +
 +
 +====== References ======
 +  - http://​www.rubygeocoder.com/​
 +  - https://​github.com/​alexreisner/​geocoder
 +  - http://​railscasts.com/​episodes/​273-geocoder
 +  - https://​developers.google.com/​maps/​documentation/​