Amazon Location Service で Tagram を使用する - Amazon Location Service

Amazon Location Service で Tagram を使用する

Tangram は柔軟性の高いマッピングエンジンで、ベクタータイルから 2D および 3D マップをリアルタイムでレンダリングできるように設計されています。MapZen が設計したスタイルと Amazon Location Service マップ API が提供する HERE タイルで使用できます。このガイドでは、基本的な HTML/JavaScript アプリケーション内で Tangram と Amazon Location を統合する方法について説明します。ただし、React や Angular などのフレームワークを使用する場合にも同じライブラリとテクニックが適用できます。

Tangram は、モバイルフレンドリーなインタラクティブマップ用のオープンソースの JavaScript ライブラリである Leaflet の上に構築されています。つまり、Leaflet と互換性のあるプラグインやコントロールの多くは Tangram でも動作するということです。

Tilezen スキーマで動作するように構築されたTangram スタイルは、HERE のマップを使用する場合には Amazon Location とほぼ互換性があります。具体的には次のとおりです。

  • Bubble Wrap — 興味のある地点をわかりやすく表示する便利なアイコンが付いた、フル機能の道案内スタイル

  • Cinnabar — クラシックな外観で、一般的なマップアプリ向け

  • Refill — Stamen Design の独創的な Toner スタイルにインスパイアされた、データ視覚化オーバーレイ用にデザインされたミニマルなマップスタイル

  • Tron — TRON のビジュアル言語によるスケール返還を探求したスタイル

  • Walkabout — ハイキングや外出に最適なアウトドア重視のスタイル

このガイドでは、Bubble Wrap と呼ばれる Tangram スタイルを使用して、基本的な HTML/JavaScript アプリケーション内で Tangram を Amazon Location と統合する方法について説明します。このサンプルは、GitHub の Amazon Location Service サンプルリポジトリから入手できます。

他の Tangram スタイルには地形情報をエンコードするラスタータイルが最適ですが、この機能はまだ Amazon Location ではサポートされていません。

重要

以下のチュートリアルの Tangram スタイルは、VectorHereContrast スタイルで設定された Amazon Location マップリソースとのみ互換性があります。

アプリケーションの構築: Scaffolding

このアプリケーションは、Web アプリケーション上にマップを構築するための JavaScript を備えた HTML ページです。マップのコンテナを含む HTML ページ (index.html) を作成します。

  • マップの id が付いた div 要素を入力し、マップの寸法をマップビューに適用します。

  • 寸法はビューポートから継承されます。

<html> <head> <style> body { margin: 0; } #map { height: 100vh; /* 100% of viewport height */ } </style> </head> <body> <!-- map container --> <div id="map" /> </body> </html>

アプリケーションの構築: 依存関係の追加

以下の依存関係を追加します。

  • Leaflet とそれに関連する CSS。

  • Tangram。

  • AWS SDK for JavaScript。

<!-- CSS dependencies --> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" crossorigin="" /> <!-- JavaScript dependencies --> <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script> <script src="https://unpkg.com/tangram"></script> <script src="https://sdk.amazonaws.com/js/aws-sdk-2.784.0.min.js"></script> <script> // application-specific code </script>

これにより、必要な前提条件を含む空のページが作成されます。次のステップでは、アプリケーションの JavaScript コードを作成する手順を説明します。

アプリケーションの構築: 設定

リソースと認証情報を使用してアプリケーションを設定します。

  1. リソースの名前と ID を入力します。

    // Cognito Identity Pool ID const identityPoolId = "us-east-1:54f2ba88-9390-498d-aaa5-0d97fb7ca3bd"; // Amazon Location Service map name; must be HERE-backed const mapName = "TangramExampleMap";
  2. マップを使用する - ステップ 2. 認証を設定する」で作成した認証されていない アイデンティティプールを使用して、認証情報プロバイダーをインスタンス化します。通常の AWS SDK ワークフローではない認証情報を使用するため、セッションの有効期限は 1 時間となっています。

    // extract the region from the Identity Pool ID; this will be used for both Amazon Cognito and Amazon Location AWS.config.region = identityPoolId.split(":", 1)[0]; // instantiate a Cognito-backed credential provider const credentials = new AWS.CognitoIdentityCredentials({ IdentityPoolId: identityPoolId, });
  3. Tangram ではタイルの取得に使用する URL を上書きできますが、リクエストをインターセプトして署名する機能は含まれていません。

    その解決策として、合成ホスト名 amazon.location を使用して Amazon Location を指すように sources.mapzen.url をオーバーライドします。このホスト名はサービスワーカーが処理します。以下は、Bubble Wrap を使用したシーン設定の例です。

    const scene = { import: [ // Bubble Wrap style "https://www.nextzen.org/carto/bubble-wrap-style/10/bubble-wrap-style.zip", "https://www.nextzen.org/carto/bubble-wrap-style/10/themes/label-7.zip", "https://www.nextzen.org/carto/bubble-wrap-style/10/themes/bubble-wrap-road-shields-usa.zip", "https://www.nextzen.org/carto/bubble-wrap-style/10/themes/bubble-wrap-road-shields-international.zip", ], // override values beneath the `sources` key in the style above sources: { mapzen: { // point at Amazon Location using a synthetic URL, which will be handled by the service // worker url: `https://amazon.location/${mapName}/{z}/{x}/{y}`, }, // effectively disable raster tiles containing encoded normals normals: { max_zoom: 0, }, "normals-elevation": { max_zoom: 0, }, }, };

アプリケーションの構築: リクエスト変換

サービスワーカーを登録して初期化するには、マップが初期化される前に呼び出される registerServiceWorker 関数を作成します。これにより、サービスワーカーが index.html をコントロールし、sw.js と呼ばれる別のファイルに提供される JavaScript コードが登録されます。

認証情報は Amazon Cognito から読み込まれ、リージョンと一緒にサービスワーカーに渡されます。この情報は、署名バージョン 4 でタイルリクエストに署名するために使われます。

/** * Register a service worker that will rewrite and sign requests using Signature Version 4. */ async function registerServiceWorker() { if ("serviceWorker" in navigator) { try { const reg = await navigator.serviceWorker.register("./sw.js"); // refresh credentials from Amazon Cognito await credentials.refreshPromise(); await reg.active.ready; if (navigator.serviceWorker.controller == null) { // trigger a navigate event to active the controller for this page window.location.reload(); } // pass credentials to the service worker reg.active.postMessage({ credentials: { accessKeyId: credentials.accessKeyId, secretAccessKey: credentials.secretAccessKey, sessionToken: credentials.sessionToken, }, region: AWS.config.region, }); } catch (error) { console.error("Service worker registration failed:", error); } } else { console.warn("Service worker support is required for this example"); } }

sw.js の Service Worker は、認証情報やリージョンの設定変更を拾うために message のイベントを待ち受けるよう実装されています。また、fetch のイベントを監視することでプロキシサーバーとしても機能します。amazon.location 合成ホスト名をターゲットとする fetch イベントは、適切な Amazon Location API を対象とするように書き替えられ、Amplify Core の Signer を使用して署名されます。

// sw.js self.importScripts( "https://unpkg.com/@aws-amplify/core@3.7.0/dist/aws-amplify-core.min.js" ); const { Signer } = aws_amplify_core; let credentials; let region; self.addEventListener("install", (event) => { // install immediately event.waitUntil(self.skipWaiting()); }); self.addEventListener("activate", (event) => { // control clients ASAP event.waitUntil(self.clients.claim()); }); self.addEventListener("message", (event) => { const { data: { credentials: newCredentials, region: newRegion }, } = event; if (newCredentials != null) { credentials = newCredentials; } if (newRegion != null) { region = newRegion; } }); async function signedFetch(request) { const url = new URL(request.url); const path = url.pathname.slice(1).split("/"); // update URL to point to Amazon Location url.pathname = `/maps/v0/maps/${path[0]}/tiles/${path.slice(1).join("/")}`; url.host = `maps.geo.${region}.amazonaws.com`; // strip params (Tangram generates an empty api_key param) url.search = ""; const signed = Signer.signUrl(url.toString(), { access_key: credentials.accessKeyId, secret_key: credentials.secretAccessKey, session_token: credentials.sessionToken, }); return fetch(signed); } self.addEventListener("fetch", (event) => { const { request } = event; // match the synthetic hostname we're telling Tangram to use if (request.url.includes("amazon.location")) { return event.respondWith(signedFetch(request)); } // fetch normally return event.respondWith(fetch(request)); });

認証情報を自動的に更新し、有効期限が切れる前にサービスワーカーに送信するには、index.html で以下の関数を使用します。

async function refreshCredentials() { await credentials.refreshPromise(); if ("serviceWorker" in navigator) { const controller = navigator.serviceWorker.controller; controller.postMessage({ credentials: { accessKeyId: credentials.accessKeyId, secretAccessKey: credentials.secretAccessKey, sessionToken: credentials.sessionToken, }, }); } else { console.warn("Service worker support is required for this example."); } // schedule the next credential refresh when they're about to expire setTimeout(refreshCredentials, credentials.expireTime - new Date()); }

アプリケーションの構築: マップの初期化

ページが読み込まれた後にマップを表示するには、マップを初期化する必要があります。マップの初期位置を調整したり、コントロールを追加したり、データをオーバーレイしたりするオプションがあります。

注記

使用する各データプロバイダーのワードマークまたはテキストアトリビューションを、アプリケーションまたはドキュメントに記載する必要があります。アトリビューション文字列は、スタイル記述子のレスポンスの sources.esri.attributionsources.here.attributionsource.grabmaptiles.attribution キーに含まれています。

Tangram はこれらのリソースをリクエストせず、HERE のマップとのみ互換性があるため、「© 2020 HERE」を使用してください。Amazon Location リソースをデータプロバイダーと併用する場合は、サービスの利用規約を必ずお読みください。

/** * Initialize a map. */ async function initializeMap() { // register the service worker to handle requests to https://amazon.location await registerServiceWorker(); // Initialize the map const map = L.map("map").setView([49.2819, -123.1187], 10); Tangram.leafletLayer({ scene, }).addTo(map); map.attributionControl.setPrefix(""); map.attributionControl.addAttribution("© 2020 HERE"); } initializeMap();

アプリケーションを実行する

このサンプルを実行するには、次の方法があります。

  • HTTPS をサポートするホストを使用する。

  • ローカル Web サーバーを使用して、サービスワーカーのセキュリティ制限に準拠する。

ローカル Web サーバーで使用するには、npx を使用できます。Node.js の一部としてインストールされるためです。npx serve は、index.html および sw.js と同じディレクトリ内から使用できます。これによってアプリケーションに localhost:5000 が提供されます。

以下は、index.html ファイルです。

<!-- index.html --> <html> <head> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" crossorigin="" /> <style> body { margin: 0; } #map { height: 100vh; } </style> </head> <body> <div id="map" /> <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script> <script src="https://unpkg.com/tangram"></script> <script src="https://sdk.amazonaws.com/js/aws-sdk-2.784.0.min.js"></script> <script> // configuration // Cognito Identity Pool ID const identityPoolId = "<Identity Pool ID>"; // Amazon Location Service Map name; must be HERE-backed const mapName = "<Map name>"; AWS.config.region = identityPoolId.split(":")[0]; // instantiate a credential provider credentials = new AWS.CognitoIdentityCredentials({ IdentityPoolId: identityPoolId, }); const scene = { import: [ // Bubble Wrap style "https://www.nextzen.org/carto/bubble-wrap-style/10/bubble-wrap-style.zip", "https://www.nextzen.org/carto/bubble-wrap-style/10/themes/label-7.zip", "https://www.nextzen.org/carto/bubble-wrap-style/10/themes/bubble-wrap-road-shields-usa.zip", "https://www.nextzen.org/carto/bubble-wrap-style/10/themes/bubble-wrap-road-shields-international.zip", ], // override values beneath the `sources` key in the style above sources: { mapzen: { // point at Amazon Location using a synthetic URL, which will be handled by the service // worker url: `https://amazon.location/${mapName}/{z}/{x}/{y}`, }, // effectively disable raster tiles containing encoded normals normals: { max_zoom: 0, }, "normals-elevation": { max_zoom: 0, }, }, }; /** * Register a service worker that will rewrite and sign requests using Signature Version 4. */ async function registerServiceWorker() { if ("serviceWorker" in navigator) { try { const reg = await navigator.serviceWorker.register("./sw.js"); // refresh credentials from Amazon Cognito await credentials.refreshPromise(); await reg.active.ready; if (navigator.serviceWorker.controller == null) { // trigger a navigate event to active the controller for this page window.location.reload(); } // pass credentials to the service worker reg.active.postMessage({ credentials: { accessKeyId: credentials.accessKeyId, secretAccessKey: credentials.secretAccessKey, sessionToken: credentials.sessionToken, }, region: AWS.config.region, }); } catch (error) { console.error("Service worker registration failed:", error); } } else { console.warn("Service Worker support is required for this example"); } } /** * Initialize a map. */ async function initializeMap() { // register the service worker to handle requests to https://amazon.location await registerServiceWorker(); // Initialize the map const map = L.map("map").setView([49.2819, -123.1187], 10); Tangram.leafletLayer({ scene, }).addTo(map); map.attributionControl.setPrefix(""); map.attributionControl.addAttribution("© 2020 HERE"); } initializeMap(); </script> </body> </html>

以下は、sw.js ファイルです。

// sw.js self.importScripts( "https://unpkg.com/@aws-amplify/core@3.7.0/dist/aws-amplify-core.min.js" ); const { Signer } = aws_amplify_core; let credentials; let region; self.addEventListener("install", (event) => { // install immediately event.waitUntil(self.skipWaiting()); }); self.addEventListener("activate", (event) => { // control clients ASAP event.waitUntil(self.clients.claim()); }); self.addEventListener("message", (event) => { const { data: { credentials: newCredentials, region: newRegion }, } = event; if (newCredentials != null) { credentials = newCredentials; } if (newRegion != null) { region = newRegion; } }); async function signedFetch(request) { const url = new URL(request.url); const path = url.pathname.slice(1).split("/"); // update URL to point to Amazon Location url.pathname = `/maps/v0/maps/${path[0]}/tiles/${path.slice(1).join("/")}`; url.host = `maps.geo.${region}.amazonaws.com`; // strip params (Tangram generates an empty api_key param) url.search = ""; const signed = Signer.signUrl(url.toString(), { access_key: credentials.accessKeyId, secret_key: credentials.secretAccessKey, session_token: credentials.sessionToken, }); return fetch(signed); } self.addEventListener("fetch", (event) => { const { request } = event; // match the synthetic hostname we're telling Tangram to use if (request.url.includes("amazon.location")) { return event.respondWith(signedFetch(request)); } // fetch normally return event.respondWith(fetch(request)); });

このサンプルは、GitHub の Amazon Location Service サンプルリポジトリから入手できます。