JavaScript Heatmap Example: A Step-by-Step Tutorial to Build Heatmaps with MapLibre GL

MapLibre GL transforms raw data into a vivid portrayal of the US Population Heatmap
MapLibre GL transforms raw data into a vivid portrayal of the US Population Heatmap

Welcome to our comprehensive tutorial on creating population heat maps using MapLibre GL map library! In this tutorial, we'll guide you through the process of building an interactive population heat map from scratch.

What You'll Learn:

In this tutorial, we'll focus on creating a population heat map using MapLibre GL. We'll base our heat map on GeoJSON data that we've generated using a Settlement Dataset. For this demonstration, we'll be using US settlement data. However, the techniques you'll learn here can be applied to create heat maps for any country or region.

Why Population Heat Maps?

Population heat maps provide a visual representation of population density in a specific area. They are widely used in various fields, including urban planning, demographics, and market analysis. By the end of this tutorial, you'll have the skills to harness the power of MapLibre GL to create engaging and informative population heat maps for your own projects.

Without further ado, let's dive into the fascinating world of population heat maps and learn how to craft them with MapLibre GL. Shall we get started?

Step 1. Preparing GeoJSON with Population Data

MapLibre GL enables the creation of heatmap visualization layers based on GeoJSON data.

Loading Data

Before we dive into the details of GeoJSON generation, make sure you've acquired the necessary settlement data for your chosen country, in this case, the United States. Once you have the data, you're ready to proceed.

Creating GeoJSON Data

We've generated a sample GeoJSON file based on US settlement data, which you can find below:

...
const cities = getLocationsData("place-city.ndjson");
const towns = getLocationsData("place-town.ndjson");
const villages = getLocationsData("place-village.ndjson");
const hamlets = getLocationsData("place-hamlet.ndjson");

const populationData = {
  type: "FeatureCollection",
  features: [
    ...cities.map(city => {
      return {
        "type": "Feature", 
        "properties": { "name": city.name, "population": city.population, "type": "city" },
        "geometry": { "type": "Point", "coordinates": city.location } 
      }
    }), 
    ...towns.map(city => {
      return {
        "type": "Feature", 
        "properties": { "name": city.name, "population": city.population, "type": "town" },
        "geometry": { "type": "Point", "coordinates": city.location } 
      }
    }), 
    ...villages.map(city => {
      return {
        "type": "Feature", 
        "properties": { "name": city.name, "population": city.population, "type": "village" },
        "geometry": { "type": "Point", "coordinates": city.location } 
      }
    }), 
    ...hamlets.map(city => {
      return {
        "type": "Feature", 
        "properties": { "name": city.name, "population": city.population, "type": "hamlet" },
        "geometry": { "type": "Point", "coordinates": city.location } 
      }
    }), 
]
}

fs.writeFileSync("output/us-population-data.json", JSON.stringify(populationData), "utf-8");

This code uses the getLocationsData() function described in Using Cities, Towns, Villages and Hamlets dataset that reads settlement data from a NDJSON file.

Then, it generates a GeoJSON FeatureCollection object, where each Feature represents a different type of settlement (city, town, village, or hamlet). Each Feature includes details about the settlement's name, population, type, and center location point. Here is an example of GeoJSON.Feature:

{
  "type": "Feature",
  "properties": {
    "name": "Honolulu",
    "population": 350964,
    "type": "city"
  },
  "geometry": {
    "type": "Point",
    "coordinates": [
      -157.855676,
      21.304547
    ]
  }
}

The resulting GeoJSON data is saved to an output file for use in creating population heatmaps using MapLibre GL.

You can use this sample as a template to create your GeoJSON data, incorporating settlement information relevant to your project. You can also use various tools and libraries to assist in GeoJSON creation or manipulate existing data to suit your requirements.

Adaptable for Any Country

It's important to note that the techniques described in this tutorial are not limited to the United States. You can adapt the process to create population heatmaps for any country. Simply replace the US settlement data with data from your target country and follow the steps outlined in this tutorial to achieve stunning results.

Now that you have an understanding of how to prepare the population data, let's proceed to the next steps in creating your population heatmap using MapLibre GL.

Step 2. Creating a MapLibre GL Map

To get started with creating your population heatmap using MapLibre GL, you'll need to set up a MapLibre GL map. Follow these inscrutions to create your map:

Install MapLibre GL

You can install MapLibre GL using npm (Node Package Manager) if you're working with a Node.js project. Use the following command:

npm i maplibre-gl

Alternatively, you can add MapLibre GL as a <script> in your HTML file if you're working with a browser-based project. You can include it using a CDN (Content Delivery Network) like this:

<script src='https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js'></script>
<link href='https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css' rel='stylesheet' />

Create a Map

Once you've installed MapLibre GL, you can create a map using the following code:

HTML
<div id="my-map"></div>
CSS
body {
     margin: 0;
     padding: 0;
}
 #my-map {
     position: absolute;
     top: 0;
     bottom: 0;
     width: 100%;
}
Javascript
// Obtain your API Key at https://myprojects.geoapify.com
const myAPIKey = "YOUR_GEOAPIFY_API_KEY";

// Bounding Box (bbox) for the United States Geographic Region
const bounds = { lat1: 51.069, lon1: -128.584, lat2: 21.657, lon2: -59.590 };

// Create a Mapbox GL Map instance
const map = new maplibregl.Map({
  center: [(bounds.lon1 + bounds.lon2) / 2, (bounds.lat1 + bounds.lat2) / 2],
  zoom: 3, // Set the initial zoom level
  container: 'my-map', // Specify the HTML container where the map will be displayed
  style: `https://maps.geoapify.com/v1/styles/osm-bright-smooth/style.json?apiKey=${myAPIKey}` // Use the specified map style with the provided API key
});

// Add map navigation controls (zoom, pan)
map.addControl(new maplibregl.NavigationControl());

// After the map has loaded
map.on('load', () => {
    // Here you can add markers, layers, or other customizations
});

This code sets up a MapLibre GL map with a specified bounding box for the United States. It uses a Geoapify API key for map styling and provides navigation controls for zoom and pan. After the map loads, you can add customizations such as markers and layers to build your population heatmap visualization.

With your MapLibre GL map set up, you're now ready to proceed to the next steps in creating your population heatmap.

Step 3: Creating Heatmap

With your MapLibre GL map in place, you're now ready to create a population heatmap. Follow these steps to add the heatmap layer and visualize your population data:

Loading GeoJSON Data

To create a heatmap, you need to load the GeoJSON data containing your population information. You can use the maplibregl.GeoJSONSource object to load your GeoJSON data onto the map. Here's an example of how to do this:

map.addSource('population', {
  'type': 'geojson',
  'data': 'https://www.geoapify.com/data-share/assets/us-population-data.json'
});

You can replace https://www.geoapify.com/data-share/assets/us-population-data.json with the other file with GeoJSON population data.

Creating the Heatmap Layer

Next, we'll create a population heatmap and a point layer on the MapLibre GL map to visualize the population data. This step involves adding two layers: a heatmap layer and a point layer.

Adding a Heatmap Layer
// Add a heatmap layer for population data
// Heatmap Layer style documentation: https://maplibre.org/maplibre-style-spec/layers/#heatmap
map.addLayer({
  'id': 'population-heat',
  'type': 'heatmap',
  'source': 'population',
  'maxzoom': 9,
  'paint': {
    'heatmap-weight': [
      'interpolate', ['linear'],
      ['get', 'population'], 0, 0, 1000, 0.1, 10000, 0.2, 100000, 0.3,
      1000000, 0.5, 10000000, 1,
    ],
    'heatmap-intensity': [
      'interpolate', ['linear'],
      ['zoom'], 0, 1, 9, 3
    ],
    'heatmap-color': [
      'interpolate', ['linear'],
      ['heatmap-density'], 0, 'rgba(50, 50, 255, 0)', 0.2,
      'rgb(0, 204, 255)', 0.4, 'rgb(128, 255, 128)', 0.6,
      'rgb(255, 255, 102)', 0.8, 'rgb(255, 128, 0)', 1, 'rgb(204, 0, 0)'
    ],
    'heatmap-radius': [
      'interpolate', ['linear'],
      ['get', 'population'], 1000, 1, 1000000, 20, 10000000, 30
    ],
    'heatmap-opacity': [
      'interpolate', ['linear'],
      ['zoom'], 7, 1, 9, 0
    ]
  }
}, 'place-other');
  • The code begins by adding a heatmap layer to the map using the map.addLayer() method.
  • The layer is given the ID 'population-heat' and is defined as a 'heatmap' layer type, which is suitable for visualizing population density.
  • The 'population' source is used to fetch the population data from previously loaded GeoJSON data.
  • The 'maxzoom' property is set to 9, meaning that the heatmap layer will be visible up to a maximum zoom level of 9.

The heatmap layer's appearance is customized using the paint property. Here's a breakdown of the styling properties:

  • 'heatmap-weight': This property assigns different weights to data points based on population values. The interpolation function calculates the weight for various population ranges.
  • 'heatmap-intensity': It controls the intensity of the heatmap based on the zoom level, allowing for different levels of heat concentration.
  • 'heatmap-color': The color scheme for the heatmap is determined by interpolating colors based on data point density.
  • 'heatmap-radius': The radius of influence for each data point varies with population size, creating variable-sized heatmap points.
  • 'heatmap-opacity': The heatmap's opacity changes based on the zoom level, giving the effect of fading in and out as users zoom in or out.

To further understand and explore the styling properties and other aspects of MapLibre GL layers, you can refer to the official MapLibre GL style documentation.

Adding a Point Layer

In addition to the heatmap layer, a point layer is added to visualize individual population data points.

// Add a point layer for population data
// Circle Layer style documentation: https://maplibre.org/maplibre-style-spec/layers/#circle
map.addLayer({
  'id': 'population-point',
  'type': 'circle',
  'source': 'population',
  'minzoom': 7,
  'paint': {
    'circle-radius': [
      'interpolate', ['linear'],
      ['zoom'], 7, ['interpolate', ['linear'],
        ['get', 'population'], 1000, 5, 10000, 10, 100000, 15, 1000000,
        30, 10000000, 50
      ], 16, ['interpolate', ['linear'],
        ['get', 'population'], 1000, 10, 10000, 20, 100000, 30, 1000000,
        100, 10000000, 200
      ]
    ],
    'circle-color': 'rgb(54, 174, 255)',
    'circle-stroke-color': 'white',
    'circle-stroke-width': 1,
    'circle-opacity': [
      'interpolate', ['linear'],
      ['zoom'], 7, 0, 8, 1
    ]
  }
}, 'place-other');
  • The layer is given the ID 'population-point' and is defined as a 'circle' layer type, suitable for displaying individual data points.
  • The 'population' source is used to fetch the population data, the same as for the heatmap layer.
  • The 'minzoom' property is set to 7, indicating that the point layer will become visible at a minimum zoom level of 7.

The point layer's appearance is defined within the paint property:

  • 'circle-radius': The size of each circle representing a data point is determined by an interpolation function based on both population and zoom level.
  • 'circle-color': It sets the fill color for the circles.
  • 'circle-stroke-color' and 'circle-stroke-width': These properties define the color and width of the circle's stroke or border.
  • 'circle-opacity': Similar to the heatmap layer, the opacity of the circles is adjusted based on the zoom level.

These layers provide a dual representation of population data: the heatmap layer visualizes density, while the point layer shows individual data points. The styling properties allow you to adapt the appearance of these layers based on population values and the zoom level, resulting in an engaging population heatmap on your MapLibre GL map.

Step 4: Live Example with JSFiddle

To see the population heatmap in action and experiment with the code, you can access a live interactive example on JSFiddle.

View the Live Example on JSFiddle

You can edit and run the code, adjust the styling, and explore the population heatmap directly within the JSFiddle environment.

Embedded JSFiddle:

Here's the embedded JSFiddle for your convenience:

Feel free to click on the "Edit on JSFiddle" button to access the full code and experiment with the live example.

Now that you have the opportunity to interact with the code and visualize the population heatmap, you can fine-tune the settings and see the immediate results. Enjoy exploring and customizing your map!

Conclusion

In this tutorial, you've learned how to create a population heatmap using MapLibre GL, a powerful library for interactive map visualizations. We've covered the following key steps:

  1. Setting Up the Map: You started by setting up a MapLibre GL map, configuring its style, center, and initial zoom level to create the canvas for your population heatmap.

  2. Loading GeoJSON Data: You learned how to load population data from a GeoJSON file, allowing you to visualize the population of different locations.

  3. Creating Heat Map: You added a heatmap layer and a point layer to the map to display population data in two different ways. The heatmap layer showcases population density, while the point layer shows individual data points.

  4. Live Example on JSFiddle: To enhance your learning experience, we provided a live example on JSFiddle, where you can interact with the code and see the population heatmap in action. Feel free to customize the code and explore the results.

Creating population heatmaps is not only informative but also visually engaging. You can use this knowledge to visualize and analyze population data for various regions, making it a valuable tool for data analysis, urban planning, and more.

We hope this tutorial has been informative and that you find population heatmaps a useful addition to your data visualization toolkit. Happy mapping!

To further enhance your mapping projects, consider exploring Geoapify, a platform that provides a range of location-based services and data to enhance your mapping applications. Geoapify offers valuable resources and tools for developers and data analysts.