通过 Amazon Location Service 使用 Tangram - Amazon Location Service

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

通过 Amazon Location Service 使用 Tangram

Tangram 是一款灵活的地图引擎,专为实时渲染矢量图块中的 2D 和 3D 地图而设计。它可以与 Mapzen 设计的样式和 Amazon Location Service Maps API 提供的 HERE 图块一起使用。本指南描述了如何在基本的 HTML/ JavaScript 应用程序中将 Tangram 与 Amazon Location 集成,尽管在使用 React 和 Angular 等框架时也适用相同的库和技术。

七巧板建立在Leaflet之上,Le aflet 是一个用于移动设备友好型交互式地图的开源 JavaScript 库。这意味着许多与 Leaflet兼容的插件和控件也可以与 Tangram 一起使用。

使用 HERE 中的地图时,专为 Tilezen 架构而设计的 Tangram 风格在很大程度上与 Amazon Location 兼容。其中包括:

  • Bubble Wrap——功能齐全的寻路风格,带有用于显示兴趣点的有用图标

  • Cinnabar——经典外观,是普通测绘应用的首选

  • Refill——一种专为数据可视化叠加而设计的极简主义地图风格,灵感来自 Stamen Design 开创性的 Toner 风格

  • Tron——浏览 TRON 视觉语言中的比例变换

  • Walkabout——以户外为重点的风格,非常适合徒步旅行或外出旅行

本指南介绍如何使用名为 Bubble Wrap 的七巧板风格在基本 HTML/ JavaScript 应用程序中将七巧板与 Amazon Location 集成。此示例作为 Amazon Location Service 示例存储库的一部分提供GitHub

虽然其他 Tangram 风格最好搭配编码地形信息的栅格图块,但 Amazon Location 尚不支持此功能。

重要

以下教程中的 Tangram 样式仅与使用 VectorHereContrast 样式配置的 Amazon Location 地图资源兼容。

构建应用程序:支架

该应用程序是一个 HTML 页面, JavaScript 用于在您的 Web 应用程序上构建地图。创建 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 开发工具包 JavaScript。

<!-- CSS dependencies --> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" 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. 输入资源的名称和标识符。

    // 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 开发工具包工作流程之外的凭证,因此会话将在 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 重写 sources.mapzen.url 指向 Amazon Location,该主机名将由 Service Worker 处理。以下是使用 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, }, }, };

构建应用程序:请求转换

要注册和初始化 Service Worker,请创建一个要在地图初始化之前调用的 registerServiceWorker 函数。这将注册一个名sw.js为服务工作者控制的单独文件中提供的 JavaScript 代码index.html

凭证从 Amazon Cognito 加载,并与该地区一起传递给 Service Worker,以提供使用 Signature Version 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)); });

要自动续订凭证并在证书到期之前将其发送给 Service Worker,请在 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 服务器遵守 Service Worker 的安全限制。

要使用本地 Web 服务器,您可以使用 npx,因为它是作为 Node.js 的一部分安装的。可以在 index.htmlsw.js 的同一个目录中使用 npx serve。这为 localhost:5000 上的应用程序提供服务。

以下是 index.html 文件:

<!-- index.html --> <html> <head> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" 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)); });

此示例作为 Amazon Location Service 示例存储库的一部分提供GitHub