@fleetbase/leaflet-routing-machine
Version:
ESM module Routing for Leaflet
378 lines (325 loc) • 11.7 kB
JavaScript
import Itinerary from './itinerary';
import Line from './line';
import Plan from './plan';
import OSRMv1 from './osrm-v1';
export default Itinerary.extend({
options: {
fitSelectedRoutes: 'smart',
routeLine: function (route, options) {
return new Line(route, options);
},
autoRoute: true,
routeWhileDragging: false,
routeDragInterval: 500,
waypointMode: 'connect',
showAlternatives: false,
markerOptions: {},
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) {
if (e.afterIndex < this.getWaypoints().length - 1) {
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 = [];
}
},
});