This repository has been archived on 2019-10-13. You can view files and clone it, but cannot push or open issues or pull requests.
2017-10-14 01:39:11 +02:00

369 lines
9.6 KiB
JavaScript

(function() {
'use strict';
var L = require('leaflet'),
corslite = require('corslite'),
polyline = require('polyline'),
osrmTextInstructions = require('osrm-text-instructions');
// Ignore camelcase naming for this file, since OSRM's API uses
// underscores.
/* jshint camelcase: false */
var Waypoint = require('./waypoint');
/**
* Works against OSRM's new API in version 5.0; this has
* the API version v1.
*/
module.exports = 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,
suppressDemoServerWarning: false,
language: 'en'
},
initialize: function(options) {
L.Util.setOptions(this, options);
this._hints = {
locations: {}
};
if (!this.options.suppressDemoServerWarning &&
this.options.serviceUrl.indexOf('//router.project-osrm.org') >= 0) {
console.warn('You are using OSRM\'s demo server. ' +
'Please note that it is **NOT SUITABLE FOR PRODUCTION USE**.\n' +
'Refer to the demo server\'s usage policy: ' +
'https://github.com/Project-OSRM/osrm-backend/wiki/Api-usage-policy\n\n' +
'To change, set the serviceUrl option.\n\n' +
'Please do not report issues with this server to neither ' +
'Leaflet Routing Machine or OSRM - it\'s for\n' +
'demo only, and will sometimes not be available, or work in ' +
'unexpected ways.\n\n' +
'Please set up your own OSRM server, or use a paid service ' +
'provider for production.');
}
},
route: function(waypoints, callback, context, options) {
var timedOut = false,
wps = [],
url,
timer,
wp,
i,
xhr;
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 Waypoint(wp.latLng, wp.name, wp.options));
}
return xhr = corslite(url, L.bind(function(err, resp) {
var data,
error = {};
clearTimeout(timer);
if (!timedOut) {
if (!err) {
try {
data = JSON.parse(resp.responseText);
try {
return this._routeDone(data, wps, options, callback, context);
} catch (ex) {
error.status = -3;
error.message = ex.toString();
}
} catch (ex) {
error.status = -2;
error.message = 'Error parsing OSRM response: ' + ex.toString();
}
} else {
error.message = 'HTTP request failed: ' + err.type +
(err.target && err.target.status ? ' HTTP ' + err.target.status + ': ' + err.target.statusText : '');
error.url = url;
error.status = -1;
error.target = err;
}
callback.call(context || callback, error);
} else {
xhr.abort();
}
}, 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 = [],
waypointIndices = [],
index = 0,
legCount = responseRoute.legs.length,
hasSteps = responseRoute.legs[0].steps.length > 0,
i,
j,
leg,
step,
geometry,
type,
modifier,
text,
stepToText;
if (this.options.stepToText) {
stepToText = this.options.stepToText;
} else {
var textInstructions = osrmTextInstructions('v5', this.options.language);
stepToText = textInstructions.compile.bind(textInstructions);
}
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);
text = stepToText(step);
if (type) {
if ((i == 0 && step.maneuver.type == 'depart') || step.maneuver.type == 'arrive') {
waypointIndices.push(index);
}
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,
text: text
});
}
index += geometry.length;
}
}
result.name = legNames.join(', ');
if (!hasSteps) {
result.coordinates = this._decodePolyline(responseRoute.geometry);
} else {
result.waypointIndices = waypointIndices;
}
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(new 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 =
true;
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;
}
},
});
})();