Uso de Tangram con Amazon Location Service
Tangram
Tangram se basa en Leaflet
Los estilos de Tangram diseñados para funcionar con el esquema de Tilezen
-
Bubble Wrap
: un estilo de orientación con todas las funciones e íconos útiles para los puntos de interés -
Cinnabar
: un diseño clásico, ideal para aplicaciones generales de cartografía -
Refill
: un estilo de mapa minimalista diseñado para superposiciones de visualización de datos, inspirado en el popular estilo de Toner de Stamen Design -
Tron
: una exploración de las transformaciones de escala en el lenguaje visual de TRON -
Walkabout
: un estilo centrado en actividades al aire libre que es perfecto para practicar senderismo o salir a pasear
Esta guía describe cómo integrar Tangram con Amazon Location en una aplicación básica de HTML/JavaScript utilizando el estilo Tangram llamado Bubble Wrap
Si bien otros estilos de Tangram se combinan mejor con mosaicos ráster, que codifican la información del terreno, Amazon Location aún no admite esta característica.
importante
Los estilos Tangram del siguiente tutorial sólo son compatibles con los recursos de mapa de Amazon Location configurados con el estilo VectorHereContrast
.
Creación de la aplicación: andamiaje
La aplicación es una página HTML con JavaScript para crear el mapa en su aplicación web. Cree una página HTML (index.html
) y cree el contenedor del mapa:
-
Introduzca un elemento
div
con unid
de mapa para aplicar las dimensiones del mapa a la vista del mapa. -
Las dimensiones se heredan de la ventana de visualización.
<html> <head> <style> body { margin: 0; } #map { height: 100vh; /* 100% of viewport height */ } </style> </head> <body> <!-- map container --> <div id="map" /> </body> </html>
Creación de la aplicación: adición de dependencias
Agregue la siguiente dependencia:
-
Leaflet y su CSS asociado.
-
Tangram.
-
SDK de AWS para 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>
Esto crea una página vacía con los requisitos previos necesarios. El siguiente paso lo guiará a través de la escritura del código JavaScript para su aplicación.
Creación de la aplicación: configuración
Para configurar su aplicación con sus recursos y credenciales:
-
Introduzca los nombres e identificadores de sus recursos.
// 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
"; -
Cree una instancia de un proveedor de credenciales mediante el grupo de identidades no autenticadas que creó en Uso de mapas: paso 2, configurar la autenticación. Como se utilizan credenciales ajenas al flujo de trabajo normal del SDK de AWS, las sesiones caducan al cabo de una hora.
// 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
, }); -
Si bien Tangram le permite anular las URL utilizadas para recuperar los mosaicos, no incluye la posibilidad de interceptar las solicitudes para firmarlas.
Para solucionar este problema, anule
sources.mapzen.url
para que apunte a Amazon Location con un nombre de host sintéticoamazon.location
, que será gestionado por un empleado del servicio.El siguiente es un ejemplo de configuración de escena que usa 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, }, }, };
Creación de la aplicación: solicite la transformación
Para registrar e inicializar el service worker, cree una función registerServiceWorker
a la que se pueda llamar antes de inicializar el mapa. Esto registra el código JavaScript proporcionado en un archivo separado llamado sw.js
como el trabajador de servicio que controla index.html
.
Las credenciales se cargan desde Amazon Cognito y se transmiten al empleado del servicio junto con la región para proporcionar la información necesaria para firmar las solicitudes de mosaicos con la versión 4 de Signature.
/** * 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"); } }
La implementación de Service Worker en sw.js
escucha los eventos message
para detectar los cambios en la configuración de las credenciales y la región. También actúa como un servidor proxy al escuchar los eventos fetch
. Los eventos fetch
que se dirijan al nombre de host sintético de amazon.location
se reescribirán para que se dirijan a la API Amazon Location correspondiente y se firmarán con 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)); });
Para renovar automáticamente las credenciales y enviárselas al empleado del servicio antes de que caduquen, utilice la siguiente función: 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()); }
Creación de la aplicación: inicialización del mapa
Para que el mapa se muestre después de cargar la página, debe inicializarlo. Tiene la opción de ajustar la ubicación inicial del mapa, agregar controles adicionales y superponer datos.
nota
Debe proporcionar una marca denominativa o una atribución de texto para cada proveedor de datos que utilice, ya sea en su solicitud o en su documentación. Los string de atribución se incluyen en la respuesta del descriptor de estilo, debajo de las claves sources.esri.attribution
, sources.here.attribution
, y source.grabmaptiles.attribution
.
Como Tangram no solicita estos recursos y solo es compatible con los mapas de HERE, use “© 2020 HERE”. Cuando utilice los recursos de Amazon Location con proveedores de datos, asegúrese de leer los términos y condiciones del servicio
/** * 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();
Ejecución de la aplicación
Para ejecutar este ejemplo, puede:
-
Utilizar un host que admita HTTPS,
-
Utilizar un servidor web local para cumplir con las restricciones de seguridad de los trabajadores del servicio.
Para usar un servidor web local, puede usar npx, porque está instalado como parte de Node.js. Puede usar npx serve
desde el mismo directorio que index.html
y sw.js
. Esto sirve a la aplicación en localhost:5000
El archivo es el siguiente: 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>
El archivo es el siguiente: 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)); });
Esta muestra está disponible como parte del repositorio de muestras de Amazon Location Service en GitHub