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.get('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 (a SQLite file, .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 SDK does
not read that directory or tar, though: it reads one SQLite file in the MBTiles layout (a tiles
table plus metadata), so the built tiles have to be packed into one.
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 |
instructionsJSON | every maneuver as one JSON array, in one call instead of one per field |
getPoints() | the whole path as a flat x0,y0,x1,y1,… buffer |
Read the path with getPoints() rather than the points 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
SGREOfflineRoutingService (experimental) 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/SGREOfflineRoutingService.i for the constructor.
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.