はじめに

前回に引き続き地理情報に関するサービスを提供するAzure Mapsについて紹介します。今回は、前回作成した地図を表示するアプリケーションに検索機能を追加する方法について説明していきます。

地図アプリへのSearch Serviceの導入

前回作成した地図表示を行うアプリケーションを拡張し、任意のキーワードで検索し、ヒットした施設を地図上に表示する機能を追加していきます。キーワード検索を行い、該当の施設を取得するために、Search Serviceを使用します。

  • 検索機能を追加した地図アプリケーション

    検索機能を追加した地図アプリケーション

なお以降の説明では、前回作成したアプリケーション(index.html)に追記するかたちで実装を行っていきますので、アプリケーションおよびAzure Mapsアカウントの作成がまだの場合は前回の内容を確認して下さい。

サービスモジュールファイルの追加

まずはじめにSearch ServiceをJavaScriptから呼び出せるようにするために、Azure Mapsのサービスモジュールファイルをscriptタグで読み込む記述を追加します。

Azure Mapsサービスモジュールファイルの追加(index.html)

<!DOCTYPE html>
<html>
  <head>

<!-- 中略 -->

<!-- (前回実装箇所)Azure Maps Web SDKの読み込み -->
<link rel="stylesheet" href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" type="text/css">
    <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js"></script>
<!-- ① Azure Mapsサービスモジュールの読み込み -->
 <script src="https://atlas.microsoft.com/sdk/javascript/service/2/atlas-service.min.js"></script>

scriptタグの追加はheadタグ内の任意の場所で構いませんが、ここでは前回実装したAzure Maps Web SDKの読み込みの直後に追加しています(①)。
サービスモジュールファイルはSearch Serviceをはじめ、前回紹介したAzure Mapsの各サービスのAPIを実行することができるスクリプトファイルです。

地図のレンダリング完了を待つイベントの追加

次に検索処理を実行するエントリーポイントの実装を行います。今回のサンプルでは、画面に地図が描画された直後に検索を行って検索結果を地図上に描画させたいので、地図描画が完了したことを通知するイベントハンドラーを使用します。

地図描画完了のイベントハンドラーの定義(index.html)

<script>
  function initMap() {

    // Azure Maps Web SDKの初期化・・・①
    let map = new atlas.Map('myMap', {

      // Azure Mapsアカウントの認証情報の設定
      authOptions: {
        authType: 'subscriptionKey',
        subscriptionKey: '<Azure Mapsアカウントの主キー>'
      }

    });

    // 地図レンダリングの完了を待つイベントハンドラーの追加・・・②
    map.events.add('ready', async function () {

      // この中に検索処理を実装する

    });
  }
</script>

今回の実装では、Azure Maps Web SDKの初期化処理の中ではAzure Mapsアカウントの認証情報の設定のみを行うようにします(①)。前回はここで地図の表示に関する設定を行っていましたが、今回は後述の検索処理の中で設定を行うように変更しています。
Azure Maps Web SDKでは、地図レンダリングの状態を通知するイベントが定義されており、これを使ったイベントハンドラーを実装することができます。今回は地図レンダリングが完了したことを検知したいため、「ready」というイベントをトリガーに動作するイベントハンドラーを追加します(②)。 以降で説明していく検索処理はすべてこのイベントハンドラーの中に実装していきます。

検索結果のピンを表示するための設定

続いては検索した結果を地図上にピンとして表示させるための設定を行っていきます。

検索結果のピン表示のための設定(index.html)

map.events.add('ready', async function () {

  // データソースを作成し、地図オブジェクトに追加する・・・①
  datasource = new atlas.source.DataSource();
  map.sources.add(datasource);

  // シンボルレイヤーを作成して、データソースと関連付ける・・・②
  let searchLayer = new atlas.layer.SymbolLayer(datasource, null, {
    iconOptions: { // シンボルの見た目を設定する ・・・②−1
      image: 'pin-darkblue',
      anchor: 'center',
      allowOverlap: true
    }
  });

  // シンボルレイヤーを地図に追加する・・・③
  map.layers.add(searchLayer);

});

イベントハンドラの中でデータソースを作成し、地図へ追加します(①)。 Azure Mapsにおけるデータソースとは、複数の位置情報を格納するデータベースとしての役割を持ったオブジェクトを指します。データソースには位置情報のみが保持されているため、地図上にデータをどのように表示するのかについては定義されていません。この表示方法を決めるのがレイヤーです。データソースとレイヤーを関連付けることで地図上にデータを表示することができるようになります。

  • 地図とデータソースとレイヤーの関係

    地図とデータソースとレイヤーの関係

Azure Mapsでは様々な形式のレイヤーが用意されています。レイヤーには、線や多角形といった基本的な図形を描画する線レイヤー、多角形レイヤーや、任意の画像を描画するイメージレイヤーやタイルレイヤーなどがあります。 今回は検索してヒットした地点をピンで表示するために、シンボルレイヤー(SymbolLayer)を使用しています(②)。シンボルレイヤーは、事前定義済みのシンボルや任意の画像を使って位置情報を示す目印を地図上に表示することができるレイヤーです。シンボルレイヤーのインスタンスは、第一引数に関連付けたいデータソースを指定し、第二引数にレイヤーに設定したいIDを指定(nullの場合は自動採番)、第三引数にシンボルのレイアウトに関するオプションオブジェクトを設定して生成します。
第三引数のオプションオブジェクトの中で「iconOptions」オブジェクトを定義することでシンボルの見た目に関する設定をすることができます(②−1)。「image」ではシンボルの形状を指定することができ、事前定義済みのシンボルを選択することが可能です。事前定義済みのシンボルには、「marker」、「pin」、「pin-round」の3種類の形式があり、シンボル名の後ろに色を指定することでシンボルの色を変更できます。

  • 事前定義済みのシンボルの例

    事前定義済みのシンボルの例

最後に作成したシンボルレイヤーも地図に追加します(③)。

検索の実行

次は実際にキーワードを使って目的地の検索を実行する部分の実装を行います。

キーワード検索の実行(index.html)

map.events.add('ready', async function () {

  // 中略

  // 認証情報を内包したパイプラインインスタンスの作成・・・①
  let pipeline = atlas.service.MapsURL.newPipeline(new atlas.service.MapControlCredential(map));

  // Azure MapsのSearch Serviceを操作するためのインスタンスの作成・・・②
  let searchURL = new atlas.service.SearchURL(pipeline);

  // 検索キーワードの設定・・・③
  let query = 'convenience-stores';

  // 検索の実行(あいまい検索)・・・④
  let results = await searchURL.searchPOI(atlas.service.Aborter.timeout(10000),
    query,
    {
      lat: 47.6,
      lon: -122.33,
      radius: 9000,
      limit: 10
    });

  // 検索結果をデータソースへ追加・・・⑤
  let data = results.geojson.getFeatures();
  datasource.add(data);

  // 検索結果に応じて地図の表示位置を調整・・・⑥
  map.setCamera({
      bounds: data.bbox,
      zoom: 10
  });

});

①および②でSearch Serviceを使った検索を実行するためのオブジェクトを構築していきます。
Search ServiceはRESTサービスとして提供されているため、HTTPリクエストを使用してサービスにアクセスします。Search Serviceが適切なクライアントから呼び出されることを保証するために、HTTPリクエストには認証情報を付与する必要があります。①で作成しているパイプラインインスタンスは、認証情報を内包したオブジェクトになります。このパイプラインインスタンスを引数に、Search ServiceのAPIを操作するためのインスタンスを作成します(②)。
なお、パイプラインインスタンスの生成時に引数に指定しているインスタンス「MapControlCredential」は、mapインスタンスに含まれている認証情報をもとにパイプラインインスタンスを作成する場合に使用します。mapインスタンスは生成時にAzure Mapsアカウントの認証情報を設定しているため、その認証情報をパイプラインインスタンスにも適用することができるようになっています。
②で作成したSearch ServiceのAPIを操作するインスタンスを使って検索を実行します(④)。検索時のパラメータとして、検索キーワードを設定しています(③)。Search Serviceではあいまい検索が可能であるため、任意の検索キーワードを設定することができます。今回は例としてコンビニエンスストア(convenience-stores)を検索キーワードに設定して検索するようにしています。
また検索オプションとして、最大取得件数、検索対象の範囲(緯度経度・半径)などを設定することが可能です。以下の表はよく使用される検索オプションの例です。

あいまい検索で使用できる検索オプションの一例

オプション名 説明
lat 検索の基準点の緯度
lon 検索の基準点の経度
radius 検索対象の半径(メートル)
limit 検索結果の表示上限数

検索結果(results)は、GeoJSONと呼ばれる座標などの空間データをJSONで表現した形式で返却されます。それをデータソースへ追加することで地図上に検索結果がピンとして表示されるようになります(⑤)。
最後に検索結果に応じて表示する地図の位置を調整します(⑥)。検索実行後に地図の表示内容がリセットされてしまうので、ここでズーム率や座標などを再設定する必要があります。

ここまで実装できたら、一度ファイルを保存してブラウザで地図を確認してみましょう。

  • コンビニエンスストアの検索結果

    コンビニエンスストアの検索結果

図のように地図上に青いピンが表示されていれば、検索内容にヒットした複数の地点がピンで強調表示されていることを確認することができます。なお、執筆時点では日本の座標を指定した検索では検索結果を取得することができなかったため、アメリカ・シアトル周辺の座標で検索を行っています。

検索結果の詳細表示機能の追加

最後に、地図上に表示されたピンに対してマウスカーソルを合わせると、その地点の詳細情報を表示するポップアップを追加していきます。

ピンにポップアップを表示する追加実装(index.html)

map.events.add('ready', async function () {

  // 中略

  // ポップアップ用のインスタンスを作成・・・①
  let popup = new atlas.Popup();

  // マウスカーソルがピン上に重なった時にポップアップを表示する・・・②
  map.events.add('mouseover', searchLayer, function (e) {
    // データソースから詳細情報を取得する・・・③
    let prop = e.shapes[0].getProperties();
    let position = e.shapes[0].getCoordinates();    

    // ポップアップ用のHTMLを構築・・・④
    let html = `
                <div style="padding:5px">
                    <div><b>${prop.poi.name}</b></div>
                    <div>${prop.address.freeformAddress}</div>
                    <div>${position[1]}, ${position[0]}</div>
                </div>`;

    // ポップアップのセットアップ・・・⑤
    popup.setPopupOptions({
      content: html,
      position: position
    });

    // ポップアップを画面に表示する・・・⑥
    popup.open(map);
  }

});

ポップアップ表示用の部品がAzure Maps Web SDKに含まれているため、SDKからポップアップ用のインスタンスを生成します(①)。
次にポップアップをどのタイミングで表示するかを検索処理の実行時と同じように地図描画のイベントを使って定義します。今回はマウスカーソルがピン上に重なったことを検知するmouseoverイベントを使ってイベントハンドラーを起動するようにします(②)。
map.events.addの第一引数には検知するイベント名(mouiseover)を、第二引数には検知する対象のインスタンスを設定します。今回はピンが対象となるため、searchLayerを指定します。第三引数にはイベントハンドラーとなるメソッドを設定します。
イベントハンドラーメソッドの引数にはイベント発生時の地図およびピンの詳細情報が含まれたオブジェクトが渡されます。ピンの詳細情報の中には、マウスカーソルが重なっている地点のピンの空間データが渡ってくるため、この情報をもとにポップアップに表示する情報を構築していきます。
まずは引数の空間データから詳細情報を取得します(③)。getPropertiesメソッドからはその地点に登録されている地名や店舗名などのメタ情報を取得することができます。またgetCoordinatesメソッドからはその地点の座標情報を取得することができます。
③で取得した情報をもとに、ポップアップ内に表示したい情報をHTMLで構築していきます(④)。今回は、地点に登録されている店舗名、住所と座標(緯度経度)を表示するようにしています。
作成したHTMLとポップアップを表示する座標情報を設定し(⑤)、最後にポップアップの表示を行います(⑥)。

ここまでの内容を保存してファイルをブラウザで表示してみましょう。

  • ピンにポップアップを追加した地図

    ピンにポップアップを追加した地図

図のようにマウスカーソルを重ねたピンからポップアップが表示されて、詳細情報が確認できることが分かります。

まとめ

今回は前回に引き続きAzure Mapsを使った地図表示アプリケーションの実装方法について説明しました。Search Serviceを使用することで地図上にキーワード検索の検索結果を表示することができるようになりました。 次回は検索機能がより使いやすくなるように、ユーザーの入力内容によって動的に検索結果を地図上に反映できるようなアプリケーションへと修正していきます。

WINGSプロジェクト 秋葉龍一著/山田祥寛監修
<WINGSプロジェクトについて>テクニカル執筆プロジェクト(代表山田祥寛)。海外記事の翻訳から、主にWeb開発分野の書籍・雑誌/Web記事の執筆、講演等を幅広く手がける。一緒に執筆をできる有志を募集中