Build applications using CARTO & Mapbox GL.

Getting started

In this guide, you will learn the basics of visualizing a CARTO dataset with the Mapbox GL JS library. There are no previous requirements to complete this guide, but a basic knowledge of the Mapbox GL JS library would be helpful.

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

Basic setup

The first thing you need to do is to add all the required Mapbox GL dependencies (library and CSS files):

1
2
<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>

Create map and set properties

Once you have a div for your map, you can use the mapboxgl.Map constructor to create your map with the desired initial view. Here we are also specifying the style property to use one of the CARTO basemaps. If you want to use a Mapbox basemap, you will need to provide your Mapbox access token:

1
2
3
4
5
6
const map = new mapboxgl.Map({
    container: 'map',
    style: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',
    center: [0, 0],
    zoom: 1
});

At this point you will have a basic map with the Voyager CARTO basemap:

View this step here

Add layer

In order to visualize a CARTO dataset, we need to provide vector tiles source URLs through the source.tiles property while calling the addLayer method on the map. We also need to indicate the ID for the layer and the styling properties:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
map.addLayer(
    {
    id: 'populated_places',
    type: 'circle',
    source: {
        type: 'vector',
        tiles: tileSourceURLs
    },
    'source-layer': 'layer0',
    paint: {
        'circle-color': '#EE4D5A',
        'circle-stroke-color': '#FFF',
        'circle-stroke-width': 1
    }
    }
);

The tiles source URLs need to be retrieved using the Maps API. You can go to the docs if you want to know more about the possibilities of the Maps API but, for this example, we will focus on the basic functionality.

MapConfig

When we want to use the Maps API, we first need to do a process called instantiating the map. This instantiation requires to send a payload called a MapConfig where we specify the SQL statement to retrieve the information for the dataset we want to visualize and additional options like the map type or the tile extent size.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const mapConfig = JSON.stringify({
    version: '1.3.1',
    buffersize: {mvt: 1},
    layers: [
        {
            type: 'mapnik',
            options: {
                sql: 'SELECT the_geom_webmercator, name FROM populated_places',
                vector_extent: 4096,
                bufferSize: 1,
                version: '1.3.1'
            }
        }
    ]
});

In order to have the better performance, we recommend you to retrieve only the fields you want to use client-side, instead of selecting all the fields (SELECT *). If you select all the fields from the dataset, the vector tiles will be bigger than needed and would take more time to encode, download and decode.

Maps API Endpoint

This MapConfig must be sent to the Maps API endpoint. This endpoint has the following template:

https://{username}.carto.com/api/v1/map/?api_key={api_key}

In order to render data from CARTO you need to have a CARTO account and then get the necessary credentials. The credentials consists of a username and a corresponding API key. The API key determines what datasets can access the Maps API, so you need to create an API key with permissions to the dataset you want to visualize. For guides and examples, we use the public CARTO account so you can try out the library:

https://public.carto.com/api/v1/map/?api_key=default_public

Instantiating the map

Now that we have our endpoint and our MapConfig, we can call the Maps API to get the tile sources URLs. We can use the fetch function with a Request object to retrieve the information asynchronously. We are instantiating an anonymous map and the response contains, among other information, a metadata object about the layers requested in the MapConfig. This metadata object contains a tilejson property with information about the tile sources URLs:

1
2
const response = await fetch(request);
tileSourceURLs = (await response.json()).metadata.tilejson.vector.tiles

MapConfig objects can be quite large and, depending on the browser and the web server, we might hit a limit with the URL length. We recommend you to always use a GET request if possible, but if you are getting URL length errors, you should change to POST requests. In the complete example, we are checking the length against a 2048 character limit but this could be different depending on the browsers you want to support.

All together

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
<!DOCTYPE html>
<html>
  
  <head>
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <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>

    const map = new mapboxgl.Map({
      container: 'map',
      style: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',
      center: [0, 0],
      zoom: 1
    });

    const REQUEST_GET_MAX_URL_LENGTH = 2048;

    addCartoLayer();

    async function addCartoLayer() {
      const tileSourceURLs = await getTileSources();
      map.addLayer(
        {
          id: 'populated_places',
          type: 'circle',
          source: {
            type: 'vector',
            tiles: tileSourceURLs
          },
          'source-layer': 'layer0',
          paint: {
            'circle-color': '#EE4D5A',
            'circle-stroke-color': '#FFF',
            'circle-stroke-width': 1
          }
        }
      );
    }

    async function getTileSources() {
      const mapConfig = JSON.stringify({
        version: '1.3.1',
        buffersize: {mvt: 1},
        layers: [
          {
            type: 'mapnik',
            options: {
              sql: 'SELECT the_geom_webmercator, name FROM populated_places',
              vector_extent: 4096,
              bufferSize: 1,
              version: '1.3.1'
            }
          }
        ]
      });
      const url = `https://public.carto.com/api/v1/map?apikey=default_public}`;
      const getUrl = `${url}&config=${encodeURIComponent(mapConfig)}`;
      let request;

      if (getUrl.length < REQUEST_GET_MAX_URL_LENGTH) {
        request = new Request(getUrl, {
          method: 'GET',
          headers: {
            Accept: 'application/json'
          }
        });

      } else {
        request = new Request(url, {
          method: 'POST',
          headers: {
            Accept: 'application/json',
            'Content-Type': 'application/json'
          },
          body: mapConfig
        });
      }

      const response = await fetch(request);
      return (await response.json()).metadata.tilejson.vector.tiles
    }

  </script>

</html>