Colorful routes can conflict visually with colourful maps, therefore having an outline to make the route stand out more makes sense. However, using black results in very high contrast levels, which can become distracting in itself. By using a dark gray, this should look much more balanced, while still fulfilling the original purpose.
257 lines
8.6 KiB
JavaScript
257 lines
8.6 KiB
JavaScript
BR.RoutingPathQuality = L.Control.extend({
|
|
initialize: function(map, layersControl, options) {
|
|
L.setOptions(this, options);
|
|
|
|
// 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();
|
|
this._routingSegments.id = 'route-quality'; // for URL hash instead of language name
|
|
layersControl.addOverlay(this._routingSegments, i18next.t('map.layer.route-quality'));
|
|
|
|
this.providers = {
|
|
incline: {
|
|
title: i18next.t('map.route-quality-incline'),
|
|
icon: 'fa-line-chart',
|
|
provider: new HotLineQualityProvider({
|
|
hotlineOptions: {
|
|
min: -8.5,
|
|
max: 8.5, // angle in degree, == 15% incline
|
|
palette: {
|
|
0.0: '#0000ff', // blue
|
|
0.25: '#00ffff', // cyan
|
|
0.5: '#00ff00', // green
|
|
0.75: '#ffff00', // yellow
|
|
1.0: '#ff0000' // red
|
|
},
|
|
outlineColor: 'dimgray',
|
|
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;
|
|
}
|
|
})
|
|
},
|
|
altitude: {
|
|
title: i18next.t('map.route-quality-altitude'),
|
|
icon: 'fa-area-chart',
|
|
provider: new HotLineQualityProvider({
|
|
hotlineOptions: {
|
|
outlineColor: 'dimgray',
|
|
renderer: renderer
|
|
},
|
|
valueFunction: function(latLng) {
|
|
return latLng.alt;
|
|
}
|
|
})
|
|
},
|
|
cost: {
|
|
title: i18next.t('map.route-quality-cost'),
|
|
icon: 'fa-usd',
|
|
provider: new HotLineQualityProvider({
|
|
hotlineOptions: {
|
|
outlineColor: 'dimgray',
|
|
renderer: renderer
|
|
},
|
|
valueFunction: function(latLng) {
|
|
var feature = latLng.feature;
|
|
return (
|
|
feature.cost.perKm +
|
|
feature.cost.elev +
|
|
feature.cost.turn +
|
|
feature.cost.node +
|
|
feature.cost.initial
|
|
);
|
|
}
|
|
})
|
|
}
|
|
};
|
|
this._initialProvider = this.options.initialProvider || 'incline';
|
|
this.selectedProvider = this._initialProvider;
|
|
|
|
this._active = false;
|
|
},
|
|
|
|
onAdd: function(map) {
|
|
this._map = map;
|
|
|
|
map.on(
|
|
'overlayadd',
|
|
function(evt) {
|
|
if (evt.layer === this._routingSegments) {
|
|
this._activate(this.routingPathButton);
|
|
}
|
|
},
|
|
this
|
|
);
|
|
map.on(
|
|
'overlayremove',
|
|
function(evt) {
|
|
if (evt.layer === this._routingSegments) {
|
|
this._deactivate(this.routingPathButton);
|
|
}
|
|
},
|
|
this
|
|
);
|
|
|
|
var states = [],
|
|
i,
|
|
keys = Object.keys(this.providers),
|
|
l = keys.length;
|
|
|
|
for (i = 0; i < l; ++i) {
|
|
var provider = this.providers[keys[i]];
|
|
var nextState = keys[(i + 1) % l];
|
|
states.push({
|
|
stateName: keys[i],
|
|
icon: provider.icon,
|
|
title: provider.title,
|
|
onClick: L.bind(function(state) {
|
|
return L.bind(function(btn) {
|
|
if (this._active) {
|
|
btn.state(state);
|
|
this.setProvider(state);
|
|
|
|
if (state === this._initialProvider) {
|
|
this._deactivate(btn);
|
|
} else {
|
|
this._getIcon(btn).classList.add('active');
|
|
}
|
|
} else {
|
|
this._activate(btn);
|
|
}
|
|
}, this);
|
|
}, this)(nextState)
|
|
});
|
|
}
|
|
|
|
this.routingPathButton = new L.easyButton({
|
|
states: states
|
|
}).addTo(map);
|
|
return new L.DomUtil.create('div');
|
|
},
|
|
|
|
_activate: function(btn) {
|
|
this._active = true;
|
|
this._getIcon(btn).classList.add('active');
|
|
this._routingSegments.addTo(this._map);
|
|
},
|
|
|
|
_deactivate: function(btn) {
|
|
this._active = false;
|
|
this._getIcon(btn).classList.remove('active');
|
|
this._map.removeLayer(this._routingSegments);
|
|
},
|
|
|
|
_getIcon: function(btn) {
|
|
return btn.button.firstChild.firstChild;
|
|
},
|
|
|
|
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);
|
|
},
|
|
|
|
_update: function(segments) {
|
|
this._routingSegments.clearLayers();
|
|
var layers = this.providers[this.selectedProvider].provider.computeLayers(segments);
|
|
if (layers) {
|
|
for (var i = 0; i < layers.length; i++) {
|
|
this._routingSegments.addLayer(layers[i]);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var HotLineQualityProvider = L.Class.extend({
|
|
initialize: function(options) {
|
|
this.hotlineOptions = options.hotlineOptions;
|
|
this.valueFunction = options.valueFunction;
|
|
},
|
|
|
|
computeLayers: function(segments) {
|
|
var layers = [];
|
|
if (segments) {
|
|
var segmentLatLngs = [];
|
|
var flatLines = [];
|
|
for (var i = 0; segments && i < segments.length; i++) {
|
|
var segment = segments[i];
|
|
var vals = this._computeLatLngVals(segment);
|
|
segmentLatLngs.push(vals);
|
|
Array.prototype.push.apply(flatLines, vals);
|
|
}
|
|
|
|
if (flatLines.length > 0) {
|
|
var hotlineOptions = L.extend({}, this.hotlineOptions);
|
|
if (!hotlineOptions.min && !hotlineOptions.max) {
|
|
var minMax = this._calcMinMaxValues(flatLines);
|
|
hotlineOptions.min = minMax.min;
|
|
hotlineOptions.max = minMax.max;
|
|
}
|
|
|
|
for (var i = 0; i < segmentLatLngs.length; i++) {
|
|
var line = segmentLatLngs[i];
|
|
var hotline = L.hotline(line, hotlineOptions);
|
|
layers.push(hotline);
|
|
}
|
|
}
|
|
}
|
|
return layers;
|
|
},
|
|
|
|
_computeLatLngVals: function(segment) {
|
|
var latLngVals = [],
|
|
segmentLatLngs = segment.getLatLngs(),
|
|
segmentLength = segmentLatLngs.length;
|
|
|
|
for (var 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: function(latLng, val) {
|
|
return [latLng.lat, latLng.lng, val];
|
|
},
|
|
|
|
_calcMinMaxValues: function(lines) {
|
|
var min = lines[0][2],
|
|
max = min;
|
|
for (var i = 1; lines && i < lines.length; i++) {
|
|
var 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
|
|
};
|
|
}
|
|
});
|