# MapLibre GL JS
MapLibre GL JS is an open-source JavaScript library for rendering interactive, GPU-accelerated vector tile maps on web pages and webview-based applications. Originally forked from Mapbox GL JS (pre-December 2020 BSD-licensed version), the library has evolved into a fully community-driven project with extensive features including 3D terrain rendering, globe projection, custom layers, clustering, heatmaps, and real-time data visualization.
The library provides a comprehensive API for creating maps with customizable styles, adding markers and popups, handling user interactions, integrating GeoJSON data sources, and building complex geospatial visualizations. It supports WebGL2 with WebGL1 fallback, offers controls for navigation, geolocation, scale display, and fullscreen mode, and enables developers to extend functionality through custom style layers and protocols.
## Map Initialization
The `Map` class is the core component that creates an interactive map instance. It accepts configuration options for container, style URL, initial view position (center, zoom, pitch, bearing), and interaction behaviors.
```javascript
// Basic map initialization
const map = new maplibregl.Map({
container: 'map', // HTML element ID or element reference
style: 'https://demotiles.maplibre.org/style.json', // Style URL or object
center: [-74.5, 40], // [longitude, latitude]
zoom: 9, // Initial zoom level (0-24)
pitch: 0, // Tilt angle in degrees (0-85)
bearing: 0, // Rotation in degrees
minZoom: 0,
maxZoom: 22,
hash: true, // Sync position with URL hash
maplibreLogo: true
});
// Wait for map to load before adding layers
map.on('load', () => {
console.log('Map fully loaded');
});
```
## Adding Markers
The `Marker` class creates customizable point markers that can be positioned on the map. Markers support custom HTML elements, colors, dragging, rotation, and popup attachments.
```javascript
// Create a default marker
const marker = new maplibregl.Marker()
.setLngLat([12.550343, 55.665957])
.addTo(map);
// Create a custom colored, draggable marker
const customMarker = new maplibregl.Marker({
color: '#FF5733',
draggable: true,
scale: 1.2,
rotation: 45,
anchor: 'bottom'
})
.setLngLat([-77.03, 38.91])
.addTo(map);
// Listen for drag events
customMarker.on('dragend', () => {
const lngLat = customMarker.getLngLat();
console.log(`Marker moved to: ${lngLat.lng}, ${lngLat.lat}`);
});
```
## Creating Popups
The `Popup` class displays information boxes anchored to specific coordinates or markers. Popups support HTML content, custom styling, and various positioning options.
```javascript
// Create a standalone popup
const popup = new maplibregl.Popup({
closeOnClick: false,
closeButton: true,
maxWidth: '300px',
anchor: 'bottom'
})
.setLngLat([-96, 37.8])
.setHTML('
Hello World!
This is a popup.
')
.addTo(map);
// Attach popup to a marker
const markerWithPopup = new maplibregl.Marker()
.setLngLat([-122.414, 37.776])
.setPopup(new maplibregl.Popup().setHTML('San Francisco
'))
.addTo(map);
// Toggle popup programmatically
markerWithPopup.togglePopup();
```
## Adding GeoJSON Sources and Layers
The `addSource()` and `addLayer()` methods enable adding vector data and styling it with various layer types including fill, line, circle, symbol, and heatmap.
```javascript
map.on('load', () => {
// Add a GeoJSON line source
map.addSource('route', {
type: 'geojson',
data: {
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: [
[-122.48369, 37.83381],
[-122.48348, 37.83317],
[-122.48339, 37.83270],
[-122.48356, 37.83205]
]
}
}
});
// Add a line layer
map.addLayer({
id: 'route',
type: 'line',
source: 'route',
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': '#888',
'line-width': 8
}
});
});
```
## Adding Polygon Layers
Fill layers render polygon geometries with customizable colors, opacity, and patterns.
```javascript
map.on('load', () => {
// Add polygon source
map.addSource('maine', {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [[
[-67.13734, 45.13745],
[-66.96466, 44.8097],
[-68.03252, 44.3252],
[-69.06, 43.98],
[-70.11617, 43.68405],
[-70.64573, 43.09008],
[-71.08482, 45.30524],
[-70.66002, 45.46022],
[-67.13734, 45.13745]
]]
}
}
});
// Add fill layer
map.addLayer({
id: 'maine',
type: 'fill',
source: 'maine',
layout: {},
paint: {
'fill-color': '#088',
'fill-opacity': 0.8
}
});
});
```
## Creating Heatmap Layers
Heatmap layers visualize point density with customizable color gradients, radius, weight, and intensity based on zoom level.
```javascript
map.on('load', () => {
map.addSource('earthquakes', {
type: 'geojson',
data: 'https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson'
});
map.addLayer({
id: 'earthquakes-heat',
type: 'heatmap',
source: 'earthquakes',
maxzoom: 9,
paint: {
// Weight based on magnitude property
'heatmap-weight': [
'interpolate', ['linear'], ['get', 'mag'],
0, 0,
6, 1
],
// Intensity increases with zoom
'heatmap-intensity': [
'interpolate', ['linear'], ['zoom'],
0, 1,
9, 3
],
// Color gradient from transparent to red
'heatmap-color': [
'interpolate', ['linear'], ['heatmap-density'],
0, 'rgba(33,102,172,0)',
0.2, 'rgb(103,169,207)',
0.4, 'rgb(209,229,240)',
0.6, 'rgb(253,219,199)',
0.8, 'rgb(239,138,98)',
1, 'rgb(178,24,43)'
],
// Radius increases with zoom
'heatmap-radius': [
'interpolate', ['linear'], ['zoom'],
0, 2,
9, 20
]
}
});
});
```
## Clustering Points
GeoJSON sources support automatic clustering of points with customizable radius and zoom thresholds.
```javascript
map.on('load', () => {
// Add clustered source
map.addSource('earthquakes', {
type: 'geojson',
data: 'https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson',
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
// Cluster circles layer
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'earthquakes',
filter: ['has', 'point_count'],
paint: {
'circle-color': [
'step', ['get', 'point_count'],
'#51bbd6', 100,
'#f1f075', 750,
'#f28cb1'
],
'circle-radius': [
'step', ['get', 'point_count'],
20, 100,
30, 750,
40
]
}
});
// Cluster count labels
map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'earthquakes',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count_abbreviated}',
'text-font': ['Noto Sans Regular'],
'text-size': 12
}
});
// Unclustered points
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'earthquakes',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#11b4da',
'circle-radius': 4,
'circle-stroke-width': 1,
'circle-stroke-color': '#fff'
}
});
// Click to zoom into cluster
map.on('click', 'clusters', async (e) => {
const features = map.queryRenderedFeatures(e.point, { layers: ['clusters'] });
const clusterId = features[0].properties.cluster_id;
const zoom = await map.getSource('earthquakes').getClusterExpansionZoom(clusterId);
map.easeTo({
center: features[0].geometry.coordinates,
zoom
});
});
});
```
## Camera Animations
The `flyTo()`, `easeTo()`, and `fitBounds()` methods animate the camera to new positions with customizable easing and duration.
```javascript
// Fly to a location with animation
map.flyTo({
center: [-74.5, 40],
zoom: 14,
pitch: 45,
bearing: -17.6,
speed: 1.2,
curve: 1.42,
essential: true // Respect prefers-reduced-motion
});
// Ease to location (simpler animation)
map.easeTo({
center: [-122.4194, 37.7749],
zoom: 12,
duration: 2000
});
// Fit map to bounding box
map.fitBounds([
[32.958984, -5.353521], // Southwest coordinates
[43.50585, 5.615985] // Northeast coordinates
], {
padding: { top: 50, bottom: 50, left: 50, right: 50 },
maxZoom: 15
});
// Jump to location instantly (no animation)
map.jumpTo({
center: [-73.9857, 40.7484],
zoom: 16
});
```
## Navigation Controls
Built-in controls provide zoom buttons, compass, pitch visualization, scale display, geolocation, and fullscreen toggle.
```javascript
// Add navigation control with all options
map.addControl(new maplibregl.NavigationControl({
visualizePitch: true,
visualizeRoll: true,
showZoom: true,
showCompass: true
}), 'top-right');
// Add scale control
map.addControl(new maplibregl.ScaleControl({
maxWidth: 100,
unit: 'metric' // 'imperial', 'metric', or 'nautical'
}), 'bottom-left');
// Add fullscreen control
map.addControl(new maplibregl.FullscreenControl());
// Add geolocation control
map.addControl(new maplibregl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true,
showUserHeading: true
}));
```
## 3D Terrain
Enable 3D terrain rendering with elevation data from raster-dem sources, hillshade layers, and terrain controls.
```javascript
const map = new maplibregl.Map({
container: 'map',
zoom: 12,
center: [11.39085, 47.27574],
pitch: 70,
style: {
version: 8,
sources: {
osm: {
type: 'raster',
tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
maxzoom: 19
},
terrainSource: {
type: 'raster-dem',
url: 'https://demotiles.maplibre.org/terrain-tiles/tiles.json',
tileSize: 256
},
hillshadeSource: {
type: 'raster-dem',
url: 'https://demotiles.maplibre.org/terrain-tiles/tiles.json',
tileSize: 256
}
},
layers: [
{ id: 'osm', type: 'raster', source: 'osm' },
{
id: 'hills',
type: 'hillshade',
source: 'hillshadeSource',
paint: { 'hillshade-shadow-color': '#473B24' }
}
],
terrain: {
source: 'terrainSource',
exaggeration: 1
},
sky: {}
},
maxPitch: 85
});
// Add terrain toggle control
map.addControl(new maplibregl.TerrainControl({
source: 'terrainSource',
exaggeration: 1
}));
```
## 3D Building Extrusions
Fill-extrusion layers render buildings and other polygons in 3D with height based on data properties.
```javascript
map.on('load', () => {
map.addSource('openfreemap', {
url: 'https://tiles.openfreemap.org/planet',
type: 'vector'
});
map.addLayer({
id: '3d-buildings',
source: 'openfreemap',
'source-layer': 'building',
type: 'fill-extrusion',
minzoom: 15,
filter: ['!=', ['get', 'hide_3d'], true],
paint: {
'fill-extrusion-color': [
'interpolate', ['linear'], ['get', 'render_height'],
0, 'lightgray',
200, 'royalblue',
400, 'lightblue'
],
'fill-extrusion-height': [
'interpolate', ['linear'], ['zoom'],
15, 0,
16, ['get', 'render_height']
],
'fill-extrusion-base': [
'case',
['>=', ['get', 'zoom'], 16],
['get', 'render_min_height'],
0
]
}
});
});
```
## Event Handling
The map emits events for user interactions, data loading, style changes, and rendering. Use `on()` to subscribe and `off()` to unsubscribe.
```javascript
// Map load event
map.on('load', () => {
console.log('Map loaded');
});
// Click on specific layer
map.on('click', 'places', (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const description = e.features[0].properties.description;
new maplibregl.Popup()
.setLngLat(coordinates)
.setHTML(description)
.addTo(map);
});
// Mouse enter/leave for cursor changes
map.on('mouseenter', 'places', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'places', () => {
map.getCanvas().style.cursor = '';
});
// Track mouse coordinates
map.on('mousemove', (e) => {
console.log(`Lng: ${e.lngLat.lng}, Lat: ${e.lngLat.lat}`);
});
// Map movement events
map.on('movestart', () => console.log('Move started'));
map.on('moveend', () => console.log('Move ended'));
map.on('zoomend', () => console.log('Zoom:', map.getZoom()));
```
## Querying Features
The `queryRenderedFeatures()` and `querySourceFeatures()` methods retrieve feature data from rendered layers or sources.
```javascript
// Query features at a point
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['places', 'buildings']
});
if (features.length > 0) {
console.log('Clicked features:', features);
console.log('First feature properties:', features[0].properties);
}
});
// Query features in a bounding box
const bbox = [
[e.point.x - 5, e.point.y - 5],
[e.point.x + 5, e.point.y + 5]
];
const features = map.queryRenderedFeatures(bbox, {
layers: ['counties']
});
// Query all features from a source
const allFeatures = map.querySourceFeatures('earthquakes', {
sourceLayer: 'original',
filter: ['>=', ['get', 'mag'], 5]
});
```
## Dynamic Style Updates
Modify layer visibility, paint properties, filters, and data sources at runtime.
```javascript
// Toggle layer visibility
map.setLayoutProperty('buildings', 'visibility', 'none');
map.setLayoutProperty('buildings', 'visibility', 'visible');
// Update paint properties
map.setPaintProperty('water', 'fill-color', '#0000ff');
map.setPaintProperty('roads', 'line-width', 3);
// Set layer filter
map.setFilter('earthquakes', ['>=', ['get', 'mag'], 4.5]);
// Update GeoJSON source data
map.getSource('points').setData({
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: { type: 'Point', coordinates: [-122.4, 37.8] },
properties: { title: 'New Point' }
}
]
});
// Get/set map center and zoom
console.log('Center:', map.getCenter());
console.log('Zoom:', map.getZoom());
map.setCenter([-74.5, 40]);
map.setZoom(10);
```
## Adding Images and Icons
Load custom images for use in symbol layers with `loadImage()` and `addImage()`.
```javascript
map.on('load', async () => {
// Load and add an image
const image = await map.loadImage('https://example.com/marker.png');
map.addImage('custom-marker', image.data);
// Add source with points
map.addSource('points', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: { type: 'Point', coordinates: [-77.03, 38.91] },
properties: { title: 'Washington DC' }
}]
}
});
// Add symbol layer using the custom image
map.addLayer({
id: 'points',
type: 'symbol',
source: 'points',
layout: {
'icon-image': 'custom-marker',
'icon-size': 0.5,
'text-field': ['get', 'title'],
'text-offset': [0, 1.5],
'text-anchor': 'top'
}
});
});
```
## Custom Protocols
Register custom protocols for loading tiles or data from custom sources using `addProtocol()`.
```javascript
// Add custom protocol handler
maplibregl.addProtocol('custom', async (params, abortController) => {
const url = params.url.replace('custom://', 'https://');
const response = await fetch(url, { signal: abortController.signal });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.arrayBuffer();
return { data };
});
// Use custom protocol in source
map.addSource('custom-tiles', {
type: 'vector',
tiles: ['custom://tiles.example.com/{z}/{x}/{y}.pbf']
});
// Remove protocol when done
maplibregl.removeProtocol('custom');
```
## LngLat and LngLatBounds Classes
Utility classes for working with geographic coordinates and bounding boxes.
```javascript
// Create LngLat instances
const point = new maplibregl.LngLat(-73.9857, 40.7484);
console.log(point.lng, point.lat); // -73.9857, 40.7484
console.log(point.toArray()); // [-73.9857, 40.7484]
// Wrap longitude to -180 to 180 range
const wrapped = point.wrap();
// Calculate distance between points (meters)
const point2 = new maplibregl.LngLat(-122.4194, 37.7749);
const distance = point.distanceTo(point2);
// Create bounding box
const bounds = new maplibregl.LngLatBounds(
[-73.9876, 40.7661], // Southwest
[-73.9397, 40.8002] // Northeast
);
// Extend bounds to include a point
bounds.extend([-74.0, 40.75]);
// Get bounds properties
console.log(bounds.getCenter()); // Center LngLat
console.log(bounds.getSouthWest()); // SW corner
console.log(bounds.getNorthEast()); // NE corner
// Check if point is within bounds
console.log(bounds.contains(point)); // true/false
```
## RTL Text Support
Enable right-to-left text rendering for Arabic and Hebrew languages using the RTL text plugin.
```javascript
// Set RTL text plugin (call before creating maps)
maplibregl.setRTLTextPlugin(
'https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.3.0/dist/mapbox-gl-rtl-text.js',
false // lazy load
);
// Check plugin status
const status = maplibregl.getRTLTextPluginStatus();
console.log('RTL plugin status:', status); // 'unavailable', 'loading', 'loaded', or 'error'
```
## Performance Configuration
Configure worker count, parallel image requests, and prewarm resources for optimal performance.
```javascript
// Set number of web workers (call before creating maps)
maplibregl.setWorkerCount(4);
console.log('Worker count:', maplibregl.getWorkerCount());
// Set max parallel image requests
maplibregl.setMaxParallelImageRequests(32);
console.log('Max parallel requests:', maplibregl.getMaxParallelImageRequests());
// Prewarm worker pool for faster map initialization
maplibregl.prewarm();
// Clear prewarmed resources when done
maplibregl.clearPrewarmedResources();
// Get library version
console.log('MapLibre GL JS version:', maplibregl.getVersion());
```
## Summary
MapLibre GL JS provides a comprehensive solution for building interactive web maps with support for vector tiles, GeoJSON data sources, 3D terrain, building extrusions, clustering, heatmaps, and custom styling. The library's GPU-accelerated rendering enables smooth performance even with large datasets, while the extensive event system and querying capabilities support complex interactive applications. Common use cases include data visualization dashboards, location-based services, real estate platforms, logistics tracking, and geographic analysis tools.
The library integrates seamlessly with modern JavaScript frameworks through bindings like react-map-gl for React and ngx-maplibre-gl for Angular. Developers can extend functionality through custom layers, protocols, and worker scripts. The style specification supports expressions for data-driven styling, making it possible to create sophisticated visualizations that respond to zoom levels, feature properties, and user interactions. With active community development and regular releases, MapLibre GL JS continues to evolve as a powerful open-source alternative for web mapping applications.