UX designers and web developers alike wish to have each component of a website meet the company’s branding requirements while providing a visual impact on its own. Web maps are no exception to this. They provide important visualizations for geographical data. The ability to customize is invaluable when a map is a big part of the user flow and needs to fit in with the rest of the site.
With Amazon Location Service, a managed AWS service for adding location data to applications, you can create a customized web map that will grab the interest of your users and increase engagement. At this time Location Service has two data providers, Esri and HERE, as well as six default map styles. So, how do you customize these map styles to meet your brand or visual design?
This README will walk you through the process of styling an existing map using Maputnik to help visualize the changes in real-time. Others may prefer to use another open source tool called Fresco, however they are not required to make edits to the style descriptor. In order to style our existing map from Amazon Location Service using these tools we will also demonstrate how to run a local proxy that handles Signature Version 4 / AWS Authentication.
The following walk through is split into 2 parts:
- How to style an existing Map from Amazon Location Service.
- How to integrate a custom map style in a React app
Note that the terminal commands will be Mac/Linux specific.
-
Amplify CLI (Note: If you have a local environment that uses several AWS accounts be sure to use the correct AWS CLI profile and log in to the correct account in the browser)
-
This walk-through uses "Amplify-Default" as the AWS CLI profile name and "us-west-2" as the AWS Region. Feel free to substitute these values.
-
Maputnik is an open source visual editor for the Mapbox Style Specification. At the time of writing this editor can be either used in the browser or downloaded as a binary and can run locally. We are going to use the latter option.
-
Go to https://github.com/maputnik/editor/wiki/Maputnik-CLI and follow the instructions for installation or optionally from your terminal you can run:
wget https://github.com/maputnik/editor/releases/download/v1.7.0/maputnik-darwin.zip unzip maputnik-darwin.zip chmod +x maputnik
The "Darwin" binary is Mac-specific; be sure to download the correct one for your OS.
-
Start up Maputnik
./maputnik
And open your browser to http://localhost:8000/#12.44/40.7356/-74.0541
-
It would be best to start with a clean canvas. Click "Open" in the top navigation bar, then scroll down to the bottom of "Gallery Styles" and selected "Empty Style".
Open A Style
Empty Canvas
-
Tessera is a pluggable map tile server. Using the power of the tilelive ecosystem, it is capable of serving and rendering map tiles from many sources. To stream data from most sources you can install the tilelive providers as npm packages. For an Amazon Location Service source we use tilelive-aws.
-
From your terminal run
mkdir tileserver cd tileserver
-
We'll need to init a new node project in the current directory
$ npm init --yes Wrote to ... /create-custom-map-style/tileserver/package.json: { "name": "tileserver", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
-
Next we need to add the Amazon Location Service API as a data source. However, in order to do this we need to use a local proxy that can handle SigV4 (TL;DR a way to provide your AWS credentials with http requests). For this we will use tessera and tilelive-aws. From a new terminal (the
maputnik
process will be occupying the other terminal) run the following:nvm use 15 npm i tessera tilelive-aws
-
Presuming you have the standard AWS environment variables (
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
,AWS_REGION
, orAWS_PROFILE
) set correctly, this should be enough to get started usingtessera
. You'll need to provide the full file path to your module, so from your terminal run:node_modules/tessera/bin/tessera -r tilelive-aws aws:///<YOUR AMAZON LOCATION SERVICE MAP NAME>
You can also add this as a script to your
package.json
file to save time on subsequent invocations. -
You should see some output in stdout like
Listening at http://0.0.0.0:8080
. -
Now go back to Maputnik in your browser and click "Data Sources" in the top navigation bar. There should not be any active data sources. If you see any be sure to delete them.
Data Sources
-
We're going to add a new data source as shown below.
-
Source ID
:esri
-
Source Type
:Vector (XYZ URLs)
-
1st Tile URL
:http://localhost:8080/{z}/{x}/{y}.pbf
-
Min Zoom
:0
-
Max Zoom
:22
New Data Source
-
In addition to Tiles, the type of web map we will be styling also makes use of Glyphs and Sprites. We can't use Tessera to proxy the endpoints for these, but we can however use some out of the box endpoints built in to Maputnik. Sprites:
https://maputnik.github.io/osm-liberty/sprites/osm-liberty
and Glyphs:https://api.maptiler.com/fonts/{fontstack}/{range}.pbf?key={key}
) -
If you want to download a copy of the Sprites and Glyphs for your Amazon Location Service Map, we've included a helper script
downloadSpritesGlyphs.sh
:./downloadSpritesGlyphs.sh <YOUR AMAZON LOCATION SERVICE MAP NAME> <YOUR AWS CLI PROFILE NAME>
This should create a folder for sprites
and a folder for glyphs
with a number of options for each. The Sprites endpoint can serve "Spritesheets" which consist of a paired set of .png
and .json
files. The .png
file will contain all of the images and the .json
file provides the offsets of each image within the .png
file. These images can be used on the map for things like points of interests, highways, etc. Glyphs are a combination of a font-family and a Unicode range. These can be used to display place names as text on the map.
-
We can start with a ready-made style template. Click "Open" in the top navigation bar and under "Upload Style" click "Upload" and select the
example-style-descriptor.json
file included in this repo. -
This is the style descriptor that is provided by Amazon Location Service as part of your Esri Map but with a few modifications. You can download this JSON file by making a request with the AWS CLI, i.e.
aws location get-map-style-descriptor --map-name <YOUR AMAZON LOCATION SERVICE MAP NAME> example-style-descriptor.json --profile <YOUR AWS PROFILE NAME>
-
Because we are proxying our map tiles through a local tileserver we have changed a few lines in the descriptor JSON to point to the local endpoint. (We'll change them back later).
-
Before
... "sources": { "amazon": { "type": "vector", "tiles": ["https://maps.geo.us-west-2.amazonaws.com/maps/v0/maps/<YOUR AMAZON LOCATION SERVICE MAP NAME>/tiles/{z}/{x}/{y}"], "minZoom": 0, "maxZoom": 22 } }, ...
After
... "sources": { "esri": { "attribution": "Esri, HERE, Garmin, FAO, NOAA, USGS, © OpenStreetMap contributors, and the GIS User Community", "maxzoom": 15, "tiles": ["http://localhost:8080/{z}/{x}/{y}.pbf"], "type": "vector" } }, "sprite": "https://maputnik.github.io/osm-liberty/sprites/osm-liberty", "glyphs": "https://api.maptiler.com/fonts/{fontstack}/{range}.pbf?key={key}", ...
-
If you see an
AccessDenied
error in thetessera
terminal you may need to manually update the IAM permissions for your role/user add actiongeo:GetMapTile
on resource:arn:aws:geo:<REGION>:<ACCOUNT>:map/<YOUR AMAZON LOCATION SERVICE MAP NAME>
. -
From the "View" dropdown let's select "Map"
-
Now we should see a list of layers appear on the left side of the screen
List of Layers
-
Let's select the
Building/fill
LayerThe Land/Not ice Layer
-
Scroll down to the JSON Editor section on the left navigation panel
The JSON Editor
-
Note that its fill color is
#f7f6d5
, let's change it to something funky like#EE84D9
Layer Before
Layer After
-
Now you can see how the map's style has changed a little, you play around some more by clicking on certain areas of the map and seeing which layer it corresponds. Feel free to change colors or outlines
To help you with your map styling decisions, here are some handy tips:
- Familiarize yourself with the MapLibre style document specification https://maplibre.org/maplibre-gl-js-docs/style-spec/
- Learn hexadecimal color values and pick a palette of colors
- Follow web accessibility guidelines to ensure there are no impediments to users with disabilities
- Plan for adding map markers later on and choose a color that will contrast
- Use gradients and color transparency for greater detail
- Reserve blue colors for water layers only
- Ensure legible text values by choosing the best label background and text halo
-
In your terminal create a workspace
mkdir blog cd blog
-
Clone this repo:
git clone https://github.com/aws-samples/amazon-location-samples.git cd amazon-location-samples/create-custom-map-style
-
Checkout Node version 15
nvm use 15
This allows you to use version 15 locally without setting a specific version globally (although you can install global packages for that particular version)
-
Install the application dependencies
npm install
-
Configure Amplify environment
amplify configure
You will be prompted with the following:$ amplify configure Follow these steps to set up access to your AWS account: Sign in to your AWS administrator account: https://console.aws.amazon.com/ Press Enter to continue Specify the AWS Region ? region: us-west-2 Specify the username of the new IAM user: ? user name: amplify-xxXXx Complete the user creation using the AWS console https://console.aws.amazon.com/iam/home?region=us-west-2#/users$new?step=final&accessKey&userNames=amplify-xxXXx&permissionType=policies&policies=arn:aws:iam::aws:policy%2FAdministratorAccess Press Enter to continue Enter the access key of the newly created user: ? accessKeyId: ******************** ? secretAccessKey: **************************************** This would update/create the AWS Profile in your local machine ? Profile Name: Amplify-Default Successfully set up the new user.
NOTE: If you give the Profile Name as Amplify-Default
as shown above you can substitute it for <YOUR AWS CLI PROFILE>
in the following steps.
-
Initialize your application
amplify init
$ amplify init ? Do you want to use an existing environment? No ? Enter a name for the environment <YOUR ENVIRONMENT NAME> Using default provider awscloudformation ? Select the authentication method you want to use: AWS profile For more information on AWS Profiles, see: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html ? Please choose the profile you want to use Amplify-Default Adding backend environment <YOUR ENVIRONMENT NAME> to AWS Amplify Console app: ....
-
Run
amplify status
to see what will be provisioned$ amplify status Current Environment: <YOUR ENVIRONMENT NAME> | Category | Resource name | Operation | Provider plugin | | -------- | ---------------------------- | --------- | ----------------- | | Hosting | S3AndCloudFront | No Change | awscloudformation | | Auth | createcustommapstyle0000000x | No Change | awscloudformation | | Geo | CreateCustomMapStyle | No Change | awscloudformation |
-
Push your backend to the cloud
amplify push
- It will create all the resources in the cloud$ amplify push ✔ Successfully pulled backend environment <YOUR ENVIRONMENT NAME> from the cloud. Current Environment: <YOUR ENVIRONMENT NAME> | Category | Resource name | Operation | Provider plugin | | -------- | ---------------------------- | --------- | ----------------- | | Hosting | S3AndCloudFront | Create | awscloudformation | | Auth | createcustommapstyle0000000x | Create | awscloudformation | | Geo | CreateCustomMapStyle | Create | awscloudformation | ? Are you sure you want to continue? Yes ⠋ Updating resources in the cloud. This may take a few minutes..
-
Wait for Cloudformation to provision the necessary resources. Once it's done you should get a message like this:
✔ All resources are updated in the cloud
-
The React App can then be run with
npm start
and the deployed app can be updated withamplify publish
whenever you like. -
You can follow the steps in How to style an existing Map from Amazon Location Service section to style the map created through Amplify. Once you are satisfied with your edits continue to the Publishing and Using Your Custom Maputnik Map section below.
-
On your Maputnik tab in the browser, click "Export" in the top navigation bar
-
You should save the file as
example-file-descriptor.json
and place it in your React project'ssrc/
directory -
Remember to change back the lines pointing to the local endpoints From
"tiles": ["http://localhost:8080/{z}/{x}/{y}.pbf"],
To
"tiles": ["https://maps.geo.us-west-2.amazonaws.com/maps/v0/maps/<YOUR AMAZON LOCATION SERVICE MAP NAME>/tiles/{z}/{x}/{y}"],
Also be sure to delete the
sprite
andglyph
attributes."sprite": "https://maps.geo.us-west-2.amazonaws.com/maps/v0/maps/<YOUR AMAZON LOCATION SERVICE MAP NAME>/sprites/sprites", "glyphs": "https://maps.geo.us-west-2.amazonaws.com/maps/v0/maps/<YOUR AMAZON LOCATION SERVICE MAP NAME>/glyphs/{fontstack}/{range}.pbf",
-
We can change the background color of the
.map-container
selector to something like#EE84D955
to see how the map style changes can be used to match the branding of the rest of a page. In your browser you can now refresh and see the changes you made in Maputnik show up in your own page.Changes Appear
-
Our React application includes an important library that uses WebGL to render interactive maps from vector tiles and Maplibre styles
npm install maplibre-gl --save
-
We start by importing this dependency in the
SamplePage
component... import maplibregl from 'maplibre-gl'; ...
-
Next, we make use of several React hooks. We add a reference (
useRef
) to the.demo-map
DOM node, make an asynchronous API call (useEffect
) to fetch data using Amplify, and we keep track of the latitude, longitude, and zoom level of the map (useState
)... import React, { useRef, useState, useEffect } from 'react'; ... import { createMap } from "maplibre-gl-js-amplify"; import style from './example-style-descriptor.json'; ... const SamplePage = () => { const map = useRef(); const [coordinates, setCoordinates] = useState({ lat: 40.7356, lng: -74.0541, zoom: 12.44, }); useEffect(async () => { const { lat, lng, zoom } = coordinates; const demoMap = await createMap({ container: map.current, center: [lng, lat], zoom, style, }); demoMap.on('move', () => { setCoordinates({ lng: demoMap.getCenter().lng.toFixed(4), lat: demoMap.getCenter().lat.toFixed(4), zoom: demoMap.getZoom().toFixed(2), }); }); return () => demoMap.remove(); }, []); ... <div ref={map} className="demo-map" />
-
maplibre-gl-js-amplify
takes care of authenticating our user before calling Amazon Location Service when fetching the map tiles. -
You should be able to start your React app and see the base map style show up in your browser now:
Esri Streets Base Map
To avoid incurring future charges, delete the resources used in this tutorial. In the CloudFormation console you can delete the root stack amplify-createcustommapstyle-<YOUR ENV NAME>-xxxxxx
which includes:
S3AndCloudFront
createcustommapstyle0000000x
CreateCustomMapStyle
Using Amazon Location Service with other AWS Services and open source tools such as Maputnik and MapLibre, you can create a fully customizable Map style especially when maps are an integral part of your application and you want to give your maps a personalized touch. Now, if you have an idea about how you can achieve the map customization, you can also try it with Fresco, Mapbox Studio, or other open source tools for editing.