地図APIを使ったアプリケーション プログラム演習8

今回はすこし、ウエブ検索APIから離れて最近流行の地図を使ったアプリケーションを作ってみましょう。地図(MAP)をサイトに掲載する方法で代表的なものは

が代表的です。どちらも JavaScript版とflash版がありますが、一般的にはGoogleJavaScript版の利用が多いようです。
今回は、あえて?Yahoo!地図API JavaScript版を利用した地図アプリを作ってみますが、Google版でも多少の細かい処理の違いはありますが、簡単に移植可能です。今回作成するアプリケーションは下記の画面イメージです

仕様は以下の通りです

【仕様】

  • 画面は地図部と住所表示部と最寄り駅リスト表示部から構成されます
  • 住所表示部は現在標示されている地図の中心の住所(文字列)が表示されます
  • 最寄り駅リスト表示部は地図中心地点から最寄りの駅20件が近い順位リストされます
  • 地図部では中心点から最寄りの駅20件の駅にアイコンが表示されます
  • 地図はマウスで移動可能で移動のたびに上の情報が更新されます

中心地点の住所、および最寄り駅情報は同じくヤフー地図APIローカルサーチAPIを利用します。

汎用的なProxyの作成

前回の演習でも説明したajaxのクロスドメイン制約が今回も立ちはだかります。今回は汎用的に使えるProxyを作成しましょう。また、今回のアプリではajaxのデータの形式としてJSONを用います。外部APIから受信したXMLJSONに変換できるようにProxyを作成します

bash-3.2$ cat prog8/proxy.php 
<?php
    require_once('XML/Unserializer.php');

    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $_GET['url']);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    $xml = curl_exec($curl);
    curl_close($curl);

    if(!isset($_GET['output']) || $_GET['output'] == 'xml') {
        header("Content-type: text/xml; charset=utf-8");
        print $xml;
    } else {
        $unserializer = new XML_Unserializer(array(
                     XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE => 'parseAttributes'
                                                   ));
        $unserializer->unserialize($xml);
        $hash = $unserializer->getUnserializedData();
        if($_GET['output'] == 'json') {
            header("Content-type: application/json; charset=utf-8");
            print json_encode($hash);
        } else if($_GET['output'] == 'debug') {
            header("Content-type: text/html; charset=utf-8");
            print "<pre>\n";
            print_r($hash);
            print "</pre>\n";
        }
    }
?>

作成したProxyは単純にURLに

url=取得したい(外部)API&output=[xml|json|debug]

という形を取ります. output=jsonjson形式. output=debugでprint_r(hash)形式で出力します。json形式への変換はjson_encodeというPHPの組み込み拡張関数を用います.json_encodeは演習4で紹介したphpinfoで

と表示されていればそのまま使えます。ない場合は pear(に登録予定の) Services_JSONで代用できます。またXMLからHashに変換する必要があるのでそこは同じく演習4で紹介したXML_Unserializerを用いました。これで

http://localhost:8888/prog8/proxy.php?output=json&url=http%3A%2F%2Fmap.yahooapis.jp%2FLocalSearchService%2FV1%2FLocalSearch%3Fappid%3Dp3O1lFOxg66up7VWRDmVkEJnF6baGNJj38rT6gUY3DzyF.kX4_PepRsfnk1AYss1J.YG%26dist%3D10%26n%3D20%26category%3Dstation%26lat%3D35.67777222222222%26lon%3D139.77048055555557
http://localhost:8888/prog8/proxy.php?output=json&url=http%3A%2F%2Fmap.yahooapis.jp%2FLocalSearchService%2FV1%2FLocalSearch%3Fappid%3Dp3O1lFOxg66up7VWRDmVkEJnF6baGNJj38rT6gUY3DzyF.kX4_PepRsfnk1AYss1J.YG%26dist%3D10%26n%3D1%26category%3Daddress%26lat%3D35.67777222222222%26lon%3D139.77048055555557

で、最寄り駅(category=station)および中心住所(category=address)が取得できます

アプリ作成

上の完成例の画面を元に雛形となるHTMLを次のように作成します

bash-3.2$ cat prog8/index.html 
<html>
<head>
<title>Sample Program 8 JavaScript-1</title>
<meta http-equiv="Content-Type" content="text/html" charset="utf-8">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<script src="./jquery-1.2.6.min.js"></script>
<script src="http://map.yahooapis.jp/MapsService/js/V2/?appid=p3O1lFOxg66up7VWRDmVkEJnF6baGNJj38rT6gUY3DzyF.kX4_PepRsfnk1AYss1J.YG"></script>
<script src="./prog8.js"></script>
</head>
<body>

<div id="address"></div>
<div id="map" style="width:600px;height:400px;float:left;"></div>
<div id="station" style="font-size:10pt;"></div>

</body>
</html>

jqueryjavascript版地図apiをhead部に読み込んでます。作成するjsファイルはprog8.jsです.アドレス表示部はdivのid="address" 最寄り駅リスト部はdivのid="station" です。配置およびレイアウトは任意でOKです。
prog8.jsは以下のようになります

bash-3.2$ cat prog8/prog8.js 
var appid = "p3O1lFOxg66up7VWRDmVkEJnF6baGNJj38rT6gUY3DzyF.kX4_PepRsfnk1AYss1J.YG";
var localApi = 'http://map.yahooapis.jp/LocalSearchService/V1/LocalSearch?appid=' + appid;
var _map;

function getStations() {
    var center = _map.getCenter();

    var api = localApi + '&dist=10&n=20&category=station';
    api += '&lat=' + center.lat;
    api += '&lon=' + center.lon;

    var proxyParam = 'output=json&url=' + encodeURIComponent(api);
    $.ajax( {
                type: "get",
                url: "./proxy.php",
                dataType: "json",
                data: proxyParam,
                success: function(data) {
                    _map.clearIcon();
                    var stationList = "";
                    for(var i=0;i<data.Item.length;i++) {
                        _map.addIcon(   i,
                                        data.Item[i].DatumTky97.Lat + ',' + data.Item[i].DatumTky97.Lon,
                                        data.Item[i].Title,
                                        'L2',
                                        data.Item[i].Title
                                        );
                        stationList += data.Item[i].Title + "<br>";
                    }
                    $("#station").html(stationList);
                }
    });
}

function getAddress() {
    var center = _map.getCenter();

    var api = localApi + '&dist=10&n=1&category=address';
    api += '&lat=' + center.lat;
    api += '&lon=' + center.lon;

    var proxyParam = 'output=json&url=' + encodeURIComponent(api);
    $.ajax( {
                type: "get",
                url: "./proxy.php",
                dataType: "json",
                data: proxyParam,
                success: function(data) {
                    $("#address").html(data.Item.Title);
                }
    });
}

$(window).load(function() {
    _map = new YahooMapsCtrl("map", "35.40.39.980,139.46.13.730", 4);
    _map.addEvent(YEventType.MAP_MOVED, getStations);
    _map.addEvent(YEventType.MAP_MOVED, getAddress);
});

ものすごく簡単に実現できることが分かると思います。 windowのloadで地図の生成. ドラック時に読み込む関数を2つ(最寄り駅を取得表示する getStation関数, 現在住所を取得表示するgetAddress関数)登録します。
それぞれの関数は jQueryajax機能でProxy経由にて取得します。

dataType: "json"

が今回のポイントです. 受信データは json形式をevalされた状態でなります。すなわちJavaScriptのオブジェクトとして即時アクセス可能です。オブジェクトがどのような形かを確かめるためにproxyのoutput=debugを使うと良いでしょう。Jsonの形式で扱うと、前回面倒だった getElementsByTagName などの記述が不要になり、シンプルなコードがかけることが分かります。

今回のプログラム一式は
http://trac.assembla.com/cX0noKTPir3A7Oab7jnrAJ/browser/prog8
にありますので、必要に応じて参照してください。

tips(緯度経度と測地系
地図で緯度経度をあつかうアプリケーションを作成する場合、ピンデータ緯度経度の測地形式が地図の測地系と一致していない場合は正確な地図の位置にピンが立たない現象が発生します。これは

という2つの測地系があるためです。たとえばヤフーの地図はGoogleの地図は世界測地系をデフォルトで記述されています。ヤフーのローカルサーチAPIの応答の各ピンの座標値を日本測地系を使った場合は正しい位置にピンが立ちません。ローカルサーチAPIは2つの測地系どちらも応答できる機能を持っていますが、その他のものはどちらか一方であることが多いので、ピンの位置があわない場合はこの測地系を疑うと良いでしょう
ヤフーの地図APIはデフォルトでは日本測地系です。YahooMapsCtrlで地図オブジェクト生成時に世界測地系を指定することも可能です(参照)