Curves

Curves tell you how winding a route is. Ask for them, and calculateRoute returns every bend of each route, with how far it turns and how tight it is, and the winding stretches those bends make up. Use them to keep a passenger prone to motion sickness off mountain roads, or to send a motorcyclist down them.

The demo below plans a trip from Innsbruck to Bolzano and ranks its routes by how much of the drive is in curves. Pick Seek them or Avoid them, then click a curvy stretch to see where it runs. Open What counts as a curve and move its sliders to change the thresholds.

Ask for the curves

Name curve and curvyStretch in sectionTypes:

import { calculateRoute } from '@tomtom-org/maps-sdk/services';

const innsbruckToBolzano = [[11.3933, 47.2692], [11.3548, 46.4983]];
const routes = await calculateRoute({
    locations: innsbruckToBolzano,
    maxAlternatives: 2,
    sectionTypes: ['curve', 'curvyStretch'],
    guidance: { type: 'coded' },
});

Choose what counts as a curve

By default, a curve turns 40° or more on a radius of 250 m or less, and a curvy stretch holds three or more curves with gaps of 500 m or less between them. To change these thresholds, pass curveDetection:

const routes = await calculateRoute({
    locations: innsbruckToBolzano,
    sectionTypes: ['curve', 'curvyStretch'],
    // Only the hard curves, and the stretches of five or more of them
    curveDetection: {
        curve: { minHeadingChangeInDegrees: 60, maxRadiusInMeters: 100 },
        curvyStretch: { minCurveCount: 5 },
    },
});

To try other thresholds on routes you already have, pass each route to withCurveSections. It finds the curves and curvy stretches again, with no new request:

import { withCurveSections } from '@tomtom-org/maps-sdk/core';

const wider = routes.features.map((route) => withCurveSections(route, { curve: { maxRadiusInMeters: 500 } }));

Note that withCurveSections reads the route as it came back. If you need each curve's maneuver and severity, ask for guidance and speedLimit sections when you calculate the route.

Rank routes by how winding they are

To compare routes, add up the time each one drives in its curves and divide it by the route's travel time. A share of the drive reads better than a count of curves, because a longer route has more curves without being any more winding.

import { getRouteProgressForSection } from '@tomtom-org/maps-sdk/core';

const shareInCurves = (route) => {
    const curves = route.properties.sections.curve ?? [];
    const seconds = curves.reduce(
        (total, curve) => total + (getRouteProgressForSection(route, curve)?.delta.travelTimeInSeconds ?? 0),
        0,
    );
    return seconds / route.properties.summary.travelTimeInSeconds;
};

const straightestFirst = [...routes.features].sort((first, second) => shareInCurves(first) - shareInCurves(second));

Avoid them

To keep a passenger prone to motion sickness off winding roads, plan with the default fast route type and take the straightest route, straightestFirst[0]. To say what it spares them, compare it with the next one:

import { compareRoutes, formatDistance } from '@tomtom-org/maps-sdk/core';

const [straightest, next] = straightestFirst;
// The service can return fewer alternatives than asked for
if (next) {
    const { avoids } = compareRoutes(straightest, next, { types: ['curve'] });
    if (avoids.curve) {
        console.log(`Avoids ${avoids.curve.sections.length} curves over ${formatDistance(avoids.curve.lengthInMeters)}`);
    }
}

avoids.curve holds the curves on the roads only the other route takes, and adds.curve the ones only this route takes.

Warn about the hard curves

Each curve's severity says how hard it is to drive, from 0 for a curve a driver takes at the speed limit to nearly 1 for a hairpin. It weighs how tight the curve is and how far it turns against the limit on the road into it, and a turn at a junction counts as one the driver slows right down for.

To warn a passenger about the hard curves ahead, keep those that score high:

const [route] = (
    await calculateRoute({
        locations: innsbruckToBolzano,
        sectionTypes: ['curve', 'speedLimit'],
        guidance: { type: 'coded' },
    })
).features;
const hardCurves = route.properties.sections.curve?.filter((curve) => curve.severity > 0.75) ?? [];

Note that the same bend scores higher on a fast road than in a town, since a driver slows down more for it there. Without speedLimit sections, severity takes a typical limit outside towns.

Seek them

To send a motorcyclist down winding roads, plan a thrilling route, which takes interesting roads and as few motorways as possible, for trips of up to 900 km. Take the curviest route, then list its curvy stretches by their curvinessScore, their curves' scores added up:

const thrilling = await calculateRoute({
    locations: innsbruckToBolzano,
    maxAlternatives: 2,
    costModel: { routeType: 'thrilling' },
    sectionTypes: ['curve', 'curvyStretch'],
    // The maneuvers keep a roundabout or a turn at a junction from counting as a hairpin
    guidance: { type: 'coded' },
});
const [curviest] = [...thrilling.features].sort((first, second) => shareInCurves(second) - shareInCurves(first));
const bestStretches = [...(curviest.properties.sections.curvyStretch ?? [])].sort(
    (first, second) => second.curvinessScore - first.curvinessScore,
);

Roadcurvature.com scores roads the same way. It lists them from a score of 300 and calls those over 1000 very twisty.

Find the hairpins

A hairpin is a bend turning 140° or more on a radius of 40 m or less, as the numbered bends of a mountain pass do. To find them, filter the curves with isHairpin:

import { isHairpin } from '@tomtom-org/maps-sdk/core';

const hairpins = curviest.properties.sections.curve?.filter(isHairpin) ?? [];

Each curvy stretch counts its own hairpins in hairpinCount.

Tell where each curve is

To tell a rider how far ahead a curvy stretch starts and how long it takes to drive, call getRouteProgressForSection:

import { formatDistance, formatDuration } from '@tomtom-org/maps-sdk/core';

const [stretch] = bestStretches;
const progress = stretch && getRouteProgressForSection(curviest, stretch);
if (progress) {
    console.log(
        `${formatDistance(progress.start.distanceInMeters)} ahead, ${formatDuration(progress.delta.travelTimeInSeconds)} to drive`,
    );
}

To read the curves against the rest of the road, ask for its other sections too, and call getSectionsWithin. For example, the curves in tunnels, where a driver sees neither the road ahead nor the oncoming traffic:

import { getSectionsWithin } from '@tomtom-org/maps-sdk/core';

const [route] = (
    await calculateRoute({ locations: innsbruckToBolzano, sectionTypes: ['curve', 'curvyStretch', 'tunnel'] })
).features;
const { curve } = getSectionsWithin(route, { within: route.properties.sections.tunnel ?? [], types: ['curve'] });
const curvesInTunnels = curve?.sections ?? [];

The same call with urban sections finds the curves in towns.

Show them on the map

The routing module draws curves and curvy stretches once you switch them on:

import { RoutingModule } from '@tomtom-org/maps-sdk/map';

const routingModule = await RoutingModule.create(map, {
    sections: { curve: { visible: true }, curvyStretch: { visible: true } },
});
await routingModule.showRoutes(routes);

API reference

Related guides and examples

Related guides

Related examples