From faae47b3b7fe86df5eec8d14e3eb21511112247b Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 14 Jun 2026 13:54:12 +0200 Subject: [PATCH 01/11] Combine railway line layers with dasharrays Fixes #979 Enabled by https://github.com/maplibre/maplibre-gl-js/issues/1235 and https://github.com/maplibre/maplibre-gl-js/pull/5812. Supported since Maplibre GL JS version 5.8.0. See https://maplibre.org/maplibre-style-spec/layers/#line-dasharray We can combine duplicated map layers that use different dashes. The `line-dasharray` supports a data-driven style. We still have to duplicate dashed lines and non-dashed lines, because `line-cap` is not data-driven. For lines with a dasharray, the line cap should be `butt` while for non-dashed lines the line cap should be `round`. --- proxy/js/styles.mjs | 348 ++++++++++++++++++++++---------------------- 1 file changed, 174 insertions(+), 174 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index 6c283e24a..f7ef49584 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -637,52 +637,22 @@ const railwayLine = (text, layers) => [ ...layers .filter(({gapWidth}) => !gapWidth) - .flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, states, sort}) => - Object.entries(states).map(([state, dash]) => ({ - id: `${id}_tunnel_casing_${state}`, - type: 'line', - minzoom, - maxzoom, - source, - 'source-layer': sourceLayer || 'railway_line_high', - filter: ['all', - ['==', ['get', 'state'], state], - ['==', ['get', 'tunnel'], true], - filter ?? true, - ].filter(it => it !== true), - layout: { - 'visibility': ['case', - visibility ? ['==', visibility, false] : false, 'none', - ['<', ['global-state', 'date'], defaultDate], 'none', - state === 'construction' ? ['global-state', 'showConstructionInfrastructure'] - : state === 'proposed' ? ['global-state', 'showProposedInfrastructure'] - : state === 'abandoned' ? ['global-state', 'showAbandonedInfrastructure'] - : state === 'razed' ? ['global-state', 'showRazedInfrastructure'] - : true, 'visible', - 'none', - ], - 'line-join': 'round', - 'line-cap': dash ? 'butt' : 'round', - 'line-sort-key': sort, - }, - paint: { - 'line-color': colors.casing, - 'line-width': width, - 'line-gap-width': railway_casing_add, - 'line-dasharray': dash ?? undefined, - }, - })) - ), - ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, sort}) => - Object.entries(states).map(([state, dash]) => ({ - id: `${id}_tunnel_fill_${state}`, + .map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, states, dash, sort}) => ({ + id: `${id}_tunnel_casing`, type: 'line', minzoom, maxzoom, source, 'source-layer': sourceLayer || 'railway_line_high', filter: ['all', - ['==', ['get', 'state'], state], + ['in', ['get', 'state'], ['literal', states]], + ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ], ['==', ['get', 'tunnel'], true], filter ?? true, ].filter(it => it !== true), @@ -690,29 +660,59 @@ const railwayLine = (text, layers) => [ 'visibility': ['case', visibility ? ['==', visibility, false] : false, 'none', ['<', ['global-state', 'date'], defaultDate], 'none', - state === 'construction' ? ['global-state', 'showConstructionInfrastructure'] - : state === 'proposed' ? ['global-state', 'showProposedInfrastructure'] - : state === 'abandoned' ? ['global-state', 'showAbandonedInfrastructure'] - : state === 'razed' ? ['global-state', 'showRazedInfrastructure'] - : true, 'visible', - 'none', + 'visible', ], 'line-join': 'round', 'line-cap': dash ? 'butt' : 'round', 'line-sort-key': sort, }, paint: { - 'line-color': ['case', - ['boolean', ['feature-state', 'hover'], false], hoverColor || colors.hover.main, - color, - ], + 'line-color': colors.casing, 'line-width': width, - 'line-gap-width': gapWidth ?? undefined, + 'line-gap-width': railway_casing_add, 'line-dasharray': dash ?? undefined, }, })), - ), - ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, states, sort}) => ({ + ...layers.map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, dash, sort}) => ({ + id: `${id}_tunnel_fill`, + type: 'line', + minzoom, + maxzoom, + source, + 'source-layer': sourceLayer || 'railway_line_high', + filter: ['all', + ['in', ['get', 'state'], ['literal', states]], + ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ], + ['==', ['get', 'tunnel'], true], + filter ?? true, + ].filter(it => it !== true), + layout: { + 'visibility': ['case', + visibility ? ['==', visibility, false] : false, 'none', + ['<', ['global-state', 'date'], defaultDate], 'none', + 'visible', + ], + 'line-join': 'round', + 'line-cap': dash ? 'butt' : 'round', + 'line-sort-key': sort, + }, + paint: { + 'line-color': ['case', + ['boolean', ['feature-state', 'hover'], false], hoverColor || colors.hover.main, + color, + ], + 'line-width': width, + 'line-gap-width': gapWidth ?? undefined, + 'line-dasharray': dash ?? undefined, + }, + })), + ...layers.map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, states, sort}) => ({ id: `${id}_tunnel_cover`, type: 'line', minzoom: Math.max(minzoom, 8), @@ -720,12 +720,13 @@ const railwayLine = (text, layers) => [ source, 'source-layer': sourceLayer || 'railway_line_high', filter: ['all', - ['any', ...Object.keys(states).map(state => - state === 'construction' ? ['all', ['global-state', 'showConstructionInfrastructure'], ['==', ['get', 'state'], state]] - : state === 'proposed' ? ['all', ['global-state', 'showProposedInfrastructure'], ['==', ['get', 'state'], state]] - : state === 'abandoned' ? ['all', ['global-state', 'showAbandonedInfrastructure'], ['==', ['get', 'state'], state]] - : state === 'razed' ? ['all', ['global-state', 'showRazedInfrastructure'], ['==', ['get', 'state'], state]] - : ['==', ['get', 'state'], state]) + ['in', ['get', 'state'], ['literal', states]], + ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, ], ['==', ['get', 'tunnel'], true], ['>=', @@ -755,22 +756,19 @@ const railwayLine = (text, layers) => [ })), ...layers .filter(({gapWidth}) => !gapWidth) - .flatMap(({id, visibility, filter, color, states}) => + .map(({id, visibility, filter, color, states}) => preferredDirectionLayer(`${id}_tunnel_preferred_direction`, ['all', ['==', ['get', 'tunnel'], true], - ['any', ...Object.keys(states).map(state => - state === 'construction' ? ['all', ['global-state', 'showConstructionInfrastructure'], ['==', ['get', 'state'], state]] - : state === 'proposed' ? ['all', ['global-state', 'showProposedInfrastructure'], ['==', ['get', 'state'], state]] - : state === 'abandoned' ? ['all', ['global-state', 'showAbandonedInfrastructure'], ['==', ['get', 'state'], state]] - : state === 'razed' ? ['all', ['global-state', 'showRazedInfrastructure'], ['==', ['get', 'state'], state]] - : ['==', ['get', 'state'], state]) - ], - ['any', - ['==', ['get', 'preferred_direction'], 'forward'], - ['==', ['get', 'preferred_direction'], 'backward'], - ['==', ['get', 'preferred_direction'], 'both'], - ], + ['in', ['get', 'state'], ['literal', states]], + ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ], + ['in', ['get', 'preferred_direction'], ['literal', ['forward', 'backward', 'both']]], filter ?? true, ].filter(it => it !== true), color, @@ -782,53 +780,22 @@ const railwayLine = (text, layers) => [ ...layers .filter(({gapWidth}) => !gapWidth) - .flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, states, sort}) => - Object.entries(states).map(([state, dash]) => ({ - id: `${id}_casing_${state}`, - type: 'line', - minzoom, - maxzoom, - source, - 'source-layer': sourceLayer || 'railway_line_high', - filter: ['all', - ['==', ['get', 'state'], state], - ['!=', ['get', 'bridge'], true], - ['!=', ['get', 'tunnel'], true], - filter ?? true, - ].filter(it => it !== true), - layout: { - 'visibility': ['case', - visibility ? ['==', visibility, false] : false, 'none', - ['<', ['global-state', 'date'], defaultDate], 'none', - state === 'construction' ? ['global-state', 'showConstructionInfrastructure'] - : state === 'proposed' ? ['global-state', 'showProposedInfrastructure'] - : state === 'abandoned' ? ['global-state', 'showAbandonedInfrastructure'] - : state === 'razed' ? ['global-state', 'showAbandonedInfrastructure'] - : true, 'visible', - 'none', - ], - 'line-join': 'round', - 'line-cap': 'butt', - 'line-sort-key': sort, - }, - paint: { - 'line-color': colors.casing, - 'line-width': width, - 'line-gap-width': railway_casing_add, - 'line-dasharray': dash ?? undefined, - }, - })) - ), - ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, sort}) => [ - ...Object.entries(states).map(([state, dash]) => ({ - id: `${id}_fill_${state}`, + .map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, states, dash, sort}) => ({ + id: `${id}_casing`, type: 'line', minzoom, maxzoom, source, 'source-layer': sourceLayer || 'railway_line_high', filter: ['all', - ['==', ['get', 'state'], state], + ['in', ['get', 'state'], ['literal', states]], + ['match', ['get', 'state'], + 'construction', ['global-state', 'showConstructionInfrastructure'], + 'proposed', ['global-state', 'showProposedInfrastructure'], + 'abandoned', ['global-state', 'showAbandonedInfrastructure'], + 'razed', ['global-state', 'showRazedInfrastructure'], + true, + ], ['!=', ['get', 'bridge'], true], ['!=', ['get', 'tunnel'], true], filter ?? true, @@ -837,12 +804,44 @@ const railwayLine = (text, layers) => [ 'visibility': ['case', visibility ? ['==', visibility, false] : false, 'none', ['<', ['global-state', 'date'], defaultDate], 'none', - state === 'construction' ? ['global-state', 'showConstructionInfrastructure'] - : state === 'proposed' ? ['global-state', 'showProposedInfrastructure'] - : state === 'abandoned' ? ['global-state', 'showAbandonedInfrastructure'] - : state === 'razed' ? ['global-state', 'showRazedInfrastructure'] - : true, 'visible', - 'none', + 'visible', + ], + 'line-join': 'round', + 'line-cap': 'butt', + 'line-sort-key': sort, + }, + paint: { + 'line-color': colors.casing, + 'line-width': width, + 'line-gap-width': railway_casing_add, + 'line-dasharray': dash ?? undefined, + }, + })), + ...layers.map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, dash, sort}) => ({ + id: `${id}_fill`, + type: 'line', + minzoom, + maxzoom, + source, + 'source-layer': sourceLayer || 'railway_line_high', + filter: ['all', + ['in', ['get', 'state'], ['literal', states]], + ['match', ['get', 'state'], + 'construction', ['global-state', 'showConstructionInfrastructure'], + 'proposed', ['global-state', 'showProposedInfrastructure'], + 'abandoned', ['global-state', 'showAbandonedInfrastructure'], + 'razed', ['global-state', 'showRazedInfrastructure'], + true, + ], + ['!=', ['get', 'bridge'], true], + ['!=', ['get', 'tunnel'], true], + filter ?? true, + ].filter(it => it !== true), + layout: { + 'visibility': ['case', + visibility ? ['==', visibility, false] : false, 'none', + ['<', ['global-state', 'date'], defaultDate], 'none', + 'visible' ], 'line-join': 'round', 'line-cap': dash ? 'butt' : 'round', @@ -857,14 +856,13 @@ const railwayLine = (text, layers) => [ 'line-gap-width': gapWidth ?? undefined, 'line-dasharray': dash ?? undefined, }, - })), - ]), + }), // Bridges ...layers .filter(({gapWidth}) => !gapWidth) - .filter(({states}) => 'present' in states) + .filter(({states}) => states.includes('present')) .flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, sort}) => [ { id: `${id}_bridge_railing`, @@ -938,45 +936,45 @@ const railwayLine = (text, layers) => [ }, ]), - ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, sort}) => - Object.entries(states).map(([state, dash]) => ({ - id: `${id}_bridge_fill_${state}`, - type: 'line', - minzoom, - maxzoom, - source, - 'source-layer': sourceLayer || 'railway_line_high', - filter: ['all', - ['==', ['get', 'state'], state], - ['==', ['get', 'bridge'], true], - filter ?? true, - ].filter(it => it !== true), - layout: { - 'visibility': ['case', - visibility ? ['==', visibility, false] : false, 'none', - ['<', ['global-state', 'date'], defaultDate], 'none', - state === 'construction' ? ['global-state', 'showConstructionInfrastructure'] - : state === 'proposed' ? ['global-state', 'showProposedInfrastructure'] - : state === 'abandoned' ? ['global-state', 'showAbandonedInfrastructure'] - : state === 'razed' ? ['global-state', 'showRazedInfrastructure'] - : true, 'visible', - 'none', - ], - 'line-join': 'round', - 'line-cap': dash ? 'butt' : 'round', - 'line-sort-key': sort, - }, - paint: { - 'line-color': ['case', - ['boolean', ['feature-state', 'hover'], false], hoverColor || colors.hover.main, - color, - ], - 'line-width': width, - 'line-gap-width': gapWidth ?? undefined, - 'line-dasharray': dash ?? undefined, - }, - })), - ), + ...layers.map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, dash, sort}) => ({ + id: `${id}_bridge_fill_${state}`, + type: 'line', + minzoom, + maxzoom, + source, + 'source-layer': sourceLayer || 'railway_line_high', + filter: ['all', + ['in', ['get', 'state'], ['literal', states]], + ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ], + ['==', ['get', 'bridge'], true], + filter ?? true, + ].filter(it => it !== true), + layout: { + 'visibility': ['case', + visibility ? ['==', visibility, false] : false, 'none', + ['<', ['global-state', 'date'], defaultDate], 'none', + 'visible' + ], + 'line-join': 'round', + 'line-cap': dash ? 'butt' : 'round', + 'line-sort-key': sort, + }, + paint: { + 'line-color': ['case', + ['boolean', ['feature-state', 'hover'], false], hoverColor || colors.hover.main, + color, + ], + 'line-width': width, + 'line-gap-width': gapWidth ?? undefined, + 'line-dasharray': dash ?? undefined, + }, + })), // Preferred direction @@ -986,12 +984,13 @@ const railwayLine = (text, layers) => [ preferredDirectionLayer( `${id}_preferred_direction`, ['all', - ['any', ...Object.keys(states).map(state => - state === 'construction' ? ['all', ['global-state', 'showConstructionInfrastructure'], ['==', ['get', 'state'], state]] - : state === 'proposed' ? ['all', ['global-state', 'showProposedInfrastructure'], ['==', ['get', 'state'], state]] - : state === 'abandoned' ? ['all', ['global-state', 'showAbandonedInfrastructure'], ['==', ['get', 'state'], state]] - : state === 'razed' ? ['all', ['global-state', 'showRazedInfrastructure'], ['==', ['get', 'state'], state]] - : ['==', ['get', 'state'], state]) + ['in', ['get', 'state'], ['literal', states]], + ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, ], ['!=', ['get', 'tunnel'], true], ['any', @@ -1012,7 +1011,7 @@ const railwayLine = (text, layers) => [ ...layers .filter(({gapWidth}) => !gapWidth) - .flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, states}) => ({ + .map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, states}) => ({ id: `${id}_text`, type: 'symbol', minzoom, @@ -1020,12 +1019,13 @@ const railwayLine = (text, layers) => [ source, 'source-layer': sourceLayer || 'railway_line_high', filter: ['all', - ['any', ...Object.keys(states).map(state => - state === 'construction' ? ['all', ['global-state', 'showConstructionInfrastructure'], ['==', ['get', 'state'], state]] - : state === 'proposed' ? ['all', ['global-state', 'showProposedInfrastructure'], ['==', ['get', 'state'], state]] - : state === 'abandoned' ? ['all', ['global-state', 'showAbandonedInfrastructure'], ['==', ['get', 'state'], state]] - : state === 'razed' ? ['all', ['global-state', 'showRazedInfrastructure'], ['==', ['get', 'state'], state]] - : ['==', ['get', 'state'], state]) + ['in', ['get', 'state'], ['literal', states]], + ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, ], filter ?? true, ].filter(it => it !== true), From bec6f0b015dae0bd1165a33ce2f17187c518a5d7 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 14 Jun 2026 13:55:19 +0200 Subject: [PATCH 02/11] fix missing brace --- proxy/js/styles.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index f7ef49584..b10e7de37 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -856,7 +856,7 @@ const railwayLine = (text, layers) => [ 'line-gap-width': gapWidth ?? undefined, 'line-dasharray': dash ?? undefined, }, - }), + })), // Bridges @@ -937,7 +937,7 @@ const railwayLine = (text, layers) => [ ]), ...layers.map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, dash, sort}) => ({ - id: `${id}_bridge_fill_${state}`, + id: `${id}_bridge_fill`, type: 'line', minzoom, maxzoom, From 2cd319e0152a7b7e20977bfda82c5ca3b05f97f1 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 14 Jun 2026 14:00:40 +0200 Subject: [PATCH 03/11] start duplication --- proxy/js/styles.mjs | 49 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index b10e7de37..4afc76d41 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -2313,9 +2313,7 @@ const layers = { maxzoom: 7, source: 'standard_railway_line_low', sourceLayer: 'standard_railway_line_low', - states: { - present: undefined, - }, + states: ['present'], width: ["interpolate", ["exponential", 1.2], ["zoom"], 0, 0.5, 7, 2, @@ -2335,12 +2333,12 @@ const layers = { // ensure that width interpolation matches low zooms { - id: 'railway_line_main_med', + id: 'railway_line_main_med_construction_proposed', minzoom: 7, maxzoom: 8, source: 'openrailwaymap_low', - states: { - present: undefined, + states: ['construction', 'proposed'], + dash: { construction: construction_dasharray, proposed: proposed_dasharray, }, @@ -2359,12 +2357,32 @@ const layers = { ], }, { - id: 'railway_line_branch_med', + id: 'railway_line_main_med', minzoom: 7, maxzoom: 8, source: 'openrailwaymap_low', - states: { - present: undefined, + states: ['present'], + filter: ['all', + ['==', ['get', 'feature'], 'rail'], + ['==', ['get', 'usage'], 'main'], + ], + width: 2, + color: ['case', + ['get', 'highspeed'], colors.styles.standard.highspeed, + colors.styles.standard.main, + ], + hoverColor: ['case', + ['get', 'highspeed'], colors.hover.alternative, + colors.hover.main, + ], + }, + { + id: 'railway_line_branch_med_construction_proposed', + minzoom: 7, + maxzoom: 8, + source: 'openrailwaymap_low', + states: ['construction', 'proposed'], + dash: { construction: construction_dasharray, proposed: proposed_dasharray, }, @@ -2375,6 +2393,19 @@ const layers = { width: 2, color: colors.styles.standard.branch, }, + { + id: 'railway_line_branch_med', + minzoom: 7, + maxzoom: 8, + source: 'openrailwaymap_low', + states: ['present'], + filter: ['all', + ['==', ['get', 'feature'], 'rail'], + ['==', ['get', 'usage'], 'branch'], + ], + width: 2, + color: colors.styles.standard.branch, + }, { id: 'railway_ferry_med', minzoom: 7, From 1d247e918883d49502ffad730f966a6806c352f4 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 14 Jun 2026 14:24:32 +0200 Subject: [PATCH 04/11] dash states layer duplication --- proxy/js/styles.mjs | 281 +++++++++++++++++++++++++------------------- 1 file changed, 160 insertions(+), 121 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index 4afc76d41..c5c294b4f 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -631,12 +631,39 @@ const searchResults = { }, }; +const duplicateLayersForDashStates = ({ states, ...rest }) => { + const statesWithoutDash = Object.entries(states).filter(([_, dash]) => !dash).map(([state, _]) => state) + const statesWithDash = Object.entries(states).filter(([_, dash]) => dash).map(([state, _]) => state) + + return [ + ...(statesWithoutDash.length > 0 + ? [{ + ...rest, + states: statesWithoutDash, + dash: undefined, + }] + : [] + ), + ...(statesWithDash.length > 0 + ? [{ + states: statesWithDash, + dash: ['match', ['get', 'state'], + ...(statesWithDash.flatMap(state => [state, states[state]])), + undefined, + ], + }] + : [] + ), + ] +} + const railwayLine = (text, layers) => [ // Tunnels ...layers .filter(({gapWidth}) => !gapWidth) + .flatMap(duplicateLayersForDashStates) .map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, states, dash, sort}) => ({ id: `${id}_tunnel_casing`, type: 'line', @@ -673,89 +700,94 @@ const railwayLine = (text, layers) => [ 'line-dasharray': dash ?? undefined, }, })), - ...layers.map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, dash, sort}) => ({ - id: `${id}_tunnel_fill`, - type: 'line', - minzoom, - maxzoom, - source, - 'source-layer': sourceLayer || 'railway_line_high', - filter: ['all', - ['in', ['get', 'state'], ['literal', states]], - ['match', ['get', 'state'], - ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), - ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), - ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), - ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), - true, - ], - ['==', ['get', 'tunnel'], true], - filter ?? true, - ].filter(it => it !== true), - layout: { - 'visibility': ['case', - visibility ? ['==', visibility, false] : false, 'none', - ['<', ['global-state', 'date'], defaultDate], 'none', - 'visible', - ], - 'line-join': 'round', - 'line-cap': dash ? 'butt' : 'round', - 'line-sort-key': sort, - }, - paint: { - 'line-color': ['case', - ['boolean', ['feature-state', 'hover'], false], hoverColor || colors.hover.main, - color, - ], - 'line-width': width, - 'line-gap-width': gapWidth ?? undefined, - 'line-dasharray': dash ?? undefined, - }, - })), - ...layers.map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, states, sort}) => ({ - id: `${id}_tunnel_cover`, - type: 'line', - minzoom: Math.max(minzoom, 8), - maxzoom, - source, - 'source-layer': sourceLayer || 'railway_line_high', - filter: ['all', - ['in', ['get', 'state'], ['literal', states]], - ['match', ['get', 'state'], - ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), - ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), - ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), - ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), - true, - ], - ['==', ['get', 'tunnel'], true], - ['>=', - ['get', 'way_length'], - ['interpolate', ["exponential", .5], ['zoom'], - 8, 1500, - 16, 0 + ...layers + .flatMap(duplicateLayersForDashStates) + .map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, dash, sort}) => ({ + id: `${id}_tunnel_fill`, + type: 'line', + minzoom, + maxzoom, + source, + 'source-layer': sourceLayer || 'railway_line_high', + filter: ['all', + ['in', ['get', 'state'], ['literal', states]], + ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, ], - ], - filter ?? true, - ].filter(it => it !== true), - layout: { - 'visibility': ['case', - visibility ? ['==', visibility, false] : false, 'none', - ['<', ['global-state', 'date'], defaultDate], 'none', - 'visible', - ], - 'line-join': 'round', - 'line-cap': 'butt', - 'line-sort-key': sort, - }, - paint: { - 'line-color': colors.styles.standard.tunnelCover, - 'line-width': width, - 'line-gap-width': gapWidth ?? undefined, - }, - })), + ['==', ['get', 'tunnel'], true], + filter ?? true, + ].filter(it => it !== true), + layout: { + 'visibility': ['case', + visibility ? ['==', visibility, false] : false, 'none', + ['<', ['global-state', 'date'], defaultDate], 'none', + 'visible', + ], + 'line-join': 'round', + 'line-cap': dash ? 'butt' : 'round', + 'line-sort-key': sort, + }, + paint: { + 'line-color': ['case', + ['boolean', ['feature-state', 'hover'], false], hoverColor || colors.hover.main, + color, + ], + 'line-width': width, + 'line-gap-width': gapWidth ?? undefined, + 'line-dasharray': dash ?? undefined, + }, + })), + ...layers + .map(({states, ...rest}) => ({ ...rest, states: Object.keys(states) })) + .map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, states, sort}) => ({ + id: `${id}_tunnel_cover`, + type: 'line', + minzoom: Math.max(minzoom, 8), + maxzoom, + source, + 'source-layer': sourceLayer || 'railway_line_high', + filter: ['all', + ['in', ['get', 'state'], ['literal', states]], + ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ], + ['==', ['get', 'tunnel'], true], + ['>=', + ['get', 'way_length'], + ['interpolate', ["exponential", .5], ['zoom'], + 8, 1500, + 16, 0 + ], + ], + filter ?? true, + ].filter(it => it !== true), + layout: { + 'visibility': ['case', + visibility ? ['==', visibility, false] : false, 'none', + ['<', ['global-state', 'date'], defaultDate], 'none', + 'visible', + ], + 'line-join': 'round', + 'line-cap': 'butt', + 'line-sort-key': sort, + }, + paint: { + 'line-color': colors.styles.standard.tunnelCover, + 'line-width': width, + 'line-gap-width': gapWidth ?? undefined, + }, + })), ...layers .filter(({gapWidth}) => !gapWidth) + .map(({states, ...rest}) => ({ ...rest, states: Object.keys(states) })) .map(({id, visibility, filter, color, states}) => preferredDirectionLayer(`${id}_tunnel_preferred_direction`, ['all', @@ -780,6 +812,7 @@ const railwayLine = (text, layers) => [ ...layers .filter(({gapWidth}) => !gapWidth) + .flatMap(duplicateLayersForDashStates) .map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, states, dash, sort}) => ({ id: `${id}_casing`, type: 'line', @@ -817,7 +850,9 @@ const railwayLine = (text, layers) => [ 'line-dasharray': dash ?? undefined, }, })), - ...layers.map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, dash, sort}) => ({ + ...layers + .flatMap(duplicateLayersForDashStates) + .map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, dash, sort}) => ({ id: `${id}_fill`, type: 'line', minzoom, @@ -862,7 +897,7 @@ const railwayLine = (text, layers) => [ ...layers .filter(({gapWidth}) => !gapWidth) - .filter(({states}) => states.includes('present')) + .filter(({states}) => Object.keys(states).includes('present')) .flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, sort}) => [ { id: `${id}_bridge_railing`, @@ -936,50 +971,53 @@ const railwayLine = (text, layers) => [ }, ]), - ...layers.map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, dash, sort}) => ({ - id: `${id}_bridge_fill`, - type: 'line', - minzoom, - maxzoom, - source, - 'source-layer': sourceLayer || 'railway_line_high', - filter: ['all', - ['in', ['get', 'state'], ['literal', states]], - ['match', ['get', 'state'], - ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), - ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), - ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), - ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), - true, - ], - ['==', ['get', 'bridge'], true], - filter ?? true, - ].filter(it => it !== true), - layout: { - 'visibility': ['case', - visibility ? ['==', visibility, false] : false, 'none', - ['<', ['global-state', 'date'], defaultDate], 'none', - 'visible' - ], - 'line-join': 'round', - 'line-cap': dash ? 'butt' : 'round', - 'line-sort-key': sort, - }, - paint: { - 'line-color': ['case', - ['boolean', ['feature-state', 'hover'], false], hoverColor || colors.hover.main, - color, - ], - 'line-width': width, - 'line-gap-width': gapWidth ?? undefined, - 'line-dasharray': dash ?? undefined, - }, - })), + ...layers + .flatMap(duplicateLayersForDashStates) + .map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, color, hoverColor, states, dash, sort}) => ({ + id: `${id}_bridge_fill`, + type: 'line', + minzoom, + maxzoom, + source, + 'source-layer': sourceLayer || 'railway_line_high', + filter: ['all', + ['in', ['get', 'state'], ['literal', states]], + ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ], + ['==', ['get', 'bridge'], true], + filter ?? true, + ].filter(it => it !== true), + layout: { + 'visibility': ['case', + visibility ? ['==', visibility, false] : false, 'none', + ['<', ['global-state', 'date'], defaultDate], 'none', + 'visible' + ], + 'line-join': 'round', + 'line-cap': dash ? 'butt' : 'round', + 'line-sort-key': sort, + }, + paint: { + 'line-color': ['case', + ['boolean', ['feature-state', 'hover'], false], hoverColor || colors.hover.main, + color, + ], + 'line-width': width, + 'line-gap-width': gapWidth ?? undefined, + 'line-dasharray': dash ?? undefined, + }, + })), // Preferred direction ...layers .filter(({gapWidth}) => !gapWidth) + .map(({states, ...rest}) => ({ ...rest, states: Object.keys(states) })) .flatMap(({id, visibility, filter, color, states}) => preferredDirectionLayer( `${id}_preferred_direction`, @@ -1011,6 +1049,7 @@ const railwayLine = (text, layers) => [ ...layers .filter(({gapWidth}) => !gapWidth) + .map(({states, ...rest}) => ({ ...rest, states: Object.keys(states) })) .map(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, states}) => ({ id: `${id}_text`, type: 'symbol', From 27c7220e8d985807498eabe86d575e79cad9f021 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 14 Jun 2026 14:25:45 +0200 Subject: [PATCH 05/11] revert state/dash changes in layer config --- proxy/js/styles.mjs | 49 +++++++++------------------------------------ 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index c5c294b4f..162ad3085 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -2352,7 +2352,9 @@ const layers = { maxzoom: 7, source: 'standard_railway_line_low', sourceLayer: 'standard_railway_line_low', - states: ['present'], + states: { + present: undefined, + }, width: ["interpolate", ["exponential", 1.2], ["zoom"], 0, 0.5, 7, 2, @@ -2372,12 +2374,12 @@ const layers = { // ensure that width interpolation matches low zooms { - id: 'railway_line_main_med_construction_proposed', + id: 'railway_line_main_med', minzoom: 7, maxzoom: 8, source: 'openrailwaymap_low', - states: ['construction', 'proposed'], - dash: { + states: { + present: undefined, construction: construction_dasharray, proposed: proposed_dasharray, }, @@ -2396,32 +2398,12 @@ const layers = { ], }, { - id: 'railway_line_main_med', - minzoom: 7, - maxzoom: 8, - source: 'openrailwaymap_low', - states: ['present'], - filter: ['all', - ['==', ['get', 'feature'], 'rail'], - ['==', ['get', 'usage'], 'main'], - ], - width: 2, - color: ['case', - ['get', 'highspeed'], colors.styles.standard.highspeed, - colors.styles.standard.main, - ], - hoverColor: ['case', - ['get', 'highspeed'], colors.hover.alternative, - colors.hover.main, - ], - }, - { - id: 'railway_line_branch_med_construction_proposed', + id: 'railway_line_branch_med', minzoom: 7, maxzoom: 8, source: 'openrailwaymap_low', - states: ['construction', 'proposed'], - dash: { + states: { + present: undefined, construction: construction_dasharray, proposed: proposed_dasharray, }, @@ -2432,19 +2414,6 @@ const layers = { width: 2, color: colors.styles.standard.branch, }, - { - id: 'railway_line_branch_med', - minzoom: 7, - maxzoom: 8, - source: 'openrailwaymap_low', - states: ['present'], - filter: ['all', - ['==', ['get', 'feature'], 'rail'], - ['==', ['get', 'usage'], 'branch'], - ], - width: 2, - color: colors.styles.standard.branch, - }, { id: 'railway_ferry_med', minzoom: 7, From b85628a8dc6a418ef65446774ba97675969de19c Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 14 Jun 2026 15:03:07 +0200 Subject: [PATCH 06/11] simplify layer config for non complex state matching --- proxy/js/styles.mjs | 112 +++++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index 162ad3085..2cd856710 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -673,13 +673,15 @@ const railwayLine = (text, layers) => [ 'source-layer': sourceLayer || 'railway_line_high', filter: ['all', ['in', ['get', 'state'], ['literal', states]], - ['match', ['get', 'state'], - ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), - ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), - ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), - ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), - true, - ], + states.includes('construction') || states.includes('proposed') || states.includes('abandoned') || states.includes('razed') + ? ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ] + : true, ['==', ['get', 'tunnel'], true], filter ?? true, ].filter(it => it !== true), @@ -711,13 +713,15 @@ const railwayLine = (text, layers) => [ 'source-layer': sourceLayer || 'railway_line_high', filter: ['all', ['in', ['get', 'state'], ['literal', states]], - ['match', ['get', 'state'], - ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), - ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), - ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), - ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), - true, - ], + states.includes('construction') || states.includes('proposed') || states.includes('abandoned') || states.includes('razed') + ? ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ] + : true, ['==', ['get', 'tunnel'], true], filter ?? true, ].filter(it => it !== true), @@ -752,13 +756,15 @@ const railwayLine = (text, layers) => [ 'source-layer': sourceLayer || 'railway_line_high', filter: ['all', ['in', ['get', 'state'], ['literal', states]], - ['match', ['get', 'state'], - ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), - ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), - ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), - ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), - true, - ], + states.includes('construction') || states.includes('proposed') || states.includes('abandoned') || states.includes('razed') + ? ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ] + : true, ['==', ['get', 'tunnel'], true], ['>=', ['get', 'way_length'], @@ -793,13 +799,15 @@ const railwayLine = (text, layers) => [ ['all', ['==', ['get', 'tunnel'], true], ['in', ['get', 'state'], ['literal', states]], - ['match', ['get', 'state'], - ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), - ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), - ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), - ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), - true, - ], + states.includes('construction') || states.includes('proposed') || states.includes('abandoned') || states.includes('razed') + ? ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ] + : true, ['in', ['get', 'preferred_direction'], ['literal', ['forward', 'backward', 'both']]], filter ?? true, ].filter(it => it !== true), @@ -982,13 +990,15 @@ const railwayLine = (text, layers) => [ 'source-layer': sourceLayer || 'railway_line_high', filter: ['all', ['in', ['get', 'state'], ['literal', states]], - ['match', ['get', 'state'], - ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), - ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), - ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), - ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), - true, - ], + states.includes('construction') || states.includes('proposed') || states.includes('abandoned') || states.includes('razed') + ? ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ] + : true, ['==', ['get', 'bridge'], true], filter ?? true, ].filter(it => it !== true), @@ -1023,13 +1033,15 @@ const railwayLine = (text, layers) => [ `${id}_preferred_direction`, ['all', ['in', ['get', 'state'], ['literal', states]], - ['match', ['get', 'state'], - ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), - ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), - ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), - ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), - true, - ], + states.includes('construction') || states.includes('proposed') || states.includes('abandoned') || states.includes('razed') + ? ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ] + : true, ['!=', ['get', 'tunnel'], true], ['any', ['==', ['get', 'preferred_direction'], 'forward'], @@ -1059,13 +1071,15 @@ const railwayLine = (text, layers) => [ 'source-layer': sourceLayer || 'railway_line_high', filter: ['all', ['in', ['get', 'state'], ['literal', states]], - ['match', ['get', 'state'], - ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), - ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), - ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), - ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), - true, - ], + states.includes('construction') || states.includes('proposed') || states.includes('abandoned') || states.includes('razed') + ? ['match', ['get', 'state'], + ...(states.includes('construction') ? ['construction', ['global-state', 'showConstructionInfrastructure']] : []), + ...(states.includes('proposed') ? ['proposed', ['global-state', 'showProposedInfrastructure']] : []), + ...(states.includes('abandoned') ? ['abandoned', ['global-state', 'showAbandonedInfrastructure']] : []), + ...(states.includes('razed') ? ['razed', ['global-state', 'showRazedInfrastructure']] : []), + true, + ] + : true, filter ?? true, ].filter(it => it !== true), paint: { From 16e6b65812e53b06fdccaafdaa6d8fa26ff87ae4 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 14 Jun 2026 15:03:48 +0200 Subject: [PATCH 07/11] fix duplicated dash layer generated style --- proxy/js/styles.mjs | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index 2cd856710..c2d33b80e 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -631,28 +631,31 @@ const searchResults = { }, }; -const duplicateLayersForDashStates = ({ states, ...rest }) => { +const duplicateLayersForDashStates = ({ id, states, ...rest }) => { const statesWithoutDash = Object.entries(states).filter(([_, dash]) => !dash).map(([state, _]) => state) const statesWithDash = Object.entries(states).filter(([_, dash]) => dash).map(([state, _]) => state) return [ ...(statesWithoutDash.length > 0 - ? [{ - ...rest, - states: statesWithoutDash, - dash: undefined, - }] - : [] + ? [{ + ...rest, + id, + states: statesWithoutDash, + dash: undefined, + }] + : [] ), ...(statesWithDash.length > 0 - ? [{ - states: statesWithDash, - dash: ['match', ['get', 'state'], - ...(statesWithDash.flatMap(state => [state, states[state]])), - undefined, - ], - }] - : [] + ? [{ + ...rest, + id: `${id}_dash`, + states: statesWithDash, + dash: ['match', ['get', 'state'], + ...(statesWithDash.flatMap(state => [state, ['literal', states[state]]])), + ['literal', [100, 0]], + ], + }] + : [] ), ] } From 4f37602297cb141375180ebe613d629a3a56ff84 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 14 Jun 2026 15:04:00 +0200 Subject: [PATCH 08/11] simplify train protection matching with `match` --- proxy/js/styles.mjs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index c2d33b80e..d1228c689 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -170,9 +170,11 @@ const turntable_casing_width = 2; const trainProtectionColor = field => ['case', ['boolean', ['feature-state', 'hover'], false], colors.hover.main, - ...signals_railway_line.train_protections.flatMap(train_protection => - [['==', ['get', field], train_protection.train_protection], train_protection.color]), - 'grey', + ['match', ['get', field], + ...signals_railway_line.train_protections.flatMap(train_protection => + [train_protection.train_protection, train_protection.color]), + 'grey', + ], ]; const railway_casing_add = 1; From 087e6ef13e5e640546315fa31732147cc1def231 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 14 Jun 2026 15:04:22 +0200 Subject: [PATCH 09/11] duplicate dual/multi train protection lines to match dashes --- proxy/js/styles.mjs | 97 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 82 insertions(+), 15 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index d1228c689..bb285d1a4 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -3923,7 +3923,23 @@ const layers = { color: trainProtectionColor('train_protection_construction'), }, { - id: 'railway_line_high_multi_train_protection', + id: 'railway_line_high_disused_preserved', + minzoom: 8, + source: 'high', + states: { + disused: disused_dasharray, + preserved: disused_dasharray, + }, + filter: ['!=', ['get', 'feature'], 'ferry'], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], + width: ["interpolate", ["exponential", 1.2], ["zoom"], + 14, 2, + 16, 3, + ], + color: trainProtectionColor('train_protection0'), + }, + { + id: 'railway_line_high_multi_train_protection_2', minzoom: 8, source: 'high', states: { @@ -3941,18 +3957,52 @@ const layers = { color: trainProtectionColor('train_protection2'), }, { - id: 'railway_line_high_dual_train_protection', + id: 'railway_line_high_multi_train_protection_1', minzoom: 8, source: 'high', states: { - present: ['case', - ['!=', ['get', 'train_protection2'], null], ['literal', [3, 3]], - ['literal', [100, 0]], - ], + present: [3, 3], + }, + filter: ['all', + ['!=', ['get', 'feature'], 'ferry'], + ['!=', ['get', 'train_protection2'], null], + ], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], + width: ["interpolate", ["exponential", 1.2], ["zoom"], + 14, 2, + 16, 3, + ], + color: trainProtectionColor('train_protection1'), + }, + { + id: 'railway_line_high_multi_train_protection_0', + minzoom: 8, + source: 'high', + states: { + present: [0, 2, 2, 2], + }, + filter: ['all', + ['!=', ['get', 'feature'], 'ferry'], + ['!=', ['get', 'train_protection2'], null], + ], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], + width: ["interpolate", ["exponential", 1.2], ["zoom"], + 14, 2, + 16, 3, + ], + color: trainProtectionColor('train_protection0'), + }, + { + id: 'railway_line_high_dual_train_protection_1', + minzoom: 8, + source: 'high', + states: { + present: undefined, }, filter: ['all', ['!=', ['get', 'feature'], 'ferry'], ['!=', ['get', 'train_protection1'], null], + ['==', ['get', 'train_protection2'], null], ], sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], @@ -3962,19 +4012,36 @@ const layers = { color: trainProtectionColor('train_protection1'), }, { - id: 'railway_line_high', + id: 'railway_line_high_dual_train_protection_0', minzoom: 8, source: 'high', states: { - present: ['case', - ['!=', ['get', 'train_protection2'], null], ['literal', [0, 2, 2, 2]], - ['!=', ['get', 'train_protection1'], null], ['literal', [3, 3]], - ['literal', [100, 0]], - ], - disused: disused_dasharray, - preserved: disused_dasharray, + present: [3, 3], }, - filter: ['!=', ['get', 'feature'], 'ferry'], + filter: ['all', + ['!=', ['get', 'feature'], 'ferry'], + ['!=', ['get', 'train_protection1'], null], + ['==', ['get', 'train_protection2'], null], + ], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], + width: ["interpolate", ["exponential", 1.2], ["zoom"], + 14, 2, + 16, 3, + ], + color: trainProtectionColor('train_protection0'), + }, + { + id: 'railway_line_high_single_train_protection', + minzoom: 8, + source: 'high', + states: { + present: undefined, + }, + filter: ['all', + ['!=', ['get', 'feature'], 'ferry'], + ['==', ['get', 'train_protection1'], null], + ['==', ['get', 'train_protection2'], null], + ], sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], 14, 2, From 842a8e80e4795748fd3d9b5289ae911992222918 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 14 Jun 2026 15:38:11 +0200 Subject: [PATCH 10/11] deduplicate historical lines --- proxy/js/styles.mjs | 77 ++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index a25378d53..7e07551d3 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -186,7 +186,7 @@ const disused_dasharray = [2.5, 2.5]; const razed_dasharray = [1, 5]; const construction_dasharray = [4.5, 4.5]; const proposed_dasharray = [1, 4]; -const present_dasharray = [1]; +const present_dasharray = [100, 0]; const train_protection_construction_dasharray = [0, 2, 2, 4]; @@ -654,7 +654,7 @@ const duplicateLayersForDashStates = ({ id, states, ...rest }) => { states: statesWithDash, dash: ['match', ['get', 'state'], ...(statesWithDash.flatMap(state => [state, ['literal', states[state]]])), - ['literal', [100, 0]], + ['literal', present_dasharray], ], }] : [] @@ -2030,70 +2030,61 @@ const layers = { ], [ { - id: 'railway_line_historical_miniature', + id: 'railway_line_historical_miniature_funicular', minzoom: 12, filter: ['all', ['==', ['get', 'class'], 'railway'], - ['==', ['get', 'type'], 'miniature'], + ['in', ['get', 'type'], ['literal', ['miniature', 'funicular']]] ], - color: colors.styles.standard.miniature, - width: 2, - }, - { - id: 'railway_line_historical_funicular', - minzoom: 12, - filter: ['all', - ['==', ['get', 'class'], 'railway'], - ['==', ['get', 'type'], 'funicular'], + color: ['match', ['get', 'type'], + 'miniature', colors.styles.standard.miniature, + 'funicular', colors.styles.standard.funicular, + 'gray', ], - color: colors.styles.standard.funicular, width: 2, }, { - id: 'railway_line_historical_disused', + id: 'railway_line_historical_disused_abandoned', minzoom: 11, filter: ['all', ['==', ['get', 'class'], 'railway'], - ['==', ['get', 'type'], 'disused'], + ['in', ['get', 'type'], ['literal', ['disused', 'abandoned']]] ], - color: colors.styles.standard.disused, - dash: disused_dasharray, - width: 1.5, - }, - { - id: 'railway_line_historical_abandoned', - minzoom: 11, - filter: ['all', - ['==', ['get', 'class'], 'railway'], - ['==', ['get', 'type'], 'abandoned'], + color: ['match', ['get', 'type'], + 'disused', colors.styles.standard.disused, + 'abandoned', colors.styles.standard.abandoned, + 'gray', ], - color: colors.styles.standard.abandoned, - dash: abandoned_dasharray, - width: 1.5, - }, - { - id: 'railway_line_historical_construction', - minzoom: 10, - filter: ['all', - ['==', ['get', 'class'], 'railway'], - ['==', ['get', 'type'], 'construction'], + dash: ['match', ['get', 'type'], + 'disused', ['literal', disused_dasharray], + 'abandoned', ['literal', abandoned_dasharray], + ['literal', present_dasharray], ], - color: colors.styles.standard.main, - dash: construction_dasharray, width: 1.5, - visibility: ['global-state', 'showConstructionInfrastructure'], }, { - id: 'railway_line_historical_proposed', + id: 'railway_line_historical_construction_proposed', minzoom: 10, filter: ['all', ['==', ['get', 'class'], 'railway'], - ['==', ['get', 'type'], 'proposed'], + ['in', ['get', 'type'], ['literal', ['construction', 'proposed']]], + ['match', ['get', 'type'], + 'construction', ['global-state', 'showConstructionInfrastructure'], + 'proposed', ['global-state', 'showProposedInfrastructure'], + true, + ], ], color: colors.styles.standard.main, - dash: proposed_dasharray, + dash: ['match', ['get', 'type'], + 'construction', ['literal', construction_dasharray], + 'proposed', ['literal', proposed_dasharray], + ['literal', present_dasharray], + ], width: 1.5, - visibility: ['global-state', 'showProposedInfrastructure'], + visibility: ['any', + ['global-state', 'showConstructionInfrastructure'], + ['global-state', 'showProposedInfrastructure'], + ], }, { id: 'railway_line_historical_narrow_gauge', From c1f906bbc8125051166a3d47bec5c7e7c4a71b7a Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 14 Jun 2026 15:54:32 +0200 Subject: [PATCH 11/11] deduplicate grouped station dasharray outline --- proxy/js/styles.mjs | 119 +++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 63 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index 7e07551d3..aac5e6cef 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -1970,22 +1970,21 @@ const layers = { ], }, }, - ...Object.entries({ - present: present_dasharray, - disused: disused_dasharray, - abandoned: abandoned_dasharray, - preserved: disused_dasharray, - construction: construction_dasharray, - proposed: proposed_dasharray, - }).map(([state, dasharray]) => ({ - id: `railway_grouped_stations_outline_${state}`, + { + id: `railway_grouped_stations_outline`, type: 'line', minzoom: 13, source: 'openrailwaymap_standard', 'source-layer': 'standard_railway_grouped_stations', filter: ['all', - ['==', ['get', 'state'], state], ['!', ['in', ['get', 'feature'], ['literal', ['site', 'junction', 'spur_junction']]]], // Sites and junctions show an icon + ['match', ['get', 'state'], + 'construction', ['global-state', 'showConstructionInfrastructure'], + 'proposed', ['global-state', 'showProposedInfrastructure'], + 'abandoned', ['global-state', 'showAbandonedInfrastructure'], + 'razed', ['global-state', 'showRazedInfrastructure'], + true, + ], ], paint: { 'line-color': ['case', @@ -2008,20 +2007,22 @@ const layers = { 'yard', 6, 2, ], - 'line-dasharray': dasharray, + 'line-dasharray': ['match', ['get', 'state'], + 'disused', ['literal', disused_dasharray], + 'abandoned', ['literal', abandoned_dasharray], + 'preserved', ['literal', disused_dasharray], + 'construction', ['literal', construction_dasharray], + 'proposed', ['literal', proposed_dasharray], + ['literal', present_dasharray], + ], }, layout: { 'visibility': ['case', ['<', ['global-state', 'date'], defaultDate], 'none', - state === 'construction' ? ['global-state', 'showConstructionInfrastructure'] - : state === 'proposed' ? ['global-state', 'showProposedInfrastructure'] - : state === 'abandoned' ? ['global-state', 'showAbandonedInfrastructure'] - : state === 'razed' ? ['global-state', 'showRazedInfrastructure'] - : true, 'visible', - 'none', + 'visible', ], - } - })), + }, + }, ...historicalRailwayLine( ['step', ['zoom'], ['coalesce', ['get', 'ref'], ''], @@ -5148,23 +5149,22 @@ const layers = { ], }, }, - ...Object.entries({ - present: present_dasharray, - disused: disused_dasharray, - abandoned: abandoned_dasharray, - preserved: disused_dasharray, - construction: construction_dasharray, - proposed: proposed_dasharray, - }).map(([state, dasharray]) => ({ - id: `railway_grouped_stations_outline_${state}`, + { + id: `railway_grouped_stations_outline`, type: 'line', minzoom: 13, source: 'openrailwaymap_standard', 'source-layer': 'standard_railway_grouped_stations', filter: ['all', ['!=', ['get', 'operator_color'], null], - ['==', ['get', 'state'], state], ['!', ['in', ['get', 'feature'], ['literal', ['site', 'junction', 'spur_junction']]]], // Sites and junctions show an icon + ['match', ['get', 'state'], + 'construction', ['global-state', 'showConstructionInfrastructure'], + 'proposed', ['global-state', 'showProposedInfrastructure'], + 'abandoned', ['global-state', 'showAbandonedInfrastructure'], + 'razed', ['global-state', 'showRazedInfrastructure'], + true, + ], ], paint: { 'line-color': ['case', @@ -5179,19 +5179,16 @@ const layers = { 'yard', 6, 2, ], - 'line-dasharray': dasharray, - }, - layout: { - 'visibility': ['case', - state === 'construction' ? ['global-state', 'showConstructionInfrastructure'] - : state === 'proposed' ? ['global-state', 'showProposedInfrastructure'] - : state === 'abandoned' ? ['global-state', 'showAbandonedInfrastructure'] - : state === 'razed' ? ['global-state', 'showRazedInfrastructure'] - : true, 'visible', - 'none', + 'line-dasharray': ['match', ['get', 'state'], + 'disused', ['literal', disused_dasharray], + 'abandoned', ['literal', abandoned_dasharray], + 'preserved', ['literal', disused_dasharray], + 'construction', ['literal', construction_dasharray], + 'proposed', ['literal', proposed_dasharray], + ['literal', present_dasharray], ], - } - })), + }, + }, ...railwayLine( ['coalesce', ['get', 'primary_operator'], ''], [ @@ -5814,22 +5811,21 @@ const layers = { ], }, }, - ...Object.entries({ - present: present_dasharray, - disused: disused_dasharray, - abandoned: abandoned_dasharray, - preserved: disused_dasharray, - construction: construction_dasharray, - proposed: proposed_dasharray, - }).map(([state, dasharray]) => ({ - id: `railway_grouped_stations_outline_${state}`, + { + id: `railway_grouped_stations_outline`, type: 'line', minzoom: 13, source: 'openrailwaymap_standard', 'source-layer': 'standard_railway_grouped_stations', filter: ['all', - ['==', ['get', 'state'], state], ['!', ['in', ['get', 'feature'], ['literal', ['site', 'junction', 'spur_junction']]]], // Sites and junctions show an icon + ['match', ['get', 'state'], + 'construction', ['global-state', 'showConstructionInfrastructure'], + 'proposed', ['global-state', 'showProposedInfrastructure'], + 'abandoned', ['global-state', 'showAbandonedInfrastructure'], + 'razed', ['global-state', 'showRazedInfrastructure'], + true, + ], ], paint: { 'line-color': ['case', @@ -5844,19 +5840,16 @@ const layers = { 'yard', 6, 2, ], - 'line-dasharray': dasharray, - }, - layout: { - 'visibility': ['case', - state === 'construction' ? ['global-state', 'showConstructionInfrastructure'] - : state === 'proposed' ? ['global-state', 'showProposedInfrastructure'] - : state === 'abandoned' ? ['global-state', 'showAbandonedInfrastructure'] - : state === 'razed' ? ['global-state', 'showRazedInfrastructure'] - : true, 'visible', - 'none', + 'line-dasharray': ['match', ['get', 'state'], + 'disused', ['literal', disused_dasharray], + 'abandoned', ['literal', abandoned_dasharray], + 'preserved', ['literal', disused_dasharray], + 'construction', ['literal', construction_dasharray], + 'proposed', ['literal', proposed_dasharray], + ['literal', present_dasharray], ], - } - })), + }, + }, ...railwayLine( ['concat', ['coalesce', ['get', 'ref'],''],