diff --git a/src/common/util/colors.js b/src/common/util/colors.js index 87963a4b8b..cd3cfea016 100644 --- a/src/common/util/colors.js +++ b/src/common/util/colors.js @@ -1,5 +1,5 @@ // Turbo Colormap -const turboPolynomials = { +export const turboPolynomials = { r: [0.13572138, 4.61539260, -42.66032258, 132.13108234, -152.94239396, 59.28637943], g: [0.09140261, 2.19418839, 4.84296658, -14.18503333, 4.27729857, 2.82956604], b: [0.10667330, 12.64194608, -60.58204836, 110.36276771, -89.90310912, 27.34824973], @@ -13,7 +13,7 @@ const interpolateChannel = (normalizedValue, coeffs) => { return Math.max(0, Math.min(1, result)); }; -const interpolateTurbo = (value) => { +export const interpolateTurbo = (value) => { const normalizedValue = Math.max(0, Math.min(1, value)); return [ Math.round(255 * interpolateChannel(normalizedValue, turboPolynomials.r)), diff --git a/src/map/MapRoutePoints.js b/src/map/MapRoutePoints.js index 1da09997f9..f8a2e8dfc1 100644 --- a/src/map/MapRoutePoints.js +++ b/src/map/MapRoutePoints.js @@ -2,9 +2,14 @@ import { useId, useCallback, useEffect } from 'react'; import { map } from './core/MapView'; import getSpeedColor from '../common/util/colors'; import { findFonts } from './core/mapUtil'; +import { SpeedLegendControl } from './legend/MapSpeedLegend'; +import { useTranslation } from '../common/components/LocalizationProvider'; +import { useAttributePreference } from '../common/util/preferences'; const MapRoutePoints = ({ positions, onClick }) => { const id = useId(); + const t = useTranslation(); + const speedUnit = useAttributePreference('speedUnit'); const onMouseEnter = () => map.getCanvas().style.cursor = 'pointer'; const onMouseLeave = () => map.getCanvas().style.cursor = ''; @@ -62,6 +67,9 @@ const MapRoutePoints = ({ positions, onClick }) => { const maxSpeed = positions.map((p) => p.speed).reduce((a, b) => Math.max(a, b), -Infinity); const minSpeed = positions.map((p) => p.speed).reduce((a, b) => Math.min(a, b), Infinity); + const control = new SpeedLegendControl(positions, speedUnit, t, maxSpeed, minSpeed); + map.addControl(control, 'bottom-left'); + map.getSource(id)?.setData({ type: 'FeatureCollection', features: positions.map((position, index) => ({ @@ -78,6 +86,7 @@ const MapRoutePoints = ({ positions, onClick }) => { }, })), }); + return () => map.removeControl(control); }, [onMarkerClick, positions]); return null; diff --git a/src/map/legend/MapSpeedLegend.js b/src/map/legend/MapSpeedLegend.js new file mode 100644 index 0000000000..250bacb968 --- /dev/null +++ b/src/map/legend/MapSpeedLegend.js @@ -0,0 +1,57 @@ +import { interpolateTurbo } from '../../common/util/colors'; +import { speedFromKnots, speedUnitString } from '../../common/util/converter'; + +export class SpeedLegendControl { + constructor(positions, speedUnit, t, maxSpeed, minSpeed) { + this.positions = positions; + this.t = t; + this.speedUnit = speedUnit; + this.maxSpeed = maxSpeed; + this.minSpeed = minSpeed; + } + + onAdd(map) { + this.map = map; + this.controlContainer = document.createElement('div'); + this.controlContainer.className = 'maplibregl-ctrl maplibregl-ctrl-scale'; + + if (this.positions.length && this.maxSpeed) { + this.controlContainer.appendChild(this.createSpeedLegend()); + } + + return this.controlContainer; + } + + onRemove() { + if (this.controlContainer && this.controlContainer.parentNode) { + this.controlContainer.parentNode.removeChild(this.controlContainer); + this.map = undefined; + } + } + + createSpeedLegend() { + const gradientStops = Array.from({ length: 10 }, (_, i) => { + const t = i / 9; + const [r, g, b] = interpolateTurbo(t); + return `rgb(${r}, ${g}, ${b})`; + }).join(', '); + + const legend = document.createElement('div'); + + const colorBar = document.createElement('div'); + colorBar.style.background = `linear-gradient(to right, ${gradientStops})`; + colorBar.style.height = '10px'; + + const speedLabel = document.createElement('span'); + const minSpeed = Math.round(speedFromKnots(this.minSpeed, this.speedUnit)); + const maxSpeed = Math.round(speedFromKnots(this.maxSpeed, this.speedUnit)); + speedLabel.textContent = `${minSpeed} - ${maxSpeed} ${speedUnitString(this.speedUnit, this.t)}`; + + legend.appendChild(colorBar); + legend.appendChild(speedLabel); + + return legend; + } +} + +export default SpeedLegendControl;