leaflet-routing-machine
Version:
351 lines (294 loc) • 8.83 kB
JavaScript
(function() {
'use strict';
var L = require('leaflet');
var Itinerary = require('./itinerary');
var Line = require('./line');
var Plan = require('./plan');
var OSRMv1 = require('./osrm-v1');
module.exports = Itinerary.extend({
options: {
fitSelectedRoutes: 'smart',
routeLine: function(route, options) { return new Line(route, options); },
autoRoute: true,
routeWhileDragging: false,
routeDragInterval: 500,
waypointMode: 'connect',
showAlternatives: false,
defaultErrorHandler: function(e) {
console.error('Routing error:', e.error);
}
},
initialize: function(options) {
L.Util.setOptions(this, options);
this._router = this.options.router || new OSRMv1(options);
this._plan = this.options.plan || new Plan(this.options.waypoints, options);
this._requestCount = 0;
Itinerary.prototype.initialize.call(this, options);
this.on('routeselected', this._routeSelected, this);
if (this.options.defaultErrorHandler) {
this.on('routingerror', this.options.defaultErrorHandler);
}
this._plan.on('waypointschanged', this._onWaypointsChanged, this);
if (options.routeWhileDragging) {
this._setupRouteDragging();
}
},
_onZoomEnd: function() {
if (!this._selectedRoute ||
!this._router.requiresMoreDetail) {
return;
}
var map = this._map;
if (this._router.requiresMoreDetail(this._selectedRoute,
map.getZoom(), map.getBounds())) {
this.route({
callback: L.bind(function(err, routes) {
var i;
if (!err) {
for (i = 0; i < routes.length; i++) {
this._routes[i].properties = routes[i].properties;
}
this._updateLineCallback(err, routes);
}
}, this),
simplifyGeometry: false,
geometryOnly: true
});
}
},
onAdd: function(map) {
if (this.options.autoRoute) {
this.route();
}
var container = Itinerary.prototype.onAdd.call(this, map);
this._map = map;
this._map.addLayer(this._plan);
this._map.on('zoomend', this._onZoomEnd, this);
if (this._plan.options.geocoder) {
container.insertBefore(this._plan.createGeocoders(), container.firstChild);
}
return container;
},
onRemove: function(map) {
map.off('zoomend', this._onZoomEnd, this);
if (this._line) {
map.removeLayer(this._line);
}
map.removeLayer(this._plan);
if (this._alternatives && this._alternatives.length > 0) {
for (var i = 0, len = this._alternatives.length; i < len; i++) {
map.removeLayer(this._alternatives[i]);
}
}
return Itinerary.prototype.onRemove.call(this, map);
},
getWaypoints: function() {
return this._plan.getWaypoints();
},
setWaypoints: function(waypoints) {
this._plan.setWaypoints(waypoints);
return this;
},
spliceWaypoints: function() {
var removed = this._plan.spliceWaypoints.apply(this._plan, arguments);
return removed;
},
getPlan: function() {
return this._plan;
},
getRouter: function() {
return this._router;
},
_routeSelected: function(e) {
var route = this._selectedRoute = e.route,
alternatives = this.options.showAlternatives && e.alternatives,
fitMode = this.options.fitSelectedRoutes,
fitBounds =
(fitMode === 'smart' && !this._waypointsVisible()) ||
(fitMode !== 'smart' && fitMode);
this._updateLines({route: route, alternatives: alternatives});
if (fitBounds) {
this._map.fitBounds(this._line.getBounds());
}
if (this.options.waypointMode === 'snap') {
this._plan.off('waypointschanged', this._onWaypointsChanged, this);
this.setWaypoints(route.waypoints);
this._plan.on('waypointschanged', this._onWaypointsChanged, this);
}
},
_waypointsVisible: function() {
var wps = this.getWaypoints(),
mapSize,
bounds,
boundsSize,
i,
p;
try {
mapSize = this._map.getSize();
for (i = 0; i < wps.length; i++) {
p = this._map.latLngToLayerPoint(wps[i].latLng);
if (bounds) {
bounds.extend(p);
} else {
bounds = L.bounds([p]);
}
}
boundsSize = bounds.getSize();
return (boundsSize.x > mapSize.x / 5 ||
boundsSize.y > mapSize.y / 5) && this._waypointsInViewport();
} catch (e) {
return false;
}
},
_waypointsInViewport: function() {
var wps = this.getWaypoints(),
mapBounds,
i;
try {
mapBounds = this._map.getBounds();
} catch (e) {
return false;
}
for (i = 0; i < wps.length; i++) {
if (mapBounds.contains(wps[i].latLng)) {
return true;
}
}
return false;
},
_updateLines: function(routes) {
var addWaypoints = this.options.addWaypoints !== undefined ?
this.options.addWaypoints : true;
this._clearLines();
// add alternatives first so they lie below the main route
this._alternatives = [];
if (routes.alternatives) routes.alternatives.forEach(function(alt, i) {
this._alternatives[i] = this.options.routeLine(alt,
L.extend({
isAlternative: true
}, this.options.altLineOptions || this.options.lineOptions));
this._alternatives[i].addTo(this._map);
this._hookAltEvents(this._alternatives[i]);
}, this);
this._line = this.options.routeLine(routes.route,
L.extend({
addWaypoints: addWaypoints,
extendToWaypoints: this.options.waypointMode === 'connect'
}, this.options.lineOptions));
this._line.addTo(this._map);
this._hookEvents(this._line);
},
_hookEvents: function(l) {
l.on('linetouched', function(e) {
this._plan.dragNewWaypoint(e);
}, this);
},
_hookAltEvents: function(l) {
l.on('linetouched', function(e) {
var alts = this._routes.slice();
var selected = alts.splice(e.target._route.routesIndex, 1)[0];
this.fire('routeselected', {route: selected, alternatives: alts});
}, this);
},
_onWaypointsChanged: function(e) {
if (this.options.autoRoute) {
this.route({});
}
if (!this._plan.isReady()) {
this._clearLines();
this._clearAlts();
}
this.fire('waypointschanged', {waypoints: e.waypoints});
},
_setupRouteDragging: function() {
var timer = 0,
waypoints;
this._plan.on('waypointdrag', L.bind(function(e) {
waypoints = e.waypoints;
if (!timer) {
timer = setTimeout(L.bind(function() {
this.route({
waypoints: waypoints,
geometryOnly: true,
callback: L.bind(this._updateLineCallback, this)
});
timer = undefined;
}, this), this.options.routeDragInterval);
}
}, this));
this._plan.on('waypointdragend', function() {
if (timer) {
clearTimeout(timer);
timer = undefined;
}
this.route();
}, this);
},
_updateLineCallback: function(err, routes) {
if (!err) {
routes = routes.slice();
var selected = routes.splice(this._selectedRoute.routesIndex, 1)[0];
this._updateLines({
route: selected,
alternatives: this.options.showAlternatives ? routes : []
});
} else if (err.type !== 'abort') {
this._clearLines();
}
},
route: function(options) {
var ts = ++this._requestCount,
wps;
if (this._pendingRequest && this._pendingRequest.abort) {
this._pendingRequest.abort();
this._pendingRequest = null;
}
options = options || {};
if (this._plan.isReady()) {
if (this.options.useZoomParameter) {
options.z = this._map && this._map.getZoom();
}
wps = options && options.waypoints || this._plan.getWaypoints();
this.fire('routingstart', {waypoints: wps});
this._pendingRequest = this._router.route(wps, function(err, routes) {
this._pendingRequest = null;
if (options.callback) {
return options.callback.call(this, err, routes);
}
// Prevent race among multiple requests,
// by checking the current request's count
// against the last request's; ignore result if
// this isn't the last request.
if (ts === this._requestCount) {
this._clearLines();
this._clearAlts();
if (err && err.type !== 'abort') {
this.fire('routingerror', {error: err});
return;
}
routes.forEach(function(route, i) { route.routesIndex = i; });
if (!options.geometryOnly) {
this.fire('routesfound', {waypoints: wps, routes: routes});
this.setAlternatives(routes);
} else {
var selectedRoute = routes.splice(0,1)[0];
this._routeSelected({route: selectedRoute, alternatives: routes});
}
}
}, this, options);
}
},
_clearLines: function() {
if (this._line) {
this._map.removeLayer(this._line);
delete this._line;
}
if (this._alternatives && this._alternatives.length) {
for (var i in this._alternatives) {
this._map.removeLayer(this._alternatives[i]);
}
this._alternatives = [];
}
}
});
})();