From 693573ead22ce8168b3ffa263774fc623c7390d1 Mon Sep 17 00:00:00 2001 From: jcardus Date: Mon, 4 Nov 2024 16:54:24 +0000 Subject: [PATCH 1/4] Add speed color legend --- src/common/util/colors.js | 4 +- src/map/MapRoutePoints.js | 9 ++++ src/map/legend/MapSpeedLegend.js | 74 ++++++++++++++++++++++++++++++++ src/map/legend/legend.css | 25 +++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 src/map/legend/MapSpeedLegend.js create mode 100644 src/map/legend/legend.css 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..360d216cc9 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 { LegendControl } 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 LegendControl(positions, speedUnit, t); + map.addControl(control, 'bottom-right'); + 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..c62ada43e0 --- /dev/null +++ b/src/map/legend/MapSpeedLegend.js @@ -0,0 +1,74 @@ +import './legend.css'; +import { formatSpeed } from '../../common/util/formatter'; +import { interpolateTurbo } from '../../common/util/colors'; + +export class LegendControl { + constructor(positions, speedUnit, t) { + this.positions = positions; + this.t = t; + this.speedUnit = speedUnit; + this.maxSpeed = positions.map((p) => p.speed).reduce((a, b) => Math.max(a, b), -Infinity); + this.minSpeed = positions.map((p) => p.speed).reduce((a, b) => Math.min(a, b), Infinity); + } + + onAdd(map) { + this.map = map; + // Create the control container + this.controlContainer = document.createElement('div'); + this.controlContainer.className = 'maplibregl-ctrl-group maplibregl-ctrl'; + + if (this.positions.length && this.maxSpeed) { + this.controlContainer.appendChild(this.createSpeedLegend(this.minSpeed, this.maxSpeed, this.t)); + } + + 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'); + legend.classList.add('legend'); + + const colorBar = document.createElement('div'); + colorBar.classList.add('legend-color-bar'); + colorBar.style.background = `linear-gradient(to right, ${gradientStops})`; + + const speedLabels = document.createElement('div'); + speedLabels.classList.add('legend-speed-labels'); + + const minSpeedLabel = document.createElement('span'); + minSpeedLabel.classList.add('legend-speed-label'); + minSpeedLabel.textContent = `${formatSpeed(this.minSpeed, this.speedUnit, this.t)}`; + + const middleSpeedLabel = document.createElement('span'); + middleSpeedLabel.classList.add('legend-speed-label'); + middleSpeedLabel.textContent = `${formatSpeed(this.maxSpeed / 2, this.speedUnit, this.t)}`; + + const maxSpeedLabel = document.createElement('span'); + maxSpeedLabel.classList.add('legend-speed-label'); + maxSpeedLabel.textContent = `${formatSpeed(this.maxSpeed, this.speedUnit, this.t)}`; + + speedLabels.appendChild(minSpeedLabel); + speedLabels.appendChild(middleSpeedLabel); + speedLabels.appendChild(maxSpeedLabel); + + legend.appendChild(colorBar); + legend.appendChild(speedLabels); + + return legend; + } +} + +export default LegendControl; diff --git a/src/map/legend/legend.css b/src/map/legend/legend.css new file mode 100644 index 0000000000..fd1b369e77 --- /dev/null +++ b/src/map/legend/legend.css @@ -0,0 +1,25 @@ +.legend { + padding: 10px; +} + +.legend-color-bar { + height: 20px; +} + +.legend-speed-labels { + display: flex; +} + +.legend-speed-label { + /* this makes the middle label position exactly in the middle */ + min-width: 70px; +} + +.legend-speed-label:nth-child(2) { + text-align: center; + padding: 0 30px; +} + +.legend-speed-label:nth-child(3) { + text-align: right; +} \ No newline at end of file From 766e4cab496a1c42edeceea03aeff3144e200fd5 Mon Sep 17 00:00:00 2001 From: jcardus Date: Mon, 4 Nov 2024 19:19:37 +0000 Subject: [PATCH 2/4] Make it smaller --- src/map/MapRoutePoints.js | 2 +- src/map/legend/MapSpeedLegend.js | 3 +-- src/map/legend/legend.css | 11 ++++------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/map/MapRoutePoints.js b/src/map/MapRoutePoints.js index 360d216cc9..6f798ba9a4 100644 --- a/src/map/MapRoutePoints.js +++ b/src/map/MapRoutePoints.js @@ -68,7 +68,7 @@ const MapRoutePoints = ({ positions, onClick }) => { const minSpeed = positions.map((p) => p.speed).reduce((a, b) => Math.min(a, b), Infinity); const control = new LegendControl(positions, speedUnit, t); - map.addControl(control, 'bottom-right'); + map.addControl(control, 'bottom-left'); map.getSource(id)?.setData({ type: 'FeatureCollection', diff --git a/src/map/legend/MapSpeedLegend.js b/src/map/legend/MapSpeedLegend.js index c62ada43e0..1fb86c911b 100644 --- a/src/map/legend/MapSpeedLegend.js +++ b/src/map/legend/MapSpeedLegend.js @@ -15,7 +15,7 @@ export class LegendControl { this.map = map; // Create the control container this.controlContainer = document.createElement('div'); - this.controlContainer.className = 'maplibregl-ctrl-group maplibregl-ctrl'; + this.controlContainer.className = 'maplibregl-ctrl'; if (this.positions.length && this.maxSpeed) { this.controlContainer.appendChild(this.createSpeedLegend(this.minSpeed, this.maxSpeed, this.t)); @@ -39,7 +39,6 @@ export class LegendControl { }).join(', '); const legend = document.createElement('div'); - legend.classList.add('legend'); const colorBar = document.createElement('div'); colorBar.classList.add('legend-color-bar'); diff --git a/src/map/legend/legend.css b/src/map/legend/legend.css index fd1b369e77..8671b44952 100644 --- a/src/map/legend/legend.css +++ b/src/map/legend/legend.css @@ -1,9 +1,5 @@ -.legend { - padding: 10px; -} - .legend-color-bar { - height: 20px; + height: 10px; } .legend-speed-labels { @@ -11,13 +7,14 @@ } .legend-speed-label { - /* this makes the middle label position exactly in the middle */ min-width: 70px; + color: black; + background: rgba(255, 255, 255, 0.8); + padding: 0 2px; } .legend-speed-label:nth-child(2) { text-align: center; - padding: 0 30px; } .legend-speed-label:nth-child(3) { From bcf1f68670b69213e94328e87e175fc8b1ad9c12 Mon Sep 17 00:00:00 2001 From: jcardus Date: Tue, 5 Nov 2024 05:41:29 +0000 Subject: [PATCH 3/4] Make it look like the scale control --- src/map/legend/MapSpeedLegend.js | 33 ++++++++------------------------ src/map/legend/legend.css | 22 --------------------- 2 files changed, 8 insertions(+), 47 deletions(-) delete mode 100644 src/map/legend/legend.css diff --git a/src/map/legend/MapSpeedLegend.js b/src/map/legend/MapSpeedLegend.js index 1fb86c911b..a49fd32027 100644 --- a/src/map/legend/MapSpeedLegend.js +++ b/src/map/legend/MapSpeedLegend.js @@ -1,6 +1,5 @@ -import './legend.css'; -import { formatSpeed } from '../../common/util/formatter'; import { interpolateTurbo } from '../../common/util/colors'; +import { speedFromKnots, speedUnitString } from '../../common/util/converter'; export class LegendControl { constructor(positions, speedUnit, t) { @@ -13,12 +12,11 @@ export class LegendControl { onAdd(map) { this.map = map; - // Create the control container this.controlContainer = document.createElement('div'); - this.controlContainer.className = 'maplibregl-ctrl'; + this.controlContainer.className = 'maplibregl-ctrl maplibregl-ctrl-scale'; if (this.positions.length && this.maxSpeed) { - this.controlContainer.appendChild(this.createSpeedLegend(this.minSpeed, this.maxSpeed, this.t)); + this.controlContainer.appendChild(this.createSpeedLegend()); } return this.controlContainer; @@ -41,30 +39,15 @@ export class LegendControl { const legend = document.createElement('div'); const colorBar = document.createElement('div'); - colorBar.classList.add('legend-color-bar'); colorBar.style.background = `linear-gradient(to right, ${gradientStops})`; + colorBar.style.height = '10px'; - const speedLabels = document.createElement('div'); - speedLabels.classList.add('legend-speed-labels'); - - const minSpeedLabel = document.createElement('span'); - minSpeedLabel.classList.add('legend-speed-label'); - minSpeedLabel.textContent = `${formatSpeed(this.minSpeed, this.speedUnit, this.t)}`; - - const middleSpeedLabel = document.createElement('span'); - middleSpeedLabel.classList.add('legend-speed-label'); - middleSpeedLabel.textContent = `${formatSpeed(this.maxSpeed / 2, this.speedUnit, this.t)}`; - - const maxSpeedLabel = document.createElement('span'); - maxSpeedLabel.classList.add('legend-speed-label'); - maxSpeedLabel.textContent = `${formatSpeed(this.maxSpeed, this.speedUnit, this.t)}`; - - speedLabels.appendChild(minSpeedLabel); - speedLabels.appendChild(middleSpeedLabel); - speedLabels.appendChild(maxSpeedLabel); + const speedLabel = document.createElement('span'); + speedLabel.textContent = `${Math.round(speedFromKnots(this.minSpeed, this.speedUnit))} - ${ + Math.round(speedFromKnots(this.maxSpeed, this.speedUnit))} ${speedUnitString(this.speedUnit, this.t)}`; legend.appendChild(colorBar); - legend.appendChild(speedLabels); + legend.appendChild(speedLabel); return legend; } diff --git a/src/map/legend/legend.css b/src/map/legend/legend.css deleted file mode 100644 index 8671b44952..0000000000 --- a/src/map/legend/legend.css +++ /dev/null @@ -1,22 +0,0 @@ -.legend-color-bar { - height: 10px; -} - -.legend-speed-labels { - display: flex; -} - -.legend-speed-label { - min-width: 70px; - color: black; - background: rgba(255, 255, 255, 0.8); - padding: 0 2px; -} - -.legend-speed-label:nth-child(2) { - text-align: center; -} - -.legend-speed-label:nth-child(3) { - text-align: right; -} \ No newline at end of file From ef3d040546c530e1c2841429da89cf8fa688e63e Mon Sep 17 00:00:00 2001 From: jcardus Date: Tue, 5 Nov 2024 17:15:21 +0000 Subject: [PATCH 4/4] Refactor LegendControl code --- src/map/MapRoutePoints.js | 4 ++-- src/map/legend/MapSpeedLegend.js | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/map/MapRoutePoints.js b/src/map/MapRoutePoints.js index 6f798ba9a4..f8a2e8dfc1 100644 --- a/src/map/MapRoutePoints.js +++ b/src/map/MapRoutePoints.js @@ -2,7 +2,7 @@ import { useId, useCallback, useEffect } from 'react'; import { map } from './core/MapView'; import getSpeedColor from '../common/util/colors'; import { findFonts } from './core/mapUtil'; -import { LegendControl } from './legend/MapSpeedLegend'; +import { SpeedLegendControl } from './legend/MapSpeedLegend'; import { useTranslation } from '../common/components/LocalizationProvider'; import { useAttributePreference } from '../common/util/preferences'; @@ -67,7 +67,7 @@ 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 LegendControl(positions, speedUnit, t); + const control = new SpeedLegendControl(positions, speedUnit, t, maxSpeed, minSpeed); map.addControl(control, 'bottom-left'); map.getSource(id)?.setData({ diff --git a/src/map/legend/MapSpeedLegend.js b/src/map/legend/MapSpeedLegend.js index a49fd32027..250bacb968 100644 --- a/src/map/legend/MapSpeedLegend.js +++ b/src/map/legend/MapSpeedLegend.js @@ -1,13 +1,13 @@ import { interpolateTurbo } from '../../common/util/colors'; import { speedFromKnots, speedUnitString } from '../../common/util/converter'; -export class LegendControl { - constructor(positions, speedUnit, t) { +export class SpeedLegendControl { + constructor(positions, speedUnit, t, maxSpeed, minSpeed) { this.positions = positions; this.t = t; this.speedUnit = speedUnit; - this.maxSpeed = positions.map((p) => p.speed).reduce((a, b) => Math.max(a, b), -Infinity); - this.minSpeed = positions.map((p) => p.speed).reduce((a, b) => Math.min(a, b), Infinity); + this.maxSpeed = maxSpeed; + this.minSpeed = minSpeed; } onAdd(map) { @@ -43,8 +43,9 @@ export class LegendControl { colorBar.style.height = '10px'; const speedLabel = document.createElement('span'); - speedLabel.textContent = `${Math.round(speedFromKnots(this.minSpeed, this.speedUnit))} - ${ - Math.round(speedFromKnots(this.maxSpeed, this.speedUnit))} ${speedUnitString(this.speedUnit, this.t)}`; + 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); @@ -53,4 +54,4 @@ export class LegendControl { } } -export default LegendControl; +export default SpeedLegendControl;