@@ -5,61 +5,7 @@ import { calcDist } from '@/utils'
55import { ApiImpl } from '@/api/Api'
66import { tr } from '@/translation/Translation'
77import styles from './RouteStats.module.css'
8-
9- // Stable color map: same value always gets the same color regardless of route
10- const VALUE_COLORS : Record < string , string > = {
11- // Surface - paved (greens)
12- asphalt : '#2E7D32' ,
13- paved : '#43A047' ,
14- concrete : '#66BB6A' ,
15- paving_stones : '#81C784' ,
16- 'concrete:plates' : '#A5D6A7' ,
17- 'concrete:lanes' : '#388E3C' ,
18- metal : '#00897B' ,
19- // Surface - unpaved (warm/natural tones)
20- compacted : '#FFB74D' ,
21- gravel : '#FF8A65' ,
22- fine_gravel : '#FFCC80' ,
23- unpaved : '#C68642' ,
24- ground : '#9E9D24' ,
25- earth : '#8D6E63' ,
26- grass : '#7CB342' ,
27- grass_paver : '#AED581' ,
28- sand : '#FFD54F' ,
29- mud : '#5D4037' ,
30- // Surface - rough/uncomfortable (reds/pinks)
31- dirt : '#E53935' ,
32- wood : '#C62828' ,
33- cobblestone : '#D81B60' ,
34- sett : '#AD1457' ,
35- unhewn_cobblestone : '#880E4F' ,
36- // Road classes
37- motorway : '#D32F2F' ,
38- trunk : '#E64A19' ,
39- primary : '#F57C00' ,
40- secondary : '#FFA726' ,
41- tertiary : '#42A5F5' ,
42- residential : '#66BB6A' ,
43- unclassified : '#78909C' ,
44- living_street : '#81C784' ,
45- service : '#A5D6A7' ,
46- cycleway : '#2E7D32' ,
47- path : '#66BB6A' ,
48- track : '#81C784' ,
49- bridleway : '#795548' ,
50- footway : '#EC407A' ,
51- pedestrian : '#F48FB1' ,
52- steps : '#FF5722' ,
53- // Network levels
54- international : '#2E7D32' ,
55- national : '#66BB6A' ,
56- regional : '#1565C0' ,
57- local : '#42A5F5' ,
58- }
59-
60- const INCLINE_COLORS = [ '#2E7D32' , '#FF9800' , '#F44336' , '#7B1FA2' ]
61- const INCLINE_LABELS = [ 'flat (<3%)' , 'mild (3–6%)' , 'steep (6–10%)' , 'very steep (≥10%)' ]
62- const SPEED_COLORS = [ '#F44336' , '#FF9800' , '#FFD54F' , '#66BB6A' , '#2E7D32' ]
8+ import { NAMED_COLOR_MAPS , INCLINE_CATEGORIES , SPEED_COLORS , getSpeedThresholds , getSpeedLabels } from '@/pathDetails/elevationWidget/colors'
639
6410const PAVED = new Set ( [ 'asphalt' , 'concrete' , 'paved' , 'paving_stones' , 'concrete:plates' , 'concrete:lanes' , 'metal' ] )
6511const UNPAVED = new Set ( [
@@ -102,7 +48,7 @@ function computeInclineDistances(coords: Position[], thresholds: number[]): numb
10248 if ( dist > 100 ) {
10349 const slope = ( 100 * Math . abs ( c [ 2 ] - prevEle [ 2 ] ) ) / dist
10450 for ( let t = 0 ; t < thresholds . length ; t ++ )
105- if ( slope >= thresholds [ t ] ) distAbove [ t ] += dist
51+ if ( slope > thresholds [ t ] ) distAbove [ t ] += dist
10652 prevEle = c
10753 dist = 0
10854 }
@@ -145,12 +91,6 @@ function formatTime(minutes: number): string {
14591 return h > 0 ? `${ h } h ${ m } min` : `${ m } min`
14692}
14793
148- function getSpeedThresholds ( profile : string ) : number [ ] {
149- if ( ApiImpl . isMotorVehicle ( profile ) ) return [ 30 , 50 , 80 ]
150- if ( ApiImpl . isFootLike ( profile ) ) return [ 3 , 4 , 5 ]
151- return [ 5 , 10 , 15 , 20 ]
152- }
153-
15494// --- Detail entries & components ---
15595
15696interface DetailEntry {
@@ -168,26 +108,26 @@ interface SummaryEntry {
168108}
169109
170110/** Get colors of the top N contributors and whether there are more */
171- function topColors ( distMap : Map < string , number > , keys : Iterable < string > , n : number = 4 ) {
111+ function topColors ( colorMap : Record < string , string > , distMap : Map < string , number > , keys : Iterable < string > , n : number = 4 ) {
172112 const sorted = [ ...keys ]
173113 . map ( k => ( { key : k , dist : distMap . get ( k ) || 0 } ) )
174114 . filter ( e => e . dist > 0 )
175115 . sort ( ( a , b ) => b . dist - a . dist )
176116 return {
177- colors : sorted . slice ( 0 , n ) . map ( e => VALUE_COLORS [ e . key ] || '#BDBDBD' ) ,
117+ colors : sorted . slice ( 0 , n ) . map ( e => colorMap [ e . key ] || '#BDBDBD' ) ,
178118 more : sorted . length > n ,
179119 }
180120}
181121
182122/** Build detail entries from a distance map, sorted by distance descending */
183- function detailEntries ( distMap : Map < string , number > , totalDist : number , missingLabel = 'unknown' ) : DetailEntry [ ] {
123+ function detailEntries ( colorMap : Record < string , string > , distMap : Map < string , number > , totalDist : number , missingLabel = 'unknown' ) : DetailEntry [ ] {
184124 return [ ...distMap . entries ( ) ]
185125 . filter ( ( [ , d ] ) => d > 0 )
186126 . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] )
187127 . map ( ( [ name , d ] ) => ( {
188128 name : ( ! name || name === 'missing' ) ? missingLabel : name ,
189129 km : fmtKm ( d ) ,
190- color : VALUE_COLORS [ name ] || '#BDBDBD' ,
130+ color : colorMap [ name ] || '#BDBDBD' ,
191131 fraction : d / totalDist ,
192132 } ) )
193133}
@@ -276,16 +216,17 @@ export default function RouteStats({ path, profile }: { path: Path; profile: str
276216 const lines : React . ReactNode [ ] = [ ]
277217
278218 // Surface
219+ const surfaceColors = NAMED_COLOR_MAPS [ 'surface' ] || { }
279220 if ( path . details . surface ) {
280221 const dist = computeDetailDistances ( coords , path . details . surface )
281222 if ( dist . size > 0 ) {
282223 const pavedDist = sumForKeys ( dist , PAVED )
283224 const unpavedDist = sumForKeys ( dist , UNPAVED )
284225 const extra : SummaryEntry [ ] = [ ]
285- if ( pavedDist > 0 ) extra . push ( { name : 'paved' , value : pct ( pavedDist , totalDist ) , ...topColors ( dist , PAVED ) } )
286- if ( unpavedDist > 0 ) extra . push ( { name : 'unpaved' , value : pct ( unpavedDist , totalDist ) , ...topColors ( dist , UNPAVED ) } )
226+ if ( pavedDist > 0 ) extra . push ( { name : 'paved' , value : pct ( pavedDist , totalDist ) , ...topColors ( surfaceColors , dist , PAVED ) } )
227+ if ( unpavedDist > 0 ) extra . push ( { name : 'unpaved' , value : pct ( unpavedDist , totalDist ) , ...topColors ( surfaceColors , dist , UNPAVED ) } )
287228 lines . push (
288- < ExpandableStat key = "surface" label = { tr ( 'route_stats_surface' ) } details = { detailEntries ( dist , totalDist ) } extraInfo = { extra } /> ,
229+ < ExpandableStat key = "surface" label = { tr ( 'route_stats_surface' ) } details = { detailEntries ( surfaceColors , dist , totalDist ) } extraInfo = { extra } /> ,
289230 )
290231 }
291232 }
@@ -297,34 +238,36 @@ export default function RouteStats({ path, profile }: { path: Path; profile: str
297238 ]
298239 for ( const [ key , label , details , active ] of networks ) {
299240 if ( active && details ) {
241+ const colors = NAMED_COLOR_MAPS [ key ] || { }
300242 const dist = computeDetailDistances ( coords , details )
301243 const onNetwork = sumForKeys ( dist , NETWORK_KEYS )
302244 if ( dist . size > 0 ) {
303245 lines . push (
304246 < ExpandableStat
305247 key = { key }
306248 label = { label }
307- details = { detailEntries ( dist , totalDist , 'none' ) }
308- extraInfo = { [ { name : 'on network' , value : pct ( onNetwork , totalDist ) , ...topColors ( dist , NETWORK_KEYS ) } ] }
249+ details = { detailEntries ( colors , dist , totalDist , 'none' ) }
250+ extraInfo = { [ { name : 'on network' , value : pct ( onNetwork , totalDist ) , ...topColors ( colors , dist , NETWORK_KEYS ) } ] }
309251 /> ,
310252 )
311253 }
312254 }
313255 }
314256
315257 // Roads
258+ const roadColors = NAMED_COLOR_MAPS [ 'road_class' ] || { }
316259 if ( path . details . road_class ) {
317260 const dist = computeDetailDistances ( coords , path . details . road_class )
318261 if ( dist . size > 0 ) {
319262 const extra : SummaryEntry [ ] = [ ]
320263 const big = sumForKeys ( dist , BIG_ROADS )
321264 const medium = sumForKeys ( dist , MEDIUM_ROADS )
322265 const small = sumForKeys ( dist , SMALL_ROADS )
323- if ( big > 0 ) extra . push ( { name : 'big roads' , value : pct ( big , totalDist ) , ...topColors ( dist , BIG_ROADS ) } )
324- if ( medium > 0 ) extra . push ( { name : 'medium' , value : pct ( medium , totalDist ) , ...topColors ( dist , MEDIUM_ROADS ) } )
325- if ( small > 0 ) extra . push ( { name : 'small' , value : pct ( small , totalDist ) , ...topColors ( dist , SMALL_ROADS ) } )
266+ if ( big > 0 ) extra . push ( { name : 'big roads' , value : pct ( big , totalDist ) , ...topColors ( roadColors , dist , BIG_ROADS ) } )
267+ if ( medium > 0 ) extra . push ( { name : 'medium' , value : pct ( medium , totalDist ) , ...topColors ( roadColors , dist , MEDIUM_ROADS ) } )
268+ if ( small > 0 ) extra . push ( { name : 'small' , value : pct ( small , totalDist ) , ...topColors ( roadColors , dist , SMALL_ROADS ) } )
326269 lines . push (
327- < ExpandableStat key = "roads" label = { tr ( 'route_stats_roads' ) } details = { detailEntries ( dist , totalDist ) } extraInfo = { extra } /> ,
270+ < ExpandableStat key = "roads" label = { tr ( 'route_stats_roads' ) } details = { detailEntries ( roadColors , dist , totalDist ) } extraInfo = { extra } /> ,
328271 )
329272 }
330273 }
@@ -335,7 +278,7 @@ export default function RouteStats({ path, profile }: { path: Path; profile: str
335278 if ( distAbove [ 0 ] > 0 ) {
336279 const segments = [ totalDist - distAbove [ 0 ] , distAbove [ 0 ] - distAbove [ 1 ] , distAbove [ 1 ] - distAbove [ 2 ] , distAbove [ 2 ] ]
337280 const inclineDetails = segments
338- . map ( ( d , i ) => ( { name : INCLINE_LABELS [ i ] , km : fmtKm ( d ) , color : INCLINE_COLORS [ i ] , fraction : d / totalDist } ) )
281+ . map ( ( d , i ) => ( { name : INCLINE_CATEGORIES [ i ] . label , km : fmtKm ( d ) , color : INCLINE_CATEGORIES [ i ] . color , fraction : d / totalDist } ) )
339282 . filter ( d => d . fraction > 0 )
340283 lines . push (
341284 < ExpandableStat
@@ -356,11 +299,7 @@ export default function RouteStats({ path, profile }: { path: Path; profile: str
356299 const thresholds = getSpeedThresholds ( profile )
357300 const distBelow = computeSpeedDistances ( coords , path . details . average_speed , thresholds )
358301 const boundaries = [ 0 , ...distBelow , totalDist ]
359- const speedLabels = [
360- `< ${ thresholds [ 0 ] } ` ,
361- ...thresholds . slice ( 0 , - 1 ) . map ( ( t , i ) => `${ t } –${ thresholds [ i + 1 ] } ` ) ,
362- `≥ ${ thresholds [ thresholds . length - 1 ] } ` ,
363- ] . map ( s => `${ s } km/h` )
302+ const speedLabels = getSpeedLabels ( thresholds ) . map ( s => `${ s } km/h` )
364303 const speedDetails = boundaries
365304 . slice ( 0 , - 1 )
366305 . map ( ( _ , i ) => ( {
0 commit comments