Handling Mapbox Style Changes

Render a basic interactive map using Mapbox with the ability to change the base map style.

When switching the base map style on a Mapbox map, existing layers and sources are often removed from the map and not re-added with the new style. The following example demonstrates how to re-add existing Aeris-related weather layers back onto a Mapbox map when changing the base map to a different style.

Mapbox interactive map example

Mapbox interactive map example

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>AerisWeather JavaScript SDK - Handling Mapbox Style Changes</title>
    <script defer src="https://cdn.aerisapi.com/sdk/js/latest/aerisweather.min.js"></script>
    <link rel="stylesheet" href="https://cdn.aerisapi.com/sdk/js/latest/aerisweather.css">

    <style>
    body {
        font-family: 'Helvetica','Arial',sans-serif;
    }
    #map {
        height: 800px;
        margin: 10px auto 30px;
        width: 1000px;
    }
    .map-styles {
        font-size: 12px;
        text-align: center;
    }
    .map-styles > a {
        border: 2px solid #ddd;
        color: #333;
        display: inline-block;
        font-weight: bold;
        margin: 0 2px;
        padding: 4px 8px;
        text-decoration: none;
    }
    .map-styles > a:hover {
        background: #ddd;
        color: #333;
    }
    .map-styles > a.selected {
        background: #333;
        border-color: #333;
        color: #fff;
    }
    </style>

</head>
<body>

<p class="map-styles">
    <a href="#" data-style="streets-v11">Streets</a>
    <a href="#" data-style="satellite-v9">Satellite</a>
    <a href="#" data-style="outdoors-v11">Outdoors</a>
    <a href="#" data-style="light-v10">Light</a>
    <a href="#" data-style="dark-v10">Dark</a>
</p>
<div id="map"></div>

<script>

    window.onload = () => {

        const aeris = new AerisWeather('CLIENT_ID', 'CLIENT_SECRET');
        const utils = aeris.utils;
        const $ = aeris.utils.$;
        let currentStyle = 'streets-v11';
        
        const setMapStyle = (map, type) => {
            if (type === currentStyle) return;

            currentStyle = type;

            // grab the existing style and cache all Aeris-related sources and layers
            // so they can be re-added after changing the map style
            const mapStyle = map.getStyle();
            const aerisLayers = mapStyle.layers.filter((layer) => /^aeris-/.test(layer.id));
            const aerisSources = Object.keys(mapStyle.sources).filter((key) => {
                return /^aeris-/.test(key);
            }).reduce((prev, result) => {
                prev[result] = mapStyle.sources[result];
                return prev;
            }, {});

            // need to wait for the new style to load before re-adding the previous map data
            map.on('style.load', () => {
                Object.keys(aerisSources).forEach((key) => {
                    const existing = map.getSource(key);
                    if (!existing) {
                        map.addSource(key, aerisSources[key]);
                    }
                });
                aerisLayers.forEach((layer) => {
                    const existing = map.getLayer(layer.id);
                    if (!existing) {
                        map.addLayer(layer)
                    }
                });
            });

            // update the map style
            map.setStyle(`mapbox://styles/mapbox/${type}`, { diff: true });
        };

        aeris.views().then(views => {
            const interactive = new views.InteractiveMap('#map', {
                strategy: 'mapbox',
                accessToken: 'MAPBOX_TOKEN',
                center: {
                    lat: 44.977,
                    lon: -88.265
                },
                zoom: 5,
                layers: 'satellite,alerts,radar,stormreports',
                timeline: {
                    from: -12 * 3600,
                    to: 0
                }
            });
            
            interactive.on('ready', () => {
                $('.map-styles > a').on('click', (e) => {
                    e.preventDefault();
                    const $el = $(e.target);
                    $('.map-styles > a').removeClass('selected');
                    $el.addClass('selected');

                    const type = $el.attr('data-style');
                    if (type) {
                        setMapStyle(interactive.map, type);
                    }
                });
                $('.map-styles > a:nth-child(1)').click();
            });
        });

    };

</script>

</body>
</html>

Last modified: July 30, 2020