#273 Geocoder
- Download:
- source codeProject Files in Zip (195 KB)
- mp4Full Size H.264 Video (18.6 MB)
- m4vSmaller H.264 Video (9.43 MB)
- webmFull Size VP8 Video (9.95 MB)
- ogvFull Size Theora Video (22.6 MB)
Si vous devez travailler avec des données géographique dans vos applications Rails, la gem Geocoder rend cela très facile. Elle peut convertir un nom de lieu en coordonnées ou l'inverse. Elle peut même transformer des IP en adresses ou trouver les lieux proches d'un point avec leur distance ainsi que leur pertinence. La gem contient bon nombre d'autres fonctionnalités.
Dans cet épisode, nous allons créer une application appelée Siteseer qui permettra au utilisateurs de recommander des lieux touristiques. Une fois l'application créée, nous devons ajouter un scaffold pour le modèle Location
(Lieu) contenant les champs address
, latitude
et longitude
.
$ rails g nifty:scaffold location adress:string latitude:float longitude:float
Les noms des champs latitude
et longitude
sont importants. Geocoder va les utiliser pour stocker les coordonnées du lieu. Les noms par défaut peuvent cependant être surchargés. Les deux champs doivent être des floats
. Migrons la base pour ajouter la table locations.
$ rake db:migrate
Maintenant que le scaffold est en place, nous pouvons créer des lieux mais nous devons saisir à la main la latitude et la longitude. Nous allons maintenant mettre à jour notre application de façon à ce que ces champs soient remplis automatiquement par Geocoder.
Installer et Utiliser Geocoder
Geocoder s'installe de la manière habituelle. Nous ajoutons d'abord une référence à la gem dans le Gemfile
et nous appelons bundle
pour l'installer.
source 'http://rubygems.org' gem 'rails', '3.0.9' gem 'sqlite3' gem 'nifty-generators' gem 'geocoder'
Nous modifions ensuite notre modèle Location
et ajoutons un appel à geocoded_by
pour préciser les attributs que nous voulons voir convertis par Geocoder, dans notre cas, le champ address
.
class Location < ActiveRecord::Base attr_accessible :address, :latitude, :longitude geocoded_by :address end
Si l'adresse est stockée dans plusieurs champs, nous pouvons spécifier une méthode et écrire cette méthode pour qu'elle retourne l'adresse complète.
Quand un lieu est créé ou mis à jour, nous devons appeler la méthode geocode
qui effectue le géocodage. Il est d'usage d'utiliser le callback after_validation
.
class Location < ActiveRecord::Base attr_accessible :address, :latitude, :longitude geocoded_by :address after_validation :geocode end
La méthode geocode envoie une requête à une API externe, l'API Google Maps par défaut. Comme c'est un appel à un service externe, il serait préférable de le mettre dans un processus en arrière plan. Nous n'allons pas le faire ici mais pour savoir comment faire, jetez un œil à l'épisode récemment passé sur Resque [regarder, lire].
Nous pouvons maintenant lancer notre application et tester Geocoder. Lorsque nous saisissons un nouveau lieu, disons “St Pancras Station, London”, sans latitude ni longitude, Geocoder va récupérer ces informations de l'API externe et les ajouter au lieu.
L'API externe sera appelée chaque fois que nous mettons le lieu à jour mais cela devrait être fait uniquement quand le champ address
change. Nous pouvons apporter une petite modification au callback after_validation
pour implémenter ceci.
class Location < ActiveRecord::Base attr_accessible :address, :latitude, :longitude geocoded_by :address after_validation :geocode, :if => :address_changed? end
Cela va utiliser le dirty tracking pour déterminer si l'adresse a changé et appeler l'API dans ce cas.
Nous pouvons également utiliser la méthode reverse_geocoded_by
pour convertir des coordonnées en adresse. Cela fonctionne de manière similaire à la méthode geocoded_by
et nous ne l'utiliserons pas dans notre application.
Obtenir les lieux à proximité
Il peut être utile, lorsque l'on consulte un lieu, d'être capable de lister ceux qui sont à proximité. Nous avons un certain nombre de lieux dans notre base maintenant. Nous allons donc ajouter cette fonctionnalité à notre application.
Nous voulons lister tous les lieux dans un périmètre donné et Geocoder facilite l'obtention de cette liste grâce à la méthode nearbys
. Par défaut, elle va retourner la liste des lieux dans un rayon de vingt miles (environ trente-deux kilomètres), nous allons restreindre ce dernier à dix. Pour lister les lieux à proximité, nous allons boucler sur chaque lieu retourné par @location.nearbys
et afficher un lien vers celui-ci ainsi que sa distance en miles.
<ul> <% for location in @location.nearbys(10) %> <li><%= link_to location.address, location %> (<%= location.distance.round(2) %> miles)</li> <% end %> </ul<% title "Location" %> <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)</li> <% end %> </ul> <p> <%= link_to "Edit", edit_location_path(@location) %> | <%= link_to "Destroy", @location, :confirm => 'Are you sure?', :method => :delete %> | <%= link_to "View All", locations_path %> </p>
Lorsque nous consultons un lieu, nous voyons maintenant la liste des lieux à proximité excepté notre lieu à Liverpool, situé à 200 miles.
Nous allons ensuite ajouter une boite de recherche sur la page des lieux pour que nous puissions chercher un lieu dans une ville donnée. Nous allons ajouter ceci dans un nouveau formulaire situé en haut de la vue index.
<% title "Locations" %> <%= form_tag locations_path, :method => :get do %> <p> <%= text_field_tag :search, params[:search] %> <%= submit_tag "Search Near", :name => nil %> <% end %> <!-- rest of page -->
Le formulaire utilise GET et va appeler la page index
avec un paramètre search
lorsqu'il est soumis. Dans l'action index
de LocationsController
, nous allons vérifier que le paramètre search
est présent et, dans ce cas, chercher les lieux à proximité.
def index if params[:search].present? @locations = Location.near(params[:search], 50, :order => :distance) else @locations = Location.all end end
Si nous rechargeons la page et cherchons “Manchester”, nous allons voir un résultat, le lieu à Liverpool, situé à trente miles.
C'est tout ce dont nous avons besoin pour trouver les lieux proches d'une ville donnée.
Ajouter des cartes
Puisque nous travaillons avec les coordonnées géographiques, il serait pratique d'avoir une carte pour chaque lieu. Nous allons en ajouter une sur la page d'un lieu pour montrer exactement où ce dernier se situe. L'API Google Maps fournit plusieurs manières d'ajouter des cartes à une page web mais, par simplicité, nous allons utiliser l'API des Cartes Statiques. Les cartes sont ajoutées grâce à une simple balise image dont l'URL contient des paramètres pour spécifier les différents aspects de la carte, y compris les coordonnées, la taille, le niveau de zoom, etc. Nous allons placer la carte juste au dessus de la liste des lieux à proximité.
<%= image_tag "http://maps.google.com/maps/api/staticmap?size=450x300&sensor=false&zoom=16&markers=#{@location.latitude}%2C#{@location.longitude}" %>
Tous les paramètres que nous passons sont statique excepté pour les coordonnées qui proviennent de l'objet @location
. Lorsque nous visitons la page d'un lieu, nous voyons maintenant la carte de ce lieu.
Pour quelque chose de plus poussé que des images statiques, cela vaut la peine de jeter un œil à la gem Google Maps For Rails qui rend facile l'interaction avec l'API JavaScript. Cela vous donne un moyen simple d'ajouter des cartes interactives à vos applications Rails.