Skip to content
GEOSAT
Back to blog
Open GIS
Open GIS2026-09-23GEOSAT5 min read

MapLibre: build a web map with GeoJSON

A complete example with fictional data, no keys and no basemap dependency.

Editorial review: 2026-09-23

MapLibreGeoJSONOpen GIS

Scope of the example

MapLibre uses declarative styles and WebGL. This example starts with a blank background and a GeoJSON source to isolate the contract between data and rendering. Coordinates are longitude, latitude. If WebGL is unavailable, the text description retains the location.

Save the following as index.html in a lab directory and serve it with python3 -m http.server 8000 --bind 127.0.0.1. Open http://localhost:8000. Libraries download from a version-pinned CDN, so a connection is required. There are no basemap requests or real operational records.

Code example
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Synthetic stations</title>
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css">
<style>#map { height: 70vh } body { margin: 1rem; font: 16px sans-serif }</style>
<h1>Synthetic stations</h1>
<p>Fictional data: Station A at longitude -75.58, latitude 6.24.</p>
<div id="map" aria-label="Map of a fictional station"></div>
<script src="https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js"></script>
<script>
const map = new maplibregl.Map({
  container: 'map', center: [-75.58, 6.24], zoom: 13,
  style: { version: 8, sources: {}, layers: [
    { id: 'background', type: 'background', paint: { 'background-color': '#e8eef0' } }
  ] }
})
map.addControl(new maplibregl.NavigationControl())
map.on('load', () => {
  map.addSource('stations', { type: 'geojson', data: {
    type: 'FeatureCollection', features: [{ type: 'Feature', id: 's1',
      properties: { name: 'Station A' },
      geometry: { type: 'Point', coordinates: [-75.58, 6.24] }
    }]
  } })
  map.addLayer({ id: 'stations', type: 'circle', source: 'stations',
    paint: { 'circle-radius': 10, 'circle-color': '#176b50' }
  })
})
</script>
</html>

Check the result

A point should appear near the center of an empty background. Test zooming, panning and resizing; check that the text remains accessible with a keyboard. The blank background is intentional: it separates data problems from a map provider's quotas, credentials and availability.

If the point does not appear, first inspect the console, script download and container height. Then check coordinate order and geometry type. When connecting an API, validate HTTP status and bound the volume before passing a response to the map. Do not download an entire operational database merely to hide attributes in the browser.

What an enterprise application still needs

Add authorized search, loading and error states, data and basemap attribution, and an equivalent table for tasks that do not require visual navigation. Establish a payload budget and test the team's slowest device. When adding a basemap, review its license, terms, quotas and provider charges: an open library does not include an unlimited free mapping service.

An application migration must also address printing, editing, permissions, exports and support. Use this lab to validate the visualization layer; assess those other capabilities separately before replacing an ArcGIS application.

Decide what the browser is responsible for

MapLibre renders a map; it does not provide your database, identity provider, editing API or basemap license. Draw the data path before replacing a web application:

ResponsibilityExample componentAcceptance question
Authorized source dataDatabase view and APICan the caller retrieve only permitted records and fields?
Map presentationMapLibre style and layersDoes the user understand status, scale and attribution?
Search and selectionBounded endpoint and stable identifierDoes a search result select the same object on the map?
EditingValidated write APIAre invalid and stale changes rejected?
Export or printingDefined output workflowDoes the delivered artifact meet the user's task?

For a public viewer, publish an intentionally restricted dataset. Removing a field from a popup does not remove it from the network response. Anyone permitted to download the source can inspect its attributes. Apply access boundaries before serialization, and keep credentials out of JavaScript bundles and style files.

Add the next dataset without losing identity

The reference lab includes data/municipal-assets.geojson with synthetic asset identifiers. Use its provided viewer and instructions for that dataset rather than copying all 10,000 records into the HTML example. Keep asset_id stable across table rows, search results, map features and detail requests.

When replacing source data, use the source's supported update mechanism after the map has loaded. Do not repeatedly create new layers with the same IDs. A style reload can remove application-added sources and layers, so reattach them through a deliberate lifecycle. Test navigation away and back in the actual web framework; a standalone HTML success does not prove component cleanup.

Bound interactive queries

A viewport-driven application should cancel superseded requests and avoid applying a slow response to a newer map extent. Debounce user movement appropriately and include explicit limits. A feature endpoint may paginate; displaying only its first page without a visible indication can give a false picture of coverage.

Use GeoJSON for bounded features and inspect payload size and parsing cost on representative devices. Consider vector tiles for large display datasets, while retaining a separate feature API for authoritative details. Generalized display geometry should not become the source of engineering measurements.

Make failure and access states visible

Test these states separately: no matching features, unauthorized access, network failure, an invalid response, missing style resources and unsupported graphics capability. Each should leave a useful message or data alternative. A blank map is ambiguous; the user cannot know whether the area contains no assets or the request failed.

Provide a text list or table for search and record details. Keyboard users should reach controls, move focus predictably and exit the map interaction. Respect reduced motion when adding camera transitions. Do not use color alone to distinguish operational states, and preserve legible attribution at narrow widths.

Acceptance for a migrated viewer

Choose five tasks from the existing application: find a known identifier, filter a status, inspect an attachment link, export an allowed selection and share a view. Repeat them against the proposed application with a read-only test account. Record missing capabilities explicitly; a good visualization does not imply replacement of every ArcGIS widget.

Measure request count, response bytes and time to a usable task on the intended network. Do not publish a universal “faster than ArcGIS” claim from a local static demo. MapLibre can be part of a lower-cost architecture, but data services, hosting, basemaps and support still belong in the cost model.

Reference: MapLibre GeoJSON source API.

Sources and documentation

Next step

Continue in the Open GIS collection. For a specific project, use the total-cost calculator and request an assessment.

Related articles