使ってみよう! Bing API/SDK

第17回使ってみよう! Bing Maps AJAX Control─⁠─地図をWebページに貼り付け(5)

はじめに

今回はBing Maps AJAX Controlの5回目です。1回目にあたる第13回のときに少しふれたように、Bing Maps AJAX Control 7.0から大幅にライブラリーの内容が変更され、機能が地図の表示に限定したコントロールライブラリーとなっています。住所から経緯度を得るジオコーディングなどを行いたい場合は、Bing Maps REST ServicesなどのWebサービスと組み合わせて使用します。今回は、Bing Maps AJAX ControlとBing Maps REST Servicesを連携したWebアプリケーションを作成してみましょう。

作成するWebアプリケーションは、Bing Mapsを単純にしたような、地図アプリケーションの基本、地図検索です。ユーザーが入力した地名の場所を検索し、検索結果の候補にプッシュピンを表示します図1⁠。

図1 地図検索
図1 地図検索

ページの作成

まずは、これまでと同じように地図を表示するところまでコードを書いておきましょう。検索用のテキストエリアとボタンも用意しています。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Sample</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.4.4.js" type="text/javascript"></script>
    <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&amp;mkt=ja-jp"></script>
    <script type="text/javascript">
        <!--
        var map = null;

        function GetMap() {
            var options = {
                credentials: "BingMapsKey"
            };
            map = new Microsoft.Maps.Map(document.getElementById("map"), options);
        }
        //-->
    </script>
</head>
<body onload="GetMap();">
    <div id="map" style="position: relative; width: 512px; height: 512px"></div>
    <div>
        <input id="query" type="text" value="東京" />
        <input type="button" value="検索" onclick="search();" />
    </div>
</body>
</html>

BingMapsKeyの部分は、Bing Maps Keyに置き換えてください。

また今回は、JavaScriptのライブラリー、jQueryを使用します。上記コードではライブラリーの参照もしています。

<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.4.4.js" type="text/javascript"></script>

以上のコードを元に地図検索用のコードを追記していきます。

Webサービスのリクエスト処理

Bing Maps REST Services

地名から経緯度を取得するには、Bing Maps REST ServicesのLocation APIを利用します。Bing Maps REST Servicesについては、第9回から第11回に渡って紹介しています。Location APIの詳細は第9回を参照してください。

Location APIは、ジオコーディング・逆ジオコーディングを提供するAPIです。地図検索のように住所や建物名などのクエリーからも地理情報を取得できます。今回のアプリケーションではクエリーから経緯度などの情報を得て利用します。

クエリーから情報を取得する場合、次のような形式のURLにアクセスします。

  • http://dev.virtualearth.net/REST/v1/Locations?
      query=東京タワー&
      key=BingMapsKey&
      c=ja-jp&o=xml

連載ではXML形式で取得していましたが、JavaScriptで扱いやすいJSON形式を利用します。またJSONPと呼ばれる一般的な仕組みを利用してAPIにアクセスし、レスポンス受信時に指定したJavaScriptの関数を呼び出して使います。これらの設定もLocation APIのURLのパラメーターに指定します。

  • http://dev.virtualearth.net/REST/v1/Locations?
      query=東京タワー&
      key=BingMapsKey&
      jsonp=callback&
      o=json
      c=ja-jp

セッションIDの取得

Bing Maps AJAX ControlおよびREST Servicesのどちらを利用するにもBing Maps Keyが必要です。それぞれにBing Maps Keyを設定してもよいですが、Bing Maps AJAX ControlのMapオブジェクトからBing Maps Keyとして使用できるセッションIDが取得できます。

Bing Mapsのライセンスの種別によって、一定期間に利用できるセッション数に制限があります。Bing Maps AJAX Controlのロードから、ユーザーがほかのWebページへ移動またはWebブラウザーを閉じるまでが1セッションとしてカウントされます。Bing Maps AJAX ControlからREST Servicesを利用する場合、セッションIDを利用するとセッションの消費を抑えられます。

セッションIDの取得には次のようにMapオブジェクトのgetCredentialsメソッドを使います。引数には、セッションIDを受け取る関数を指定します。

// map は Map オブジェクト
map.getCredentials(function (credentials) {
    if (credentials !== null) {
        // credentials を Bing Maps Key に指定し REST Services を呼び出せます
    }
});

Location APIの呼び出し

さて以上を踏まえて、コードからLocation APIを利用してみましょう。先ほど示したコードに追記していきます。まず検索ボタンをクリックしたら呼ばれるsearch関数です。

function search() {
    map.entities.clear(); // 地図上のオブジェクトを削除
    map.getCredentials(createGeocodeRequest);
}

次にセッションIDを受け取り、ジオコーディングを行う部分です。Location APIの呼出しは、jQueryを使って記述すると次のようになります。

function createGeocodeRequest(credentials) {
    if (credentials === null) {
        alert("Credentials is null.");
        return;
    }

    $.ajax({
        type: "GET",
        url: "http://dev.virtualearth.net/REST/v1/Locations",
        dataType: "jsonp",
        data: {
            key: credentials,
            query: $("#query").val(),
            c: "ja-JP",
            o: "json"
        },
        jsonp: "jsonp",
        success: function (data, dataType) {
            geocodeCallback(data);
        }
    });
}

非同期通信のためレスポンスは、geocodeCallback関数で受け取ります。

レスポンス処理

次はLocation APIのレスポンスを処理しましょう。通信に成功した場合、geocodeCallback関数が呼ばれるようにリクエストでは指定していました。受け取るJSON形式のデータは次のような内容になっています。

{
    "authenticationResultCode": "ValidCredentials", 
    "brandLogoUri": "http://dev.virtualearth.net/Branding/logo_powered_by.png", 
    "copyright": "Copyright © 2011 Microsoft ...", 
    "resourceSets": [
        {
            "estimatedTotal": 2, 
            "resources": [
                {
                    "__type": "Location:http://schemas.microsoft.com/search/local/ws/rest/v1", 
                    "address": {
                        "countryRegion": "日本国", 
                        "formattedAddress": "社台駅"
                    }, 
                    "bbox": [
                        42.576469847999995, 
                        141.41462562599997, 
                        42.581877012, 
                        141.421300374
                    ], 
                    "confidence": "Medium", 
                    "entityType": "RailwayStation", 
                    "name": "社台駅", 
                    "point": {
                        "coordinates": [
                            42.579173429999997, 
                            141.41796299999999
                        ], 
                        "type": "Point"
                    }
                }, 
                {
                  ...(略)...
                }
            ]
        }
    ], 
    "statusCode": 200, 
    "statusDescription": "OK", 
    "traceId": "..."
}

上記では2件目の地理情報を省略していますが、検索結果が2件ある場合の内容です。resources配列に結果の個数分の地理情報が含まれているのがわかります。今回使用する地理情報は、適切な表示範囲を示している矩形を表す2点の経緯度を含むbboxと、対象の地点を表すpointです。

何かしらのエラーが発生し検索結果が得られない場合は、次のようなレスポンスを得ます。エラーの内容がerrorDetails配列に格納されています。

{
    "authenticationResultCode": "ValidCredentials", 
    "brandLogoUri": "http://dev.virtualearth.net/Branding/logo_powered_by.png", 
    "copyright": "Copyright © 2011 Microsoft ...", 
    "errorDetails": [
        "One or more parameters are not valid.", 
        "query: This parameter is missing or invalid."
    ], 
    "resourceSets": [], 
    "statusCode": 400, 
    "statusDescription": "Bad Request", 
    "traceId": "..."
}

それでは、レスポンスを処理するコードを記述します。エラーのレスポンスの場合はエラーメッセージを表示し、そうでない場合は、検索結果の件数分のプッシュピンを地図上に表示します。地図の表示する場所は、1件目のbboxの値を使います。

function geocodeCallback(response) {
    if (response && response.errorDetails) {
        var text = "";
        $.each(response.errorDetails, function () {
            text += this + "\n";
        });
        alert(text); // error!
        return false;
    }

    if (response &&
        response.resourceSets &&
        response.resourceSets.length > 0 &&
        response.resourceSets[0].resources &&
        response.resourceSets[0].resources.length > 0) {

        // 地図の表示範囲設定(1件目の情報を使用)
        var bbox = response.resourceSets[0].resources[0].bbox;
        var bounds = Microsoft.Maps.LocationRect.fromLocations(
            new Microsoft.Maps.Location(bbox[0], bbox[1]), new Microsoft.Maps.Location(bbox[2], bbox[3]));
        map.setView({ bounds: bounds });

        // プッシュピンの追加
        var code = "A".charCodeAt(0);
        $.each(response.resourceSets[0].resources, function () {
            var point = this.point;
            var location = new Microsoft.Maps.Location(point.coordinates[0], point.coordinates[1]);
            var pin = new Microsoft.Maps.Pushpin(location, { text: String.fromCharCode(code++) });
            map.entities.push(pin);
        });
    } else {
        alert("No result.");
    }
}

プッシュピンには1件目の検索結果から順にA、B、Cとテキストを表示するようにもしています。

ここまでを一度実行してみましょう。うまく動いたでしょうか。検索結果によっては、複数同じ経緯度の結果が得られる場合があります。その場合のチェックはしていませんので、プッシュピンが完全に重なった状態で表示される場合があります。

機能の追加

ここまでで、Bing Maps REST Servicesを利用した地図検索のWebアプリケーションができました。ここからはもう少し、アプリケーションをそれらしいものにするためブラッシュアップします。といっても大層な内容ではなく、プッシュピンにマウスポインターを合わせた時に地名を表示する、以前にも紹介したものです。今回はjQueryを使用しているので、簡単にフェードイン・フェードアウト効果も付けることができます。

まず地名を表示するためのdiv要素をHTMLのbody要素内に追加します。

<div id="info" style="position: absolute; border: 1px solid gray; background-color: white; padding: 5px; display: none;"></div>

次に、プッシュピンの追加時に、プッシュピンのイベントにイベントハンドラーを関連付けるよう処理します。geocodeCallback関数内のループ処理に、次の関数呼び出しを追記します。Pushpinオブジェクトと地名の文字列を渡しています。

// map.entities.push(pin); の次の行に以下のコードを追加
addEventHnadler(pin, this.name);

イベントハンドラーの関連付けの処理は次の通りです。

function addEventHnadler(pin, text) {
    // mouseover イベント処理
    pin.mouseoverHandlerId = Microsoft.Maps.Events.addHandler(pin, "mouseover", function (e) {
        // Pushpin のピクセル座標取得
        var point = map.tryLocationToPixel(pin.getLocation(), Microsoft.Maps.PixelReference.page);

        // div 要素のテキストと位置の設定
        var info = $("#info");
        info.text(text);
        info.css("left", point.x - Math.floor(info.outerWidth() / 2) + "px");
        info.css("top", point.y - info.outerHeight() - pin.getHeight() - 10 + "px");

        // div 要素の表示
        info.stop(true, false).fadeTo("normal", 1);
    });

    // mouseout イベント処理
    pin.mouseoutHandlerId = Microsoft.Maps.Events.addHandler(pin, "mouseout", function (e) {
        $("#info").stop(true, false).fadeTo("normal", 0);
    });
}

最後にイベントハンドラー関連付けの削除処理も書いておきましょう。上記コードでは、プッシュピンのイベントハンドラー関連付けのとき、戻り値をmouseoverHandlerIdとmouseoutHandlerIdというプロパティに設定していました。これらは、Bing Maps AJAX Controlに用意されているものではなく、このアプリケーションで記憶用に拡張したものです。次のコードをGetMap関数内に追記します。

// map = new Microsoft.Maps.Map(document.getElementById("map"), options);
// の次の行に以下のコードを追加
Microsoft.Maps.Pushpin.prototype.mouseoverHanderId = null;
Microsoft.Maps.Pushpin.prototype.mouseoutHanderId = null;

// map.entities コレクション内のオブジェクトが削除されるとき(再検索時)にイベントハンドラー関連付けの削除
var id = Microsoft.Maps.Events.addHandler(map.entities, "entityremoved", function (e) {
    Microsoft.Maps.Events.removeHandler(e.entity.mouseoverHandlerId);
    Microsoft.Maps.Events.removeHandler(e.entity.mouseoutHandlerId);
});

// Web ページがアンロードされるときにイベントハンドラー関連付けの削除
$(window).unload(function () {
    map.entities.clear();
    Microsoft.Maps.Events.removeHandler(id);
});

以上で、最初に示した図と同じWebアプリケーションの完成です。うまく動いたでしょうか。

ルートの探索

最後におまけとして、ドライビングルートの探索機能も追加してみましょう。ルート探索には、Bing Maps REST ServicesのRoutes APIを利用します。以前に紹介した通り、このAPIは日本語に対応していません。出発・到着地は英語で入力する必要があります。Routes APIの詳細は第11回を参照してください。

Webページに出発・到着地点を入力するテキストエリアと探索ボタンを追加します。

<input id="start" type="text" value="Seattle" />
<input id="end" type="text" value="Portland" />
<input type="button" value="探索" onclick="route()" />

探索ボタンのクリックで呼び出されるroute関数、そしてリクエスト処理を行うcreateRouteRequest関数、最後にレスポンス処理を行うrouteCallback関数は、それぞれ次のようになります。

function route() {
    map.getCredentials(createRouteRequest);
}
function createRouteRequest(credentials) {
    if (credentials === null) {
        alert("Credentials is null.");
        return;
    }

    $.ajax({
        type: "GET",
        url: "http://dev.virtualearth.net/REST/v1/Routes",
        dataType: "jsonp",
        data: {
            key: credentials,
            "wp.0": $("#start").val(),
            "wp.1": $("#end").val(),
            routePathOutput: "Points",
            c: "ja-JP",
            o: "json"
        },
        jsonp: "jsonp",
        success: function (data, dataType) {
            routeCallback(data);
        }
    });
}
function routeCallback(response) {
    if (response && response.errorDetails) {
        var text = "";
        $.each(response.errorDetails, function () {
            text += this + "\n";
        });
        alert(text); // error!
        return false;
    }

    if (response &&
        response.resourceSets &&
        response.resourceSets.length > 0 &&
        response.resourceSets[0].resources &&
        response.resourceSets[0].resources.length > 0) {

        var resource = response.resourceSets[0].resources[0];

        // 地図の表示範囲設定(1件目の情報を使用)
        var bbox = resource.bbox;
        var bounds = Microsoft.Maps.LocationRect.fromLocations(
            new Microsoft.Maps.Location(bbox[0], bbox[1]), new Microsoft.Maps.Location(bbox[2], bbox[3]));
        map.setView({ bounds: bounds });

        // ルートの描画(ポリラインの描画)
        var routeline = resource.routePath.line;
        var routepoints = new Array();

        $.each(routeline.coordinates, function () {
            routepoints.push(new Microsoft.Maps.Location(this[0], this[1]));
        });

        var polyline = new Microsoft.Maps.Polyline(routepoints, { strokeColor: new Microsoft.Maps.Color(200, 0, 0, 200) });
        map.entities.push(polyline);
    }
}

基本的な流れは、Location APIのときと変わりません。ルート探索ではプッシュピンの追加の代わりにポリラインを作成し、地図上に表示しています。実行結果は図2のようになります。

図2 ルート探索
図2 ルート探索

今回はここまでです。いかがでしたでしょうか。今回利用したBing Maps REST Servicesの代わりにほかのWebサービスを同様に利用すれば、マッシュアップ アプリケーションのできあがりです。Bing Maps AJAX Controlを活かしたアプリケーションをぜひ作成してみてください。

おすすめ記事

記事・ニュース一覧