leaflet-routing-machine
Version:
324 lines (281 loc) • 8.38 kB
JavaScript
(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'));
/**
* This class works against OSRM version 4 and lower,
* for version 5, use the L.Routing.OSRMv1 class.
*/
L.Routing.OSRM = L.Class.extend({
options: {
serviceUrl: 'https://router.project-osrm.org/viaroute',
timeout: 30 * 1000,
routingOptions: {},
polylinePrecision: 6
},
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;
url = this.buildRouteUrl(waypoints, L.extend({}, this.options.routingOptions, options));
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, 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;
},
_routeDone: function(response, inputWaypoints, callback, context) {
var coordinates,
alts,
actualWaypoints,
i;
context = context || callback;
if (response.status !== 0 && response.status !== 200) {
callback.call(context, {
status: response.status,
message: response.status_message
});
return;
}
coordinates = this._decodePolyline(response.route_geometry);
actualWaypoints = this._toWaypoints(inputWaypoints, response.via_points);
alts = [{
name: this._createName(response.route_name),
coordinates: coordinates,
instructions: response.route_instructions ? this._convertInstructions(response.route_instructions) : [],
summary: response.route_summary ? this._convertSummary(response.route_summary) : [],
inputWaypoints: inputWaypoints,
waypoints: actualWaypoints,
waypointIndices: this._clampIndices(response.via_indices, coordinates)
}];
if (response.alternative_geometries) {
for (i = 0; i < response.alternative_geometries.length; i++) {
coordinates = this._decodePolyline(response.alternative_geometries[i]);
alts.push({
name: this._createName(response.alternative_names[i]),
coordinates: coordinates,
instructions: response.alternative_instructions[i] ? this._convertInstructions(response.alternative_instructions[i]) : [],
summary: response.alternative_summaries[i] ? this._convertSummary(response.alternative_summaries[i]) : [],
inputWaypoints: inputWaypoints,
waypoints: actualWaypoints,
waypointIndices: this._clampIndices(response.alternative_geometries.length === 1 ?
// Unsure if this is a bug in OSRM or not, but alternative_indices
// does not appear to be an array of arrays, at least not when there is
// a single alternative route.
response.alternative_indices : response.alternative_indices[i],
coordinates)
});
}
}
// only versions <4.5.0 will support this flag
if (response.hint_data) {
this._saveHintData(response.hint_data, inputWaypoints);
}
callback.call(context, null, alts);
},
_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;
for (i = 0; i < vias.length; i++) {
wps.push(L.Routing.waypoint(L.latLng(vias[i]),
inputWaypoints[i].name,
inputWaypoints[i].options));
}
return wps;
},
_createName: function(nameParts) {
var name = '',
i;
for (i = 0; i < nameParts.length; i++) {
if (nameParts[i]) {
if (name) {
name += ', ';
}
name += nameParts[i].charAt(0).toUpperCase() + nameParts[i].slice(1);
}
}
return name;
},
buildRouteUrl: function(waypoints, options) {
var locs = [],
wp,
computeInstructions,
computeAlternative,
locationKey,
hint;
for (var i = 0; i < waypoints.length; i++) {
wp = waypoints[i];
locationKey = this._locationKey(wp.latLng);
locs.push('loc=' + locationKey);
hint = this._hints.locations[locationKey];
if (hint) {
locs.push('hint=' + hint);
}
if (wp.options && wp.options.allowUTurn) {
locs.push('u=true');
}
}
computeAlternative = computeInstructions =
!(options && options.geometryOnly);
return this.options.serviceUrl + '?' +
'instructions=' + computeInstructions.toString() + '&' +
'alt=' + computeAlternative.toString() + '&' +
(options.z ? 'z=' + options.z + '&' : '') +
locs.join('&') +
(this._hints.checksum !== undefined ? '&checksum=' + this._hints.checksum : '') +
(options.fileformat ? '&output=' + options.fileformat : '') +
(options.allowUTurns ? '&uturns=' + options.allowUTurns : '');
},
_locationKey: function(location) {
return location.lat + ',' + location.lng;
},
_saveHintData: function(hintData, waypoints) {
var loc;
this._hints = {
checksum: hintData.checksum,
locations: {}
};
for (var i = hintData.locations.length - 1; i >= 0; i--) {
loc = waypoints[i].latLng;
this._hints.locations[this._locationKey(loc)] = hintData.locations[i];
}
},
_convertSummary: function(osrmSummary) {
return {
totalDistance: osrmSummary.total_distance,
totalTime: osrmSummary.total_time
};
},
_convertInstructions: function(osrmInstructions) {
var result = [],
i,
instr,
type,
driveDir;
for (i = 0; i < osrmInstructions.length; i++) {
instr = osrmInstructions[i];
type = this._drivingDirectionType(instr[0]);
driveDir = instr[0].split('-');
if (type) {
result.push({
type: type,
distance: instr[2],
time: instr[4],
road: instr[1],
direction: instr[6],
exit: driveDir.length > 1 ? driveDir[1] : undefined,
index: instr[3]
});
}
}
return result;
},
_drivingDirectionType: function(d) {
switch (parseInt(d, 10)) {
case 1:
return 'Straight';
case 2:
return 'SlightRight';
case 3:
return 'Right';
case 4:
return 'SharpRight';
case 5:
return 'TurnAround';
case 6:
return 'SharpLeft';
case 7:
return 'Left';
case 8:
return 'SlightLeft';
case 9:
return 'WaypointReached';
case 10:
// TODO: "Head on"
// https://github.com/DennisOSRM/Project-OSRM/blob/master/DataStructures/TurnInstructions.h#L48
return 'Straight';
case 11:
case 12:
return 'Roundabout';
case 15:
return 'DestinationReached';
default:
return null;
}
},
_clampIndices: function(indices, coords) {
var maxCoordIndex = coords.length - 1,
i;
for (i = 0; i < indices.length; i++) {
indices[i] = Math.min(maxCoordIndex, Math.max(indices[i], 0));
}
return indices;
}
});
L.Routing.osrm = function(options) {
return new L.Routing.OSRM(options);
};
module.exports = L.Routing;
})();