GPS 対応の Web アプリケーションを作成する

PHP、XML、jQuery、ブラウザーの GPS 機能を使用して位置情報対応のニュース・フィードを作成する

この記事では PHP を使用して、GPS 対応の Web アプリケーションのバックエンドとフロントエンドを作成する手順を説明します。

Jack D. Herrington, Senior Software Engineer, Fortify Software, Inc.

Photo of Jack HerringtonJack Herrington はサンフランシスコ湾岸地域に住む技術者、著作者、講演者です。彼の最新の活動や記事を http://jackherrington.com で知ることができます。



2011年 9月 09日

GPS 対応の Web アプリケーション

よく使われる頭文字語

  • Ajax: Asynchronous JavaScript + XML
  • API: Application Programming Interface
  • DOM: Document Object Mode
  • GPS: Global Positioning System
  • HTML: HyperText Markup Language
  • HTTP: HyperText Transfer Protocol
  • PDO: PHP Data Object
  • SQL: Structured Query Language
  • URL: Uniform Resource Locator
  • W3C: World Wide Web Consortium
  • XML: Extensible Markup Language

現在、インターネット上では、地理的な位置情報を基にサービスを提供する Web サイトが極めて一般的になっています。Foursquare、Yelp、Google マップなどのサイトはすべて、ユーザーが現在いる位置に関する情報を活用することで、その場所と最も関係のある情報を提供します。皆さんも容易に誰かの位置情報を取得して、その場所に応じた情報を提供することができます。

この記事では、GPS による位置情報を使用してニュース・コンテンツをユーザーに表示するアプリケーションのバックエンドとフロントエンドの両方を作成します。バックエンドは PHP で作成し、MySQL を使用して、ニュース記事の一覧、ニュース記事に関連する場所、その場所の座標などを保持します。フロントエンドの Web ページは webkit ブラウザーでサポートされているロケーション・サービスを使用してユーザーの GPS 座標を取得します。そしてこれらの座標を Ajax リクエストとしてバックエンドに送信します。PHP のバックエンド・システムは XML によるニュース記事一覧で応答し、フロントエンドはその一覧を動的に表示します。

この記事では、2 つの異なる方法で位置情報を使用します。1 つ目の方法では、新しいニュース記事をデータベースに追加する際に位置情報を使用します。PHP のバックエンドに位置情報 (例えばカリフォルニア州フリーモント (Fremont, CA) やワシントン特別区 (Washington, DC) など) を提供すると、そのページは Yahoo! が提供するジオロケーション・サービスを使用し、その位置情報を GPS 座標に変換します。2 つ目の方法では、Web ページがブラウザーに対してリクエストを発行してユーザーの位置情報を取得し、その情報と Ajax を使用してデータベースに対してクエリーを実行します。


バックエンドを作成する

バックエンドのコードの作成はデータベースから始めます。この場合のデータベースは MySQL です。リスト 1 は 1 つのテーブルを持つデータベースのスキーマを示しています。

リスト 1. db.sql
DROP TABLE IF EXISTS articles;
CREATE TABLE articles(
     lon FLOAT,
     lat FLOAT,
     address TEXT,
     title TEXT,
     url TEXT,
     summary TEXT );

このテーブルには 6 つの列があり、最初に緯度と経度の値、そしてニュース記事に関係する住所 (例えばカリフォルニア州フリーモント (Fremont, CA) など) があり、次に、タイトル、URL、概要など、ニュース記事に関する基本的な情報がいくつかあります。

データベースを構築するためには、まず mysqladmin を使用してデータベースを作成し、次に mysql コマンドを使用して db.sql スクリプトを実行します。

% mysqladmin --user=root --password=foo create articles
% mysql --user=root --password=foo articles < db.sql

データベースを作成できると、データベースにレコードを追加するための PHP ページを作成することができます。リスト 2 は insert.php ページのコードを示しています (注: 5 行目と 6 行目にある $url の値は、あくまでも体裁の関係上 2 つの文字列のように見えますが、実際には 1 つの文字列として記述する必要があります)。

リスト 2. insert.php
<?php
$dd = new PDO('mysql:host=localhost;dbname=articles', 'root', '');
if ( isset( $_POST['url'] ) ) {
// You need a Yahoo! PlaceFinder application key
// Go to: http://developer.yahoo.com/geo/placefinder/
  $url = "http://where.yahooapis.com/geocode?q=".urlencode($_POST['address']).
         "&appid=[yourappid] ";

  $ch = curl_init(); 
  curl_setopt($ch, CURLOPT_URL, $url); 
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 
  $body = curl_exec($ch); 

  preg_match( "/\<latitude\>(.*?)\<\/latitude\>/", $body, $lat );
  preg_match( "/\<longitude\>(.*?)\<\/longitude\>/", $body, $lon );

  $sql = 'INSERT INTO articles VALUES ( ?, ?, ?, ?, ?, ? )';
  $sth = $dd->prepare($sql);
  $sth->execute( array( 
    $lon[1],
    $lat[1],
    $_POST['address'],
    $_POST['title'],
    $_POST['url'],
    $_POST['summary']
    ) );
}
?>
<html>
<body>
<form method="post">
<table>
<tr><th>Title</td><th><input type="text" name="title" /></td></tr>
<tr><th>Summary</th><td><input type="text" name="summary" /></td></tr>
<tr><th>Address</th><td><input type="text" name="address" /></td></tr>
<tr><th>URL</th><td><input type="text" name="url" /></td></tr>
</table>
<input type="submit" value="Add Article" />
</form>
</body>
</html>

このページはデータベースにレコードを挿入するための PHP ページとして極めて標準的なものです。最初に、ユーザーがこのページを投稿したかどうかをチェックします。投稿した場合には、PDO ライブラリーを使用してデータベースにレコードを追加します。このスクリプトの最後には、フィールドを入力するための HTML フォームがあります。

ここで興味深い点は Curl ライブラリーを使用して Yahoo! の位置情報 Web サービスにリクエストを発行している部分にあります。このコードを実行させるためには Yahoo! の PlaceFinder サービスから提供されるアプリケーションの ID が必要です。このサービスの URL については、「参考文献」と、リスト 2 のコードを参照してください。

PlaceFinder サービスは指定された位置情報に対する XML レスポンスを返します。このレスポンスには緯度と経度 (そしてそれ以外の多種多様な興味深い情報) が含まれています。この XML から、単純な正規表現を使用して緯度と経度を抽出しています。

このコードは、必ず住所は座標に変換されるものとして作成されています。このコードを皆さん自身のシステムで使用する場合には、位置情報が返されるかどうか、また位置情報に変換されない場合には、そのことを知らせるエラー・メッセージがユーザーに対して表示されるかどうかを確認する必要があります。

このコードをテストするために、PHP サーバーに insert.php ページをインストールし、ブラウザーで表示します。すると、図 1 のようなフォームが表示されるはずです。

図 1. ニュース記事入力のためのフォーム
ニュース記事入力のためのフォームのスクリーン・キャプチャーが表示されており、フォームには「Title (タイトル)」、「Summary (概要)」、「Address (住所)」、「URL」というフィールドと「Add Article (ニュース記事を追加)」ボタンがあります。

図 1 は 4 つのフィールドを持つ単純な Web フォームを示しており、「Title (タイトル)」、「Summary (概要)」、「Address (住所)」、「URL」というフィールドと、データベースにニュース記事を追加するためのボタンがあります。図 2 は各フィールドに該当する値が入力された状態を示しています。

図 2. ニュース記事入力のためのフォームに値を入力した状態
ニュース記事入力のためのフォームに値を入力した状態のスクリーン・キャプチャー

図 2 では、ある裁判官による判決に関する地方のニュース記事のタイトル、概要、URL がフィールドに入力されています。位置情報は「Address (住所)」フィールドに「fremont, ca (カリフォルニア州フリーモント)」と指定されています。位置情報としては、番地、郵便番号、その他にも緯度と経度のペアに変換できる任意のものを指定することができます。

この時点で、皆さんの地域に関するニュース記事を insert.php ページを使用してデータベースに追加する必要があります。または単にどこか他から入手したニュース記事を使用することもできますが、ブラウザーから提供される GPS 位置情報によって何らかの結果が返されるように、住所を入力する必要があります。

データベースを設定してデータを入力できると、フロントエンドが使用する検索サービスを作成することができます。


検索サービスに必要なものは、緯度と経度のペアと半径を取得し、それによって描かれる円内に位置情報が含まれるすべてのニュース記事を返す PHP ページです。返される内容は、Web ページ (あるいは他の任意のアプリケーション) によって容易に読み取れるように、XML としてエンコードされている必要があります。リスト 3 は、そうした PHP ページの単なる一例です。

リスト 3. find.php
<?php
define( 'LATMILES', 1 / 69 );
define( 'LONMILES', 1 / 53 );

// Change these default coordinates to your current location
$lat = 37.3328;
$lon = -122.036;
$radius = 1.0;

if ( isset( $_GET['lat'] ) ) { $lat = (float)$_GET['lat']; }
if ( isset( $_GET['lon'] ) ) { $lon = (float)$_GET['lon']; }
if ( isset( $_GET['radius'] ) ) { $radius = (float)$_GET['radius']; }

$minlat = $lat - ( $radius * LONMILES );
$minlon = $lon - ( $radius * LATMILES );
$maxlat = $lat + ( $radius * LONMILES );
$maxlon = $lon + ( $radius * LATMILES );

$dbh = new PDO('mysql:host=localhost;dbname=articles', 'root', '');

$sql = 'SELECT * FROM articles WHERE lat >= ? AND lat <= ? AND lon >= ? AND lon <= ?';

$params = array( $minlat, $maxlat, $minlon, $maxlon );

if ( isset( $_GET['q'] ) ) {
  $sql .= " AND name LIKE ?";
  $params []= '%'.$_GET['q'].'%';
}

$q = $dbh->prepare( $sql );
$q->execute( $params );

$doc = new DOMDocument();
$r = $doc->createElement( "locations" );
$doc->appendChild( $r );

foreach ( $q->fetchAll() as $row) {
  $dlat = ( (float)$row['lat'] - $lat ) / LATMILES;
  $dlon = ( (float)$row['lon'] - $lon ) / LONMILES;
  $d = sqrt( ( $dlat * $dlat ) + ( $dlon * $dlon ) );
  if ( $d <= $radius ) {
    $e = $doc->createElement( "article" );
    $e->setAttribute( 'lat', $row['lat'] );
    $e->setAttribute( 'lon', $row['lon'] );
    $te = $doc->createElement('title');
    $te->appendChild( $doc->createTextNode( utf8_encode( $row['title'] ) ) );
    $e->appendChild( $te );
    $se = $doc->createElement('summary');
    $se->appendChild( $doc->createTextNode( utf8_encode( $row['summary'] ) ) );
    $e->appendChild( $se );
    $ue = $doc->createElement('url');
    $ue->appendChild( $doc->createTextNode( utf8_encode( $row['url'] ) ) );
    $e->appendChild( $ue );
    $ae = $doc->createElement('address');
    $ae->appendChild( $doc->createTextNode( utf8_encode( $row['address'] ) ) );
    $e->appendChild( $ae );
    $e->setAttribute( 'd', $d );
    $r->appendChild( $e );
  }
}

print $doc->saveXML();
?>

このコードは長くて複雑に見えますが、実際にはそんなことはありません。このコードは 2 つの異なる部分に分けることができます。このスクリプトは最初の部分で、緯度と経度の最大値と最小値で規定される四角形内に位置情報が含まれるすべてのニュース記事を SELECT 文で検索しています。

このコードの 2 番目の部分では、上記の結果得られた各ニュース記事に対して繰り返し処理を実行し、各ニュース記事の位置情報が指定の円内に含まれるかどうかをチェックすることで、さらに結果を絞り込んでいます。foreach ループの先頭にあるのは、そのためのちょっとした三角法コードです。現在処理しているニュース記事がその円内に入る場合には、そのニュース記事はオンザフライで作成される XML DOM 文書に追加されます。コードの最後の部分では、作成される XML 文書を単純に出力しています。

XML を作成する部分は確かに少し長いのですが、これは多くのノードが作成されるためです。ニュース記事に関する情報のうち、緯度と経度以外はすべて、サイズが制限されないようにサブノードとして表現されます。この方法は「Summary (概要)」フィールドの場合、特に重要です。「Summary (概要)」フィールドはニュース記事の概要を含み、それがいくつかの段落で記述されているため、非常に長くなる可能性があるからです。

この検索サービスをテストするために、まずこのサービスをサーバーにインストールし、次にブラウザーの中でこのサービスにナビゲートします。すると図 3 のような結果が表示されるはずです。

図 3. 検索サービスで作成された XML をブラウザーに表示する
検索サービスで作成された XML をブラウザーに表示した場合のスクリーン・キャプチャー

図 3 は、一種の Web ページのようなものとして返された XML による結果をブラウザーに表示した状態を示しています。ただし見栄えが悪く、雑然としており、とても整形されているとは言えません。この結果が XML としてどのようなものであるかを表示するには、ブラウザーで「View Source (ソースを表示)」を選択します。すると図 4 のようなものが表示されます。

図 4. find.php の実行結果のソースを表示する
find.php の実行結果の XML ソースのスクリーン・キャプチャー

図 4 はクエリーの結果をそのままの XML フォーマットで表示したものです。

何も結果が表示されない場合には、いくつかの問題が考えられます。このスクリプトがデータベースに接続できていないのかもしれません。位置情報に問題があるのかもしれません。find.php スクリプトのデフォルトの位置情報を、皆さんが選んだニュース記事で指定されている地点の近くに変更したり、あるいは Web ページの URL に lat パラメーターと lon パラメーターを追加したりする必要があります。別の可能性として、皆さんが選んだニュース記事の位置情報が無効なのかもしれません。有効な位置情報であるかどうかを調べるためには、MySQL サーバーにログインして articles テーブルに対してクエリーを実行し、lat フィールドと lon フィールドに適切に値が入力されているかどうかを確認します。

find.php スクリプトが何らかの結果を返すようであれば、このシステムにフロントエンドを追加する作業へ進むことができます。


ユーザー・インターフェースの最初のバージョンを作成する

ユーザー・インターフェースの最初のバージョンは非常に単純です。このインターフェースはブラウザーから GPS 座標を取得し、ハードコーディングされた検索半径 20 マイルと共にその座標をサーバーに送信し、返されたデータをフォーマット設定して表示します。この最初のバージョンのコードをリスト 4 に示します。

リスト 4. index.php
<html>
<head>
<script src="jquery-1.6.1.min.js"></script>
<script>
$(document).ready(function() {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
  } else {
    alert('GPS not supported');
  }
});

function successCallback(position) {
  loadArticles( position.coords.latitude, position.coords.longitude );
}

function loadArticles( lat, lon ) {
  $.ajax({
    url: "find.php",
    dataType: 'xml',
    data: {
    lat:lat,
    lon:lon,
    radius:20
    },
    success: buildArticlePage
  });
}

function buildArticlePage( data ) {
  $(data).find("article").each(function() {
  var title = $(this).find("title").text();
  var summary = $(this).find("summary").text();
  var url = $(this).find("url").text();
  var address = $(this).find("address").text();
    $("#output").append( "<a target=\"_blank\" href=\""+url+"\">"+title+"</a><br/>");
    $("#output").append( "<i>"+address+"</i><br />");
    $("#output").append( summary+"<br /><br />");
  });
}

function errorCallback(error) {
  alert('Error getting GPS:'+error);
}
</script>
</head>
<body>
<div id="output">
</div>
</body> 
</html>

リスト 4 のコードで最も興味深い部分は、このファイルの先頭です。このコードはロードされると、まず navigator.geolocation がないかどうかを検索し、navigator.geolocation がある場合には navigator.geolocation の getCurrentPosition メソッドを実行します。getCurrentPosition メソッドは 2 つの引数を取り、どちらの引数も関数です。指定された位置情報が見つかると、最初の関数が呼び出され、この情報が見つからない場合には 2 番目の関数が呼び出されます。

ある位置情報が検出されると successCallback が呼び出され、successCallback は指定された座標を使用して loadArticles を呼び出します。ここではこの 2 つの関数呼び出しを分割し、ブラウザーで座標を指定する際に問題が発生した場合にデバッグしやすいようにしてあります。どのような座標を選択した場合でも、いつでも loadArticles を直接呼び出すことができます。

loadArticles 関数は jQuery の Ajax ラッパーを使用して Ajax リクエストをサーバーに送信します。そして Web サーバーが正常に応答すると buildArticlePage が呼び出されます。もし、このページとは別のディレクトリーに find.php が配置されている場合には、find.php が配置されている場所を指すように loadArticles 内で指定している URL を変更する必要があるかもしれません。

buildArticlePage は一連の jQuery 呼び出しを使用して XML を構文解析すると共に、HTML の body セクションにある "output" div に新しい HTML タグを追加します。

たったこれだけで、すべて終わりです。信じて欲しいのですが、独自の API を使用して iOS や Android その他のプラットフォームで位置情報を取得するよりも、Web ページに GPS のサポートを追加することの方が、はるかに容易なのです。位置情報が急速に変化するような場合には、ブラウザー・イベントのジオロケーション・サービスが位置情報の追跡をサポートしています。

このページをテストするためには、Web サーバーに index.html ページをインストールしてブラウザーで開きます (図 5)。

図 5. GPS 座標を使用して検索されたニュース記事を取得した後の index ページ
GPS 座標を基に検索されたニュース記事の一覧を表示した index ページのスクリーン・キャプチャー

図 5 は、Web ブラウザーで指定された位置の近くの位置情報を持つニュース記事をデータベースから取得して Web ページに表示したものです。データが表示される前に、図 6 のような警告が表示される場合があります。

図 6. Safari における位置情報に関する警告のプロンプト
Safari における位置情報に関する警告のプロンプトのスクリーン・キャプチャー

図 6 に示したのは、この Web サイトに皆さんの現在の位置情報を提供してよいかどうかについてのサーバーからの警告です。許可するか拒否するかは皆さん次第ですが、テストのためには許可する必要があります。そうすれば JavaScript が座標を取得して Ajax 呼び出しを行い、その結果を表示することができます。

「GPS not supported」エラーが表示された場合には、webkit 非対応のブラウザー (おそらく Microsoft Internet Explorer) を使用しているためです。この記事の執筆時点で、Internet Explorer はロケーション・サービスをサポートしていません。

すべてが動作しているにもかかわらず何も結果が得られない場合には、20 マイルという検索半径が小さすぎるのかもしれません。単純に半径を 2000 に変更すると、何らかの結果が表示され始めるはずです。もちろん、皆さんが指定する位置情報と同じ大陸のデータをデータベースに格納してある、という前提です。

この記事の最後のステップでは、半径を制御できるようにアップグレードします。


ユーザー・インターフェースを機能強化する

半径の制御をページに追加するために、サポートしたい各半径の値を選択肢として持つ <select> タグをページに追加します。次に、半径制御の値をモニターしてページを更新するための jQuery コードを追加します (リスト 5)。

リスト 5. index2.php
<html>
<head>
<script src="jquery-1.6.1.min.js"></script>
<script>
var startLan = null;
var startLon = null;
$(document).ready(function() {
  $('#radius').change( function() {
    loadArticles( startLan, startLon );
  } );
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
  } else {
    alert('GPS not supported');
  }
});

function successCallback(position) {
  loadArticles( position.coords.latitude, position.coords.longitude );
}

function loadArticles( lat, lon ) {
  startLan = lat;
  startLon = lon;
  $.ajax({
    url: "find.php",
    dataType: 'xml',
    data: {
    lat:lat,
    lon:lon,
    radius:$('#radius').val()
    },
    success: buildArticlePage
  });
}

function buildArticlePage( data ) {
  $('#output').empty();
  $(data).find("article").each(function() {
  var title = $(this).find("title").text();
  var summary = $(this).find("summary").text();
  var url = $(this).find("url").text();
  var address = $(this).find("address").text();
    $("#output").append( "<a target=\"_blank\" href=\""+url+"\">"+title+"</a><br/>");
    $("#output").append( "<i>"+address+"</i><br />");
    $("#output").append( summary+"<br /><br />");
  });
}

function errorCallback(error) {
  alert('Error getting GPS:'+error);
}
</script>
</head>
<body>
<select id="radius">
  <option value="5" selected>5</option>
  <option value="15">15</option>
  <option value="50">50</option>
  <option value="150">150</option>
</select>
<div id="output">
</div>
</body> 
</html>

リスト 5 で、変更はページの先頭から始まり、半径制御に change リスナーを追加しています。次にこのコードは、半径制御が変更されると loadArticles 関数を呼び出します。コードを単純にするために、loadArticles メソッドは最後に使用された位置を保存します。こうすることで、半径が変更された場合、最後に使用された位置を再利用することができます。このページの HTML 部分も更新され、<select> タグと半径の選択肢が追加されています。

この機能強化をテストするために、まず更新された Web ページを index2.html としてサーバーにインストールし、次にこのページまで Web ブラウザーでナビゲートします。すると、図 7 のような画面が表示されるはずです。

図 7. 半径制御が追加された検索ページ
検索半径を制御するためのドロップダウン・フィールドを持つニュース記事一覧のスクリーン・キャプチャー

図 7 はニュース記事の一覧を示しており、ページの先頭には半径を制御するための新しいドロップダウン・コントロールが追加されています。この半径制御を変更すると、更新された半径の値によって Ajax リクエストが再度実行され、ページが動的に再構築されます。次に、出力用の <div> タグを空にし、新たに検索された一連のニュース記事を追加します。図 8 は半径の値を 150 に変更した場合の検索結果です。

図 8. 半径が 150 マイルの場合の検索結果
半径 150 マイル内で検索した結果のスクリーン・キャプチャー

図 8 を見ると、ページの最後にいくつかの結果が追加されていることがわかります。

まとめ

FourSquare、Yelp、Google マップなど、位置情報ベースのサービスに人気があるのは、人々が自分たちのいる地域に何か良いものがないかを知りたがるからであり、また人々が自分たちの周囲にいる人達とのつながりを持ちたがるからです。この新しいロケーション API と、簡単な PHP、jQuery JavaScript、XML、そしてWeb ブラウザーの位置情報サポートを利用することで、位置情報に対応した Web アプリケーションを実現することができます。この記事のコードを出発点として使用することで、皆さん独自のデータ・モデルによる位置情報ベースの独自のサービスを作成することができます。また、クライアントから連続的に位置情報データを供給する必要のあるアプリケーションの場合には、それもジオロケーション・サービスで実現することができ、さらに可能性が広がります。

参考文献

学ぶために

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Open source, Web development
ArticleID=755532
ArticleTitle=GPS 対応の Web アプリケーションを作成する
publish-date=09092011