# 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.