Routing
Massif Maps embeds Valhalla: turn-by-turn routing for car, bicycle, pedestrian and more, run either against an HTTP endpoint or entirely offline from tiles on the device. The same service also does map matching — snapping a recorded GPS trace back onto the road network. Indoor routing uses a separate engine, SGRE, over a graph you supply.
Routing is a service you create, a request you create, and a call. Nothing about it touches the map, so a route can be computed with no map view at all.
The CARTO routing endpoints are gone with the rest of the CARTO services. Point
customServiceURL at a Valhalla server you run or a public one, or ship offline tiles.
Online
- Java
- TypeScript (NativeScript)
MassifObject router = Massif.object("routing", "router", Spec.of("valhalla-online"));
router.set("customServiceURL", "https://valhalla1.openstreetmap.de/{service}");
router.set("profile", "bicycle");
MassifObject request = Massif.object("routing", "query", Spec.of("request")
.set("projection", "EPSG:4326")
.set("points", new double[][] {{5.7249, 45.1877}, {5.7148, 45.1916}}));
// Free-form Valhalla options are JSON, so they are a call rather than a property.
request.call("setCustomParameter", "language", "fr-FR");
router.callAsync("calculateRoute", route -> {
double metres = route.getDouble("totalDistance", 0);
long steps = route.getLong("instructionCount", 0);
for (int i = 0; i < steps; i++) {
MassifObject step = route.call("getInstruction", i);
Log.i(TAG, step.getString("streetName", "") + ": " + step.getString("instruction", ""));
}
}, request);
import { create } from '@nativescript-community/ui-massifmaps/api';
const router = create('routing', 'router', { type: 'valhalla-online' });
router.set('customServiceURL', 'https://valhalla1.openstreetmap.de/{service}');
router.set('profile', 'bicycle');
const request = create('routing', 'query', {
type: 'request',
projection: 'EPSG:4326',
points: [[5.7249, 45.1877], [5.7148, 45.1916]]
});
const metres = await router.callAsync('calculateRoute', [request],
(route) => route.getDouble('totalDistance'));
Points are [longitude, latitude] in the projection the request names. {service} in the URL
is substituted per Valhalla endpoint (route, trace_route, …).
Measured against the public OSM endpoint on a phone: a 1 km walking route came back in 681 ms, 72 positions and 21 instructions.
Offline
Ship a Valhalla tile package (.vtiles) with the app or download it, then point the service at it.
No network, no key, and the same request and result shape:
MassifObject router = Massif.object("routing", "router", Spec.of("valhalla-offline")
.set("path", "/sdcard/routing/france.vtiles"));
router.set("profile", "auto");
multi-valhalla-offline takes several packages and picks the one that covers the request, which is
what a per-country download UI wants:
MassifObject router = Massif.object("routing", "router", Spec.of("multi-valhalla-offline"));
router.call("add", "/sdcard/routing/france.vtiles");
router.call("add", "/sdcard/routing/switzerland.vtiles");
Building the tiles is Valhalla's own job — valhalla_build_tiles over an OSM extract. The result
is a directory or a tar; the SDK reads it as it is.
Offline routing runs on the device's CPU, so a very long route over a large package takes seconds rather than the hundreds of milliseconds a server takes. Keep the packages regional.
Reading the result
totalDistance / totalTime | metres and seconds |
pointCount / instructionCount | counts, as properties |
getInstruction(i) | one maneuver: action, pointIndex, distance, streetName, instruction |
getPoints() | the whole path as a flat x0,y0,x1,y1,… buffer |
The path is deliberately not readable as a property: a 9 km cycling route is 562 positions, which
as JSON is a 15 KB string to build, cross and parse. The flat buffer is one crossing and no copy —
and it applies no projection, so read projection.name to know what the numbers are.
To draw the route, feed those positions to a line element, or — for many routes at once — through
the GeoJSON tiling pyramid. Turn arrows are a feature of
their own: maneuver arrows.
Map matching
Same service, a different request. match-request adds the accuracy of the measured points, in
metres:
MassifObject request = Massif.object("routing", "trace", Spec.of("match-request")
.set("projection", "EPSG:4326")
.set("accuracy", 10)
.set("points", recordedTrace));
router.callAsync("matchRoute", result -> …, request);
Indoor routing
SGRERoutingService routes over a graph the app provides as GeoJSON — corridors, stairs, lifts,
with your own edge costs. It is a separate engine from Valhalla and is not tied to OSM data at
all. See the SGRE sources and
all/modules/routing/ for the request shape.
Routing without the map
The repository also ships routing-lib: Valhalla and its bindings, with no renderer and no map
view. If your app only needs routes, that is a much smaller dependency — see
the installation guide.
Full spec keys, properties and methods: routing reference.