Change timeline range

Change timeline range

This example demonstrates how to update the timeline start and/or end date range in real-time.

When time-based layers are active on the map, then new time intervals will be calculated for the new time range whenever the start or end times change. Thus, new data may be required to load when resuming playback of the timeline if the previous data does not cover the new time range and required intervals.

change-timeline-range.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>MapsGL SDK - Change timeline range</title>
    <meta name="description" content="Update the timeline start and/or end date range in real-time." />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
 
    <link href="https://api.mapbox.com/mapbox-gl-js/v2.8.0/mapbox-gl.css" rel="stylesheet" />
    <script defer src="https://api.mapbox.com/mapbox-gl-js/v2.8.0/mapbox-gl.js"></script>
 
    <link href="https://cdn.aerisapi.com/sdk/js/mapsgl/latest/aerisweather.mapsgl.css" rel="stylesheet" />
    <script defer src="https://cdn.aerisapi.com/sdk/js/mapsgl/latest/aerisweather.mapsgl.js"></script>
 
    <style>
    body, html {
        margin: 0;
        padding: 0;
    }
    #map {
        height: 100vh;
        width: 100%;
    }
    #controls {
        display: flex;
        flex-direction: column;
        gap: 12px;
        position: absolute;
        top: 10px;
        left: 10px;
        z-index: 1;
        background: rgba(255, 255, 255, 0.8);
        padding: 10px;
        border-radius: 5px;
        font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
        font-size: 12px;
    }
    #controls h3 {
        margin: 0 0 8px;
        font-size: 14px;
    }
    #controls .container {
        display: flex;
        flex-direction: row;
        justify-content: center;
        align-items: center;
        gap: 20px;
    }
    #controls .control {
        display: flex;
        flex-direction: row;
        gap: 4px;
    }
    #controls .control-field {
        display: flex;
        flex-direction: column;
        gap: 2px;
    }
    #controls .control label {
        margin-top: 2px;
        font-weight: bold;
    }
    #controls .control span {
        font-size: 10px;
    }
    #controls .animation {
        display: flex;
        flex-direction: row;
        align-items: center;
        gap: 8px;
        min-height: 20px;
    }
    #controls .loader {
        display: none;
        height: 20px;
    }
    .loader .ring {
        display: inline-block;
        position: relative;
        width: 20px;
        height: 20px;
    }
    .loader .ring div {
        box-sizing: border-box;
        display: block;
        position: absolute;
        width: 18px;
        height: 18px;
        margin: 1px;
        border: 2px solid #333;
        border-radius: 50%;
        animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
        border-color: #333 transparent transparent transparent;
    }
    .loader .ring div:nth-child(1) {
        animation-delay: -0.45s;
    }
    .loader .ring div:nth-child(2) {
        animation-delay: -0.3s;
    }
    .loader .ring div:nth-child(3) {
        animation-delay: -0.15s;
    }
    @keyframes loader-ring {
        0% {
            transform: rotate(0deg);
        }
        100% {
            transform: rotate(360deg);
        }
    }
    </style>
 
</head>
<body>
    <div id="map"></div>
    <div id="controls">
        <div class="animation">
            <button class="btn-play">Play</button>
            <button class="btn-pause">Pause</button>
            <div class="loader">
                <div class="ring">
                    <div></div><div></div><div></div><div></div>
                </div>
            </div>
        </div>
        <div class="container">
            <div id="timeline-start" class="control">
                <label for="range-start">Start</label>
                <div class="control-field">
                    <select id="range-start"></select>
                    <span class="value"></span>
                </div>
            </div>
            <div id="timeline-end" class="control">
                <label for="ramge-end">End</label>
                <div class="control-field">
                    <select id="range-end"></select>
                    <span class="value"></span>
                </div>
            </div>
        </div>
    </div>
 
    <script>
        function formatDate(date) {
            return `${date.toLocaleDateString('en', { weekday: 'short' })} ${date.toLocaleTimeString('en')}`;
        }
 
        window.addEventListener('load', () => {
 
            // Set up UI elements
            const controls = {
                play: document.querySelector('#controls .btn-play'),
                pause: document.querySelector('#controls .btn-pause'),
                loader: document.querySelector('#controls .loader'),
                selectStart: document.querySelector('#range-start'),
                selectEnd: document.querySelector('#range-end'),
                labelStart: document.querySelector('#timeline-start span'),
                labelEnd: document.querySelector('#timeline-end span'),
            };
 
            const dateRanges = [
                { label: 'Now', value: '0' },
                { label: '1 day', value: '24' },
                { label: '3 days', value: '72' },
                { label: '7 days', value: '168' }
            ];
            dateRanges.forEach(({ label, value }) => {
                [controls.selectStart, controls.selectEnd].forEach((select, index) => {
                    const option = document.createElement('option');
                    option.value = value;
                    option.text = /\d+/.test(label) ? (index === 0 ? `-${label}` : `+${label}`) : label;
                    select.append(option);
                });
            });
 
            // Set initial range values
            controls.selectStart.value = '0';
            controls.selectEnd.value = '24';
 
            // Create the Mapbox map instance
            mapboxgl.accessToken = 'MAPBOX_TOKEN';
            const map = new mapboxgl.Map({
                container: document.getElementById('map'),
                style: 'mapbox://styles/mapbox/light-v9',
                center: [20, 47],
                zoom: 4
            });
 
            // Set up MapsGL account and map controller with the timeline range
            const account = new aerisweather.mapsgl.Account('CLIENT_ID', 'CLIENT_SECRET');
            const controller = new aerisweather.mapsgl.MapboxMapController(map, { 
                account,
                animation: {
                    start: new Date(Date.now() - (controls.selectStart.value * 3600000)),
                    end: new Date(Date.now() + (controls.selectEnd.value * 3600000))
                }
            });
 
            // Update timeline when the selected start/end date changes
            controls.selectStart.addEventListener('change', () => {
                controller.timeline.startDate = new Date(Date.now() - (controls.selectStart.value * 3600000));
                controls.labelStart.innerHTML = `${formatDate(controller.timeline.startDate)}`;
            });
            controls.selectEnd.addEventListener('change', () => {
                controller.timeline.endDate = new Date(Date.now() + (controls.selectEnd.value * 3600000));
                controls.labelEnd.innerHTML = `${formatDate(controller.timeline.endDate)}`;
            });
 
            // Set initial timeline range
            controls.labelStart.innerHTML = `${formatDate(controller.timeline.startDate)}`;
            controls.labelEnd.innerHTML = `${formatDate(controller.timeline.endDate)}`;
 
            // Set up timeline controls and loading indicator
            controls.pause.disabled = true;
            controller.on('load:start', () => {
                controls.loader.style.display = 'block';
            });
            controller.on('load:complete', () => {
                controls.loader.style.display = 'none';
            });
            controller.timeline.on('play', () => {
                controls.play.textContent = 'Stop';
                controls.pause.textContent = 'Pause';
                controls.pause.disabled = false;
            }).on('stop', () => {
                controls.play.textContent = 'Play';
                controls.pause.textContent = 'Pause';
                controls.pause.disabled = true;
            }).on('pause', () => {
                controls.pause.textContent = 'Resume';
            }).on('resume', () => {
                controls.pause.textContent = 'Pause';
            });
            controls.play.addEventListener('click', (e) => {
                e.preventDefault();
                if (controller.timeline.isActive) {
                    controller.timeline.stop();
                } else {
                    controller.timeline.play();
                }
            });
            controls.pause.addEventListener('click', (e) => {
                e.preventDefault();
                if (controller.timeline.isPaused) {
                    controller.timeline.resume();
                } else {
                    controller.timeline.pause();
                }
            });
            
            controller.on('load', () => {
                controller.addWeatherLayer('wind-particles');
            });
        });
 
    </script>
</body>
</html>