Share this

Lightning map – create your own using Highcharts

Highsoft Avatar

by

9 minutes read

Lightning map – create your own using Highcharts

Globally, lightning strikes about 100 times per second, totaling 8 million strikes daily. These strikes can cause severe damage, including forest fires, power outages, and fatalities. In the U.S., lightning results in 20 to 30 deaths and hundreds of injuries annually, costing billions in damages. Effective lightning data visualization is essential due to these significant impacts.

The unpredictability of lightning makes accurate visualization crucial for decision-making in meteorology, aviation, and public safety. Traditional static maps can’t capture this dynamic, real-time data. Interactive lightning maps, however, allow users to zoom, examine trends, and predict patterns, making them vital for informed decisions in time-sensitive situations like flight rerouting and issuing safety warnings.

This post will explore an interactive lightning map created in Highcharts® Maps. Part of the Highcharts suite, it is designed for interactive, responsive maps. It supports various map types and integrates complex data sources, such as real-time lightning strikes. Its features, like zooming, panning, and drill-downs, enable detailed data exploration.

To see more examples and get an even better understanding of the opportunities Highcharts offers, please head over to the demo section of our website or read up on the technical documentation on how to get started. Once you get the hang of it, the API reference will help you customize your charts in no time.

Whether you’re a developer working with JavaScript, .NET, React or other common frameworks, we’re confident you’ll find the inspiration you need.

Highcharts also integrates seamlessly with popular languages such as Python, R, PHP and Java, as well as mobile platforms like iOS and Android. Additional support for frameworks like Svelte, Angular, and Vue, makes it a versatile tool for various development environments.

 

Highcharts® Maps features and benefits

  • Real-time data integration for up-to-the-minute lightning strike information: Highcharts® Maps excels in integrating real-time data, a crucial feature for creating a lightning map. With the ability to update data dynamically, users can track lightning strikes as they happen, providing a near-instantaneous view of storm activity.
  • Interactive elements like zooming, panning, and tooltips for enhanced user experience: Highcharts® Maps offers a range of interactive features that enhance the user experience. Zooming and panning allow users to focus on specific regions, while tooltips provide additional information when hovering over data points. These features make it easier to interpret complex data and draw actionable insights.
  • Customizable design to suit specific project needs: Highcharts® Maps is highly customizable, allowing developers to tailor the appearance and functionality of their maps to meet specific project requirements. From adjusting color schemes to adding custom markers, the possibilities are extensive, enabling a seamless integration into any application or website.

 

Interactive lightning map example

 

Go directly to the Highcharts demo page for “Lightning Map” to view its code, play around with different theme colors or edit it on JSFiddle or Codepen.

 

Here’s how to recreate the lightning map

 

Please note that this example uses historical lightning strike data from frost.met.no. However, the same approach can be applied to create a real-time lightning strike map using similar techniques. To learn more on how you can customize your own lightning map with live data, map navigation and more, visit the documentation here.

 

For basics, see Highcharts installation. The framework requirements and installation is the same for Highcharts® Maps as for Highcharts. To load Highcharts® Maps as a standalone product (if you don’t have a license for Highcharts), include this script tag:

 

<script src="https://code.highcharts.com/maps/highmaps.js"></script>

 

If you already have Highcharts installed in the web page and want to run Highcharts® Maps as a plugin, include this script tag after highcharts.js:

 

<script src="https://code.highcharts.com/maps/modules/map.js"></script>

 

Create a container for the chart and some controls

<figure class="highcharts-figure">
    <div id="container"></div>

    <div id="controls">
        <input id="date-range" type="range">
        <button id="play-pause">▶︎</button>
        <span id="report-time"></span>
    </div>

    <p class="highcharts-description">
        This is an advanced demo showing how to use a dynamic mapbubble series
        on top of a map, with extended graphic effects to highlight changes in
        the data.
    </p>
</figure>

* <!-- Data from https://frost.met.no/api.html#!/lightning/getLightning -->

 

* Go to the demo page for “Lightning Map” to view its code and get all the lightning data

 

Add some CSS to control the size and style of your container and other elements

.highcharts-figure {
    margin: 0 auto;
    min-width: 360px;
    max-width: 800px;
    padding: 0 10px;
}

#container {
    margin: 0 -10px;
}

#controls {
    margin-bottom: 1em;
}

#date-range {
    margin-top: 1rem;
    width: 100%;
}

#play-pause {
    width: 3rem;
    height: 3rem;
    border-radius: 1.5rem;
    background-color: #1767ce;
    color: white;
    border-width: 0;
    text-align: center;
    font-size: 1rem;
    transition: background-color 250ms;
}

#play-pause:hover {
    background-color: #68a9ff;
    color: white;
}

#report-time {
    float: right;
    margin-top: 0.8rem;
}

.loading {
    margin-top: 10em;
    text-align: center;
    color: gray;
}

#data {
    display: none;
}

 

Load and initialize the lightning map

// Start at this time
let currentTime = Date.UTC(2022, 6, 3, 20);

// Get the data class of a lightning strike based on the time ago
const getDataClass = (now, datetime) => {
    if (now - datetime > 20 * 60000) {
        return 3;
    }
    if (now - datetime > 10 * 60000) {
        return 2;
    }
    if (now - datetime > 60000) {
        return 1;
    }
    return 0;
};

// Parse the data which comes in the ualf format, a subset of CSV
const parseData = () => {
    const ualf = document.getElementById('data').innerText,
        lines = ualf.split('\n');

    const data = lines
        .map(line => {
            const values = line.split(' ');

            const p = {
                lon: parseFloat(values[9]),
                lat: parseFloat(values[8]),
                datetime: Date.UTC(
                    values[1],
                    parseInt(values[2], 10) - 1,
                    values[3],
                    values[4],
                    values[5],
                    values[6]
                ),
                peakCurrent: parseFloat(values[10]),
                cloudIndicator: Boolean(+values[21]),
                z: Math.round(parseFloat(values[10])),
                colorValue: 0
            };

            return p.cloudIndicator ? undefined : p;
        })
        .filter(p => p !== undefined)
        .sort((a, b) => a.datetime - b.datetime);

    return data;
};

const ualf = parseData();

// Get the data for the initial time
const getInitialData = time => ualf.slice(
    ualf.findIndex(p => p.datetime > time - 30 * 60000),
    ualf.findLastIndex(p => p.datetime <= time)
).map(p => {
    p.colorValue = getDataClass(time, p.datetime);
    return p;
});

const displayTime = time => {
    document.getElementById('report-time').innerText = Highcharts.dateFormat(
        '%B %e, %Y %H:%M',
        time
    );
};

(async () => {

    // Load the map
    const topology = await fetch(
        'https://code.highcharts.com/mapdata/custom/europe.topo.json'
    ).then(response => response.json());

    // Create the chart
    const chart = Highcharts.mapChart('container', {
        chart: {
            map: topology,
            height: '80%',
            margin: 0,
            backgroundColor: '#222',
            animation: false
        },

        accessibility: {
            enabled: false
        },

        title: {
            text: 'Lightning strikes',
            align: 'left',
            style: {
                color: 'rgba(255,255,196,1)'
            }
        },

        subtitle: {
            text: 'Source: <a href="https://frost.met.no/api.html#!/lightning/getLightning" style="color: inherit">frost.met.no</a>',
            align: 'left',
            style: {
                color: '#aaa'
            }
        },

        legend: {
            align: 'right',
            verticalAlign: 'top',
            layout: 'vertical',
            itemStyle: {
                color: '#ddd'
            }
        },

        mapView: {
            center: [12, 56.8],
            zoom: 7,
            projection: {
                rotation: [-15]
            }
        },

        colorAxis: {
            dataClasses: [{
                from: 0,
                to: 0,
                color: 'rgba(255,255,196,1)',
                name: '< 1 min'
            }, {
                from: 1,
                to: 1,
                color: 'rgba(255,196,0,1)',
                name: '1 - 10 min'
            }, {
                from: 2,
                to: 2,
                color: 'rgba(196,128,0,1)',
                name: '10 - 20 min'
            }, {
                from: 3,
                to: 3,
                color: 'rgba(196,64,0,1)',
                name: '20 - 30 min'
            }]
        },

        series: [{
            name: 'Map',
            nullColor: '#444',
            borderColor: '#666',
            dataLabels: {
                enabled: true,
                nullFormat: '{point.name}',
                style: {
                    color: '#888',
                    textOutline: 'none',
                    fontSize: '16px',
                    fontWeight: 'normal'
                }
            }
        }, {
            name: 'Lightning strike',
            id: 'lightnings',
            type: 'mapbubble',
            animation: false,
            data: getInitialData(currentTime),
            tooltip: {
                pointFormat: '{point.datetime:%Y-%m-%d %H:%M:%S}'
            },
            minSize: 4,
            maxSize: 8,
            marker: {
                lineWidth: 0,
                radius: 3
            },
            colorKey: 'colorValue'
        }]
    });
    displayTime(currentTime);

    // Update the colors of the data points
    const updateColors = time => {
        let redraw = false;
        // Modify older points
        chart.get('lightnings').points.forEach(p => {
            let colorValue;
            if (time - p.options.datetime > 30 * 60000) {
                p.remove();
            } else  {
                colorValue = getDataClass(time, p.options.datetime);
            }

            if (colorValue && colorValue !== p.options.colorValue) {
                p.update({ colorValue }, false);
                redraw = true;
            }
        });
        return redraw;
    };

    // For the strongest lightning strikes, light up the chart with a temporary
    // flash
    const flash = point => {
        const pos = chart.mapView.lonLatToPixels(point);

        chart.renderer.circle(pos.x, pos.y, point.z * 2)
            .attr({
                fill: {
                    radialGradient: { cx: 0.5, cy: 0.5, r: 0.5 },
                    stops: [
                        [0, 'rgba(255, 255, 0, 0.25)'],
                        [0.1, 'rgba(255, 255, 0, 0.125)'],
                        [1, 'rgba(255, 255, 0, 0)']
                    ]
                },
                zIndex: 10
            })
            .add()
            .animate(
                { opacity: 0 },
                { duration: 250 },
                function () {
                    this.destroy();
                }
            );
    };

    if (updateColors(currentTime)) {
        chart.redraw();
    }

    // The remainder of the data after currentTime
    let data = ualf.slice(ualf.findIndex(p => p.datetime > currentTime));

    const endTime = data.at(-1).datetime,
        step = 10000,
        series = chart.get('lightnings');

    let timer;

    const pause = () => {
        clearTimeout(timer);
        document.getElementById('play-pause').textContent = '▶︎';
    };

    // Add the lightning strikes for the last n minutes
    const addPoints = time => {

        const rangeInput = document.getElementById('date-range');

        // Internal Highcharts CI sample verification
        if (!rangeInput) {
            return;
        }

        currentTime = time;
        let redraw = false;

        displayTime(time);
        rangeInput.value = time;

        if (updateColors(time)) {
            redraw = true;
        }

        // Add new points
        const points = [];
        while (data[0] && data[0].datetime <= time) {
            points.push(data.shift());
        }
        points.forEach(point => {

            redraw = true;

            series.addPoint(point, false);

            if (point.z > 10) {
                flash(point);
            }
        });

        if (redraw) {
            chart.redraw();
        }

        if (time + step <= endTime) {
            timer = setTimeout(() => addPoints(time + step), 25);
        } else {
            pause();
        }

    };

    const play = () => {
        document.getElementById('play-pause').textContent = '▮▮';
        data = ualf.slice(ualf.findIndex(p => p.datetime > currentTime));
        timer = setTimeout(() => addPoints(currentTime + step), 25);
    };

    const setUpInputs = () => {
        // Range input
        const input = document.getElementById('date-range');
        input.min = ualf[0].datetime;
        input.max = ualf.at(-1).datetime;
        input.value = currentTime;

        input.addEventListener('input', () => {
            pause();
            currentTime = Number(input.value);
            chart.series[1].setData(getInitialData(input.value));
            displayTime(currentTime);
        });

        // Play/pause
        document.getElementById('play-pause').addEventListener(
            'click',
            function () {
                if (this.textContent === '▶︎') {
                    play();
                } else {
                    pause();
                }
            }
        );
    };
    setUpInputs();
    // eslint-disable-next-line no-underscore-dangle
    if (!window.__karma__) { // CI tests
        // Wait a bit for Visual review tool
        setTimeout(() => play(), 100);
    }

})();

 

Conclusion and additional resources

Creating an interactive lightning map using Highcharts® Maps offers a powerful way to visualize weather data. From setting up the map and integrating data to customizing the visualization and optimizing performance, Highcharts® Maps provides all the tools you need to develop a detailed and responsive map.

Ready to take your weather data visualization to the next level? Get started with Highcharts® Maps today and see how easy it is to create dynamic, interactive lightning maps that can transform your projects.

 

 

Related posts

 

Stay in touch

No spam, just good stuff

We're on discord. Join us for challenges, fun and whatever else we can think of
XSo MeXSo Me Dark
Linkedin So MeLinkedin So Me Dark
Facebook So MeFacebook So Me Dark
Github So MeGithub So Me Dark
Youtube So MeYoutube So Me Dark
Instagram So MeInstagram So Me Dark
Stackoverflow So MeStackoverflow So Me Dark
Discord So MeDiscord So Me Dark

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.