UNPKG

@fleetbase/leaflet-routing-machine

Version:
375 lines (328 loc) 12.6 kB
import corslite from '@mapbox/corslite'; import polyline from '@mapbox/polyline'; import getOsrmTextInstructions from 'osrm-text-instructions'; import Waypoint from './waypoint'; const osrmTextInstructions = getOsrmTextInstructions('v5'); /** * Works against OSRM's new API in version 5.0; this has * the API version v1. */ export default 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 { var message = err.type + (err.target && err.target.status ? ' HTTP ' + err.target.status + ': ' + err.target.statusText : ''); if (err.responseText) { try { data = JSON.parse(err.responseText); if (data.message) message = data.message; } catch (ex) {} } error.message = 'HTTP request failed: ' + message; 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 { stepToText = L.bind(osrmTextInstructions.compile, osrmTextInstructions, this.options.language); } 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, { legCount: legCount, legIndex: i }); 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; } }, });