#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)
Cuando tengamos que trabajar con datos geográficos en nuestras aplicaciones Rails, la gema de Geocoder puede simplificar su tratamiento. Con esta gema se pueden convertir los topónimos a coordenadas y viceversa, e incluso es posible convertir direcciones IP a direcciones de calles. Puede también localizar lugares cercanos a una distancia dada, así como otras muchas prestaciones útiles.
En este episodio veremos una aplicación llamada Siteseer que permite a los usuarios recomendar lugares turísticos. Una vez que hayamos creado la aplicación lo siguiente que haremos es crear el andamiaje de Location
con los campos address
, latitude
, y longitude
.
$ rails g nifty:scaffold location address:string latitude:float longitude:float
El nombre de los campos latitude
y longitude
es importante porque Geocoder emplea dichos campos para guardar las coordenadas del lugar, si bien los nombres por defecto se pueden cambiar. Ambos campos tienen que ser de tipo float
. Para crear la tabla de lugares tenemos que migrar la base de datos.
$ rake db:migrate
Una vez que hemos terminado con el andamiaje podemos empezar a crear nuevos lugares pero es necesario introducir la latitud y longitud manualmente. Vamos a cambiar la aplicación para rellenar estos datos utilizando Geocoder.
Instalación y uso de Geocoder
Geocoder se instala de la manera habitual. Primero hay que añadir una referencia a la gema en el Gemfile
y luego se usa bundle
para instalarla.
source 'http://rubygems.org' gem 'rails', '3.0.9' gem 'sqlite3' gem 'nifty-generators' gem 'geocoder'
A continuación modificaremos el modelo Location
y añadiremos una llamada a geocoded_by
para decirle a Geocode qué atributo queremos que use, en este caso address
.
class Location < ActiveRecord::Base attr_accessible :address, :latitude, :longitude geocoded_by :address end
Aquí podemos poner un método en lugar de un campo por si nuestra dirección se encuentra almacenada en varios atributos y tenemos que componer de alguna manera la dirección completa.
Cuando se crea o actualiza un lugar, tenemos que invocar al método geocode
para que se realice el geoposicionamiento. Para hacer esto por lo general se utiliza el método after_validation
.
class Location < ActiveRecord::Base attr_accessible :address, :latitude, :longitude geocoded_by :address after_validation :geocode end
El método geocode
lanza una petición a una API externa, por defecto la API de Google Maps. Como se trata de una llamada a un servicio externo, lo mejor sería pasar esta llamada a un proceso en segundo plano, cosa que no haremos aquí pero para ver cómo se hace podemos echar un vistazo al episodio que acabamos de ver sobre Resque [verlo, leerlo].
Ya podemos arrancar nuestra aplicación y empezar a probar Geocoder. Cuando introduzcamos un nuevo lugar, por ejemplo “St Pancras Station, London” sin una latitud y longitud Geocoder recuperará los datos de la API externa y los pondrá en la base de datos.
Cada vez que actualicemos un lugar se invocará a la API externa pero esto sólo debería ocurrir si cambia el campo address
. Podemos hacer un cambio sencillo al método after_validation
para hacer esto.
class Location < ActiveRecord::Base attr_accessible :address, :latitude, :longitude geocoded_by :address after_validation :geocode, :if => :address_changed? end
Esta técnica utiliza el seguimiento de atributos sucios para determinar si la dirección ha cambiado y sólo llamará a la API si es así.
También podemos utilizar el método reverse_geocoded_by
para convertir una latitud y longitud a una dirección aproximada. Funciona de forma simular a geocoded_by
aunque no lo vamos a emplear en nuestra aplicación.
Cómo recuperar lugares próximos
Sería útil poder ver en la ficha de un lugar un listado con otros lugares cercanos. Como ya tenemos en nuestra base de datos varios lugares distintos podemos añadir esta funcionalidad en nuestra página de lugar.
Queremos recuperar todos los lugares que se encuentran a menos de una cierta distancia, y con Geocoder es fácil hacerlo gracias a su método nearbys
. Por defecto devuelve lugares a menos de veinte millas, pero podemos bajar este radio a diez. Tan sólo tenemos que iterar sobre @location.nearbys
y mostrar un enlace junto con la distancia en millas.
<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>
Cuando carguemos la página de un lugar ahora veremos los sitios más cercanos pero el sitio que tenemos dado de alta en Liverpool, a unas 200 millas, no aparece en el listado.
Añadamos a continuación una caja de búsqueda para poder buscar localizaciones dentro de una ciudad determinada. Podemos añadir esto con un nuevo formulario en la parte superior de la vista principal.
<% 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 -->
El formulario utiliza GET y cuando se envíe irá a la página index
con el parámetro search
. En la acción index
del controlador LocationsController
comprobaremos que si dicho parámetro existe buscaremos lugares más cercanos.
def index if params[:search].present? @locations = Location.near(params[:search], 50, :order => :distance) else @locations = Location.all end end
Si ahora recargamos la págia y buscamos “Manchester” veremos el sitio que tenemos a 30 millas en liverpool.
Esto es todo lo que tenemos que hacer para encontrar sitios cercanos a una determinadaciudad.
Inclusión de mapas
Como estamos trabajando con localizaciones geográficas sería muy útil mostrar en un mapa cada lugar. Añadiremos uno en la página de un lugar para ver exactamente su ubicación. La API de Google Maps permite añadir mapas a una web de varias maneras pero la más simple es la API de mapas estáticos. Los mapas se añaden con una sencilla etiqueta de imagen cuya URL tiene los parámetros necesarios para especificar la latitud, la longitud, el nivel de zoom, etc. Añadamos el mapa directamente sobre la lista de lugares próximos.
<%= image_tag "http://maps.google.com/maps/api/staticmap?size=450x300&sensor=false&zoom=16&markers=#{@location.latitude}%2C#{@location.longitude}" %>
Todos los parámetros que pasamos son estáticos con la excepción de la latitud y la longitud que vienen de nuestro objeto @location
. Con esto el mapa del sitio aparecerá en su página.
Podemos usar la gema de Google Maps para Rails si necesitamos algo más sofisticado que una imagen estática del mapa para poder interactuar mediante JavaScript. De esta forma será muy fácil añadir mapas interactivos en nuestras aplicaciones Rails.