Web Mapping Developer Guide
A comprehensive guide to building interactive web maps with GeoJSON Leaflet and modern tools.
The Web Mapping Stack
Every web map is built from the same core pieces: a basemap (tiles that provide the visual backdrop), data layers (your GeoJSON features drawn on top), and interaction handlers (zoom, pan, click, hover). The library you choose determines how these pieces fit together.
GeoJSON is the lingua franca of web mapping. Every major library can load and render it natively. This guide covers the most popular platforms and how each one works with GeoJSON.
Choosing a Library
| Library | Rendering | License | Best For |
|---|---|---|---|
| Leaflet | SVG / Canvas | BSD-2 (free) | Simple maps, quick prototypes, small datasets |
| Mapbox GL JS | WebGL | Proprietary (free tier) | High-performance maps, large datasets, 3D |
| MapLibre GL JS | WebGL | BSD-3 (free) | Same as Mapbox GL JS but fully open-source |
| OpenLayers | Canvas / WebGL | BSD-2 (free) | Enterprise GIS, complex projections, OGC standards |
| ArcGIS Maps SDK for JS | WebGL | Proprietary (free tier) | Enterprise GIS, Esri ecosystem, 3D scenes |
| Google Maps JS API | WebGL | Proprietary (pay-per-use) | Google ecosystem, Places/Geocoding integration |
| Deck.gl | WebGL | MIT (free) | Large-scale data visualization, GPU-accelerated layers |
| CesiumJS | WebGL | Apache 2.0 (free) | 3D globes, terrain, time-dynamic data |
Leaflet
Leaflet is the most popular open-source mapping library. It's lightweight (~42 KB gzipped), simple to learn, and has a massive plugin ecosystem. It renders GeoJSON using SVG or Canvas.
Basic GeoJSON Example
JavaScript (Leaflet)import L from "leaflet";
const map = L.map("map").setView([40.7, -74.0], 10);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© OpenStreetMap contributors"
}).addTo(map);
// Add GeoJSON directly
L.geoJSON(geojsonData).addTo(map);Styling and Interaction
JavaScript (Leaflet)L.geoJSON(geojsonData, {
style: (feature) => ({
color: feature.properties.type === "park" ? "#2d6a4f" : "#e63946",
weight: 2,
fillOpacity: 0.4
}),
onEachFeature: (feature, layer) => {
layer.bindPopup(feature.properties.name);
},
pointToLayer: (feature, latlng) => {
return L.circleMarker(latlng, { radius: 8 });
}
}).addTo(map);Loading from a URL
JavaScript (Leaflet)fetch("/data/parks.geojson")
.then(res => res.json())
.then(data => {
const layer = L.geoJSON(data).addTo(map);
map.fitBounds(layer.getBounds());
});Limitations: Leaflet uses SVG/Canvas rendering, which slows down with more than a few thousand features. For larger datasets, consider Mapbox GL JS, MapLibre, or clustering plugins like leaflet.markercluster.
Mapbox GL JS
Mapbox GL JS uses WebGL for GPU-accelerated rendering, enabling smooth handling of large datasets, 3D terrain, and custom vector styles. It requires a Mapbox access token.
Adding a GeoJSON Source and Layer
JavaScript (Mapbox GL JS)import mapboxgl from "mapbox-gl";
mapboxgl.accessToken = "YOUR_TOKEN";
const map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/light-v11",
center: [-74.0, 40.7],
zoom: 10
});
map.on("load", () => {
map.addSource("parks", {
type: "geojson",
data: "/data/parks.geojson"
});
map.addLayer({
id: "parks-fill",
type: "fill",
source: "parks",
paint: {
"fill-color": "#2d6a4f",
"fill-opacity": 0.4
}
});
map.addLayer({
id: "parks-outline",
type: "line",
source: "parks",
paint: {
"line-color": "#1b4332",
"line-width": 2
}
});
});Data-Driven Styling
JavaScriptmap.addLayer({
id: "points",
type: "circle",
source: "my-data",
paint: {
"circle-radius": ["interpolate", ["linear"], ["get", "magnitude"], 1, 4, 6, 20],
"circle-color": ["match", ["get", "type"],
"park", "#2d6a4f",
"school", "#457b9d",
"#e63946"
]
}
});Click Interaction
JavaScript (Mapbox GL JS)map.on("click", "parks-fill", (e) => {
const props = e.features[0].properties;
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(`<h3>${props.name}</h3><p>${props.description}</p>`)
.addTo(map);
});MapLibre GL JS
MapLibre GL JS is the open-source fork of Mapbox GL JS (from v1). The API is nearly identical to Mapbox GL JS, but it's BSD-licensed and works with any vector tile source.
JavaScript (MapLibre GL JS)import maplibregl from "maplibre-gl";
const map = new maplibregl.Map({
container: "map",
style: "https://demotiles.maplibre.org/style.json",
center: [-74.0, 40.7],
zoom: 10
});
map.on("load", () => {
map.addSource("my-data", {
type: "geojson",
data: geojsonData
});
map.addLayer({
id: "my-layer",
type: "fill",
source: "my-data",
paint: {
"fill-color": "#088",
"fill-opacity": 0.5
}
});
});If you're using Mapbox GL JS code, migrating to MapLibre usually requires changing the import and providing your own tile source — the GeoJSON API is the same.
OpenLayers
OpenLayers is a full-featured open-source library with strong support for OGC standards (WMS, WFS, WMTS), custom projections, and enterprise GIS workflows. It has a steeper learning curve but more flexibility for complex use cases.
JavaScript (OpenLayers)import Map from "ol/Map";
import View from "ol/View";
import TileLayer from "ol/layer/Tile";
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import OSM from "ol/source/OSM";
import GeoJSON from "ol/format/GeoJSON";
import { Style, Fill, Stroke } from "ol/style";
const vectorSource = new VectorSource({
url: "/data/parks.geojson",
format: new GeoJSON()
});
const map = new Map({
target: "map",
layers: [
new TileLayer({ source: new OSM() }),
new VectorLayer({
source: vectorSource,
style: new Style({
fill: new Fill({ color: "rgba(45, 106, 79, 0.4)" }),
stroke: new Stroke({ color: "#1b4332", width: 2 })
})
})
],
view: new View({
center: [0, 0],
zoom: 2
})
});Note: OpenLayers uses EPSG:3857 (Web Mercator) for its view by default. The GeoJSON format reader automatically reprojects from EPSG:4326.
ArcGIS Maps SDK for JavaScript
Esri's ArcGIS Maps SDK for JavaScript is the mapping library for the ArcGIS platform. It supports 2D maps and 3D scenes, integrates with ArcGIS Online and Enterprise services, and includes advanced tools like spatial analysis, geocoding, and routing out of the box.
Loading GeoJSON
JavaScript (ArcGIS)import Map from "@arcgis/core/Map";
import MapView from "@arcgis/core/views/MapView";
import GeoJSONLayer from "@arcgis/core/layers/GeoJSONLayer";
const geojsonLayer = new GeoJSONLayer({
url: "/data/parks.geojson",
renderer: {
type: "simple",
symbol: {
type: "simple-fill",
color: [45, 106, 79, 0.4],
outline: { color: [27, 67, 50], width: 2 }
}
},
popupTemplate: {
title: "{name}",
content: "{description}"
}
});
const map = new Map({
basemap: "topo-vector",
layers: [geojsonLayer]
});
const view = new MapView({
container: "map",
map: map,
center: [-74.0, 40.7],
zoom: 10
});Data-Driven Rendering
JavaScript (ArcGIS)const geojsonLayer = new GeoJSONLayer({
url: "/data/points.geojson",
renderer: {
type: "unique-value",
field: "type",
uniqueValueInfos: [
{
value: "park",
symbol: { type: "simple-marker", color: "#2d6a4f", size: 10 }
},
{
value: "school",
symbol: { type: "simple-marker", color: "#457b9d", size: 10 }
}
]
}
});Loading Inline GeoJSON
JavaScript// Create a Blob URL from inline GeoJSON data
const blob = new Blob([JSON.stringify(geojsonData)], {
type: "application/json"
});
const url = URL.createObjectURL(blob);
const layer = new GeoJSONLayer({ url });Note: GeoJSONLayer requires a URL — for inline data, create a Blob URL as shown above. The ArcGIS Maps SDK also supports FeatureLayer with a source array for client-side data.
The ArcGIS Maps SDK is free for non-commercial use and includes a generous free tier for commercial applications. An API key or OAuth token is required.
Google Maps JavaScript API
The Google Maps JavaScript API has built-in GeoJSON support through its Data layer. It integrates with Google's Places, Geocoding, Directions, and other services.
Loading GeoJSON
JavaScript (Google Maps)const map = new google.maps.Map(document.getElementById("map"), {
center: { lat: 40.7, lng: -74.0 },
zoom: 10
});
// Load from URL
map.data.loadGeoJson("/data/parks.geojson");
// Or add inline GeoJSON
map.data.addGeoJson(geojsonData);Styling
JavaScriptmap.data.setStyle((feature) => ({
fillColor: feature.getProperty("type") === "park" ? "#2d6a4f" : "#e63946",
fillOpacity: 0.4,
strokeColor: "#1b4332",
strokeWeight: 2
}));Click Events
JavaScript (Google Maps)const infoWindow = new google.maps.InfoWindow();
map.data.addListener("click", (event) => {
const name = event.feature.getProperty("name");
infoWindow.setContent(`<h3>${name}</h3>`);
infoWindow.setPosition(event.latLng);
infoWindow.open(map);
});Pricing: Google Maps requires an API key and charges per map load after the free monthly credit ($200/month). The Data layer API works well for small to medium GeoJSON datasets.
Deck.gl
Deck.gl is a WebGL-powered framework designed for large-scale data visualization. Built by Vis.gl (formerly Uber's visualization team), it excels at rendering hundreds of thousands of features with GPU acceleration.
JavaScript (deck.gl)import { DeckGL } from "@deck.gl/react";
import { GeoJsonLayer } from "@deck.gl/layers";
import { Map } from "react-map-gl/maplibre";
function App() {
const layer = new GeoJsonLayer({
id: "parks",
data: "/data/parks.geojson",
filled: true,
getFillColor: [45, 106, 79, 100],
getLineColor: [27, 67, 50],
getLineWidth: 2,
pickable: true
});
return (
<DeckGL
initialViewState={{ longitude: -74, latitude: 40.7, zoom: 10 }}
controller={true}
layers={[layer]}
>
<Map mapStyle="https://demotiles.maplibre.org/style.json" />
</DeckGL>
);
}Deck.gl is ideal when you need to visualize tens or hundreds of thousands of GeoJSON features without performance degradation. It pairs well with MapLibre or Mapbox as a basemap.
CesiumJS
CesiumJS is the leading open-source library for 3D globes and maps. It supports GeoJSON natively and is ideal for applications that need 3D terrain, time-dynamic data, or globe views.
JavaScript (CesiumJS)import { Viewer, GeoJsonDataSource } from "cesium";
const viewer = new Viewer("cesiumContainer");
GeoJsonDataSource.load("/data/parks.geojson", {
fill: Cesium.Color.fromCssColorString("#2d6a4f").withAlpha(0.4),
stroke: Cesium.Color.fromCssColorString("#1b4332"),
strokeWidth: 2
}).then((dataSource) => {
viewer.dataSources.add(dataSource);
viewer.zoomTo(dataSource);
});Performance Tips
- Simplify geometries — use our GeoJSON Simplifier to reduce vertex counts before loading into the browser.
- Reduce coordinate precision — 6 decimal places (~0.1m accuracy) is more than enough for web maps. Use our Coordinate Precision Tool.
- Use vector tiles for large datasets — for datasets over 5–10 MB, convert to vector tiles (Mapbox/MapLibre) or use server-side rendering (GeoServer, ArcGIS).
- Cluster point data — Mapbox GL JS, MapLibre, Leaflet (with plugins), and ArcGIS all support point clustering to improve rendering speed and readability.
- Load data lazily — fetch GeoJSON only when the user pans/zooms to the relevant area, rather than loading everything upfront.
- Use WebGL-based libraries for large datasets — if you have more than a few thousand features, choose Mapbox GL JS, MapLibre, Deck.gl, or ArcGIS over SVG-based Leaflet.
Framework Integration
Most mapping libraries work with React, Vue, Angular, and other frameworks through wrapper packages:
| Library | React | Vue | Angular |
|---|---|---|---|
| Leaflet | react-leaflet | @vue-leaflet/vue-leaflet | ngx-leaflet |
| Mapbox GL JS | react-map-gl | vue-mapbox | ngx-mapbox-gl |
| MapLibre | react-map-gl/maplibre | vue-maplibre-gl | ngx-maplibre-gl |
| ArcGIS | @arcgis/map-components | @arcgis/map-components | @arcgis/map-components |
| Google Maps | @vis.gl/react-google-maps | vue3-google-map | @angular/google-maps |
| Deck.gl | @deck.gl/react | Use imperative API | Use imperative API |
Further Reading
- What is GeoJSON? — introduction to the format
- GeoJSON Examples — copy-paste-ready examples for all geometry types