キーワードで位置情報を検索して検索結果の任意の情報をDBへ保存、そして、DBへ保存した位置情報をGoogle Mapsで表示するアプリケーションを作ってみます。

キーワードから位置情報を検索するのに、Google Places APIのRuby用のラッパーライブラリであるmarceldegraaf/google_placesをRails4アプリケーションにインストールします。

DBへ保存した位置情報をGoogle Mapsで表示させるのに、gmaps4railsをRails4アプリケーションにインストールします。

また、作成したアプリケーションはherokuにpushしますのでRailsのデータベースドライバとしてPostgreSQLを選択します。

PostgreSQLの準備

PostgreSQLインストール

CentOS6でPostgreSQLインストール

データベースユーザ作成

PostgreSQLにpostgresユーザで接続します。

1
psql --username=postgres

create userSQLを実行して、データベースユーザとしてgoogle_place_sampleをDB作成権限を付与して作成します。

1
create user google_place_sample with createdb password 'google_place_sample';

Railsプロジェクト作成

Railsプロジェクト作成

新しいRailsプロジェクトを作成します。

1
2
3
rails new google_place_sample --database=postgresql
cd google_place_sample
bin/spring stop

therubyracer

GemfileのJavascriptエンジンであるtherubyracerの設定行をアンコメントします。

Gemfile
1
2
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
gem 'therubyracer',  platforms: :ruby

therubyracerライブラリをインストールします。

1
bundle install

PostgreSQLデータベース接続設定

PostgreSQLデータベース接続情報に接続データベースユーザの情報を追記します。

config/database.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
default: &default
  adapter: postgresql
  encoding: unicode
  pool: 5

development:
  <<: *default
  database: google_place_sample_development
  user: google_place_sample
  password: google_place_sample

test:
  <<: *default
  database: google_place_sample_test
  user: google_place_sample
  password: google_place_sample

production:
  <<: *default
  database: google_place_sample_production
  username: google_place_sample
  password: <%= ENV['GOOGLE_PLACE_SAMPLE_DATABASE_PASSWORD'] %>

Bootstrap3インストール

Gemライブラリのインストール


Gemfileに追記します。

Gemfile
1
2
3
4
5
6
7
8
# Install Twitter Bootstrap3
# https://github.com/twbs/bootstrap-sass
gem 'bootstrap-sass', '~> 3.2.0'

# -webkit-border-radius みたいなブラウザベンダープレフィックスをよしなに管理してくれる
# Parse CSS and add vendor prefixes to rules by Can I Use
# https://twitter.com/autoprefixer
gem 'autoprefixer-rails'

Gemライブラリをインストールします。

1
bundle install

bootstrap-sassを使用する準備

今回はSassというプリプロセッサに対応したbootstrap3をインストールしているのですが、CSSの//= require行はSassでは文法として使用できない>ので注意が必要です。そして、Sass拡張子のファイルやその他のスタイルシートであっても、Bootstrapからmixinsや変数を利用できないので//= require>行は利用できないということです。

本家GiHubのREADMEに従い、app/assets/stylesheets/application.cssは削除します。

1
rm app/assets/stylesheets/application.css

app/assets/stylesheets/application.css.scssを新規作成します。

app/assets/stylesheets/application.css.scss
1
2
@import "bootstrap-sprockets";
@import "bootstrap";

app/assets/javascripts/application.jsにBootstrap関連のJavascriptsライブラリをrequireします。

app/assets/javascripts/application.js
1
//= require bootstrap-sprockets

app/views/layouts/application.html.erbheadタグにbootstrap関連のスタイルシートをincludeする設定とメディアクエリーを使用するためのviewport設定を追記します。

app/views/layouts/application.html.erb headタグ内の記述
1
2
  <%= stylesheet_link_tag    "bootstrap", media: "all", "data-turbolinks-track" => true %>
  <meta content="width=device-width, initial-scale=1" name="viewport">

ナビゲーションメニュー

app/views/layouts/application.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
    <div class="container">
      <div class="navbar-header">
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="/">Project name</a>
      </div>
      <div class="collapse navbar-collapse">
        <ul class="nav navbar-nav">
          <li class="active"><a href="/">Home</a></li>
          <li><a href="#about">About</a></li>
          <li><a href="#contact">Contact</a></li>
        </ul>
        <form class="navbar-form navbar-left">
          <input type="text" class="form-control col-lg-8" placeholder="Search">
        </form>
      </div><!--/.nav-collapse -->
    </div><!-- /.container -->
  </div><!-- /.navbar -->

Jumbotron(ジャンボトロン)

app/assets/stylesheets/theme-style.css.scssを作成し、スタイルを追加します。

app/assets/stylesheets/theme-style.css.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
body {
  padding-top: 70px;
  padding-bottom: 30px;
}

.theme-dropdown .dropdown-menu {
  position: static;
  display: block;
  margin-bottom: 20px;
}

.theme-showcase > p > .btn {
  margin: 5px 0;
}

.theme-showcase .navbar .container {
  width: auto;
}

app/assets/stylesheets/application.css.scssに作成したtheme-style.css.scssを読む込むように設定します。

app/assets/stylesheets/application.css.scss
1
@import "theme-style";

次にJumbotron(ジャンボトロン)を表示するためのコードを記述します。

app/views/layouts/application.html.erb
1
2
3
4
5
6
7
8
9
10
  <div class="container theme-showcase" role="main">
    <!-- Main jumbotron -->
    <div class="jumbotron">
      <h1>Hello, world!</h1>
      <p class="text-warning">bootstrap3-sample</p>
    </div><!-- /.jumbotron -->

    <%= yield %>

  </div><!-- /.container -->

Railsのフラッシュメッセージ表示

コントローラ内でModelの保存や削除の成功/失敗などのメッセージを変数に格納された場合にはJumbotron(ジャンボトロン)の上部にします。

app/views/layouts/application.html.erb
1
2
3
4
5
6
7
8
9
  <% if (notice) %>
    <div class="alert alert-info alert-dismissible" role="alert">
      <button type="button" class="close" data-dismiss="alert">
        <span aria-hidden="true">&times;</span>
        <span class="sr-only">閉じる</span>
      </button>
      <strong><%= notice %></strong>
    </div>
  <% end %>

google_placesライブラリでキーワードから位置情報を取得する

https://github.com/marceldegraaf/google_placesをインストールすることで、東京 焼き肉とか沖縄 しまぶた屋などのキーワードで検索して位置情報などを取得することができるようになります。

Gemライブラリのインストール

Gemfileに追記します。

Gemfile
1
2
3
# A Ruby wrapper around the Google Places API
# https://github.com/marceldegraaf/google_places
gem 'google_places'

Gemライブラリをインストールします。

1
bundle install

コントローラとビューの生成

今回はscaffoldで生成しません。

rails g controllerコマンドを使用してコントローラとビューを生成します。

アクション名 役割 ビューの生成
index 検索フォームと登録済み位置情報テーブル
list Google place API検索結果の表示
show 当該レコードの位置情報を表示
create 当該位置情報をDBへ保存
destroy 当該位置情報をDBから削除
1
bundle exec rails g controller place index list show

app/controllers/place_controller.rb

app/controllers/place_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class PlaceController < ApplicationController
  before_action :set_place, only: [:show, :destroy]

  def index
    @places = Place.all
  end

  def show
  end

  def list
    keyword = params[:search]
    @client = GooglePlaces::Client.new( ENV['GOOGLE_API_KEY'] )
    @places = @client.spots_by_query( keyword )
  end

  def create
    @place = Place.new(place_params)

    respond_to do |format|
      if @place.save
        format.html { redirect_to place_index_path, notice: "#{@place.name} の位置情報を保存しました" }
      else
        format.html { render :index, notice: "#{@place.name} の位置情報を保存できませんでした" }
      end
    end
  end

  def destroy
    @place.destroy

    respond_to do |format|
      format.html { redirect_to place_index_path, notice: "#{@place.name} の位置情報を削除しました" }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_place
      @place = Place.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def place_params
      params.require(:place).permit(:name, :latitude, :longitude, :address)
    end

end

app/views/place/index.html.erb


app/views/place/index.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<h1>位置情報を検索してみよう</h1>

<div class="col-md-6">
  <%= form_tag place_list_path, :role =>"form", :method => :get do %>
    <div class="form-group">
      <%= text_field_tag :search, params[:search], { :class => "form-control", :required => true, } %>
      <%= button_tag( {:type => "submit", :name => nil, :class => "btn btn-default" } ) do %>
        <span class="glyphicon glyphicon-search">キーワード検索</span>
      <% end %>
    </div>
  <% end %>
</div>

<div class="col-md-12">
  <table class="table table-striped">
    <thead>
      <tr>
        <th>Name</th>
        <th>Latitude</th>
        <th>Longitude</th>
        <th>Address</th>
        <th colspan="2"></th>
      </tr>
    </thead>

    <tbody>
      <% @places.each do |place| %>
        <tr>
          <td><%= place.name %></td>
          <td><%= place.latitude %></td>
          <td><%= place.longitude %></td>
          <td><%= place.address %></td>
          <td><%= link_to ( place ), :title => "show" do %>
            <span class="glyphicon glyphicon-stats"></span>
          <% end %></td>
          <td><%= link_to( place, method: :delete, data: { confirm: "#{place.name} の位置情報を削除します" }, :title => "delete" ) do %>
            <span class="glyphicon glyphicon-trash"></span>
          <% end %></td>
        </tr>
      <% end %>
    </tbody>
  </table>
</div>

app/views/place/list.html.erb


app/views/place/list.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<h1>希望の場所は見つかったかな?</h1>

<div class="col-md-12">
  <table class="table table-striped">
    <thead>
      <tr>
        <th>Name</th>
        <th>Latitude</th>
        <th>Longitude</th>
        <th>Address</th>
        <th></th>
      </tr>
    </thead>

    <tbody>
      <% @places.each do |place| %>
        <tr>
          <td><%= place.name %></td>
          <td><%= place.lat %></td>
          <td><%= place.lng %></td>
          <td><%= place.formatted_address %></td>
          <td><%= link_to( '登録', place_index_path( :place => { :name      => place.name,
                                                                 :latitude  => place.lat,
                                                                 :longitude => place.lng,
                                                                 :address   => place.formatted_address, } ),:method => 'post' ) %>
          </td>
        </tr>
      <% end %>
    </tbody>
  </table>

  <button type="button" class="btn pull-right btn-lg btn-default">
    <%= link_to 'Back', place_index_path %>
  </button>

</div>

app/views/place/show.html.erb


app/views/place/show.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<div class="col-md-12">
  <table class="table table-striped">
    <thead>
      <tr>
        <th>Name</th>
        <th>Latitude</th>
        <th>Longitude</th>
        <th>Address</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><%= @place.name %></td>
        <td><%= @place.latitude %></td>
        <td><%= @place.longitude %></td>
        <td><%= @place.address %></td>
      </tr>
    </tbody>
  </table>

  <button type="button" class="btn pull-right btn-lg btn-default">
    <%= link_to 'Back', place_index_path %>
  </button>

</div>

モデルの生成とDBマイグレーション

位置情報を保存するためのplaceモデルを生成します。

1
bundle exec rails g model place name:string address:string latitude:float longitude:float

DBを作成、マイグレーションを実行します。

1
2
bundle exec rake db:create
bundle exec rake db:migrate

ルーティング設定

config/routes.rb
1
2
3
4
5
6
7
8
9
10
Rails.application.routes.draw do
  root 'place#index'

  namespace :place do
    # get 'place/list' request
    get 'list'
  end

  resources :place, :only => [ :index, :show, :create, :destroy ]
end

ここまでのサンプルアプリケーション

google_placeライブラリを使った位置情報検索サンプルアプリケーション


gmaps4railsライブラリでDBに保存した位置情報からマップを作成する

ここまでの作業でキーワード検索でリストアップされた施設の位置情報をDBへ保存することが出来ました。

ここで保存している位置情報とは施設名住所経度緯度です。

経度緯度の情報があればGoogle Maps上にマークを表示させることが出来ます。

apneadiving/Google-Maps-for-Rails (gmaps4rails)ライブラリを利用することで簡単にDBに保存された経度緯度を使用してGoogle Mapsを利用できます。

Gemライブラリのインストール

Gemfile
1
2
3
4
# Enables easy Google map + overlays creation in Ruby apps 
# https://github.com/apneadiving/Google-Maps-for-Rails
# http://apneadiving.github.io/
gem 'gmaps4rails'

Gemライブラリをインストールします。

1
bundle install

gmaps4railsライブラリの使用準備

app/views/layouts/application.html.erbheadタグにGoogle Maps関連のライブラリを読み込む設定を追記します。

app/views/layouts/application.html.erb headタグ内の記述
1
2
  <script src="//maps.google.com/maps/api/js?v=3.13&amp;sensor=false&amp;libraries=geometry" type="text/javascript"></script>
  <script src='//google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.14/src/markerclusterer_packed.js' type='text/javascript'></script>

app/assets/javascripts/application.jsにGoogle Maps関連のJavascriptsライブラリをrequireします。

app/assets/javascripts/application.js
1
2
//= require underscore
//= require gmaps/google

underscore/underscore.jsapp/assets/javascripts/underscore.jsとして作成します。

Google Mapsをレスポンシブで表示するスタイルシート

app/assets/stylesheets/gmap4rails.css.scss

app/assets/stylesheets/gmap4rails.css.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.map_container {
    position: relative;
    width: 100%;
    margin-bottom: 20px;
    padding-bottom: 56.25%; /* Ratio 16:9 ( 100%/16*9 = 56.25% ) */
}

.map_container .map_canvas {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    margin: 0;
    padding: 0;
}

app/assets/stylesheets/gmap4rails.css.scssをimportします。

app/assets/stylesheets/application.css.scss
1
@import "gmap4rails";

showアクションでMapを表示する

app/controllers/place_controller.rb

app/controllers/place_controller.rb のshowアクション
1
2
3
4
5
6
7
  def show
    @hash = Gmaps4rails.build_markers(@place) do |place,marker|
      marker.lat place.latitude
      marker.lng place.longitude
      marker.json({title: place.name})
    end
  end

app/views/place/show.html.erb

先述したapp/views/place/show.html.erb</table>タグと<button type="button" class="btn pull-right btn-lg btn-default">タグの間に以下コードを追記します。

app/views/place/show.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  <div class="map_container">
    <div id="map" class="map_canvas"></div>
  </div>


  <script type="text/javascript">
    handler = Gmaps.build('Google');
    handler.buildMap({ provider: {}, internal: {id: 'map'}}, function(){
      markers = handler.addMarkers(<%=raw @hash.to_json %>);
      handler.bounds.extendWith(markers);
      handler.fitMapToBounds();
      handler.getMap().setZoom(12);
      handler.map.centerOn(marker);
    });
  </script>


無事、Google Mapsが表示されました!

ここまでのサンプルアプリケーション

google_placeとgmaps4railsライブラリを使った位置情報検索サンプルアプリケーション