UNPKG

leaflet-routing-machine

Version:
340 lines (290 loc) 8.44 kB
(function() { 'use strict'; var L = require('leaflet'), corslite = require('corslite'), polyline = require('polyline'); // Ignore camelcase naming for this file, since OSRM's API uses // underscores. /* jshint camelcase: false */ L.Routing = L.Routing || {}; L.extend(L.Routing, require('./L.Routing.Waypoint')); /** * Works against OSRM's new API in version 5.0; this has * the API version v1. */ L.Routing.OSRMv1 = L.Class.extend({ options: { serviceUrl: 'https://router.project-osrm.org/route/v1', profile: 'driving', timeout: 30 * 1000, routingOptions: { alternatives: true, steps: true }, polylinePrecision: 5, useHints: true }, initialize: function(options) { L.Util.setOptions(this, options); this._hints = { locations: {} }; }, route: function(waypoints, callback, context, options) { var timedOut = false, wps = [], url, timer, wp, i; options = L.extend({}, this.options.routingOptions, options); url = this.buildRouteUrl(waypoints, options); if (this.options.requestParameters) { url += L.Util.getParamString(this.options.requestParameters, url); } timer = setTimeout(function() { timedOut = true; callback.call(context || callback, { status: -1, message: 'OSRM request timed out.' }); }, this.options.timeout); // Create a copy of the waypoints, since they // might otherwise be asynchronously modified while // the request is being processed. for (i = 0; i < waypoints.length; i++) { wp = waypoints[i]; wps.push(new L.Routing.Waypoint(wp.latLng, wp.name, wp.options)); } corslite(url, L.bind(function(err, resp) { var data, errorMessage, statusCode; clearTimeout(timer); if (!timedOut) { errorMessage = 'HTTP request failed: ' + err; statusCode = -1; if (!err) { try { data = JSON.parse(resp.responseText); try { return this._routeDone(data, wps, options, callback, context); } catch (ex) { statusCode = -3; errorMessage = ex.toString(); } } catch (ex) { statusCode = -2; errorMessage = 'Error parsing OSRM response: ' + ex.toString(); } } callback.call(context || callback, { status: statusCode, message: errorMessage }); } }, this)); return this; }, requiresMoreDetail: function(route, zoom, bounds) { if (!route.properties.isSimplified) { return false; } var waypoints = route.inputWaypoints, i; for (i = 0; i < waypoints.length; ++i) { if (!bounds.contains(waypoints[i].latLng)) { return true; } } return false; }, _routeDone: function(response, inputWaypoints, options, callback, context) { var alts = [], actualWaypoints, i, route; context = context || callback; if (response.code !== 'Ok') { callback.call(context, { status: response.code }); return; } actualWaypoints = this._toWaypoints(inputWaypoints, response.waypoints); for (i = 0; i < response.routes.length; i++) { route = this._convertRoute(response.routes[i]); route.inputWaypoints = inputWaypoints; route.waypoints = actualWaypoints; route.properties = {isSimplified: !options || !options.geometryOnly || options.simplifyGeometry}; alts.push(route); } this._saveHintData(response.waypoints, inputWaypoints); callback.call(context, null, alts); }, _convertRoute: function(responseRoute) { var result = { name: '', coordinates: [], instructions: [], summary: { totalDistance: responseRoute.distance, totalTime: responseRoute.duration } }, legNames = [], index = 0, legCount = responseRoute.legs.length, hasSteps = responseRoute.legs[0].steps.length > 0, i, j, leg, step, geometry, type, modifier; for (i = 0; i < legCount; i++) { leg = responseRoute.legs[i]; legNames.push(leg.summary && leg.summary.charAt(0).toUpperCase() + leg.summary.substring(1)); for (j = 0; j < leg.steps.length; j++) { step = leg.steps[j]; geometry = this._decodePolyline(step.geometry); result.coordinates.push.apply(result.coordinates, geometry); type = this._maneuverToInstructionType(step.maneuver, i === legCount - 1); modifier = this._maneuverToModifier(step.maneuver); if (type) { result.instructions.push({ type: type, distance: step.distance, time: step.duration, road: step.name, direction: this._bearingToDirection(step.maneuver.bearing_after), exit: step.maneuver.exit, index: index, mode: step.mode, modifier: modifier }); } index += geometry.length; } } result.name = legNames.join(', '); if (!hasSteps) { result.coordinates = this._decodePolyline(responseRoute.geometry); } return result; }, _bearingToDirection: function(bearing) { var oct = Math.round(bearing / 45) % 8; return ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'][oct]; }, _maneuverToInstructionType: function(maneuver, lastLeg) { switch (maneuver.type) { case 'new name': return 'Continue'; case 'depart': return 'Head'; case 'arrive': return lastLeg ? 'DestinationReached' : 'WaypointReached'; case 'roundabout': case 'rotary': return 'Roundabout'; case 'merge': case 'fork': case 'on ramp': case 'off ramp': case 'end of road': return this._camelCase(maneuver.type); // These are all reduced to the same instruction in the current model //case 'turn': //case 'ramp': // deprecated in v5.1 default: return this._camelCase(maneuver.modifier); } }, _maneuverToModifier: function(maneuver) { var modifier = maneuver.modifier; switch (maneuver.type) { case 'merge': case 'fork': case 'on ramp': case 'off ramp': case 'end of road': modifier = this._leftOrRight(modifier); } return modifier && this._camelCase(modifier); }, _camelCase: function(s) { var words = s.split(' '), result = ''; for (var i = 0, l = words.length; i < l; i++) { result += words[i].charAt(0).toUpperCase() + words[i].substring(1); } return result; }, _leftOrRight: function(d) { return d.indexOf('left') >= 0 ? 'Left' : 'Right'; }, _decodePolyline: function(routeGeometry) { var cs = polyline.decode(routeGeometry, this.options.polylinePrecision), result = new Array(cs.length), i; for (i = cs.length - 1; i >= 0; i--) { result[i] = L.latLng(cs[i]); } return result; }, _toWaypoints: function(inputWaypoints, vias) { var wps = [], i, viaLoc; for (i = 0; i < vias.length; i++) { viaLoc = vias[i].location; wps.push(L.Routing.waypoint(L.latLng(viaLoc[1], viaLoc[0]), inputWaypoints[i].name, inputWaypoints[i].options)); } return wps; }, buildRouteUrl: function(waypoints, options) { var locs = [], hints = [], wp, latLng, computeInstructions, computeAlternative = true; for (var i = 0; i < waypoints.length; i++) { wp = waypoints[i]; latLng = wp.latLng; locs.push(latLng.lng + ',' + latLng.lat); hints.push(this._hints.locations[this._locationKey(latLng)] || ''); } computeInstructions = !(options && options.geometryOnly); return this.options.serviceUrl + '/' + this.options.profile + '/' + locs.join(';') + '?' + (options.geometryOnly ? (options.simplifyGeometry ? '' : 'overview=full') : 'overview=false') + '&alternatives=' + computeAlternative.toString() + '&steps=' + computeInstructions.toString() + (this.options.useHints ? '&hints=' + hints.join(';') : '') + (options.allowUTurns ? '&continue_straight=' + !options.allowUTurns : ''); }, _locationKey: function(location) { return location.lat + ',' + location.lng; }, _saveHintData: function(actualWaypoints, waypoints) { var loc; this._hints = { locations: {} }; for (var i = actualWaypoints.length - 1; i >= 0; i--) { loc = waypoints[i].latLng; this._hints.locations[this._locationKey(loc)] = actualWaypoints[i].hint; } }, }); L.Routing.osrmv1 = function(options) { return new L.Routing.OSRMv1(options); }; module.exports = L.Routing; })();