ちょっと話題の記事

位置情報の取得とWebSocketによる送信 [Geolocation APIとwebsocket-railsによる位置情報通知アプリ #1]

2014.05.09

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

ブラウザにて位置情報を取得し、その情報を別のクライアントへPush通知する処理の
サンプルをRuby on Railsで作成してみました。

なんらかの業務で、ある社員が持つ端末より位置情報を取得し、本社側でリアルタイムで
その情報を把握する、というような用途を想定しています。

アプリの機能としては

  1. JavaScriptにて、Geolocation APIを使用して位置情報を取得する
  2. websocket-railsを使い、WebSocketでサーバ側へ送信する
  3. サーバ側は、受け取った位置情報をwebsocket-railsを使い、別画面へPush通知する

の3つに分けることができます。

これらの機能を実装するポイントについて書いていきたいと思います。
今回は、1.と2.についてです。

Geolocation APIを使用して位置情報を取得する

位置情報の取得は上記に書いた通り、JavaScriptを使いクライアント端末のGPSを起動して行います。

Geolocation.watchPosition()
位置情報を連続して取得する
を参考にしました。

まずは、位置情報を表示する画面のソースです。
次回以降に使用する、位置情報を別端末にて表示する画面と同じソースを
使用するため、テンプレートを使用しています。

app/views/welcome/_location.html.erb

<div>
  <div class="div-geolocation-header">緯度</div>
  <div id="latitude">-</div>
  <div class="div-geolocation-header">経度</div>
  <div id="longitude">-</div>
  <div class="div-geolocation-header">移動方向</div>
  <div id="heading">-</div>
  <div class="div-geolocation-header">移動速度</div>
  <div id="speed">-</div>
</div>

見ての通り、緯度・経度などの位置情報を表示するdivタグを定義しています。

このdivタグに位置情報を表示するJavaScriptは以下のようになります。

app/assets/javascripts/welcome_index.js

document.addEventListener("DOMContentLoaded", function() {

    var options = {
        enableHighAccuracy: true,
        timeout: 60000,
        maximumAge: 0
    };
    // 位置情報取得
    window.navigator.geolocation.watchPosition(success, error, options);
}, false);


function success(pos) {
    // 緯度
    document.querySelector('#latitude').textContent = pos.coords.latitude;
    // 経度
    document.querySelector('#longitude').textContent = pos.coords.longitude;
    // 移動方向
    document.querySelector('#heading').textContent = pos.coords.heading;
    // 移動速度
    document.querySelector('#speed').textContent = pos.coords.speed;

    sendMessage(pos);
}

var dispatcher = new WebSocketRails(location.host + '/websocket', true);

function sendMessage(pos) {
    var location = {
        latitude: pos.coords.latitude,
        longitude: pos.coords.longitude,
        heading: pos.coords.heading,
        speed: pos.coords.heading
    };

    dispatcher.trigger('location_message', location);
}

function error(err) {
    console.warn('ERROR(' + err.code + '): ' + err.message);
}

9行目でGeolocationAPIのwatchPosition()メソッドを使用して、位置情報を取得しています。
位置情報を正常に取得した場合、13行目から始まるsuccess()メソッドを呼び出し
取得した位置情報を画面にセットしています。

この辺りの流れは、上記の参考記事とほぼ同じです。

websocket-railsを使い、サーバ側へ送信する

サーバへの送信、および別のクライアントへのPush送信は、websocket-railsを使用します。
今回はサーバ側へ送信する箇所について解説します。まずは、上記のJavaScriptのソースからです。

位置情報を表示した後、サーバに通知するために23行目でsendMessage()メソッドを呼び出します。

sendMessage()メソッドを実行する前に、26行目でWebSocketRailsのインスタンスを作成してます。
インスタンス作成時の引数として、サーバのURL + 「websocket」というURLを指定するのがポイントです。

sendMessage()メソッド内では、WebSocketRailsのインスタンスのtrigger()メソッドを呼び出し
メッセージをサーバへ送信しています。

第一引数には送信先のコントローラを表すイベントを、第二引数には送信するメッセージ(今回は位置情報のHash) を指定します。
第一引数に指定したイベントについては、後述します。

websocket-railsの準備

恐らく実際に作業する時には、最初に行うことになるかと思いますが、上記で呼び出した
websocket-railsを使うために以下の準備が必要です。

websocket-railsのインストール

Gemfileに以下を記述します。

gem 'websocket-rails'

記述後、以下のコマンドを実行します。

$ bundle install
$ rails g websocket_rails:install

websocket-railsのイベントの定義

websocket-railsのインストール時に作成された config/events.rb に、イベントを定義します。

config/events.rb

WebsocketRails::EventMap.describe do
  subscribe :location_message, :to => LocationController, :with_method => :show
end

「location_message」というイベントを定義し、呼び出し先として「LocationController」の「show」アクションを指定しています。
この「location_message」は、上記のJavaScript内のdispatcher.trigger()で指定したイベントです。

websocket-railsでの受信

websocket-railsのイベントで呼び出し先として定義されたコントローラにてメッセージを受信します。

app/controllers/location_controller.rb

class LocationController < WebsocketRails::BaseController def show puts message WebsocketRails[:location_channel].trigger "location", message end end [/ruby]

websocket-railsでは「message」という変数に、クライアントから送られるメッセージが格納されています。
今回は受け取ったメッセージが分かるよう、putsで出力しています。
その下の「WebSocketRails・・・」については、次回以降に解説します。

まとめ

今回はGeolocation APIを使用して位置情報を取得し、websocket-railsを使い、WebSocketでサーバ側へ送信するところまでを書きましたが、 次回は「サーバ側は、受け取った位置情報をwebsocket-railsを使い、別画面へPush通知する」処理について書きたいと思います。