Skip to content
I.STEM Lab

Tutorial · Web GIS

Show your data on a web map with MapLibre

Add an interactive map, a GeoJSON layer, popups and data-driven colours to any web page.

Beginner12 min read

Key takeaways

  • MapLibre GL JS is a free, open-source library for fast interactive vector maps.
  • GeoJSON is a simple format for points, lines and polygons, and it uses [longitude, latitude] order.
  • A source holds data, and a layer decides how it is drawn.
  • Data-driven styling lets one layer show many colours based on a property.

A web map is the friendliest way to share spatial data. Anyone with a browser can zoom, click and explore, and nothing needs to be installed. MapLibre GL JS is a free, open-source library that draws smooth, interactive maps using the graphics card, and it works with any data you can express as GeoJSON.

In this tutorial you will put a map on a page, add a layer of areas from a GeoJSON file, colour the areas by a property, add popups and fit the view to your data. You need only a text editor and a browser.

What you need

  • A text editor, and any modern browser.
  • A GeoJSON file named areas.geojson with polygons that have a property called landuse, for example forest, water or farm. You can export one from QGIS or draw one at geojson.io.
  • A way to serve files locally, because browsers block local files from loading data. In a terminal, run: python -m http.server 8000, then open http://localhost:8000.

Step 1: the page

Create an index.html file that loads the library from a CDN and reserves space for the map.

html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>My first web map</title>
  <link href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css" rel="stylesheet" />
  <script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
  <style>
    body { margin: 0; }
    #map { height: 100vh; }
  </style>
</head>
<body>
  <div id="map"></div>
  <script src="map.js"></script>
</body>
</html>

Step 2: create the map

In map.js, create the map. The style is the basemap. This demo style is free and good for learning. For a real project, choose a style from a provider whose terms fit your use and show its attribution.

javascript
const map = new maplibregl.Map({
  container: "map",
  style: "https://demotiles.maplibre.org/style.json",
  center: [85.3237, 27.6808], // [longitude, latitude]
  zoom: 10,
});

map.addControl(new maplibregl.NavigationControl());

Step 3: add your data

Wait for the map to finish loading, then add a source that points to your file, and a layer that draws it as filled polygons. The color expression below is data-driven: it looks at each area’s landuse property and picks a colour.

javascript
map.on("load", () => {
  map.addSource("areas", { type: "geojson", data: "areas.geojson" });

  map.addLayer({
    id: "areas-fill",
    type: "fill",
    source: "areas",
    paint: {
      "fill-color": [
        "match", ["get", "landuse"],
        "forest", "#2b5243",
        "water", "#4a78a8",
        "farm", "#c9b458",
        "#999999", // anything else
      ],
      "fill-opacity": 0.65,
    },
  });

  map.addLayer({
    id: "areas-outline",
    type: "line",
    source: "areas",
    paint: { "line-color": "#111111", "line-width": 1 },
  });
});

Step 4: add popups

A map is more useful when people can click for details. We build the popup from plain text nodes rather than HTML strings. That keeps the page safe even if a property contains characters like angle brackets, and it matters whenever the data comes from outside.

javascript
map.on("click", "areas-fill", (e) => {
  const props = e.features[0].properties;
  const box = document.createElement("div");
  box.textContent = `Land use: ${props.landuse}`;

  new maplibregl.Popup().setLngLat(e.lngLat).setDOMContent(box).addTo(map);
});

// show a pointer cursor over clickable areas
map.on("mouseenter", "areas-fill", () => (map.getCanvas().style.cursor = "pointer"));
map.on("mouseleave", "areas-fill", () => (map.getCanvas().style.cursor = ""));

Step 5: zoom to your data

Instead of guessing a centre and zoom level, calculate the bounds of your data and fit the view to them. This version reads the file once and works through every coordinate.

javascript
fetch("areas.geojson")
  .then((r) => r.json())
  .then((geojson) => {
    const bounds = new maplibregl.LngLatBounds();
    const extend = (coords) =>
      Array.isArray(coords[0]) ? coords.forEach(extend) : bounds.extend(coords);
    geojson.features.forEach((f) => extend(f.geometry.coordinates));
    map.fitBounds(bounds, { padding: 40 });
  });

Make it ready for real use

  • Attribution. Show the credit your basemap and data require. It is usually a licence condition.
  • Large data. A GeoJSON file of tens of thousands of shapes will be slow. Convert it to vector tiles, which load only what is on screen.
  • Accessibility. Add a text summary or table of the key information, so that the map is not the only way to read it.
  • Mobile. Test on a phone, where the map should fill the screen and controls need to be easy to tap.
  • Hosting. A map is just static files, so any static host will do.

Troubleshooting

  • Blank grey map: open the browser console, and check that the style URL loaded.
  • Data does not appear: confirm you added it inside the load event, and that the file path is correct.
  • Points land in the wrong country: your coordinates are probably swapped to latitude, longitude.
  • Shapes look distorted or offset: GeoJSON must be in standard WGS 84 coordinates. Reproject if it is not.

Adding a layer toggle

Once a map has more than one layer, people need a way to switch them on and off. A simple checkbox is enough. Add this to your page, and a few lines to map.js.

html
<label style="position:absolute;top:12px;left:12px;background:#fff;padding:6px 10px">
  <input type="checkbox" id="toggle" checked /> Show areas
</label>
javascript
document.getElementById("toggle").addEventListener("change", (e) => {
  map.setLayoutProperty("areas-fill", "visibility", e.target.checked ? "visible" : "none");
  map.setLayoutProperty("areas-outline", "visibility", e.target.checked ? "visible" : "none");
});

The same pattern works for any layer. For many layers, build the checkboxes from a list, and keep their state in sync with the map.

Choosing a data and hosting strategy

  • Small data, up to a few thousand shapes: a GeoJSON file served as a static asset, as in this tutorial.
  • Medium data: simplify the geometry and reduce the decimal places in the coordinates, which can shrink files by half or more.
  • Large data: convert to vector tiles with a tool such as tippecanoe, and serve them from static storage. Only the tiles in view are loaded.
  • Live data: serve it from an API, and refresh the source with a timer or a stream.
  • Hosting: a static host or a content delivery network is usually enough, and inexpensive.

Frequently asked questions

Is MapLibre the same as Mapbox?
MapLibre began as an open-source fork of an earlier version of Mapbox GL JS, and is developed by a community. It has no token requirement, and works with many tile providers.
Can I use MapLibre with React?
Yes. You create the map inside an effect and clean it up when the component is removed. Wrapper libraries exist, but the core code is the same as here.
How do I show satellite imagery as the background?
Use a style that includes a raster imagery source from a provider whose terms allow your use, and include the credit they require.
Why is my map slow with a large file?
Because the browser has to download and process every shape. Simplify the data, or switch to vector tiles.

You now have the foundation of almost every web map: a basemap, a source, a layer and some interaction. From here you can add filters, time sliders, legends and your own analysis results.

Share

Join the conversation

Reactions & comments

Comments are opening soon.

In the meantime, tell us what you think by email at info.istem.lab@gmail.com.

Start a project

Need this built for your organization?

If you would rather have a working system than a tutorial, tell us your goal and we will scope it with you.