#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)
Если вам приходится обрабатывать географические данные в Rails приложении, то Geocoder гем может упросить задачу в разы. Он может конвертировать названия мест в координаты и наоборот, и даже конвертировать IP адрес в почтовый. Он также позволяет находить места поблизости в зависимости от расстояния и направления, и имеет еще много полезных фич.
В этом выпуске мы создадим приложение Siteseer, которое даст возможность людям рассказывать о туристических местах. После того, как мы создали приложение, создадим Location
скафолд с полями под адрес, широту и долготу.
$ rails g nifty:scaffold location address:string latitude:float longitude:float
Поля latitude
(широта) и longitude
(долгота) важно назвать именно так, потому что Geocoder будет их использовать для хранения координат местоположения, хотя значения по умолчанию могут быть переопределены. Оба поля должны быть floats
для координат, которые представляют собой числа с плавающей точкой. Запускаем выполнение миграции и создаем новую таблицу для местоположений.
$ rake db:migrate
После этого, на руках имеем развернутый скафолд, который позволяет создавать точки местоположения, только широту и долготу приходится вбивать ручками. Через пару минут мы обновим наше приложение таким образом, что поля будут заполняться автоматически Geocoder'ом.
Установка и использование Geocoder
Установка Geocoder производится как обычно. Сначала мы указываем на гем в Gemfile
, после выполняем команду bundle
для установки гема.
source 'http://rubygems.org' gem 'rails', '3.0.9' gem 'sqlite3' gem 'nifty-generators' gem 'geocoder'
Теперь, внесем изменения в модель Location
и добавим метод geocoded_by
, чтобы определить аттрибут, по чему нам хочется, чтобы Geocoder конвертировал, в данном случае - по адресу (address
).
class Location < ActiveRecord::Base attr_accessible :address, :latitude, :longitude geocoded_by :address end
Если наш адрес хранится в нескольких кусками в нескольких полях, то можно написать метод, который будет возвращать полный адрес.
Нам нужно вызывать метод geocode
, который выполняет геокодирование, во время создания или обновления местоположения. Обычно для таких целей используется колбэк after_validation
.
class Location < ActiveRecord::Base attr_accessible :address, :latitude, :longitude geocoded_by :address after_validation :geocode end
Геокодирование использует внешний API (по умолчанию это Google Maps API), а следовательно, отправляет туда запросы. Поэтому, лучше всего помещать этот вызов в фоновый режим. Этого в данном случае мы делать не будем, но узнать, как это сделать можно в выпуске Resque [смотреть, читать].
Ну что же, опробуем Geocoder. Запускаем наше приложение. Когда мы вводим новое местоположение, скажем “St Pancras Station, London”, без указания широты и долготы, Geocoder получает эти данные через внешний API и добавляет информацию в местоположение.
Обращение к внешнему API будет происходить при каждом обновлении местоположения, но на самом деле это нужно только при смене адреса, если поле address
меняется. Сделаем небольшое изменение в колбэке after_validation
, чтобы исправить недоразумение.
class Location < ActiveRecord::Base attr_accessible :address, :latitude, :longitude geocoded_by :address after_validation :geocode, :if => :address_changed? end
Таким образом, мы отследим и определим момент, когда адрес изменится и только в этом случае будет выполнено обращение к API.
Можно было бы использовать и reverse_geocoded_by
, метод, который конвертирует широту и долготу в адрес. Работает он таким же образом, как и метод geocoded_by
, но мы не будем использовать его в нашем приложении.
Места поблизости
Было бы здорово, во время просмотра какого-то местоположения, видеть еще и список мест поблизости. В нашей базе уже много различных мест, так что добавим эту фичу прямо на страничку местоположения.
Мы хотим отобразить список всех мест на заданном расстоянии, и это лего сделать с помощью метода nearbys
гема Geocoder. По умолчанию, возвратятся места в радиусе 20 миль (~32,2км), но мы ограничимся десятью (~16,1км). Чтобы вывести список, мы проходимся по каждому местоположению из @location.nearbys
и выводим ссылку на него вместе с расстоянием в милях.
<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>
Теперь во время просмотра места мы видим еще и список мест поблизости, но местоположение, которое находится в Ливерпуле, за 200 миль от Гринвической обсерватории, в списке не отобразится.
Следующим шагом добавим строку поиска на страничку местоположений, чтобы можно было искать различные места в указанном городе. Сделаем новую форму наверху вида 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 -->
Форма использует GET запрос и будет получать index
страничку при передаче параметров search
. В LocationsController
index
экшене мы проверяем параметр, и если он задан, то делаем поиск ближайших местечек.
def index if params[:search].present? @locations = Location.near(params[:search], 50, :order => :distance) else @locations = Location.all end end
Если перезагрузить страничку и поискать “Manchester”, то мы найдем местоположение одной точки в Ливерпуле аж за 30 миль оттуда.
Это всё, что нужно, чтобы найти места поблизости с заданным городом.
Добавляя карты
Так как мы работаем с географическими данными о местоположениях, то неплохо еще и карту для их отображения использовать. Мы добавим одну на страничке местоположения, чтобы показать, где на самом деле находится это место. Google Maps API предоставляет много разных способов, как добавить карту на сайт, но для простоты мы будем использовать Static Maps API. Карты добавляются просто через вставку изображения, в url которого передаются различные определяющие параметры, включая широту и долготу, размер, приближение и так далее. Добавим карту сразу над списком ближайших адресов.
<%= image_tag "http://maps.google.com/maps/api/staticmap?size=450x300&sensor=false&zoom=16&markers=#{@location.latitude}%2C#{@location.longitude}" %>
Как мы видим, большинство передаваемых параметров, за исключением широты и долготы, которые мы получаем из объекта @location
. Если теперь откроем страничку местоположения, то увидим его и на карте.
Если вам хочется чего-то более интересного, чем просто статическая картинка, то разберитесь с Google Maps For Rails гемом, который упращает работу с джаваскриптовым JavaScript API. Это очень простой способ добавить интерактивную карту в ваше rails приложение.