This is the documentation of version 0.62.2. Go to the latest version, 1.0.0-rc.5.

Route sections

The Routing module's showRoutes() draws a separate overlay for each stretch of road the response describes: urban stretches, motorways, low-emission zones, tunnels, ferries, traffic jams, toll roads and so on. Every one of them is configured under sections, keyed by section type, on the same set of knobs.

const routingModule = await RoutingModule.create(map, {
    sections: {
        urban: { visible: true },                                            // draw a type nothing draws by default
        lowEmissionZone: { color: '#1B7A43', opacity: 0.8 },                 // deepen the green of one that is
        tunnel: { color: '#402060', style: 'halo' },                         // restyle one that is drawn already
        tollRoad: { icon: { image: 'poi-toll_plaza', placement: 'along' } },  // put an icon on another
        traffic: { visible: false }                                          // and drop one
    }
});

Which sections draw by default

Whether a type is drawn when you configure nothing is per type, and follows one rule: a type draws itself when its stretches are sparse along a route and consequential for the driver. carTrain, ferry, lowEmissionZone, tollRoad, tollVignette, traffic, tunnel and vehicleRestricted qualify — money to pay, a permit to hold, a road the vehicle may not use, or the vehicle going onto a train — and switch off with { visible: false }.

speedLimit draws itself too, and it is the one type that does so while running nearly a route's whole length — it can, because it posts a sign per section rather than banding the route. See below.

The other six are opt-in and switch on with { visible: true }. motorway and urban each run nearly a route's whole length, so a band drawn for them says nothing and buries the route line — and both are already legible from the base map underneath.

sectionDrawsByDefault(type) reports which side of that a type falls on, for a UI that shows the types as switches. Reading the map instead cannot answer it: a route carrying no ferry sections draws no ferry layer, so an absent layer looks the same as a type switched off.

Section knobs

Knob What it does
visible Whether the section is drawn at all.
color Line colour. On a type drawn with more than one line, this sets the principal one and the rest are derived from it.
sign The road sign a section posts its own value on, for the one type drawn that way: speedLimit — see below.
opacity Line opacity, 0 to 1.
width Line width preset, s, m or l. Defaults to the route's own width.
widthFactor Multiplier over width, 0.5 to 2 (ROUTE_WIDTH_FACTOR_RANGE). Defaults to the route's own widthFactor.
style halo or inline — see below.
pattern solid, dashed or dotted.
icon An icon on the section: { image, placement, sizeFactor }, where image is a sprite id, placement is center or along, and sizeFactor multiplies the size the type draws its icon at, 0.25 to 2 (SECTION_ICON_SIZE_FACTOR_RANGE). On ferry, tollRoad, tunnel and vehicleRestricted it replaces the icon they draw where each section starts — see tunnel and closed-road signs.

style chooses between the two ways a section can mark a stretch of the route, and sets both the layer order and the width, since neither reads as intended without the other:

Switching a type's style at runtime restacks its layers, so applyConfig is enough to move a section from one side of the route line to the other.

Not every type answers to every knob, and which knobs a type takes is part of its type: traffic colours its line by magnitudeOfDelay and takes its icons from the incident data, so sections.traffic.color does not compile rather than compiling and drawing nothing. speedLimit draws no line at all, so it takes visible and sign and none of the line knobs. For a UI that builds controls from a section type, sectionSupportsKnob(type, knob) answers the same question at runtime.

The types covered: carpool, carTrain, ferry, importantRoadStretch, lowEmissionZone, motorway, pedestrian, speedLimit, tollRoad, tollVignette, traffic, tunnel, unpaved, urban and vehicleRestricted — every one live on the route sections playground.

Layer overrides

When the knobs are not enough, the full layer spec is reachable under layers.sections, keyed by the same types. For the generated types the layer id is derived from the section type, so motorway is drawn by routeSectionMotorwayLine and speedLimit by routeSectionSpeedLimitSign:

const routingModule = await RoutingModule.create(map, {
    layers: {
        sections: {
            motorway: {
                routeSectionMotorwayLine: {
                    paint: { 'line-color': '#1F6FB2', 'line-dasharray': [2, 1] }
                }
            },
            tunnel: {
                routeTunnelLine: { paint: { 'line-blur': 2 } }
            }
        }
    }
});

A traffic section is drawn in the colour of its delay (minor, moderate or major), and those colours follow the map's incident colour knobs: see Borrowed looks.

Tunnels and closed roads post a sign where they start

tunnel and vehicleRestricted post a road sign where each of their sections starts, in the direction of travel. A vehicleRestricted stretch is one the requested vehicle is not permitted to use, which the route still takes. Asking for the sections is all it takes:

const routes = await calculateRoute({ locations, sectionTypes: ['tunnel', 'vehicleRestricted'] });
await routingModule.showRoutes(routes);

Both draw from zoom 10 on the selected route, and scale with the zoom as the speed limit signs do. The section knobs reach them: { visible: false } drops the sign with the rest of the section, and icon replaces it with a sprite image.

An icon posted where a section starts gives way to a waypoint icon standing on that point, rather than draw flush against it. It keeps room on the sides that face the waypoint icon's anchor: above it for the default pin, and on every side for an icon a layers.waypoints.routeWaypointSymbol override anchors at its centre.

The sign layers themselves are routeTunnelSymbol and routeVehicleRestrictedSymbol, under layers.sections. They take any MapLibre layer property but visibility, which the module sets itself each time it shows a route:

const routingModule = await RoutingModule.create(map, {
    layers: {
        sections: {
            tunnel: { routeTunnelSymbol: { minzoom: 13 } },                    // post tunnel signs only close in
            vehicleRestricted: { routeVehicleRestrictedSymbol: { minzoom: 8 } } // post the closed sign earlier
        }
    }
});

A click on a sign reaches the same scope as a click on its stretch, and hands over the section:

routingModule.events.vehicleRestricted.on('click', (section) => {
    console.log('Closed to vehicles from route point', section.properties.startPointIndex);
});

Both signs are live on the route sections playground, where clicking the tunnel or vehicleRestricted count flies to the next sign.

Speed limits are posted, not banded

speedLimit is the one type whose information is a number rather than a stretch, and it is drawn accordingly: the module posts a road sign per section, and that is the whole of what the type draws. Asking for the sections is all it takes.

// The signs need no configuration — asking for the sections is enough.
const routes = await calculateRoute({ locations, sectionTypes: ['speedLimit', 'country'] });
await routingModule.showRoutes(routes);

The sign follows the road rather than the reader. Ask for country sections alongside the speed limits and each sign takes the convention of the country its stretch runs through:

Country Face Unit Numerals
most of Europe white disc km/h near-black
Sweden, Finland, Iceland yellow disc km/h near-black
United Kingdom white disc mph near-black
United States SPEED LIMIT plaque mph near-black
Japan white disc km/h blue

A country the SDK has no entry for posts the white disc in km/h. Where mph is posted the number is converted and rounded to the step signs come in, so a London stretch reads 30 and a Californian one 65, both from the same maxSpeedLimitInKmh. Without country sections the display units decide the face and the unit, and sign.unit overrides both.

A sign stands where the limit changes, as the road's own does. That is exact: speed limit sections tile a route end to end and no two consecutive ones carry the same limit, so every section boundary is a change. Signs scale with the zoom as the ferry and toll-road icons do.

sign.placement: 'along' repeats the sign down the stretch instead, at a fixed screen spacing. Nothing then marks where the limit changed, but a reader zoomed into the middle of a 50 km stretch still sees the limit in force; the default posts nothing between one change and the next.

sign.minZoom defaults to 10, where the signs stop shrinking. Further out, a route's changes fall in the same few pixels and the handful that survive are an arbitrary sample of the drive. Lower it if your map is about the limits themselves.

Since the signs are all the type draws, { visible: false } switches them off the way it switches off any other section. The sign knob carries the rest:

Knob What it does
sign.placement atChange (the default) posts one sign per limit change; along repeats it down the stretch instead.
sign.minZoom The lowest zoom they draw at. Defaults to 10 — see above.
sign.priority What the signs give way to: belowMapLabels, belowRouteIcons or aboveRouteIcons. The default follows the placement — see below.
sign.unit 'km/h' or 'mph' for every sign, whatever the road posts.
const routingModule = await RoutingModule.create(map, {
    sections: {
        speedLimit: {
            sign: { minZoom: 8, priority: 'aboveRouteIcons' }
        }
    }
});

priority settles which symbol survives when two want the same spot. MapLibre resolves collisions from the topmost layer down, so the layer underneath gives way:

The default follows placement, because what a dropped sign costs does: belowRouteIcons under atChange, where that sign is the only one posting its change, and belowMapLabels under along, where the next repeat posts the same limit again.

Both knobs are live on the route sections playground; speed limit signs by country posts one limit four ways.

Border crossings

country sections partition a route end to end, each ending where the next begins, so every seam between two of them is a border. A crossing is that seam — a point rather than a stretch, which is why it takes knobs of its own rather than a place among the section knobs above. Each one is a plaque naming both countries in the direction of travel by their ISO 3166-1 alpha-2 codes, ES → FR. Ask for country sections and the crossings draw themselves:

const routes = await calculateRoute({ locations, sectionTypes: ['country'] });
await routingModule.showRoutes(routes);

The module reads the route it was given, so a route that stays inside one country, or that was never asked for its country sections, draws none. Re-entering a country counts as a second crossing, back the way it came — Geneva to Lausanne via Annecy draws CH → FR and then FR → CH.

Knob What it does
visible Whether the crossings draw. Defaults to true.
minZoom The lowest zoom a crossing draws at. Defaults to 4, low enough for a continental route to show its borders while framed whole.
color The plaque colour. Defaults to the SDK's near-black on a light map and its near-white on a dark one.
textColor The label colour. Defaults to whichever of the two reads on the plaque, so setting color alone stays legible.
alignment 'viewport' (default) keeps the plaque level with the screen; 'route' turns it to the route's bearing, kept upright.
const routingModule = await RoutingModule.create(map, {
    countryCrossings: { minZoom: 6, color: '#0B5FA5' }
});

Crossings get an event scope and a getShown() entry like the rest of the route. A clicked crossing also hands over the two country sections it joins, so you can reach the stretch either side of the border rather than only the pair of codes on the plaque:

routingModule.events.countryCrossings.on('click', (crossing) => {
    const { label, fromSection, toSection } = crossing.properties;
    console.log(label, fromSection.endPointIndex, toSection.startPointIndex); // 'ES → FR' 1941 1941
});

Anything these knobs cannot express is a layers.countryCrossings.routeCountryCrossing override, which merges into the layer like any other. Crossings draw under the waypoint pins and the summary bubbles, so a plaque never covers a stop the drive was planned around; a beforeID on that override puts them anywhere else in the stack.

Section events and shown data

Each section also gets its own event scope and getShown() entry. The generated types are keyed <type>Sections; the other five keep the names their sources have always had (ferries, tollRoads, incidents, tunnels, vehicleRestricted):

routingModule.events.lowEmissionZoneSections.on('click', (feature, lngLat) => {
    console.log('Low-emission zone section');
});

const urbanSections = routingModule.getShown().urbanSections;

Related guides and examples

Related guides

Related examples