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

第3回位置情報を取得してみよう(後編)

連載第3回目の今回は、JavaScriptを利用した位置情報の取得方法を解説します。

Geolocation APIを利用する

Geolocation APIとは、JavaScriptで位置情報を取得するAPIのことで、現在W3Cで仕様を策定中ではありますが、Firefox 3.5やGoogle Chrome 5、Safari 5など、いくつかのモダンなブラウザではすでに利用できます。

ただし、プラグインや設定などによって利用できるかどうかは変わってくるため、コードの中で利用可否の判定を行う場合は、ブラウザ名とバージョンで判定するのではなく、geolocationオブジェクトが利用可能かどうかで判定するのが一般的です。

位置情報を取り扱うgeolocationオブジェクトは、window.navigatorから取得することができます。window.navigator.geolocationについて、仕様では現在のところ以下の3つのメソッドが提供されています。

navigator.geolocation.getCurrentPosition( successCallback , errorCallback , option)
現在の位置を取得する
navigator.geolocation.watchPosition( successCallback , errorCallback , option)
位置を定期的に取得する
navigator.geolocation.clearWatch( watchId)
watchPositionの定期的な位置情報取得を止める

getCurrentPositionとwatchPositionの第一引数には、位置情報取得が成功したときのコールバック関数を指定します。コールバック関数は、以下のpositionオブジェクトを受け取ります。

position.coords緯度経度などを保持するCordinatesオブジェクト。
position.timestamp位置情報を取得した時刻。

緯度経度を保持するCordinatesオブジェクトは、以下のプロパティを保持しています。緯度経度に加えて、高度や方位、速度が定義されていますが、デバイスが対応していない場合は取得することはできません。

coords.latitude緯度
coords.longitude経度
coords.altitude高度
coords.accuracy正確性(値が小さいほど正確)
coords.altitudeAccuracy高度の正確性
coords.heading方位
coords.speed速度

getCurrentPositionとwatchPositionの第二引数には、位置情報取得が失敗したときのコールバック関数を指定します。コールバック関数は、以下のerrorオブジェクトを受け取ります。

error.code エラーコード
1.位置情報の取得が許可されていない
2.位置情報の取得が利用できない
3.タイムアウト
error.messageエラーメッセージ

getCurrentPositionとwatchPositionの第三引数には、関数実行時のオプションを指定します。

option.enableHighAccuracyより高い正確性を求める(ただし、取得時間が長くなる)
スマートフォンでこのオプションを指定するとGPSを利用する
option.timeout処理のタイムアウト
option.maximumAgeキャッシュされている位置情報オブジェクトを許容するキャシュ時間

watchPositionはlong値のidを返します。clearWatch 関数にこのidを渡すと、位置情報の監視を停止します。

Gearsを利用する

Gearsとは、元々GoogleがGoogle Gearsという名称で提供していたブラウザのプラグインのことです。Gearsを利用すると、ブラウザでローカルDatabaseや、スレッド処理を利用できるようになります。そのGearsの機能のひとつに、Geolocation APIがあります。

名前が、W3Cで定義されているGeolocation APIと同じですが、微妙に機能が拡張されています。

GearsのGeolocation APIを利用するには、以下のようにGeolocation オブジェクトを生成します。

var geolocation = google.gears.factory.create(‘beta.geolocation’);

使い方は、基本的にJavaScriptのGeolocation APIと同じなので、geolocationオブジェクトを生成した後のコードは、そのまま利用できます。

W3Cの定義から拡張されている内容の一つに、Addressクラスがあります。Addressクラスは、watchPositionなどで位置情報を取得した際に、Positionクラスのプロパティの一つとして取得することができ、国名や県名、市町村名など住所のテキストが構造化された形で格納されています。

Gearsを利用すると、JavaScriptのGeolocation APIに対応していないブラウザでも、位置情報を取得できます。ただし、Gearsをプラグインとしてインストールする必要があるので、利用者としては少々敷居が高くなってしまいます。

サンプルコード

ブラウザで位置情報を取得して、ページ内に表示するサンプルを以下に作成してみました。

ページ内のstartをクリックすると、位置情報を取得してページ内に緯度経度を表示します。パソコンのブラウザだけでなく、スマートフォンのブラウザでも試してみてください。 コードは以下のURLで公開しています。

サンプルコード解説

13~24行目では、navigator.gelocationがない場合はGearsが利用できるか試してみて、geolocationオブジェクトが取得できなかった場合は、アラートを表示して処理を終了します。

13~24行目
try {
   if(typeof(navigator.geolocation) == 'undefined'){
     geolocation = google.gears.factory.create('beta.geolocation');
   } else {
     geolocation = navigator.geolocation;
   }
 } catch(e) {}
 if (!geolocation) {
   alert('位置情報は利用できません');
   return;
 }

28~40行目では、位置情報取得が成功した場合に実行される関数を、42~48行目では、取得が失敗した場合に実行される関数を定義しています。それぞれ、取得したオブジェクトの内容をページ中に表示する内容になっています。

50~54行目では、位置情報を取得する際のオプションを指定しています。高精度な位置情報を利用するようにし、タイムアウトの時間は10秒、maximumAgeを0にすることで、キャッシュされている位置情報は利用しないようにしています。

50~54行目
 var option = {
   enableHighAccuracy: true,
   timeout : 10000,
   maximumAge: 0
 };  

56~59行目は、位置情報取得処理を開始する関数です。ボタンのラベルを「stop」に変更します。

56~59行目
 function start() {
   watchId = geolocation.watchPosition(success, error, option);
   $('#controler').attr('value','stop');
 }

61~64行目は、位置情報取得処理を停止する関数です。ボタンのラベルを「start」に変更します。

61~64行目
 function stop() {
   geolocation.clearWatch(watchId);
   $('#controler').attr('value','start');
 }

66行目では、ボタンにクリックしたときの処理を紐付けます。

66行目
 $('#controler').toggle(start, stop);

ジオコーディングを利用する

ジオコーディングとは、住所や地名、ランドーマーク名などから、緯度経度の情報に変換することを指します。逆に、緯度経度の情報から、住所や地名を取得することをリバースジオコーディングや逆ジオコーディングと呼びます。

ジオコーディングのAPIはいくつか公開されています。代表的なAPIは、GoogleのThe Google Geocoding API 」と、Yahoo! JapanのジオコーダーAPI 」です。

以下に、引数に与えたテキスト情報でThe Google Geocoding APIへアクセスし、緯度経度を表示するサンプルを示します。

http://github.com/chris4403/geolocation/blob/master/perl/geocording.pl
#!/usr/bin/env perl
use strict;
use warnings;

use LWP::Simple qw(get);
use Data::Dumper;
use Perl6::Say;
use JSON::XS;

my $url = sprintf "http://maps.googleapis.com/maps/api/geocode/json?sensor=false&language=ja&address=%s", shift;

my $res = get $url;
$res = decode_json $res;
my $results = $res->{results};
foreach my $result (@$results) {
    say "----------";
    say $result->{formatted_address};
    say $result->{geometry}->{location}->{lat} . "," .$result->{geometry}->{location}->{lng};
}

例えば、はてな東京オフィスの住所を引数に与えて実行すると、以下のような結果が得られます。

% perl geocording.pl 東京都目黒区中目黒2-10-15
----------
日本, 東京都目黒区中目黒2丁目10?15
35.6406465,139.7027264

ジオコーディングを利用するとテキストを入力する手間は増えますが、Geolocation APIやGearsに対応していないデバイスでも、一応位置情報を取得することができるようになります。ただし、テキストの入力内容はユーザーに委ねることになるため、当然ですが、取得された位置情報は実際にそのユーザーがいる場所と一致するわけではありません。

ジオコーディングの限界

住所や地名から緯度経度を取得できる便利なジオコーディングですが、弱点もあります。

「東京タワー」のような、日本に一箇所しかなさそうな名称の場合は、問題ありませんが、例えば八坂神社 のように、日本全国に同名の地名が多数ある場合は、都道府県や町の名前の情報がないと一意に特定するのは難しいです。

また、ジオコーディングは京都において特に難しいとされています。というのも、京都には正式な住所の他に、⁠通り名」という「室町通り三条下ル」というような通りの名前を利用した住所の表現が広く使われているからです。通り名についての詳しい解説は、こちらのブログに詳細が掲載されています。さきほど紹介した一般的なジオコーディングAPIでは、京都の住所に対応しきれないのですが、ジオどすAPIを利用すると、かなりの精度で位置を返してくれます。

しかしながら、専門のAPIをしても100%の精度がでないのが、ジオコーディングの難しさといえるでしょう。

サーバーで位置情報を取得する

取得した位置情報をサービスで利用するには、サーバ側へ緯度経度を送信する必要があります。デバイスやキャリアの種類によって、緯度経度のフォーマットが異なることは前回解説しました。

はてなココでは、以下のようなユーティリティ関数を用意して、サーバ側へ送信されるいくつかのパターンの緯度経度を一意に扱うようにしています。

http://github.com/chris4403/geolocation/blob/master/perl/HTTP.pm
package Geo::Format::HTTP;
use strict;
use warnings;
our $VERSION = '1.0';
use Exporter::Lite;

our @EXPORT = qw(
get_location_from_req
);

sub get_location_from_req ($) {
    my $req = shift;
    no warnings 'uninitialized';
    
    my ($lat, $lon);
    if (my $pos = $req->param('pos')) {
        # Softbank
        $pos =~ /^([NS])([0-9]+)\.([0-9]+)\.([0-9]+)\.(?:[0-9]+)([EW])([0-9]+)\.([0-9]+)\.([0-9]+)\.(?:[0-9]+)$/;

        $lat = $2 + ($3 / 60) + ($4 / 60 / 60);
        $lat *= -1 if $1 eq 'S';
        $lon = $6 + ($7 / 60) + ($8 / 60 / 60);
        $lon *= -1 if $5 eq 'W';

    } elsif (my $ll = $req->param('ll')) {
        my @latlon = split ',' , $ll;
        $lat = $latlon[0] || 0;
        $lon = $latlon[1] || 0;
    } else {
        $lat = $req->param('lat') || 0;
        $lon = $req->param('lon') || 0;

        # Fraction part of second is not support at the moment.

        if ($lat =~ /([+-]?[0-9]+)\.([0-9]+)\.([0-9.]+)/) {
            # au, docomo (au's non-GPS data does not have leading "+")
            $lat = $1 + ($2 / 60) + ($3 / 60 / 60);
        } else {
            $lat += 0;
        }
        
        if ($lon =~ /([+-]?[0-9]+)\.([0-9]+)\.([0-9.]+)/) {
            $lon = $1 + ($2 / 60) + ($3 / 60 / 60);
        } else {
            $lon += 0;
        }
    }

    if ($lat > +90) {
        $lat = +90;
    } elsif ($lat < -90) {
        $lat = -90;
    }
    
    if ($lon > +180) {
        $lon = +180;
    } elsif ($lon < -180) {
        $lon = -180;
    }

    # It returns false values if the position cannot be retrieved from
    # $pos.
    return ($lat, $lon);
}

1;

上記ファイルをuseすることで、get_location_from_reqという関数が利用できるようになり、以下のように利用することができます。

my ($lat, $lon) = get_location_from_req $request;

これにより、サーバに送信された緯度経度のフォーマットのばらつきを吸収し、手軽に位置情報を扱うことができます。

本当にその場所にいる/いたのか?

位置情報を利用したサービスを提供する場合、サーバーに送信された緯度経度情報をどの程度信頼するのかという問題に直面します。

結論からいうと、送信された場所に、実際にユーザーがいるかを知る方法はありません。サーバーへ送信されてくるのは、所詮HTTPリクエストのパラメーターに過ぎないので、いくらでも偽装できます。偽装しにくい仕組みはある程度作れても、完全に偽装を防ぐことはできません。

携帯電話からのリクエストに話を絞ると、IPをチェックして、携帯各社の回線から送信されているかを調べることで、位置情報の偽装の難易度をかなり上げることができます。スマートフォン対応された位置情報ゲームで、WiFiではなく3G回線を利用しなければならないのはこのためです。

次回予告

第3回は、JavaScriptを利用した位置情報の取得方法を解説しました。

次回は、⁠位置情報を表示する」というテーマで、地図系のAPIを解説したいと思います。

おすすめ記事

記事・ニュース一覧