Style Lines with a Data-Driven Property in Maptoolkit Maps JS
Data-driven line styling in Maptoolkit Maps JS uses get expressions to read a feature property and map it to a visual value such as line-width or line-color. This lets a single layer display lines with different weights or colors based on attributes like road type, speed limit, or activity count. Use this to visualize network data, route comparisons, or any dataset where line appearance should reflect an underlying attribute.
const API_KEY = 'YOUR_API_KEY';
const map = new maptoolkit.Map({
container: 'map',
apiKey: API_KEY,
style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
center: [12.0, 47.5],
zoom: 6,
attributionControl: { compact: false }
});
map.addControl(new maptoolkit.NavigationControl(), 'top-right');
map.on('load', () => {
map.addSource('routes', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: { type: 'LineString', coordinates: [[11.39085, 47.27574], [13.0550, 47.8095], [16.3738, 48.2082]] },
properties: { name: 'Innsbruck-Vienna', speed: 200, type: 'highway' }
},
{
type: 'Feature',
geometry: { type: 'LineString', coordinates: [[11.39085, 47.27574], [8.5417, 47.3769]] },
properties: { name: 'Innsbruck-Zurich', speed: 120, type: 'regional' }
},
{
type: 'Feature',
geometry: { type: 'LineString', coordinates: [[13.0550, 47.8095], [15.4395, 47.0707]] },
properties: { name: 'Salzburg-Graz', speed: 80, type: 'local' }
}
]
}
});
map.addLayer({
id: 'routes',
type: 'line',
source: 'routes',
paint: {
'line-width': ['match', ['get', 'type'], 'highway', 6, 'regional', 4, 2],
'line-color': ['match', ['get', 'type'], 'highway', '#ee8a65', 'regional', '#3887be', '#56b881']
}
});
});<!DOCTYPE html>
<html lang="en">
<head>
<title>Style Lines with a Data-Driven Property - Maptoolkit Maps JS</title>
<meta property="og:description" content="Style line width and color based on feature properties using data-driven expressions." />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.css" />
<style>
html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
#map { width: 100%; height: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
const API_KEY = 'YOUR_API_KEY';
const map = new maptoolkit.Map({
container: 'map',
apiKey: API_KEY,
style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
center: [12.0, 47.5],
zoom: 6,
attributionControl: { compact: false }
});
map.addControl(new maptoolkit.NavigationControl(), 'top-right');
map.on('load', () => {
map.addSource('routes', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: { type: 'LineString', coordinates: [[11.39085, 47.27574], [13.0550, 47.8095], [16.3738, 48.2082]] },
properties: { name: 'Innsbruck-Vienna', speed: 200, type: 'highway' }
},
{
type: 'Feature',
geometry: { type: 'LineString', coordinates: [[11.39085, 47.27574], [8.5417, 47.3769]] },
properties: { name: 'Innsbruck-Zurich', speed: 120, type: 'regional' }
},
{
type: 'Feature',
geometry: { type: 'LineString', coordinates: [[13.0550, 47.8095], [15.4395, 47.0707]] },
properties: { name: 'Salzburg-Graz', speed: 80, type: 'local' }
}
]
}
});
map.addLayer({
id: 'routes',
type: 'line',
source: 'routes',
paint: {
'line-width': ['match', ['get', 'type'], 'highway', 6, 'regional', 4, 2],
'line-color': ['match', ['get', 'type'], 'highway', '#ee8a65', 'regional', '#3887be', '#56b881']
}
});
});
</script>
</body>
</html>Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.
How it works
A data-driven property reads a value off each feature instead of applying one constant to the
layer. ['get', 'name'] fetches a property, and wrapping it in match or interpolate maps
that value to a colour or a width.
The benefit is one layer instead of many. Colouring five route types with constants means five
layers and five filters; one match expression does it in one.
Two things to watch. Vector tile properties often arrive as strings, so a numeric
comparison against "5" fails silently unless coerced with to-number. And every match
needs a fallback for values you did not enumerate, or those features fall back to the layer
default and look unstyled.
Expressions evaluate per feature per frame, so keep them shallow on large sources.
Next steps
The obvious extension is styling on more than one property at once, width by importance and colour by type, which is how road networks are drawn and what the Schema Reference lists the fields for and is readable in a way either alone is not.
A legend is the other half. A data-driven map that does not explain its encoding is a decoration, and the stops in your expression are exactly the entries the legend needs.