Amazon Location Service のマップをOpenLayersを利用して表示してみた

2021.06.09

こんにちは!DA(データアナリティクス)事業本部 インテグレーション部の大高です。

先日Amazon Location Serviceのマップを、地図表示ライブラリの「Mapbox GL JS」を利用しているサンプルコードで表示してみました。

本エントリでは、このサンプルコードを参考に、オープンソースの地図表示ライブラリであるOpenLayersを利用してマップを表示してみたいと思います。

前提と事前準備

前提条件として下記のドキュメントに記載のとおり「map」リソースが作成されている必要があります。今回は検証環境に既にexplore.mapという名前の「map」が存在していたので、これを利用しています。

また、リソースへのリクエストについてはCognitoを利用した認証設定が必要になります。今回は地図表示をするために必要となるので、下記のドキュメントに従ってIDプールを作成します。

ドキュメントの通りですが、作成時のポイントとしては以下になります。

  • 「認証されていない ID に対してアクセスを有効にする」にチェックを入れて作成
  • 「Identify the IAM roles to use with your new identity pool」の画面が表示される
    • 「詳細を表示」をクリックする
    • 「ロールの概要」の「IAM ロール」プルダウンで「新しい IAM ロールの作成」を選択
      • 下の方の「Your unauthenticated identities would like access to Cognito.」であることに注意
    • 「ポリシードキュメントを表示」をクリックして「編集」リンクをクリック
      • ブラウザの高さがある程度ないと隠れて見えないので注意
    • ポリシードキュメントは、サンプルと同様に以下のように記載する(Resourceの箇所は自分のMapリソースに合わせる)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "MapsReadOnly",
      "Effect": "Allow",
      "Action": [
        "geo:GetMapStyleDescriptor",
        "geo:GetMapGlyphs",
        "geo:GetMapSprites",
        "geo:GetMapTile"
      ],
      "Resource": "arn:aws:geo:ap-northeast-1:XXXXXXXXXXXX:map/explore.map"
    }
  ]
}

なお、ドキュメントにも記載されているとおり、セキュリティを考慮して不必要なリソースへのアクセスは許可しないように十分留意してください。

今回は簡単な検証のために認証されていないIDに対してもアクセスを有効化していますが、本番利用の際には用途にあわせて設定を行う必要があります。

プロジェクトの準備

今回はOpenLayersのサイトで公開されている以下のサンプル「Mapbox-gl Layer」が参考になりそうなので、こちらをベースに作っていきます。先日試してみた公式サンプルに「mapbox-gl-js」のサンプルコードがありますので、こちらが一番親和性が高いと判断しました。

なお、サンプルコードからもわかるように、最近のOpenLayersはモジュール形式になっているのでnpmyarnを利用する必要があります。

サンプルコードのmain.jsindex.htmlpackage.jsonをフォルダ内に配置したら、下記のようにして必要なパッケージのインストールをして簡単に動作確認しておきます。

$ yarn install
$ yarn start

なお、この時点ではサンプルコードの実行に必要なAPIキーは設定していないので認証エラーで地図画像はでません。

サンプルコードの修正

では、このサンプルコードをベースとして修正を加えていきます。基本的な方針としては、下記で公開されているサンプルの「mapbox-gl-js」の処理を組み込んでいく方針です。

まず、index.htmlには、aws-sdkとamplifyのライブラリを読み込ませるようにします。また、地図の表示範囲もちょっと広げておきましょう。

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Mapbox-gl Layer</title>
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.784.0.min.js"></script>
    <script src="https://unpkg.com/@aws-amplify/core@3.7.0/dist/aws-amplify-core.min.js"></script>
    <!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
    <script src="https://unpkg.com/elm-pep"></script>
    <!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
    <script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=fetch,requestAnimationFrame,Element.prototype.classList,URL,TextDecoder"></script>
    <script src="https://unpkg.com/mapbox-gl@1.11.1/dist/mapbox-gl.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/mapbox-gl@1.11.1/dist/mapbox-gl.css">
    <style>
      .map {
        width: 100%;
        height: 95vh;
      }
      /* Reset font size changed by Mapbox CSS */
      .map {
        font-size: medium;
        font-family: 'Quattrocento Sans', sans-serif;
      }
    </style>
  </head>
  <body>
    <div id="map" class="map"></div>
    <script src="main.js"></script>
  </body>
</html>

次に、main.jsです。identityPoolIdmapNameはそれぞれ利用しているものに合わせて修正します。ちょっと気をつけるポイントとしては、リクエストのパラメータに署名が必要なので、initializeMapで async/await を利用してcredentials.getPromise()を呼んでいるあたりになります。また、今回は地図の中心を東京にしてみました。

main.js

import "ol/ol.css";
import Layer from "ol/layer/Layer";
import Map from "ol/Map";
import View from "ol/View";
import { fromLonLat, toLonLat } from "ol/proj";

// use Signer from @aws-amplify/core
const { Signer } = window.aws_amplify_core;

// configuration
// Cognito Identity Pool ID
const identityPoolId = "<Identity Pool ID>";

// Amazon Location Service Map Name
const mapName = "<Map name>";

// extract the region from the Identity Pool ID
AWS.config.region = identityPoolId.split(":")[0];

// instantiate a credential provider
const credentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: identityPoolId,
});

var center = [139.7696, 35.6796];

/**
 * Sign requests made by Mapbox GL using AWS SigV4.
 */
function transformRequest(url, resourceType) {
  if (resourceType === "Style" && !url.includes("://")) {
    // resolve to an AWS URL
    url = `https://maps.geo.${AWS.config.region}.amazonaws.com/maps/v0/maps/${url}/style-descriptor`;
  }

  if (url.includes("amazonaws.com")) {
    // only sign AWS requests (with the signature as part of the query string)
    return {
      url: Signer.signUrl(url, {
        access_key: credentials.accessKeyId,
        secret_key: credentials.secretAccessKey,
        session_token: credentials.sessionToken,
      }),
    };
  }

  // don't sign
  return { url };
}

/**
 * Initialize a map.
 */
async function initializeMap() {
  // load credentials and set them up to refresh
  await credentials.getPromise();

  const mbMap = new mapboxgl.Map({
    container: "map",
    center: center, // initial map centerpoint
    zoom: 10, // initial map zoom
    style: mapName,
    transformRequest,
  });

  var mbLayer = new Layer({
    render: function (frameState) {
      var canvas = mbMap.getCanvas();
      var viewState = frameState.viewState;

      var visible = mbLayer.getVisible();
      canvas.style.display = visible ? "block" : "none";

      var opacity = mbLayer.getOpacity();
      canvas.style.opacity = opacity;

      // adjust view parameters in mapbox
      var rotation = viewState.rotation;
      mbMap.jumpTo({
        center: toLonLat(viewState.center),
        zoom: viewState.zoom - 1,
        bearing: (-rotation * 180) / Math.PI,
        animate: false,
      });

      // cancel the scheduled update & trigger synchronous redraw
      // see https://github.com/mapbox/mapbox-gl-js/issues/7893#issue-408992184
      // NOTE: THIS MIGHT BREAK IF UPDATING THE MAPBOX VERSION
      if (mbMap._frame) {
        mbMap._frame.cancel();
        mbMap._frame = null;
      }
      mbMap._render();

      return canvas;
    },
  });

  var map = new Map({
    target: "map",
    view: new View({
      center: fromLonLat(center),
      zoom: 4,
    }),
    layers: [mbLayer],
  });
}

initializeMap();

地図を表示してみる

では、実際に地図を表示させてみましょう。

$ yarn start

ローカルサーバが立ち上がるのでhttp://localhost:1234にアクセスして確認します。

想定どおりに表示されました!

まとめ

以上、Amazon Location Service のマップをOpenLayersを利用して表示してみました。

当初は、OpenLayersの他のサンプルをベースに試していましたが、なかなかうまくいかずに困っていました。ですが、途中で「Mapbox-gl Layer」のサンプルを見つけ、このサンプルがあったおかげで簡単に表示することができました。今回は単純に1レイヤを表示していますが、OpenLayersを利用することで任意のレイヤーを重ねることもできそうですね。

どなたかのお役に立てば幸いです。それでは!