Skip to content

Vector maps for web

Vector tiles are the best way to get maps on your website. They offer the best experience for your users because they look great at any zoom level on any screen (Apple retina displays, for example). They also allow for dynamic applications with adaptive styling based on real-time data.

Displaying a map

Our vector maps are compatible with any renderer capable of rendering Mapbox Vector Tiles (also known as MVTs). Below is an example of typical usage. The important piece to note is the style URL. Most of the rest is boilerplate. See our style library for additional styles.

MapLibre GL JS

MapLibre GL JS is a fully free and open-source renderer with 3D rendering, dynamic styling, and many other features. It was originally forked Mapbox GL JS v1, but has now taken on a life and direction thanks to a community of active developers. Stadia Maps is a founding member of the MapLibre project, which facilitates the ongoing development of MapLibre GL JS and other rendering technologies.

Tip

If you are switching to Stadia from Mapbox for your web application, we recommend using MapLibre GL JS. It is similar enough to Mapbox GL JS to be familiar, but is free of onerous licensing.

Edit in JSFiddle
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Vector Map Demo</title>
        <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
        <script type="text/javascript" src="//unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.js"></script>
        <link href="//unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.css" rel="stylesheet" />
        <style type="text/css">
            body {
              margin: 0;
              padding: 0;
            }

            #map {
              position: absolute;
              top: 0;
              bottom: 0;
              width: 100%;
            }
        </style>
    </head>
    <body>
        <div id="map"></div>
        <script type="text/javascript">
         var map = new maplibregl.Map({
           container: 'map',
           style: 'https://tiles.stadiamaps.com/styles/alidade_smooth.json',  // Style URL; see our documentation for more options
           center: [12, 53],  // Initial focus coordinate
           zoom: 4
         });

         // MapLibre GL JS does not handle RTL text by default, so we recommend adding this dependency to fully support RTL rendering. 
         maplibregl.setRTLTextPlugin('https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.1/mapbox-gl-rtl-text.js');

         // Add zoom and rotation controls to the map.
         map.addControl(new maplibregl.NavigationControl());
        </script>
    </body>
</html>

Adding markers to the map

Once you have a map, you probably want to add some markers to it! The example below can serve as a starting point, and covers all the main points. You can easily substitute your own branded markers, style the popup (or remove it), etc. Please refer to the MapLibre documentation for additional details.

Edit in JSFiddle
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Vector Map Demo</title>
        <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
        <script type="text/javascript" src="//unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.js"></script>
        <link href="//unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.css" rel="stylesheet" />
        <style type="text/css">
        body {
            margin: 0;
            padding: 0;
        }

        #map {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        }

        /* We recommend using an icon that is 2x the intended display size, so that it renders nicely on retina displays. */
        .marker {
            /* The following icon is owned by Stadia Maps. While you maintain an account with us, we grant you royalty-free use of
             * this image when displayed on our maps.
             */
            background-image: url('data:image/png;base64,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');
            background-size: cover;
            width: 27px;
            height: 42px;
            cursor: pointer;
        }
        </style>
    </head>
    <body>
        <div id="map"></div>
        <script type="text/javascript">
        var map = new maplibregl.Map({
            container: 'map',
            style: 'https://tiles.stadiamaps.com/styles/alidade_smooth.json',  // Style URL; see our documentation for more options
            center: [6.942373, 50.332852],  // Initial focus coordinate (long, lat)
            zoom: 14
        });

        // Add zoom and rotation controls to the map.
        map.addControl(new maplibregl.NavigationControl());

        // First, we define our marker locations. You can use whatever format you want when
        // working with custom markers, but we have chosen to use GeoJSON for this example, as
        // a lot of geospatial data comes in this form. If you have a lot of data, you may want to
        // put it in another file that is loaded separately.
        var markerCollection = {
            "type": "FeatureCollection",
            "features": [{
                "type": "Feature",
                "geometry": {
                    "type": "Point",
                    // NOTE: in GeoJSON notation, LONGITUDE comes first. GeoJSON
                    // uses x, y coordinate notation, just like you used to describe
                    // graph coordinates in high school. What you may not realize is
                    // that latitude, often said first in English, is actually the y axis. 
                    "coordinates": [6.940046, 50.333947]
                },
                "properties": {
                    "title": "Nürburgring"
                }
            }]
        };

        // Next, we can add markers to the map
        markerCollection.features.forEach(function(point) {
            // Since these are HTML markers, we create a DOM element first, which we will later
            // pass to the Marker constructor.
            var elem = document.createElement('div');
            elem.className = 'marker';

            // Now, we construct a marker and set it's coordinates from the GeoJSON. Note the coordinate order.
            var marker = new maplibregl.Marker(elem);
            marker.setLngLat(point.geometry.coordinates);

            // You can also create a popup that gets shown when you click on a marker. You can style this using
            // CSS as well if you so desire. A minimal example is shown. The offset will depend on the height of your image.
            var popup = new maplibregl.Popup({ offset: 24, closeButton: false });
            popup.setHTML('<div>' + point.properties.title + '</div>');

            // Set the marker's popup.
            marker.setPopup(popup);

            // Finally, we add the marker to the map.
            marker.addTo(map);
        });
        </script>
    </body>
</html>

Alternative renderers

Stadia Maps serves tiles that conform to the MVT specification, which means you have the option of using any renderer that supports the MVT format, not just MapLibre GL JS. You can use our vector tiles with Leaflet, OpenLayers, and Tangram with varying degrees of support and polish.

Stadia Maps will always support your rights to use any library that you choose. While we aren't quite ready to distribute styles for alternative renderers with our stamp of quality, we are actively working toward official styles for other renderers like OpenLayers and Tangram.

Leaflet

Leaflet is one of the most mature browser-based map rendering libraries, with a rich set of plugins and a simple API. While Leaflet does not natively support vector tile rendering, the maplibre-gl-leaflet plugin works quite well in most cases. You can keep using tho familiar (and powerful) Leaflet APIs and get most of the benefits of vector tiles, including the ability to use a custom style.

Edit in JSFiddle
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        <title>Raster Map Demo (Leaflet)</title>
        <!-- Leaflet -->
        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" />
        <script type="text/javascript" src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>

        <!-- MapLibre GL JS (still required to handle the rendering) -->
        <script type="text/javascript" src="//unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.js"></script>
        <link href="//unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.css" rel="stylesheet" />

        <!-- Mapbox GL Leaflet -->
        <script src="https://unpkg.com/@maplibre/maplibre-gl-leaflet@0.0.19/leaflet-maplibre-gl.js"></script>
        <style type="text/css">
        html, body, #map {
            width: 100%;
            height: 100%;
            margin: 0;
        }
        </style>
    </head>
    <body>
        <div id="map"></div>
        <script type="text/javascript">
            // Initialize a map centered at (53, 12) at zoom level 5
            var map = L.map('map').setView([53, 12], 5);

            // MapLibre GL JS does not handle RTL text by default, so we recommend adding this dependency to fully support RTL rendering. 
            maplibregl.setRTLTextPlugin('https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.1/mapbox-gl-rtl-text.js');

            L.maplibreGL({
                style: 'https://tiles.stadiamaps.com/styles/alidade_smooth_dark.json',  // Style URL; see our documentation for more options
                attribution: '&copy; <a href="https://stadiamaps.com/">Stadia Maps</a>, &copy; <a href="https://openmaptiles.org/">OpenMapTiles</a> &copy; <a href="https://openstreetmap.org">OpenStreetMap</a> contributors',
            }).addTo(map);
        </script>
    </body>
</html>

OpenLayers

You can use any (OpenMapTiles schema-compatible) JSON style with OpenLayers thanks to the ol-mapbox-style plugin. This library will attempt to convert a GL JSON stylesheet into an OpenLayers one. You’ll notice there are a few differences between the OpenLayers version and the MapLibre GL rendering, but it is close enough and enables you to leverage the power of OpenLayers with our vector tiles.

Edit in JSFiddle
<!DOCTYPE html>
<html>
    <head>
        <title>OpenLayers Vector Tile Demo</title>
        <!-- Note that fonts will not perfectly match our Mapbox GL styles at present -->
        <link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Open+Sans" />
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.4.3/css/ol.css">
        <style>
            html, body {
                height: 100%;
                margin: 0;
            }
            #map {
                width: 100%;
                height: 100%;
                background-color: #f8f4f0;
            }
        </style>
    </head>
    <body>
        <div id="map"></div>
        <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=fetch,Promise"></script>
        <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.4.3/build/ol.js"></script>
        <script src="https://unpkg.com/ol-mapbox-style@6.2.1/dist/olms.js"></script>
        <script>
            olms('map', 'https://tiles.stadiamaps.com/styles/alidade_smooth_dark.json').then(function(map) {
                // Callback to configure the map if necessary
            });
        </script>
    </body>
</html>

Tangram

Tangram offers a number of unique features compared to other vector renderers, including custom GLSL shaders, unique 3D rendering customizations, and custom camera and lighting controls. We don't yet offer official styles, but have an experimental tool that enables easy migration of our JSON styles to Tangram ones. You can follow the development of the Cartogrify project on GitHub.

Further Reading

For more details on MapLibre GL JS, check out the official documentation for more information.

You can also check out the OpenLayers, Leaflet, and Tangram projects.