348 lines
8.6 KiB
JavaScript
348 lines
8.6 KiB
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();
|
|
}
|
|
|
|
if (this.options.autoRoute) {
|
|
this.route();
|
|
}
|
|
},
|
|
|
|
_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) {
|
|
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: 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 = [];
|
|
}
|
|
}
|
|
});
|
|
})();
|