Skip to content

Commit

Permalink
fix(routing): Use GraphHopper for follow streets routing
Browse files Browse the repository at this point in the history
Mapzen (and thus the valhalla routing service) is no longer in operation. This addresses #60, but
needs to be cherry-picked into a separate branch for merging into dev.

refs #60
  • Loading branch information
landonreed committed Jan 29, 2018
1 parent 549bf2b commit b570069
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 4 deletions.
1 change: 1 addition & 0 deletions configurations/default/env.yml.tmp
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ MAPBOX_ACCESS_TOKEN: test-access-token
MAPBOX_MAP_ID: mapbox.streets
MAPBOX_ATTRIBUTION: <a href="https://www.mapbox.com/about/maps/" target="_blank">&copy; Mapbox &copy; OpenStreetMap</a> <a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a>
# R5_URL: http://localhost:8080
GRAPH_HOPPER_KEY: graph-hopper-routing-key
89 changes: 85 additions & 4 deletions lib/scenario-editor/utils/valhalla.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fetch from 'isomorphic-fetch'
import {decode as decodePolyline} from 'polyline'
import {isEqual as coordinatesAreEqual} from '@conveyal/lonlat'
import lineString from 'turf-linestring'
import lineSliceAlong from '@turf/line-slice-along'

import type {
Coordinates,
Expand Down Expand Up @@ -77,10 +78,64 @@ type ValhallaResponse = {
}
}

/**
* Convert GraphHopper routing JSON response to polyline.
*/
function handleGraphHopperRouting (json, individualLegs = false) {
if (json && json.paths && json.paths[0]) {
const decodedPolyline = decodePolyline(json.paths[0].points)
.map(coordinate => ([coordinate[1], coordinate[0]]))
// console.log('decoded polyline', json.paths[0].points, decodedPolyline)
if (individualLegs) {
// Reconstruct individual legs from the instructions. NOTE: we do not simply
// use the waypoints found in the response because for lines that share
// street segments, slicing on these points results in unpredictable splits.
// Slicing the line along distances is much more reliable.
const segments = []
const waypointDistances = [0]
let distance = 0
json.paths[0].instructions.forEach(instruction => {
// Iterate over the instructions, accumulating distance and storing the
// distance at each waypoint encountered.
if (instruction.text.match(/Waypoint (\d+)/)) {
// console.log(`adding waypoint ${waypointDistances.length} at ${distance} meters`)
waypointDistances.push(distance)
} else {
distance += instruction.distance
}
})
// Add last distance measure.
// FIXME: Should this just be the length of the entire line?
// console.log(waypointDistances, json.paths[0].distance)
waypointDistances.push(distance)
const decodedLineString = lineString(decodedPolyline)
if (waypointDistances.length > 2) {
for (var i = 1; i < waypointDistances.length; i++) {
const slicedSegment = lineSliceAlong(
decodedLineString,
waypointDistances[i - 1] / 1000,
waypointDistances[i] / 1000
)
segments.push(slicedSegment.geometry.coordinates)
}
// console.log('individual legs', segments)
return segments
} else {
// FIXME does this work for two input points?
return [decodedPolyline]
}
} else {
return decodedPolyline
}
} else {
return null
}
}

/**
* Convert Mapzen routing JSON response to polyline.
*/
function handleMapzenRouting (json, individualLegs = false) {
export function handleMapzenRouting (json, individualLegs = false) {
if (json && json.trip) {
const legArray = json.trip.legs.map((leg, index) => {
return decodePolyline(leg.shape)
Expand Down Expand Up @@ -118,20 +173,27 @@ export function routeWithMapzen (points: Array<LatLng>): ?Promise<ValhallaRespon
).then(res => res.json())
}

/**
* Route between two or more points using external routing service.
* @param {[type]} points array of two or more LatLng points
* @param {[type]} individualLegs whether to return coordinates as set of
* distinct segments for each pair of points
* @param {[type]} useMapzen FIXME: not implemented. boolean to select service to use.
* @return {[type]} Array of coordinates or Array of arrays of coordinates.
*/
export async function polyline (
points: Array<LatLng>,
individualLegs: boolean = false,
useMapzen: boolean = true
): Promise<?Array<[number, number]>> {
let json
try {
json = await routeWithMapzen(points)
json = await routeWithGraphHopper(points)
} catch (e) {
console.log(e)
return null
}
const geometry = handleMapzenRouting(json, individualLegs)
// console.log(geometry)
const geometry = handleGraphHopperRouting(json, individualLegs)
return geometry
}

Expand Down Expand Up @@ -179,6 +241,25 @@ export async function getSegment (
return geometry
}

/**
* Call GraphHopper routing service with lat/lng coordinates.
*/
export function routeWithGraphHopper (points: Array<LatLng>): ?Promise<any> {
// https://graphhopper.com/api/1/route?point=49.932707,11.588051&point=50.3404,11.64705&vehicle=car&debug=true&&type=json
if (points.length < 2) {
console.warn('need at least two points to route with graphhopper', points)
return null
}
if (!process.env.GRAPH_HOPPER_KEY) {
throw new Error('GRAPH_HOPPER_KEY not set')
}
const GRAPH_HOPPER_KEY: string = process.env.GRAPH_HOPPER_KEY
const locations = points.map(p => (`point=${p.lat},${p.lng}`)).join('&')
return fetch(
`https://graphhopper.com/api/1/route?${locations}&key=${GRAPH_HOPPER_KEY}&vehicle=car&debug=true&&type=json`
).then(res => res.json())
}

/**
* Call Mapbox routing service with set of lat/lng coordinates.
*/
Expand Down

0 comments on commit b570069

Please sign in to comment.