Added hotline based route overlay
Added a overlay which reflects the quality of the route based on either: * cost * altitude * incline
This commit is contained in:
parent
90dfa5b7f3
commit
9eeca7e2d5
7 changed files with 269 additions and 1 deletions
|
|
@ -199,6 +199,8 @@
|
|||
requestUpdate: requestUpdate
|
||||
});
|
||||
|
||||
routingPathQuality = new BR.RoutingPathQuality(map, layersControl);
|
||||
|
||||
routing = new BR.Routing({
|
||||
routing: {
|
||||
router: L.bind(router.getRouteSegment, router)
|
||||
|
|
@ -233,6 +235,7 @@
|
|||
segmentsLayer = routing._segments;
|
||||
|
||||
elevation.update(track, segmentsLayer);
|
||||
routingPathQuality.update(track, segmentsLayer);
|
||||
if (BR.conf.transit) {
|
||||
itinerary.update(track, segments);
|
||||
} else {
|
||||
|
|
@ -244,6 +247,7 @@
|
|||
}
|
||||
|
||||
routing.addTo(map);
|
||||
routingPathQuality.addTo(map);
|
||||
elevation.addBelow(map);
|
||||
|
||||
sidebar = BR.sidebar({
|
||||
|
|
|
|||
201
js/plugin/RoutingPathQuality.js
Normal file
201
js/plugin/RoutingPathQuality.js
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
BR.RoutingPathQuality = L.Control.extend({
|
||||
initialize: function(map, layersControl) {
|
||||
// hotline uses canvas and cannot be moved in front of the svg, so we create another pane
|
||||
map.createPane('routingQualityPane');
|
||||
map.getPane('routingQualityPane').style.zIndex = 450;
|
||||
map.getPane('routingQualityPane').style.pointerEvents = 'none';
|
||||
var renderer = new L.Hotline.Renderer({ pane: 'routingQualityPane' });
|
||||
|
||||
this._routingSegments = L.featureGroup();
|
||||
layersControl.addOverlay(this._routingSegments, i18next.t('map.layer.route-quality'));
|
||||
|
||||
this.providers = {
|
||||
cost: {
|
||||
title: i18next.t('map.route-quality-cost'),
|
||||
icon: 'fa-usd',
|
||||
provider: new HotlineProvider({
|
||||
hotlineOptions: {
|
||||
renderer: renderer
|
||||
},
|
||||
valueFunction: function(latLng) {
|
||||
let feature = latLng.feature;
|
||||
return (
|
||||
feature.cost.perKm +
|
||||
feature.cost.elev +
|
||||
feature.cost.turn +
|
||||
feature.cost.node +
|
||||
feature.cost.initial
|
||||
);
|
||||
}
|
||||
})
|
||||
},
|
||||
altitude: {
|
||||
title: i18next.t('map.route-quality-altitude'),
|
||||
icon: 'fa-area-chart',
|
||||
provider: new HotlineProvider({
|
||||
hotlineOptions: {
|
||||
renderer: renderer
|
||||
},
|
||||
valueFunction: function(latLng) {
|
||||
feature = latLng.feature;
|
||||
return latLng.alt;
|
||||
}
|
||||
})
|
||||
},
|
||||
incline: {
|
||||
title: i18next.t('map.route-quality-incline'),
|
||||
icon: 'fa-line-chart',
|
||||
provider: new HotlineProvider({
|
||||
hotlineOptions: {
|
||||
min: -15,
|
||||
max: 15,
|
||||
palette: {
|
||||
0.0: '#ff0000',
|
||||
0.5: '#00ff00',
|
||||
1.0: '#ff0000'
|
||||
},
|
||||
renderer: renderer
|
||||
},
|
||||
valueFunction: function(latLng, prevLatLng) {
|
||||
var deltaAltitude = latLng.alt - prevLatLng.alt, // in m
|
||||
distance = prevLatLng.distanceTo(latLng); // in m
|
||||
if (distance === 0) {
|
||||
return 0;
|
||||
}
|
||||
return (Math.atan(deltaAltitude / distance) * 180) / Math.PI;
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
this.selectedProvider = this.options.initialProvider || 'cost';
|
||||
},
|
||||
|
||||
onAdd: function(map) {
|
||||
var self = this;
|
||||
this._map = map;
|
||||
this._routingSegments.addTo(map);
|
||||
|
||||
let states = [];
|
||||
var i,
|
||||
keys = Object.keys(this.providers),
|
||||
l = keys.length;
|
||||
|
||||
for (i = 0; i < l; ++i) {
|
||||
let provider = this.providers[keys[i]];
|
||||
let nextState = keys[(i + 1) % l];
|
||||
states.push({
|
||||
stateName: keys[i],
|
||||
icon: provider.icon,
|
||||
title: provider.title,
|
||||
onClick: function(btn) {
|
||||
btn.state(nextState);
|
||||
self.setProvider(nextState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.routingPathButton = new L.easyButton({
|
||||
states: states
|
||||
}).addTo(map);
|
||||
return new L.DomUtil.create('div');
|
||||
},
|
||||
|
||||
update: function(track, layer) {
|
||||
var segments = [];
|
||||
layer.eachLayer(function(layer) {
|
||||
segments.push(layer);
|
||||
});
|
||||
this.segments = segments;
|
||||
this._update(this.segments);
|
||||
},
|
||||
|
||||
setProvider: function(provider) {
|
||||
this.selectedProvider = provider;
|
||||
this._update(this.segments);
|
||||
this._routingSegments.addTo(this._map);
|
||||
},
|
||||
|
||||
_update: function(segments) {
|
||||
this._routingSegments.clearLayers();
|
||||
let layers = this.providers[this.selectedProvider].provider.computeLayers(segments);
|
||||
if (layers) {
|
||||
for (let i = 0; i < layers.length; i++) {
|
||||
this._routingSegments.addLayer(layers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
class HotlineProvider {
|
||||
constructor(options) {
|
||||
this.hotlineOptions = options.hotlineOptions;
|
||||
this.valueFunction = options.valueFunction;
|
||||
}
|
||||
|
||||
computeLayers(segments) {
|
||||
let layers = [];
|
||||
if (segments) {
|
||||
let segmentLatLngs = [];
|
||||
for (let i = 0; segments && i < segments.length; i++) {
|
||||
let segment = segments[i];
|
||||
segmentLatLngs.push(this._computeLatLngVals(segment));
|
||||
}
|
||||
let flatLines = segmentLatLngs.flat();
|
||||
|
||||
if (flatLines.length > 0) {
|
||||
let hotlineOptions = Object.assign(new Object(), this.hotlineOptions);
|
||||
if (!hotlineOptions.min && !hotlineOptions.max) {
|
||||
let minMax = this._calcMinMaxValues(flatLines);
|
||||
hotlineOptions.min = minMax.min;
|
||||
hotlineOptions.max = minMax.max;
|
||||
}
|
||||
|
||||
for (let i = 0; i < segmentLatLngs.length; i++) {
|
||||
const line = segmentLatLngs[i];
|
||||
let hotline = L.hotline(line, hotlineOptions);
|
||||
layers.push(hotline);
|
||||
}
|
||||
}
|
||||
}
|
||||
return layers;
|
||||
}
|
||||
|
||||
_computeLatLngVals(segment) {
|
||||
let latLngVals = [],
|
||||
segmentLatLngs = segment.getLatLngs(),
|
||||
segmentLength = segmentLatLngs.length;
|
||||
|
||||
for (let i = 0; i < segmentLength; i++) {
|
||||
var val = this.valueFunction.call(
|
||||
this,
|
||||
segmentLatLngs[i],
|
||||
segmentLatLngs[Math.max(i - 1, 0)],
|
||||
i,
|
||||
segmentLatLngs
|
||||
);
|
||||
latLngVals.push(this._convertToArray(segmentLatLngs[i], val));
|
||||
}
|
||||
return latLngVals;
|
||||
}
|
||||
|
||||
_convertToArray(latLng, val) {
|
||||
return [latLng.lat, latLng.lng, val];
|
||||
}
|
||||
|
||||
_calcMinMaxValues(lines) {
|
||||
let min = lines[0][2],
|
||||
max = min;
|
||||
for (let i = 1; lines && i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
max = Math.max(max, line[2]);
|
||||
min = Math.min(min, line[2]);
|
||||
}
|
||||
if (min === max) {
|
||||
max = min + 1;
|
||||
}
|
||||
return {
|
||||
min: min,
|
||||
max: max
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -154,7 +154,7 @@ L.BRouter = L.Class.extend({
|
|||
|
||||
try {
|
||||
geojson = JSON.parse(xhr.responseText);
|
||||
layer = L.geoJSON(geojson).getLayers()[0];
|
||||
layer = this._assignFeatures(L.geoJSON(geojson).getLayers()[0]);
|
||||
|
||||
return cb(null, layer);
|
||||
} catch (e) {
|
||||
|
|
@ -190,6 +190,55 @@ L.BRouter = L.Class.extend({
|
|||
xhr.send(profileText);
|
||||
},
|
||||
|
||||
_assignFeatures: function(segment) {
|
||||
if (segment.feature.properties.messages) {
|
||||
let featureMessages = segment.feature.properties.messages,
|
||||
segmentLatLngs = segment.getLatLngs(),
|
||||
segmentLength = segmentLatLngs.length,
|
||||
featureSegmentIndex = 0;
|
||||
|
||||
for (let mi = 1; mi < featureMessages.length; mi++) {
|
||||
var featureLatLng = this._getFeatureLatLng(featureMessages[mi]);
|
||||
|
||||
for (let fi = featureSegmentIndex; fi < segmentLength; fi++) {
|
||||
let segmentLatLng = segmentLatLngs[fi],
|
||||
featureMessage = featureMessages[mi];
|
||||
|
||||
segmentLatLng.feature = this._getFeature(featureMessage);
|
||||
segmentLatLng.message = featureMessage;
|
||||
|
||||
if (featureLatLng.equals(segmentLatLngs[fi])) {
|
||||
featureSegmentIndex = fi + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return segment;
|
||||
},
|
||||
|
||||
_getFeature: function(featureMessage) {
|
||||
//["Longitude", "Latitude", "Elevation", "Distance", "CostPerKm", "ElevCost", "TurnCost", "NodeCost", "InitialCost", "WayTags", "NodeTags"]
|
||||
return {
|
||||
cost: {
|
||||
perKm: parseInt(featureMessage[4]),
|
||||
elev: parseInt(featureMessage[5]),
|
||||
turn: parseInt(featureMessage[6]),
|
||||
node: parseInt(featureMessage[7]),
|
||||
initial: parseInt(featureMessage[8])
|
||||
},
|
||||
wayTags: featureMessage[9],
|
||||
nodeTags: featureMessage[10]
|
||||
};
|
||||
},
|
||||
|
||||
_getFeatureLatLng: function(message) {
|
||||
var lon = message[0] / 1000000,
|
||||
lat = message[1] / 1000000;
|
||||
|
||||
return L.latLng(lat, lon);
|
||||
},
|
||||
|
||||
_handleProfileResponse: function(xhr, cb) {
|
||||
var response;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue