位置情報サービスのはじめ方

第7回Google App Engineで位置情報サービスを作ってみよう

連載の最終回となる今回は、これまでの内容のおさらいとして、Google App Engineで自分の位置情報を記録するサイトを作ってみます。

アプリケーションの内容

今回作成するアプリケーションは、簡単な位置情報サービスということで、以下のようなサイトにしたいと思います。

  • Goelocation APIに対応したブラウザでトップページを開いていると、測位された位置が変わるごとにサーバへ緯度経度をPOSTし、Geohashに変換してGoogle App Engineのデータストアに保存する。
  • 履歴ページ(/history)へアクセスすると、地図を表示し、それまでに記録されている位置を線でつないで地図上に表示する。

今回必要となる技術要素は以下のようになります。

トップページ:
  • Google Maps JavaScript APIを利用した地図の表示
  • JavaScriptによる位置情報の取得
  • サーバ上での緯度経度からGeohashへの変換
  • Google App Engineでのデータの保存
履歴ページ:
  • Google Maps JavaScript APIを利用した地図の表示
  • Google Maps JavaScript APIを利用した地図上へのPolylineの描画
  • JavaScriptでのGeohashから緯度経度への変換

アプリケーションを使ってみよう

これらの条件で作成したアプリケーションを以下のURLで公開しているので、スマートフォン、もしくはGeolocation APIを利用できるブラウザでアクセスしてみてください。

図1 上記URLのQRコード
図1

アクセスすると、Googleのアカウントでのログインを求められるので、案内にしたがってGoogle アカウントでログインしてください。

図2
スマートフォンで見たとき:

図2
PCで見たとき:
図2

ログインすると、画面いっぱいに地図が表示され、ブラウザが位置情報の取得を許可する旨のアラートを表示するので、⁠OK」ボタンをクリックしてください。

図3
スマートフォンで見たとき:

図3

その後、位置情報の取得に成功した場合は、地図上にマーカーが配置されます。スマートフォンを利用している方は、このブラウザを落とさずに、近辺をうろうろしてみると、位置情報が変わったタイミングで、サーバーへその情報がPOSTされ、データストアへ記録されます。

ある程度時間が経ったら、以下の履歴ページを見てみましょう。

図4 上記URLのQRコード
図4

記録されている位置が、赤い線で繋がって表示されるはずです。以下の図は、休日にショッピングセンターへ買い物にでかけ、その帰り道で近くの公園に立ち寄ったときの記録になります。

図5
PCで見たとき:

図5

位置情報サービスと言いながら、スポットの作成や、チェックインもありませんし、また、位置を記録していくのはGoogle latitudeというサービスを利用してもできてしまいます。しかし、サンプルとしてのまとまり(ある程度コンパクトに)と、これまでの連載の内容の復習という観点から、このようなサンプルアプリケーションの内容としました。

Google App Engineをセットアップ

まずは、Google App Engineを開発する環境を整えましょう。こちらの記事を参考に、Pythonで開発できる環境をセットアップしましょう。

Pythonのインストールと、Google App EngineのSDKをインストールするだけで、ローカルで簡単に開発環境を構築することができます。

サンプルアプリケーションをローカルで動かす

先ほどアクセスしてもらったサイトのコードをgithubで公開しているので、git cloneしてローカルの環境で動かしてみましょう。

git clone後、dev_appserver.pyを使って起動します。

git clone git@github.com:chris4403/geolocationsample.git
dev_appserver.py geolocationsample

サーバーが起動したら、http://localhost:8080 へアクセスしてみましょう。すると、以下のような画面が表示されます。Google アカウントを認証に利用する機能を使っている場合、ローカルでの認証画面は以下のような画面になるので、そのまま「Login」を押しましょう。すると、画面上に地図が表示されます。

図5
図5

サンプルコードの構成

サンプルアプリケーションは、以下のようなファイル構成になっています。

geolocationsample
├static : staticフォルダ。jsやcss、imageなどを保存する。
├templates : Pythonアプリケーションのhtmlのテンプレートファイルを保存する。
├README : READMEファイル。
├app.yaml : デプロイ用の設定ファイル。
├geohasy.py : http://pypi.python.org/pypi/Geohash/ からダウンロードしたGeohashのライブラリ。
├index.yaml : GAEのデータストアの検索indexを設定するファイル。
├main.py : アプリケーションのハンドラ。
└model.py : アプリケーションのモデルクラスを記述するファイル。

自分のGoogle App Engineにデプロイしてみる

Google App Engineのサイトを作成し、サンプルコードをデプロイしてみましょう。

Google App Engineの管理コンソールにアクセスして、⁠Create Application」をクリックします。そして、Application Identifer(URLになります)と、Application Title(アプリケーションのタイトル)を入力して保存します。保存に成功したら、サンプルコードのapp.yamlを開いて、application : の横の「geolocationsample」を、自分のApplication Identiferに書き換えて保存しましょう。その後、以下のコマンドを入力し、Google アカウントのEメールとパスワードを入力するとデプロイが開始されます。

appcfg.py update (Application Identifer)

サンプルコードの解説

ここからは、サンプルコードのポイント部分を解説します。

main.py(トップページ部分)

main.py(トップページ部分)の以下の部分で、URLとファイル内のClassをマッピングしています。

application = webapp.WSGIApplication(
                                    [('/', MainPage),
                                     ('/history', HistoryPage),
                                     ('/geohash', GeohashPage)],
                                    debug=True)

トップページへのアクセスはMainPage Classが、履歴ページへのアクセスはHistoryPage Classが処理を行います。

まず、MainPage ClassのGETリクエストの部分を見てみましょう。

class MainPage(webapp.RequestHandler):
 def get(self):
   user = users.get_current_user()
   if user == None:
     self.redirect(users.create_login_url(self.request.uri))
   template_values = {
     'method' : 'get',
   }
   self.response.out.write(template.render('templates/index.html', template_values))

users.get_current_user()で、ログイン中のユーザを取得し、if user == Noneの部分で、ログインしているかどうかを判定しています。ログインしていない場合は、self.redirect(users.create_login_url(self.request.uri)) と記述することで、簡単にログインページへリダイレクトさせることができます。また、ログイン後に戻ってくるURLも指定できます。

最後に、self.response.out.write(template.render('templates/index.html', template_values)) の部分で、利用するtemplateファイルを指定しています。トップページへのアクセスではログインチェックのみで、ログインしていたらindex.htmlを表示するという簡単な内容になっています。

続いて、POSTリクエストの部分を見てみましょう。

 def post(self):
   user = users.get_current_user()
   if user == None:
     self.response.out.write()
   else :
     lat = float(self.request.get('lat'))
     lon = float(self.request.get('lon'))
     hash = geohash.encode(lat,lon)
     point = Point()
     point.geohash = hash
     point.owner   = users.get_current_user()
     point.put()
     template_values = {
       'method' : 'post',
     }
     self.response.headers['Content-Type'] = 'application/json'
     self.response.out.write('{result:"OK", geohash:"' + hash + '"}')

こちらも、GETのときと同様に最初にログインチェックを行っています。

ログインしていたら、リクエストパラメータのlat、lonからGeohashを静止して、Pointオブジェクトを作成して保存しています。ここへは、index.htmlからAjaxでアクセスするので、レスポンスはJSONで出力しています。

templates/wrapper.html

HTMLテンプレートの共通部分をwrapper.htmlとして切りだしています。

templates/index.html

wrapper.htmlに渡す値をそれぞれのブロックで定義しています。

<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />

viewportを指定することで、スマートフォンでサイトにアクセスした際、最適なサイズで閲覧できるようにしています。また、user-scalable=noを指定することで、ユーザーが地図をダブルタップした際にブラウザが拡大しないようにしています。

google.load("maps","3", {"other_params":"sensor=true"});

Google のAPIを利用して、Google Maps APIのversion3をロードしています。

 var success = function (position) {
   if (position.coords.accuracy > 100) return;
   var lat = position.coords.latitude;
   var lon = position.coords.longitude;
   var latlng = new google.maps.LatLng(lat, lon);
   map.setCenter(latlng);
   var marker = new google.maps.Marker({
       position: latlng,
       map: map
   });
   $.ajax({
     type: "POST",
     url: "/",
     data: "lat=" + lat + "&lon=" + lon,
     success: function(msg){
        var data = eval('(' + msg + ')');
     }
   });
   google.maps.event.addListener(marker, 'click', function() {
     map.setZoom(8);
   });
 }

位置情報を取得した際に実行される関数を定義しています。

if (position.coords.accuracy > 100) return; の部分で、取得した位置情報の精度が100mよりも大きい時は、後続の処理を行わないようにしています。

取得した位置情報をもとに、マーカーを表示した後、jQueryのajaxメソッドを利用して、サーバへ緯度経度をPOSTしています。

main.py(履歴ページ部分)

続いて、main.py ⁠履歴ページ部分⁠⁠ のコードを確認してみましょう。

class HistoryPage(webapp.RequestHandler):
 def get(self):
   user = users.get_current_user()
   if user == None:
     self.redirect(users.create_login_url(self.request.uri))
   userPoints = Point.gql("WHERE owner = :1 ORDER BY created DESC ",users.get_current_user())
   template_values = {
     'method' : 'get',
     'points' : userPoints
   }
   self.response.out.write(template.render('templates/history.html', template_values))

ログインチェック後、ログインしているユーザーを条件にして、Pointオブジェクトを作成日の降順で取得しています。users.get_current_user()で取得できるユーザーオブジェクトをそのまま検索の条件に利用できるのがGoogle App Engineのデータストアのおもしろいところです。

templates/history.html

<script type="text/javascript" charset="utf-8" src="/static/js/geohash.js"></script>

JavaScriptでGeohashを利用するライブラリを読み込んでいます。

 var geohashes = [{% for point in points %}'',{% endfor %}];

テンプレートに渡されたPointオブジェクトの配列をループで回して、Geohashの値をJavaScriptの配列として記述しています。

 var pathPoints = [];
 for (var i = 0, len = geohashes.length ; i < len ; i++) {
   var geohash = geohashes[i];
   if (geohash && geohash.length) {
     var data = decodeGeoHash(geohash);
     var latlng = new google.maps.LatLng(data.latitude[2], data.longitude[2]);
     pathPoints.push(latlng);
   }
 }

JavaScript上で、Geohashの配列の値を1つずつ取得して、緯度経度に変換し、さらにGoogle MapsのLatLngオブジェクトを生成しています。生成されたLatLngオブジェクトは、Polylineオブジェクトを作成するために、pathPointsという配列に格納していきます。

   // Mapを適切な範囲にfit
   var bounds = new google.maps.LatLngBounds(pathPoints[0],pathPoints[1]);
   for (var i = 2 , len = pathPoints.length ; i < len ; i++) {
     bounds.extend(pathPoints[i]);
   }
   map.fitBounds(bounds);

LatLngBoundsオブジェクトを生成し、線を引く予定のLatLngオブジェクトをextend関数に渡して、Boundsオブジェクトを拡張し、最終的にMapオブジェクトのfitBounds関数にBoundsオブジェクトを渡すことで、地図を適切な緯度経度、ズームレベルで表示します。

   // Pathを描画
   var path = new google.maps.Polyline({
     path: pathPoints,
     strokeColor: "#FF0000",
     strokeOpacity: 1.0,
     strokeWeight: 2
   });
   path.setMap(map);

Polylineオブジェクトを作成して、地図上に表示します。pathにLatLngオブジェクトの配列を渡すだけで簡単に線が描画できます。線の色、透明度、太さなどは、strokeColor、strokeOpacity、strokeWeightプロパティで指定します。

 } else if (pathPoints.length == 1) {
   map.setCenter(pathPoints[0]);
   new google.maps.Marker({
     position : pathPoints[0],
     map: map
   });
 }

記録されているのが1点だけの場合は、マーカーを表示します。

最後に

本連載では、位置情報の取得、表示、保存を解説してきました。これから位置情報サービスを作ってみようという方のお力に少しでもなれれば光栄です。

位置情報サービスはまだまだこれからの領域だと思います。おもしろいアイデアを思いつかれた方は、ぜひ形にして公開してみてください。

おすすめ記事

記事・ニュース一覧