In a previous tutorial, we covered route visualization with Leaflet. Now let's explore how to achieve the same with MapLibre GL.
MapLibre GL offers powerful styling capabilities through its expression-based paint properties. Unlike raster-based libraries, MapLibre GL renders everything with WebGL, which means smooth zooming, rotation, and crisp lines at any scale.
The key difference from Leaflet: MapLibre GL uses layers and sources instead of direct GeoJSON rendering. This approach offers more flexibility - you can update styles without re-adding layers, and the GPU handles rendering efficiently.
This tutorial shows how to build a route visualization with interactive styling controls using MapLibre GL. You will learn to work with GeoJSON sources, line layers, and the Map Marker Icon API for custom markers.
APIs used:
- Routing API - calculate route between waypoints
- Map Tiles API - vector map style
- Map Marker Icon API - custom waypoint markers
What you will learn:
- How to add GeoJSON sources and line layers in MapLibre GL
- Creating route outlines with layer ordering
- Using
setPaintPropertyfor dynamic style updates - Building custom markers with HTML elements
- Differences from Leaflet route visualization
Table of Contents
- Why MapLibre GL for Routes
- Set Up the Map
- Add Route Layers
- Update Styles Dynamically
- Create Custom Markers
- Build Interactive Controls
- MapLibre GL vs Leaflet: Key Differences
- Explore the Demo
- Summary
- FAQ
Why MapLibre GL for Routes
MapLibre GL brings several advantages for route visualization:
- Vector rendering: Lines stay crisp at any zoom level
- GPU acceleration: Smooth performance even with complex routes
- Dynamic styling: Update colors and widths without re-rendering
- Built-in line offset: Native support for parallel route lines (useful for multi-route displays)
- 3D capabilities: Pitch and bearing for immersive views
The trade-off is a steeper learning curve. MapLibre GL uses a declarative approach where you define sources (data) and layers (how to render data) separately.
Set Up the Map
MapLibre GL loads a complete style JSON instead of individual tile layers:
const map = new maplibregl.Map({
container: "map",
style: `https://maps.geoapify.com/v1/styles/osm-bright/style.json?apiKey=${API_KEY}`,
center: [2.3376, 48.8606], // Paris [lng, lat]
zoom: 13
});
map.addControl(new maplibregl.NavigationControl(), "bottom-right");
Note the coordinate order: MapLibre GL uses [longitude, latitude] (GeoJSON standard), while Leaflet uses [latitude, longitude].
Key: Sign up at geoapify.com to get your API key.
Add Route Layers
In MapLibre GL, add a GeoJSON source first, then create layers that reference it:
map.addSource("route", {
type: "geojson",
data: routeData
});
// Outline layer (rendered first, appears below)
map.addLayer({
id: "route-outline",
type: "line",
source: "route",
layout: { "line-join": "round", "line-cap": "round" },
paint: {
"line-color": outlineColor,
"line-width": routeWidth + outlineWidth * 2,
"line-opacity": routeOpacity
}
});
// Main route layer
map.addLayer({
id: "route-line",
type: "line",
source: "route",
layout: { "line-join": "round", "line-cap": "round" },
paint: {
"line-color": routeColor,
"line-width": routeWidth,
"line-opacity": routeOpacity
}
});
By adding the outline layer first, MapLibre GL draws it below the main route. The outline width is routeWidth + outlineWidth * 2 so outlineWidth pixels show on each side.
Here's how the route looks with and without outline:
Update Styles Dynamically
MapLibre GL's setPaintProperty method changes styles instantly without re-adding layers:
map.setPaintProperty("route-line", "line-color", routeColor);
map.setPaintProperty("route-line", "line-width", routeWidth);
map.setPaintProperty("route-line", "line-opacity", routeOpacity);
map.setPaintProperty("route-outline", "line-color", outlineColor);
map.setPaintProperty("route-outline", "line-width", showOutline ? routeWidth + outlineWidth * 2 : 0);
This is more efficient than Leaflet's approach. The GPU updates rendering without recreating geometry.
Create Custom Markers
MapLibre GL markers use HTML elements. Create a div with the marker image as background:
const el = document.createElement("div");
el.style.width = `${dimensions.width}px`;
el.style.height = `${dimensions.height}px`;
el.style.backgroundImage = `url(${iconUrl})`;
el.style.backgroundSize = "contain";
const marker = new maplibregl.Marker({element: el, anchor: dimensions.anchor})
.setLngLat([wp.lon, wp.lat])
.setPopup(popup)
.addTo(map);
The anchor property determines which part of the element sits on the coordinates:
-
"center": Element center on coordinates (good for circles) -
"bottom": Bottom center on coordinates (good for pins)
Build Interactive Controls
Controls call updateRouteStyle() instead of re-rendering layers:
document.getElementById("route-color").addEventListener("input", (e) => {
document.getElementById("route-color-value").textContent = e.target.value.toUpperCase();
updateRouteStyle();
});
document.getElementById("marker-type").addEventListener("change", renderMarkers);
The pattern: listen for input changes, update the displayed value, and call the appropriate update function.
MapLibre GL vs Leaflet: Key Differences
| Aspect | Leaflet | MapLibre GL |
|---|---|---|
| Coordinate order | [lat, lng] | [lng, lat] |
| Adding routes | L.geoJSON(data).addTo(map) |
Source + Layer |
| Style updates | Remove and re-add layer | setPaintProperty() |
| Layer ordering | Custom panes | Layer add order |
| Markers |
L.icon() with image URL |
HTML elements |
| Line offset | Plugin required | Built-in line-offset
|
| Performance | Good for simple routes | Better for complex routes |
When to use MapLibre GL:
- Complex routes with many points
- Need for 3D views (pitch/bearing)
- Multiple overlapping routes (built-in line offset)
- Frequent style updates
- Vector tile base maps
When to use Leaflet:
- Simpler learning curve
- Existing Leaflet codebase
- Raster tile base maps
- Extensive plugin ecosystem
Explore the Demo
The interactive demo lets you experiment with all styling parameters in real-time. Try adjusting:
- Route styling - color, width, and opacity
- Outline effects - toggle, color, and width
- Marker types - awesome pins, material pins, or circles
- Marker size - from 32px to 80px
- Shadow toggle - for markers
MapLibre GL's setPaintProperty() makes these updates instant without re-rendering the entire route. Play with different combinations to see how MapLibre GL handles dynamic styling.
👉 Try the live demo:
Summary
MapLibre GL provides powerful tools for route visualization with excellent performance and flexible styling. This tutorial covered:
- Sources and layers: The MapLibre GL approach to data and rendering
- Layer ordering: Outline below route by adding layers in order
-
Dynamic updates: Using
setPaintProperty()for efficient style changes - HTML markers: Creating custom markers with the Map Marker Icon API
-
Coordinate order: Remember
[lng, lat]for MapLibre GL
Recommended settings:
- Route width: 5-8 pixels
- Outline width: 1-3 pixels (appears on each side)
- Opacity: 0.8-1.0 for clear visibility
- Marker size: 40-56 pixels
Key takeaways:
- MapLibre GL uses sources (data) and layers (rendering) separately
-
setPaintProperty()updates styles without re-rendering - Layer order determines z-index (first added = bottom)
- HTML markers offer full customization flexibility
- Built-in line offset makes multi-route visualization easier
Useful links:
- Geoapify Routing API Documentation
- Routing API Playground
- Map Marker Icon API
- MapLibre GL Documentation
- MapLibre GL Line Layer
FAQ
Q: What's the main advantage of MapLibre GL over Leaflet for routes?
A: Dynamic style updates without re-rendering. With MapLibre GL, you can change colors, widths, and other properties instantly using setPaintProperty(). Leaflet requires removing and re-adding layers. This makes MapLibre GL better for interactive styling or real-time updates.
Q: Why does MapLibre GL use [lng, lat] instead of [lat, lng]?
A: MapLibre GL follows the GeoJSON specification, which uses [longitude, latitude] order. This is the international standard. Leaflet uses [lat, lng] for historical reasons and to match common geographic notation.
Q: Can I use raster tiles with MapLibre GL?
A: Yes, but it's not the primary use case. MapLibre GL is optimized for vector tiles. If you need raster tiles, Leaflet is often simpler. However, MapLibre GL can load raster sources if needed.
Q: How do I handle route clicks in MapLibre GL?
A: Use map.on('click', 'layer-id', function(e) { ... }) where 'layer-id' is your route layer ID. MapLibre GL's event system is layer-based rather than object-based like Leaflet.
Q: What's the performance difference for complex routes?
A: MapLibre GL uses GPU rendering, which handles complex geometries better than Leaflet's canvas/SVG approach. For routes with thousands of coordinate points or multiple routes, MapLibre GL typically performs better, especially at higher zoom levels.
Q: Can I animate routes with MapLibre GL?
A: Yes. You can animate by updating the source data progressively or using the line-dasharray property with animation. MapLibre GL's GPU rendering makes animations smoother than Leaflet for complex routes.
Q: How do I show multiple routes with different colors?
A: Add each route as a separate feature in the GeoJSON source, then use data-driven styling with expressions in the paint properties. For example: 'line-color': ['get', 'color'] where each feature has a 'color' property.
Try It Now
👉 Open the Live Demo
Sign up at geoapify.com to get your API key and start building beautifully styled routes with MapLibre GL.



Top comments (0)