Build applications using CARTO & Amazon Location.

Getting started

In this guide, you will learn the basics of visualizing a CARTO layer with the Amazon Location. There are no previous requirements to complete this guide, but a basic knowledge of web development would be helpful.

After completing this guide, you will have your first Amazon Location map with a CARTO dataset!

Introduction

There are two main steps for visualizing a CARTO layer: first you create a map resource in your AWS account, and then you create a web application that uses a rendering library to visualize the map and the CARTO layer on top of this map.

For this guide, we have already created a map resource called “Rivers” in our AWS account. If you want to create your own map resource, you can follow the instructions from the Developer Guide.

Basic setup

The first thing you need to do is to add all the required Amazon (AWS SDK for Javascript and AWS Amplify core) and Mapbox GL dependencies (library and CSS files):

1
2
3
4
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.775.0.min.js"></script>
<script src="https://unpkg.com/@aws-amplify/core@3.7.0/dist/aws-amplify-core.min.js"></script>
<script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet" />

Add map container

Next, you need to create a div inside the body where the map will be drawn and you need to style them to ensure the map displays at full width:

1
2
3
<body style="margin: 0; padding: 0;">
    <div id="map" style="position: absolute; top: 0; bottom: 0; width: 100%;"></div>
</body>

Setup credentials to access to your map resource

In order to provide access to your map resource, you can use IAM directly or you can use Amazon Cognito authentication if you want to provide access to unauthenticated users or you want to use your own authentication system.

In this guide, we want our map to be accessible to unauthenticated users, so we are using Amazon Cognito. We have created an identity pool that allows access to unauthenticated entities. We have also added a policy to grant read-only access to our map resource.

Now, in the application code, we instantiate a credential provider using Cognito:

1
2
3
4
const identityPoolId = "us-east-2:303d12f6-e24e-4571-8a79-66cc7c6a6bdc"; // Cognito Identity Pool ID
const credentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: identityPoolId,
});

Initialize the map

Before instantiating the Mapbox GL JS map we need to obtain the credentials from the Cognito identity provider. This is an asynchronous operation, so we need to wait for the credentials. After we have obtained the credentials, we create our Map object assigning the style property to our Amazon Location Service map name. We also need to define the transformRequest handler to sign our requests to AWS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function initializeMap() {
  await credentials.getPromise();

  const mapName = "Rivers";

  // actually initialize the map
  map = new mapboxgl.Map({
    container: "map",
    center: [20, 49], 
    zoom: 4, 
    style: mapName,
    transformRequest,
  });

  map.addControl(new mapboxgl.NavigationControl(), "top-left");
}

Sign the requests to AWS

We need to sign the requests from Mapbox GL JS to our map resource using the credentials. In order to do that, we take advantage of the transformRequest option to intercept the requests and modify them before they are sent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
AWS.config.region = identityPoolId.split(":")[0];

const { Signer } = window.aws_amplify_core;

function transformRequest(url, resourceType) {
  if (resourceType === "Style" && !url.includes("://")) {
    url = `https://maps.geo.${AWS.config.region}.amazonaws.com/maps/v0/maps/${url}/style-descriptor`;
  }
  if (url.includes("amazonaws.com")) {
    return {
      url: Signer.signUrl(url, {
        access_key: credentials.accessKeyId,
        secret_key: credentials.secretAccessKey,
        session_token: credentials.sessionToken,
      }),
    };
  }
  return { url };
}

Initialize map

Now we just need to call the function to initialize our map:

1
initializeMap();

At this point you will have a basic map with the Rivers Amazon Location map resource:

View this step here

Add CARTO layer

In order to visualize the CARTO tileset, we are going to take advantage of the new TileJSON endpoints in the Maps API v2. We just need to provide the endpoint URL through the source.url property while calling the addLayer method on the map.

We are using a public tileset generated using our BigQuery Tiler and we are assigning a different color to each line representing a river, depending on the value of the bearing attribute.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
map.addLayer(
  {
    id: 'rivers_carto',
    type: 'line',
    source: {
      type: 'vector',
      url: 'https://maps-api-v2.us.carto.com/user/public/bigquery/tileset?source=cartobq.maps.eurivers&format=tilejson&api_key=default_public&client=amazon-location'
    },
    'source-layer': 'default',
    "paint": {
      "line-color": {
        "property": "bearing",
        "type": "interval",
        "stops": [
          [{"zoom": 0, "value": 0}, "rgba(255, 0, 128, 1)"],
          [{"zoom": 0, "value": 18}, "rgba(212, 7, 146, 1)"],
          [{"zoom": 0, "value": 37}, "rgba(212, 7, 146, 1)"],
          [{"zoom": 0, "value": 56}, "rgba(170, 13, 164, 1)"],
          [{"zoom": 0, "value": 75}, "rgba(128, 20, 181, 1)"],
          [{"zoom": 0, "value": 94}, "rgba(85, 26, 199, 1)"],
          [{"zoom": 0, "value": 113}, "rgba(43, 33, 217, 1)"],
          [{"zoom": 0, "value": 132}, "rgba(0, 39, 235, 1)"],
          [{"zoom": 0, "value": 151}, "rgba(3, 72, 217, 1)"],
          [{"zoom": 0, "value": 170}, "rgba(43, 33, 217, 1)"],
          [{"zoom": 0, "value": 189}, "rgba(9, 138, 181, 1)"],
          [{"zoom": 0, "value": 208}, "rgba(12, 170, 164, 1)"],
          [{"zoom": 0, "value": 227}, "rgba(15, 203, 146, 1)"],
          [{"zoom": 0, "value": 246}, "rgba(18, 236, 128, 1)"],
          [{"zoom": 0, "value": 265}, "rgba(58, 197, 128, 1)"],
          [{"zoom": 0, "value": 284}, "rgba(97, 157, 128, 1)"],
          [{"zoom": 0, "value": 303}, "rgba(136, 118, 128, 1)"],
          [{"zoom": 0, "value": 322}, "rgba(176, 79, 128, 1)"],
          [{"zoom": 0, "value": 341}, "rgba(215, 39, 128, 1)"],
          [{"zoom": 0, "value": 360}, "rgba(255, 0, 128, 1)"]
        ]
      }
    }
  }
);

All together

Finally we need to add the layer to the map after it is loaded:

1
2
3
4
5
6
async function initializeMap() {
  ...
  map.on('load', () => {
    addCartoLayer();
  })
}

You can explore the final step here

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<!DOCTYPE html>
<html>
  
  <head>
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.775.0.min.js"></script>
    <script src="https://unpkg.com/@aws-amplify/core@3.7.0/dist/aws-amplify-core.min.js"></script>
    <script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet" />
  </head>

  <body style="margin: 0; padding: 0;">
    <div id="map" style="position: absolute; top: 0; bottom: 0; width: 100%;"></div>
  </body>
  
  <script>

    let map;

    // instantiate a Cognito-backed credential provider
    const identityPoolId = "us-east-2:303d12f6-e24e-4571-8a79-66cc7c6a6bdc"; // Cognito Identity Pool ID
    const credentials = new AWS.CognitoIdentityCredentials({
      IdentityPoolId: identityPoolId,
    });

    /**
     * Sign requests made by Mapbox GL using AWS SigV4.
     */
    AWS.config.region = identityPoolId.split(":")[0];
    const { Signer } = window.aws_amplify_core;
    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 mapName = "Rivers"; // Amazon Location Service Map Name

      map = new mapboxgl.Map({
        container: "map",
        center: [20, 49], 
        zoom: 4, 
        style: mapName,
        transformRequest,
      });

      map.addControl(new mapboxgl.NavigationControl(), "top-left");

      map.on('load', () => {
        addCartoLayer();
      })
    }

    async function addCartoLayer() {
      map.addLayer(
        {
          id: 'rivers_carto',
          type: 'line',
          source: {
            type: 'vector',
            url: 'https://maps-api-v2.us.carto.com/user/public/bigquery/tileset?source=cartobq.maps.eurivers&format=tilejson&api_key=default_public&client=amazon-location'

          },
          'source-layer': 'default',
          "paint": {
            "line-color": {
              "property": "bearing",
              "type": "interval",
              "stops": [
                [{"zoom": 0, "value": 0}, "rgba(255, 0, 128, 1)"],
                [{"zoom": 0, "value": 18}, "rgba(212, 7, 146, 1)"],
                [{"zoom": 0, "value": 37}, "rgba(212, 7, 146, 1)"],
                [{"zoom": 0, "value": 56}, "rgba(170, 13, 164, 1)"],
                [{"zoom": 0, "value": 75}, "rgba(128, 20, 181, 1)"],
                [{"zoom": 0, "value": 94}, "rgba(85, 26, 199, 1)"],
                [{"zoom": 0, "value": 113}, "rgba(43, 33, 217, 1)"],
                [{"zoom": 0, "value": 132}, "rgba(0, 39, 235, 1)"],
                [{"zoom": 0, "value": 151}, "rgba(3, 72, 217, 1)"],
                [{"zoom": 0, "value": 170}, "rgba(43, 33, 217, 1)"],
                [{"zoom": 0, "value": 189}, "rgba(9, 138, 181, 1)"],
                [{"zoom": 0, "value": 208}, "rgba(12, 170, 164, 1)"],
                [{"zoom": 0, "value": 227}, "rgba(15, 203, 146, 1)"],
                [{"zoom": 0, "value": 246}, "rgba(18, 236, 128, 1)"],
                [{"zoom": 0, "value": 265}, "rgba(58, 197, 128, 1)"],
                [{"zoom": 0, "value": 284}, "rgba(97, 157, 128, 1)"],
                [{"zoom": 0, "value": 303}, "rgba(136, 118, 128, 1)"],
                [{"zoom": 0, "value": 322}, "rgba(176, 79, 128, 1)"],
                [{"zoom": 0, "value": 341}, "rgba(215, 39, 128, 1)"],
                [{"zoom": 0, "value": 360}, "rgba(255, 0, 128, 1)"]
              ]
            }
          }
        }
      );
    }

    initializeMap();

  </script>

</html>