homeASCIIcasts

273: Geocoder 

(view original Railscast)

Other translations: En Fr Es Ru

Other formats:

Written by Naomi Fujimoto

Railsアプリケーションで地理データを扱う場合、Geocoder gemが作業をとても簡単にしてくれます。地名から地理座標への変換あるいはその逆、またIPアドレスを住所に変換することもできます。近くの観光スポットを探して距離と方位を提示したり、その他にも役に立つ機能を多く持っています。

今回のエピソードではユーザが観光スポットを推薦するSiteseerというアプリケーションを作成します。アプリケーションを新規作成したら、次はLocationのscaffoldをaddresslatitudelongitudeフィールドを指定して作成します。

$ rails g nifty:scaffold location address:string latitude:float longitude:float

latitude(緯度)とlongitude(経度)のフィールド名は、Geocoderが観光スポットの地理座標を保存するために使用するので重要です。ただしこのデフォルトのフィールド名は変更することもできます。どちらのフィールドもfloat(浮動小数)にする必要があります。データベースのマイグレーションを行って、新しくlocationsテーブルを作成します。

$ rake db:migrate

scaffoldの設定ができたので新しい観光スポットを作成できるのですが、このままでは緯度と経度を手入力しなくてはいけません。そこでGeocoderが自動的にこれらのフィールドを埋めるようにアプリケーションを改良していきます。

scaffoldで作成された新しい観光スポットページ

Geocoderのインストールと使用

Geocoderのインストール方法は通常どおりです。まずGemfileでgemへの参照情報を追加して、bundleコマンドを実行してインストールします。

/Gemfile

source 'http://rubygems.org'

gem 'rails', '3.0.9'
gem 'sqlite3'
gem 'nifty-generators'

gem 'geocoder'

次にLocationモデルにgeocoded_byの呼び出しを追加してGeocoderに変換させたい属性(今回の場合はaddress(住所))を指定します。

/app/models/location.rb

class Location < ActiveRecord::Base
  attr_accessible :address, :latitude, :longitude
  geocoded_by :address
end

もし住所が複数のフィールドに分かれて保存されている場合は代わりにメソッドを指定して、完全な住所を返すメソッドを作成します。

観光スポットが作成あるいは更新されたらgeocodeメソッドを呼び出します。通常はこれをafter_validationコールバックを使って行います。

/app/models/location.rb

class Location < ActiveRecord::Base
  attr_accessible :address, :latitude, :longitude
  geocoded_by :address
  after_validation :geocode
end

geocodeメソッドはリクエストを外部API(デフォルトではGoogle Maps API)に送ります。外部サービスを呼び出す場合は、この呼び出しをバックグラウンドプロセスに移した方がいいでしょう。ここではそれは行いませんが、その設定方法を知るためにはResqueについての最近のエピソード[動画を見る, 読む]を参照してください。

それではアプリケーションを起動してGeocoderを試してみましょう。新しい観光スポット(例えばSt Pancras Station, London)を緯度や経度なしで入力すると、Geocoderが外部APIからデータを取得して観光スポットに追加します。

新しい観光スポットのデータをGeocoderが表示する

外部APIは観光スポットが更新されるたびに呼び出されますが、これはaddressフィールドが変更されたときのみに限定したほうがいいでしょう。これを実装するために、after_validationコールバックを少し修正します。

/app/models/location.rb

class Location < ActiveRecord::Base
  attr_accessible :address, :latitude, :longitude
  geocoded_by :address
  after_validation :geocode, :if => :address_changed?end

これはdirty trackingを用いて住所が変更されたかどうかをチェックして、変更されている場合のみAPIを呼び出します。

またreverse_geocoded_byメソッドを使って、逆に緯度と経度を住所に変換することもできます。これはgeocoded_byメソッドと同じ要領で動作します。今回のアプリケーションでは使用しません。

近くの観光スポットを取得する

ある場所を見ているときに近くの観光スポットを見ることができたら便利でしょう。データベースにいくつか観光地の情報があるので、観光スポットページにこの機能を追加してみましょう。

ある距離内に存在する観光スポットをリスト表示したいのですが、Geocoderはnearbysメソッドで近くのスポットを簡単に取得することができます。デフォルトではこれは半径20マイル内の観光スポットを返しますが、ここでは10マイルに狭めます。近くの観光スポットをリストアップするために@location.nearbysの各locationをループして、距離(マイル)と一緒にリンクを表示します。

/app/views/locations/show.html.erb

<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マイル離れたLiverpoolにあるスポットは表示されません。

観光スポットページに近くのスポットが表示される

次に観光スポットページに検索ボックスを追加して、ある街にあるスポットを検索できるようにします。これをindexビューの一番上の新しいフォームに追加します。

/app/views/locations/index.html.erb

<% 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を使ってsearchパラメータを付けてindexページを呼び出します。LocationsControllerindexアクションでパラメータを確認し、もし存在したら近くの観光スポットを探します。

/app/controllers/locations_controller.rb

def index
  if params[:search].present?    @locations = Location.near(params[:search], 50, ↵
      :order => :distance)
  else
    @locations = Location.all
  end
end

ページを再度読み込んでManchesterを検索すると、30マイル離れたLiverpoolに1件見つかりました。

街の検索結果

これである街の近くにある観光スポットを探せました。

地図を追加する

地理情報を扱っているのであれば、各場所について地図を表示できたら便利でしょう。観光スポットページに、観光スポットの場所を正確に示す地図を追加してみましょう。Google Maps APIはWebページに地図を追加する各種の方法を提供していますが、今回は簡単に実装できるStatic Maps APIを使用します。簡単なimageタグで地図を追加できますが、指定されるURLには地図のいろいろな外観を指定するパラメータ(緯度、経度、サイズ、ズーム、など)がついています。近くの観光スポットのリストの上に直接地図を追加します。

/app/views/locations/show.html.erb

<%= image_tag "http://maps.google.com/maps/api/staticmap?size=450x300&sensor=false&zoom=16&markers=#{@location.latitude}%2C#{@location.longitude}" %>

渡すパラメータはほとんどが静的に指定された値ですが、緯度と経度だけは@locationオブジェクトから取得した値です。観光スポットのページを見てみると、その場所の地図が表示されています。

観光スポットのページに地図が表示される

静的な画像よりも手が込んだものが欲しければ、JavaScript APIとのやりとりを簡単にしてくれるGoogle Maps For Rails gemを見てみることをお勧めします。これを使えば、あなたのRailsアプリケーションに簡単にインタラクティブな地図を追加することができます。