From dec7a52183bfe6a87eebec14280e31c207265e12 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Fri, 29 May 2026 19:02:41 +0200 Subject: [PATCH 01/27] Import multiple train protection systems --- import/openrailwaymap.lua | 8 ++++---- import/tags.lua.js | 14 +++++++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/import/openrailwaymap.lua b/import/openrailwaymap.lua index 4aa793ad3..c6747c130 100644 --- a/import/openrailwaymap.lua +++ b/import/openrailwaymap.lua @@ -205,9 +205,9 @@ local railway_line = osm2pgsql.define_table({ { column = 'loading_gauge', type = 'text' }, { column = 'track_class', type = 'text' }, { column = 'reporting_marks', sql_type = 'text[]' }, - { column = 'train_protection', type = 'text' }, + { column = 'train_protection', sql_type = 'text[]' }, { column = 'train_protection_rank', type = 'smallint' }, - { column = 'train_protection_construction', type = 'text' }, + { column = 'train_protection_construction', sql_type = 'text[]' }, { column = 'train_protection_construction_rank', type = 'smallint' }, { column = 'operator', sql_type = 'text[]' }, { column = 'owner', sql_type = 'text' }, @@ -1387,9 +1387,9 @@ function osm2pgsql.process_way(object) loading_gauge = tags['loading_gauge'], track_class = tags['railway:track_class'], reporting_marks = split_semicolon_to_sql_array(tags['reporting_marks']), - train_protection = railway_train_protection, + train_protection = to_sql_array(railway_train_protection), train_protection_rank = railway_train_protection_rank, - train_protection_construction = train_protection_construction, + train_protection_construction = to_sql_array(train_protection_construction), train_protection_construction_rank = train_protection_construction_rank, operator = split_semicolon_to_sql_array(tags['operator']), owner = tags.owner, diff --git a/import/tags.lua.js b/import/tags.lua.js index d700773dc..504cc29bd 100644 --- a/import/tags.lua.js +++ b/import/tags.lua.js @@ -10,10 +10,18 @@ const station_references = yaml.parse(fs.readFileSync('stations.yaml', 'utf8')). * Template that builds Lua functions used in the Osm2Psql Lua import, and taking the YAML configuration into account */ const lua = ` -function train_protection(tags, prefix)${signals_railway_line.features.map((feature, featureIndex) => ` - if ${feature.tags.map(tag => `${tag.value ? `tags[prefix .. '${tag.tag}'] == '${tag.value}'`: `(${tag.values.map(value => `tags[prefix .. '${tag.tag}'] == '${value}'`).join(' or ')})`}`).join(' and ')} then return '${feature.train_protection}', ${signals_railway_line.features.length - featureIndex} end`).join('')} +function train_protection(tags, prefix) + local systems = {} + local rank = 0 + local has_systems = false + ${signals_railway_line.features.map((feature, featureIndex) => ` + if ${feature.tags.map(tag => `${tag.value ? `tags[prefix .. '${tag.tag}'] == '${tag.value}'`: `(${tag.values.map(value => `tags[prefix .. '${tag.tag}'] == '${value}'`).join(' or ')})`}`).join(' and ')} then table.insert(systems, '${feature.train_protection}'); rank = math.max(rank, ${signals_railway_line.features.length - featureIndex}); has_systems = true end`).join('')} - return nil, 0 + if has_systems then + return systems, rank + else + return nil, nil + end end local signal_tags = {${signals_railway_signals.tags.map(tag => ` From f587f664569492c1489151e60cce7566be945ef2 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Fri, 29 May 2026 19:41:33 +0200 Subject: [PATCH 02/27] Test for railway line import --- import/test/test_import_railway_line.lua | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/import/test/test_import_railway_line.lua b/import/test/test_import_railway_line.lua index 6c10011f8..75a1bcd14 100644 --- a/import/test/test_import_railway_line.lua +++ b/import/test/test_import_railway_line.lua @@ -47,6 +47,23 @@ osm2pgsql.process_way({ }) assert.eq(osm2pgsql.get_and_clear_imported_data(), { railway_line = { - { id = '123-0', tunnel = false, bridge = false, highspeed = false, rank = 40, train_protection_rank = 0, way_length = 1, way = way, feature = 'rail', state = 'present', train_protection_construction_rank = 0, radio = 'lte-r' }, + { id = '123-0', tunnel = false, bridge = false, highspeed = false, rank = 40, way_length = 1, way = way, feature = 'rail', state = 'present', radio = 'lte-r' }, + }, +}) + +osm2pgsql.process_way({ + id = 123, + type = 'way', + tags = { + ['railway'] = 'rail', + ['railway:aws'] = 'yes', + ['railway:tpws'] = 'yes', + ['construction:railway:etcs'] = '3', + }, + as_linestring = as_linestring_mock, +}) +assert.eq(osm2pgsql.get_and_clear_imported_data(), { + railway_line = { + { id = '123-0', tunnel = false, bridge = false, highspeed = false, rank = 40, way_length = 1, way = way, feature = 'rail', state = 'present', train_protection = '{"aws","tpws"}', train_protection_rank = 44, train_protection_construction = '{"etcs_2"}', train_protection_construction_rank = 77 }, }, }) From cc5ce8501456700f6123787ee9d0cdbd922bdd4a Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Fri, 29 May 2026 19:56:22 +0200 Subject: [PATCH 03/27] tiles --- import/sql/tile_views.sql | 38 ++++++++++++++++++++++----------- proxy/js/legend.mjs | 44 +++++++++++++++++++-------------------- proxy/js/styles.mjs | 18 ++++++++-------- 3 files changed, 57 insertions(+), 43 deletions(-) diff --git a/import/sql/tile_views.sql b/import/sql/tile_views.sql index 8d44d7d00..8486fcb31 100644 --- a/import/sql/tile_views.sql +++ b/import/sql/tile_views.sql @@ -155,9 +155,13 @@ RETURN ( maxspeed, speed_label, train_protection_rank, - train_protection, + train_protection[1] as train_protection0, + train_protection[2] as train_protection1, + train_protection[3] as train_protection2, train_protection_construction_rank, - train_protection_construction, + train_protection_construction[1] as train_protection_construction0, + train_protection_construction[2] as train_protection_construction1, + train_protection_construction[3] as train_protection_construction2, electrification_state, voltage, frequency, @@ -250,9 +254,13 @@ DO $do$ BEGIN "track_ref": "string", "maxspeed": "number", "speed_label": "string", - "train_protection": "string", + "train_protection0": "string", + "train_protection1": "string", + "train_protection2": "string", "train_protection_rank": "integer", - "train_protection_construction": "string", + "train_protection_construction0": "string", + "train_protection_construction1": "string", + "train_protection_construction2": "string", "train_protection_construction_rank": "integer", "electrification_state": "string", "frequency": "number", @@ -1250,10 +1258,14 @@ RETURN ( feature, any_value(state) as state, any_value(usage) as usage, - train_protection_rank, - train_protection, - train_protection_construction_rank, - train_protection_construction, + max(train_protection_rank) as train_protection_rank, + train_protection[1] as train_protection0, + train_protection[2] as train_protection1, + train_protection[3] as train_protection2, + max(train_protection_construction_rank) as train_protection_construction_rank, + train_protection_construction[1] as train_protection_construction0, + train_protection_construction[2] as train_protection_construction1, + train_protection_construction[3] as train_protection_construction2, max(rank) as rank FROM railway_line_low WHERE way && ST_TileEnvelope(z, x, y) @@ -1261,9 +1273,7 @@ RETURN ( feature, ref, name, - train_protection_rank, train_protection, - train_protection_construction_rank, train_protection_construction ORDER by rank NULLS LAST @@ -1282,9 +1292,13 @@ DO $do$ BEGIN "feature": "string", "state": "string", "usage": "string", - "train_protection": "string", + "train_protection0": "string", + "train_protection1": "string", + "train_protection2": "string", "train_protection_rank": "integer", - "train_protection_construction": "string", + "train_protection_construction0": "string", + "train_protection_construction1": "string", + "train_protection_construction2": "string", "train_protection_construction_rank": "integer" } } diff --git a/proxy/js/legend.mjs b/proxy/js/legend.mjs index fafc0f108..8ea01bc84 100644 --- a/proxy/js/legend.mjs +++ b/proxy/js/legend.mjs @@ -1757,17 +1757,17 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: train_protection.train_protection, + train_protection0: train_protection.train_protection, train_protection_rank: 1, - train_protection_construction: null, + train_protection_construction0: null, train_protection_construction_rank: 0, }, variants: [ { properties: { - train_protection: null, + train_protection0: null, train_protection_rank: 0, - train_protection_construction: train_protection.train_protection, + train_protection_construction0: train_protection.train_protection, train_protection_construction_rank: 1, } } @@ -1783,9 +1783,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: null, + train_protection0: null, train_protection_rank: 0, - train_protection_construction: null, + train_protection_construction0: null, train_protection_construction_rank: 0, }, }, @@ -1808,17 +1808,17 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: train_protection.train_protection, + train_protection0: train_protection.train_protection, train_protection_rank: 1, - train_protection_construction: null, + train_protection_construction0: null, train_protection_construction_rank: 0, }, variants: [ { properties: { - train_protection: null, + train_protection0: null, train_protection_rank: 0, - train_protection_construction: train_protection.train_protection, + train_protection_construction0: train_protection.train_protection, train_protection_construction_rank: 1, } } @@ -1834,9 +1834,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: null, + train_protection0: null, train_protection_rank: 0, - train_protection_construction: null, + train_protection_construction0: null, train_protection_construction_rank: 0, }, }, @@ -1859,17 +1859,17 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: train_protection.train_protection, + train_protection0: train_protection.train_protection, train_protection_rank: 1, - train_protection_construction: null, + train_protection_construction0: null, train_protection_construction_rank: 0, }, variants: [ { properties: { - train_protection: null, + train_protection0: null, train_protection_rank: 0, - train_protection_construction: train_protection.train_protection, + train_protection_construction0: train_protection.train_protection, train_protection_construction_rank: 1, } } @@ -1885,9 +1885,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: null, + train_protection0: null, train_protection_rank: 0, - train_protection_construction: null, + train_protection_construction0: null, train_protection_construction_rank: 0, }, }, @@ -1901,9 +1901,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: 'etcs', + train_protection0: 'etcs', train_protection_rank: 1, - train_protection_construction: null, + train_protection_construction0: null, train_protection_construction_rank: 0, }, }, @@ -1917,9 +1917,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: 'etcs', + train_protection0: 'etcs', train_protection_rank: 1, - train_protection_construction: null, + train_protection_construction0: null, train_protection_construction_rank: 0, }, }, diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index a7a6b9c3d..96d9d44ab 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -3748,7 +3748,7 @@ const layers = { 0, 0.5, 7, 2, ], - color: trainProtectionColor('train_protection'), + color: trainProtectionColor('train_protection0'), }, { id: 'railway_line_low_construction', @@ -3761,14 +3761,14 @@ const layers = { }, filter: ['all', ['!=', ['get', 'feature'], 'ferry'], - ['!=', null, ['get', 'train_protection_construction']], + ['!=', null, ['get', 'train_protection_construction0']], ], sort: ['get', 'train_protection_construction_rank'], width: ["interpolate", ["exponential", 1.2], ["zoom"], 0, 0.5, 7, 2, ], - color: trainProtectionColor('train_protection_construction'), + color: trainProtectionColor('train_protection_construction0'), }, { id: 'railway_line_med', @@ -3783,7 +3783,7 @@ const layers = { filter: ['!=', ['get', 'feature'], 'ferry'], sort: ['get', 'train_protection_rank'], width: 2, - color: trainProtectionColor('train_protection'), + color: trainProtectionColor('train_protection0'), }, { id: 'railway_line_med_construction', @@ -3797,11 +3797,11 @@ const layers = { }, filter: ['all', ['!=', ['get', 'feature'], 'ferry'], - ['!=', null, ['get', 'train_protection_construction']], + ['!=', null, ['get', 'train_protection_construction0']], ], sort: ['get', 'train_protection_construction_rank'], width: 2, - color: trainProtectionColor('train_protection_construction'), + color: trainProtectionColor('train_protection_construction0'), }, { id: 'railway_line_high', @@ -3820,7 +3820,7 @@ const layers = { 14, 2, 16, 3, ], - color: trainProtectionColor('train_protection'), + color: trainProtectionColor('train_protection0'), }, { id: 'railway_line_high_construction', @@ -3835,14 +3835,14 @@ const layers = { }, filter: ['all', ['!=', ['get', 'feature'], 'ferry'], - ['!=', null, ['get', 'train_protection_construction']], + ['!=', null, ['get', 'train_protection_construction0']], ], sort: ['get', 'train_protection_construction_rank'], width: ["interpolate", ["exponential", 1.2], ["zoom"], 14, 2, 16, 3, ], - color: trainProtectionColor('train_protection_construction'), + color: trainProtectionColor('train_protection_construction0'), }, ], ), From 1827b7d7e02d47eafca1e0c7490cd2d3ec1ae3c1 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Fri, 29 May 2026 20:46:29 +0200 Subject: [PATCH 04/27] features --- proxy/js/legend.mjs | 94 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 6 deletions(-) diff --git a/proxy/js/legend.mjs b/proxy/js/legend.mjs index 8ea01bc84..f04b875dc 100644 --- a/proxy/js/legend.mjs +++ b/proxy/js/legend.mjs @@ -1743,8 +1743,22 @@ const legendData = { key: [ 'feature', 'state', - 'train_protection', - 'train_protection_construction', + 'train_protection0', + 'train_protection_construction0', + ], + matchKeys: [ + [ + 'feature', + 'state', + 'train_protection1', + 'train_protection_construction1', + ], + [ + 'feature', + 'state', + 'train_protection2', + 'train_protection_construction2', + ], ], features: [ ...signals_railway_line.train_protections.map(train_protection => ({ @@ -1758,16 +1772,24 @@ const legendData = { bridge: false, tunnel: false, train_protection0: train_protection.train_protection, + train_protection1: null, + train_protection2: null, train_protection_rank: 1, train_protection_construction0: null, + train_protection_construction1: null, + train_protection_construction2: null, train_protection_construction_rank: 0, }, variants: [ { properties: { train_protection0: null, + train_protection1: null, + train_protection2: null, train_protection_rank: 0, train_protection_construction0: train_protection.train_protection, + train_protection_construction1: null, + train_protection_construction2: null, train_protection_construction_rank: 1, } } @@ -1784,8 +1806,12 @@ const legendData = { bridge: false, tunnel: false, train_protection0: null, + train_protection1: null, + train_protection2: null, train_protection_rank: 0, train_protection_construction0: null, + train_protection_construction1: null, + train_protection_construction2: null, train_protection_construction_rank: 0, }, }, @@ -1794,8 +1820,20 @@ const legendData = { 'openrailwaymap_low-railway_line_high': { key: [ 'state', - 'train_protection', - 'train_protection_construction', + 'train_protection0', + 'train_protection_construction0', + ], + matchKeys: [ + [ + 'state', + 'train_protection1', + 'train_protection_construction1', + ], + [ + 'state', + 'train_protection2', + 'train_protection_construction2', + ], ], features: [ ...signals_railway_line.train_protections.map(train_protection => ({ @@ -1809,16 +1847,24 @@ const legendData = { bridge: false, tunnel: false, train_protection0: train_protection.train_protection, + train_protection1: null, + train_protection2: null, train_protection_rank: 1, train_protection_construction0: null, + train_protection_construction1: null, + train_protection_construction2: null, train_protection_construction_rank: 0, }, variants: [ { properties: { train_protection0: null, + train_protection1: null, + train_protection2: null, train_protection_rank: 0, train_protection_construction0: train_protection.train_protection, + train_protection_construction1: null, + train_protection_construction2: null, train_protection_construction_rank: 1, } } @@ -1835,8 +1881,12 @@ const legendData = { bridge: false, tunnel: false, train_protection0: null, + train_protection1: null, + train_protection2: null, train_protection_rank: 0, train_protection_construction0: null, + train_protection_construction1: null, + train_protection_construction2: null, train_protection_construction_rank: 0, }, }, @@ -1845,8 +1895,20 @@ const legendData = { 'high-railway_line_high': { key: [ 'state', - 'train_protection', - 'train_protection_construction', + 'train_protection0', + 'train_protection_construction0', + ], + matchKeys: [ + [ + 'state', + 'train_protection1', + 'train_protection_construction1', + ], + [ + 'state', + 'train_protection2', + 'train_protection_construction2', + ], ], features: [ ...signals_railway_line.train_protections.map(train_protection => ({ @@ -1860,16 +1922,24 @@ const legendData = { bridge: false, tunnel: false, train_protection0: train_protection.train_protection, + train_protection1: null, + train_protection2: null, train_protection_rank: 1, train_protection_construction0: null, + train_protection_construction1: null, + train_protection_construction2: null, train_protection_construction_rank: 0, }, variants: [ { properties: { train_protection0: null, + train_protection1: null, + train_protection2: null, train_protection_rank: 0, train_protection_construction0: train_protection.train_protection, + train_protection_construction1: null, + train_protection_construction2: null, train_protection_construction_rank: 1, } } @@ -1886,8 +1956,12 @@ const legendData = { bridge: false, tunnel: false, train_protection0: null, + train_protection1: null, + train_protection2: null, train_protection_rank: 0, train_protection_construction0: null, + train_protection_construction1: null, + train_protection_construction2: null, train_protection_construction_rank: 0, }, }, @@ -1902,8 +1976,12 @@ const legendData = { bridge: false, tunnel: false, train_protection0: 'etcs', + train_protection1: null, + train_protection2: null, train_protection_rank: 1, train_protection_construction0: null, + train_protection_construction1: null, + train_protection_construction2: null, train_protection_construction_rank: 0, }, }, @@ -1918,8 +1996,12 @@ const legendData = { bridge: false, tunnel: false, train_protection0: 'etcs', + train_protection1: null, + train_protection2: null, train_protection_rank: 1, train_protection_construction0: null, + train_protection_construction1: null, + train_protection_construction2: null, train_protection_construction_rank: 0, }, }, From 1ea28825c381674b7973884234371847b1cdbc58 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Fri, 29 May 2026 20:46:34 +0200 Subject: [PATCH 05/27] high zooms --- proxy/js/styles.mjs | 66 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index 96d9d44ab..582d21c90 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -3803,13 +3803,45 @@ const layers = { width: 2, color: trainProtectionColor('train_protection_construction0'), }, + { + id: 'railway_line_construction', + minzoom: 8, + source: 'high', + states: { + construction: construction_dasharray, + }, + filter: ['!=', ['get', 'feature'], 'ferry'], + sort: ['get', 'train_protection_rank'], + width: ["interpolate", ["exponential", 1.2], ["zoom"], + 14, 2, + 16, 3, + ], + color: trainProtectionColor(['coalesce', 'train_protection_construction0', 'train_protection0']), + }, + { + id: 'railway_line_high_train_protection_construction', + minzoom: 8, + source: 'high', + states: { + present: train_protection_construction_dasharray, + }, + filter: ['all', + ['!=', ['get', 'feature'], 'ferry'], + ['!=', null, ['get', 'train_protection_construction0']], + ], + sort: ['get', 'train_protection_construction_rank'], + width: ["interpolate", ["exponential", 1.2], ["zoom"], + 14, 4, + 16, 5, + ], + color: trainProtectionColor('train_protection_construction0'), + }, { id: 'railway_line_high', minzoom: 8, source: 'high', states: { present: undefined, - construction: construction_dasharray, proposed: proposed_dasharray, disused: disused_dasharray, preserved: disused_dasharray, @@ -3823,26 +3855,40 @@ const layers = { color: trainProtectionColor('train_protection0'), }, { - id: 'railway_line_high_construction', + id: 'railway_line_high_dual_train_protection', minzoom: 8, source: 'high', states: { - present: train_protection_construction_dasharray, - construction: train_protection_construction_dasharray, - proposed: train_protection_construction_dasharray, - disused: train_protection_construction_dasharray, - preserved: train_protection_construction_dasharray, + present: gauge_dual_gauge_dashes, }, filter: ['all', ['!=', ['get', 'feature'], 'ferry'], - ['!=', null, ['get', 'train_protection_construction0']], + ['!=', ['get', 'train_protection1'], null], ], - sort: ['get', 'train_protection_construction_rank'], + sort: ['get', 'train_protection_rank'], width: ["interpolate", ["exponential", 1.2], ["zoom"], 14, 2, 16, 3, ], - color: trainProtectionColor('train_protection_construction0'), + color: trainProtectionColor('train_protection1'), + }, + { + id: 'railway_line_high_multi_train_protection', + minzoom: 8, + source: 'high', + states: { + present: gauge_dual_gauge_dashes, + }, + filter: ['all', + ['!=', ['get', 'feature'], 'ferry'], + ['!=', ['get', 'train_protection2'], null], + ], + sort: ['get', 'train_protection_rank'], + width: ["interpolate", ["exponential", 1.2], ["zoom"], + 14, 2, + 16, 3, + ], + color: trainProtectionColor('train_protection2'), }, ], ), From 26ff477e717282c1fdfe82a97bc389e42d83621a Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Fri, 29 May 2026 20:46:37 +0200 Subject: [PATCH 06/27] TODO --- features/train_protection.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/features/train_protection.yaml b/features/train_protection.yaml index d77140cbf..3c05e49d9 100644 --- a/features/train_protection.yaml +++ b/features/train_protection.yaml @@ -334,6 +334,8 @@ features: tags: - { tag: 'railway:twc', value: 'yes' } + # TODO negative matching + - train_protection: none tags: - { tag: 'railway:pzb', value: 'no' } From 0005833d324186850a039d5249bd9a88dce81195 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Fri, 29 May 2026 20:57:30 +0200 Subject: [PATCH 07/27] lookup array --- proxy/js/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/js/ui.js b/proxy/js/ui.js index 3a2bae554..02ae12e53 100644 --- a/proxy/js/ui.js +++ b/proxy/js/ui.js @@ -2405,7 +2405,7 @@ function popupContent(feature, abortController) { console.warn('Lookup catalog', format.lookup, 'not found for feature', feature); return stringValue; } else { - const {catalogKey: lookUpCatalogKey, keyVariable: lookUpKeyVariable} = constructCatalogKey(value); + const {catalogKey: lookUpCatalogKey, keyVariable: lookUpKeyVariable} = constructCatalogKey(stringValue); const lookedUpValue = lookupCatalog.features[lookUpCatalogKey]; if (!lookedUpValue) { console.warn(`Lookup catalog ${format.lookup} did not contain key ${value} (catalog key ${lookUpCatalogKey}${lookUpKeyVariable ? ` with variable ${lookUpKeyVariable}`: ''}) for feature`, feature); From 06bc47fe8f2c3f47ac3b0c0e51707d7fc685248e Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Fri, 29 May 2026 21:20:09 +0200 Subject: [PATCH 08/27] match explicit no train protection system --- features/train_protection.yaml | 60 +--------------------------------- import/tags.lua.js | 18 ++++++++-- 2 files changed, 16 insertions(+), 62 deletions(-) diff --git a/features/train_protection.yaml b/features/train_protection.yaml index 3c05e49d9..f304b9d44 100644 --- a/features/train_protection.yaml +++ b/features/train_protection.yaml @@ -50,6 +50,7 @@ train_protections: - { train_protection: 'zub', legend: 'Zugbeeinflussung (ZUB)', color: 'hsl(118, 100%, 30%)' } - { train_protection: 'satp', legend: 'Автоматическая блокировка (SATP)', color: 'hsl(187, 100%, 40%)' } - { train_protection: 'twc', legend: 'Track Warrant Control (TWC)', color: '#884400' } + # Matched when any of the train protection tags are set to 'no' and all train protection tags are either set to 'no' or missing - { train_protection: 'none', legend: 'No train protection', color: 'black' } features: @@ -333,62 +334,3 @@ features: - train_protection: twc tags: - { tag: 'railway:twc', value: 'yes' } - - # TODO negative matching - - - train_protection: none - tags: - - { tag: 'railway:pzb', value: 'no' } - - { tag: 'railway:lzb', value: 'no' } - - { tag: 'railway:etcs', value: 'no' } - - - train_protection: none - tags: - - { tag: 'railway:atb', value: 'no' } - - { tag: 'railway:etcs', value: 'no' } - - - train_protection: none - tags: - - { tag: 'railway:atc', value: 'no' } - - { tag: 'railway:etcs', value: 'no' } - - - train_protection: none - tags: - - { tag: 'railway:atc', value: 'no' } - - - train_protection: none - tags: - - { tag: 'railway:etms', value: 'no' } - - - train_protection: none - tags: - - { tag: 'railway:ptc', value: 'no' } - - - train_protection: none - tags: - - { tag: 'railway:scmt', value: 'no' } - - { tag: 'railway:etcs', value: 'no' } - - - train_protection: none - tags: - - { tag: 'railway:asfa', value: 'no' } - - { tag: 'railway:etcs', value: 'no' } - - - train_protection: none - tags: - - { tag: 'railway:kvb', value: 'no' } - - { tag: 'railway:tvm', value: 'no' } - - { tag: 'railway:etcs', value: 'no' } - - - train_protection: none - tags: - - { tag: 'railway:ls', value: 'no' } - - { tag: 'railway:etcs', value: 'no' } - - - train_protection: none - tags: - - { tag: 'railway:zsi127', value: 'no' } - - - train_protection: none - tags: - - { tag: 'railway:jkv', value: 'no' } diff --git a/import/tags.lua.js b/import/tags.lua.js index 504cc29bd..18cfa87c3 100644 --- a/import/tags.lua.js +++ b/import/tags.lua.js @@ -6,22 +6,34 @@ const signals_railway_signals = yaml.parse(fs.readFileSync('signals_railway_sign const pois = yaml.parse(fs.readFileSync('poi.yaml', 'utf8')) const station_references = yaml.parse(fs.readFileSync('stations.yaml', 'utf8')).references +const trainProtectionTags = [...new Set(signals_railway_line.features.flatMap(feature => feature.tags).map(tag => tag.tag))].toSorted(); + /** * Template that builds Lua functions used in the Osm2Psql Lua import, and taking the YAML configuration into account */ const lua = ` function train_protection(tags, prefix) + -- Match a known system local systems = {} local rank = 0 local has_systems = false ${signals_railway_line.features.map((feature, featureIndex) => ` if ${feature.tags.map(tag => `${tag.value ? `tags[prefix .. '${tag.tag}'] == '${tag.value}'`: `(${tag.values.map(value => `tags[prefix .. '${tag.tag}'] == '${value}'`).join(' or ')})`}`).join(' and ')} then table.insert(systems, '${feature.train_protection}'); rank = math.max(rank, ${signals_railway_line.features.length - featureIndex}); has_systems = true end`).join('')} - + if has_systems then return systems, rank - else - return nil, nil end + + -- Match explicit no train protection system + local any_tag_set_to_no = ${trainProtectionTags.map(tag => `tags[prefix .. '${tag}'] == 'no'`).join(' or ')} + local all_tags_set_to_no_or_empty = ${trainProtectionTags.map(tag => `(tags[prefix .. '${tag}'] or 'no') == 'no'`).join(' and ')} + + if any_tag_set_to_no and all_tags_set_to_no_or_empty then + return 'none', 1 + end + + -- Unknown + return nil, nil end local signal_tags = {${signals_railway_signals.tags.map(tag => ` From 07e5c304d3d13a1bd1b9bc7ea2c49310ecffab52 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Fri, 29 May 2026 21:20:21 +0200 Subject: [PATCH 09/27] no rank --- proxy/js/styles.mjs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index 582d21c90..7aa924a7a 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -3743,7 +3743,7 @@ const layers = { present: undefined, }, filter: ['!=', ['get', 'feature'], 'ferry'], - sort: ['get', 'train_protection_rank'], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], 0, 0.5, 7, 2, @@ -3763,7 +3763,7 @@ const layers = { ['!=', ['get', 'feature'], 'ferry'], ['!=', null, ['get', 'train_protection_construction0']], ], - sort: ['get', 'train_protection_construction_rank'], + sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], 0, 0.5, 7, 2, @@ -3781,7 +3781,7 @@ const layers = { proposed: proposed_dasharray, }, filter: ['!=', ['get', 'feature'], 'ferry'], - sort: ['get', 'train_protection_rank'], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: 2, color: trainProtectionColor('train_protection0'), }, @@ -3799,7 +3799,7 @@ const layers = { ['!=', ['get', 'feature'], 'ferry'], ['!=', null, ['get', 'train_protection_construction0']], ], - sort: ['get', 'train_protection_construction_rank'], + sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], width: 2, color: trainProtectionColor('train_protection_construction0'), }, @@ -3811,7 +3811,7 @@ const layers = { construction: construction_dasharray, }, filter: ['!=', ['get', 'feature'], 'ferry'], - sort: ['get', 'train_protection_rank'], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], 14, 2, 16, 3, @@ -3829,7 +3829,7 @@ const layers = { ['!=', ['get', 'feature'], 'ferry'], ['!=', null, ['get', 'train_protection_construction0']], ], - sort: ['get', 'train_protection_construction_rank'], + sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], 14, 4, 16, 5, @@ -3847,7 +3847,7 @@ const layers = { preserved: disused_dasharray, }, filter: ['!=', ['get', 'feature'], 'ferry'], - sort: ['get', 'train_protection_rank'], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], 14, 2, 16, 3, @@ -3865,7 +3865,7 @@ const layers = { ['!=', ['get', 'feature'], 'ferry'], ['!=', ['get', 'train_protection1'], null], ], - sort: ['get', 'train_protection_rank'], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], 14, 2, 16, 3, @@ -3883,7 +3883,7 @@ const layers = { ['!=', ['get', 'feature'], 'ferry'], ['!=', ['get', 'train_protection2'], null], ], - sort: ['get', 'train_protection_rank'], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], 14, 2, 16, 3, From 789a6b80f7d3f59512670fc68501d290f8eb6345 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Fri, 29 May 2026 21:22:12 +0200 Subject: [PATCH 10/27] rank 1 for none --- import/tags.lua.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/import/tags.lua.js b/import/tags.lua.js index 18cfa87c3..65eb7bcbd 100644 --- a/import/tags.lua.js +++ b/import/tags.lua.js @@ -18,7 +18,7 @@ function train_protection(tags, prefix) local rank = 0 local has_systems = false ${signals_railway_line.features.map((feature, featureIndex) => ` - if ${feature.tags.map(tag => `${tag.value ? `tags[prefix .. '${tag.tag}'] == '${tag.value}'`: `(${tag.values.map(value => `tags[prefix .. '${tag.tag}'] == '${value}'`).join(' or ')})`}`).join(' and ')} then table.insert(systems, '${feature.train_protection}'); rank = math.max(rank, ${signals_railway_line.features.length - featureIndex}); has_systems = true end`).join('')} + if ${feature.tags.map(tag => `${tag.value ? `tags[prefix .. '${tag.tag}'] == '${tag.value}'`: `(${tag.values.map(value => `tags[prefix .. '${tag.tag}'] == '${value}'`).join(' or ')})`}`).join(' and ')} then table.insert(systems, '${feature.train_protection}'); rank = math.max(rank, ${signals_railway_line.features.length - featureIndex + 1}); has_systems = true end`).join('')} if has_systems then return systems, rank From f350eb723018380c40839e0a9bcf29d1558352c2 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Fri, 29 May 2026 21:24:12 +0200 Subject: [PATCH 11/27] default type --- import/tags.lua.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/import/tags.lua.js b/import/tags.lua.js index 65eb7bcbd..41866686d 100644 --- a/import/tags.lua.js +++ b/import/tags.lua.js @@ -37,7 +37,7 @@ function train_protection(tags, prefix) end local signal_tags = {${signals_railway_signals.tags.map(tag => ` - { tag = '${tag.tag}', type = '${tag.type}' },`).join('')} + { tag = '${tag.tag}', type = '${tag.type ?? 'string'}' },`).join('')} } local poi_railway_values = {${pois.features.flatMap(feature => [...(feature.variants || []), feature]).flatMap(feature => feature.tags).filter(tag => tag.tag === 'railway').flatMap(tag => tag.value ? [tag.value] : (tag.values ? tag.values : [])).map(tag => ` From 603f2ec149e71b103b1c0986b21df9d57885cbdc Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Fri, 29 May 2026 21:31:14 +0200 Subject: [PATCH 12/27] none matching --- import/tags.lua.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/import/tags.lua.js b/import/tags.lua.js index 41866686d..fa1b28dd4 100644 --- a/import/tags.lua.js +++ b/import/tags.lua.js @@ -28,8 +28,12 @@ function train_protection(tags, prefix) local any_tag_set_to_no = ${trainProtectionTags.map(tag => `tags[prefix .. '${tag}'] == 'no'`).join(' or ')} local all_tags_set_to_no_or_empty = ${trainProtectionTags.map(tag => `(tags[prefix .. '${tag}'] or 'no') == 'no'`).join(' and ')} + if tags['ref:FR:SNCF_Reseau'] == '229000,1,V2,117.287,140.835' then + print(any_tag_set_to_no, all_tags_set_to_no_or_empty, dump(tags)) + end + if any_tag_set_to_no and all_tags_set_to_no_or_empty then - return 'none', 1 + return {'none'}, 1 end -- Unknown From 208c6e2549e331889e10d54638dd7ae36bd50bb6 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 11:52:34 +0200 Subject: [PATCH 13/27] construction dasharray around line --- proxy/js/styles.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index 7aa924a7a..d02f54683 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -186,7 +186,9 @@ const construction_dasharray = [4.5, 4.5]; const proposed_dasharray = [1, 4]; const present_dasharray = [1]; -const train_protection_construction_dasharray = [2, 8]; +// TODO clean up +// const train_protection_construction_dasharray = [2, 8]; +const train_protection_construction_dasharray = [1, 2]; // Turbo color map // See https://research.google/blog/turbo-an-improved-rainbow-colormap-for-visualization/ @@ -3877,7 +3879,7 @@ const layers = { minzoom: 8, source: 'high', states: { - present: gauge_dual_gauge_dashes, + present: gauge_multi_gauge_dashes, }, filter: ['all', ['!=', ['get', 'feature'], 'ferry'], From 9351cbdeb7df1c937fc09e7dabead842dbab22b4 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 12:11:00 +0200 Subject: [PATCH 14/27] low/med zooms construction train protection --- proxy/js/styles.mjs | 57 ++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index d02f54683..b35721722 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -186,8 +186,6 @@ const construction_dasharray = [4.5, 4.5]; const proposed_dasharray = [1, 4]; const present_dasharray = [1]; -// TODO clean up -// const train_protection_construction_dasharray = [2, 8]; const train_protection_construction_dasharray = [1, 2]; // Turbo color map @@ -3735,6 +3733,26 @@ const layers = { ...railwayLine( '', [ + { + id: 'railway_line_low_train_protection_construction', + minzoom: 0, + maxzoom: 7, + source: 'signals_railway_line_low', + sourceLayer: 'signals_railway_line_low', + states: { + present: train_protection_construction_dasharray, + }, + filter: ['all', + ['!=', ['get', 'feature'], 'ferry'], + ['!=', null, ['get', 'train_protection_construction0']], + ], + sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], + width: ["interpolate", ["exponential", 1.2], ["zoom"], + 0, 2.5, + 7, 4, + ], + color: trainProtectionColor('train_protection_construction0'), + }, { id: 'railway_line_low', minzoom: 0, @@ -3753,11 +3771,10 @@ const layers = { color: trainProtectionColor('train_protection0'), }, { - id: 'railway_line_low_construction', - minzoom: 0, - maxzoom: 7, - source: 'signals_railway_line_low', - sourceLayer: 'signals_railway_line_low', + id: 'railway_line_med_train_protection_construction', + minzoom: 7, + maxzoom: 8, + source: 'openrailwaymap_low', states: { present: train_protection_construction_dasharray, }, @@ -3766,44 +3783,36 @@ const layers = { ['!=', null, ['get', 'train_protection_construction0']], ], sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], - width: ["interpolate", ["exponential", 1.2], ["zoom"], - 0, 0.5, - 7, 2, - ], + width: 4, color: trainProtectionColor('train_protection_construction0'), }, { - id: 'railway_line_med', + id: 'railway_line_med_construction', minzoom: 7, maxzoom: 8, source: 'openrailwaymap_low', states: { - present: undefined, construction: construction_dasharray, proposed: proposed_dasharray, }, filter: ['!=', ['get', 'feature'], 'ferry'], sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: 2, - color: trainProtectionColor('train_protection0'), + color: trainProtectionColor(['coalesce', 'train_protection_construction0', 'train_protection0']), }, { - id: 'railway_line_med_construction', + id: 'railway_line_med', minzoom: 7, maxzoom: 8, source: 'openrailwaymap_low', states: { - present: train_protection_construction_dasharray, - construction: train_protection_construction_dasharray, - proposed: train_protection_construction_dasharray, + construction: construction_dasharray, + proposed: proposed_dasharray, }, - filter: ['all', - ['!=', ['get', 'feature'], 'ferry'], - ['!=', null, ['get', 'train_protection_construction0']], - ], - sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], + filter: ['!=', ['get', 'feature'], 'ferry'], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: 2, - color: trainProtectionColor('train_protection_construction0'), + color: trainProtectionColor('train_protection0'), }, { id: 'railway_line_construction', From e4cc9607de974a86da6e2355611b1f6074cc83a9 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 12:11:14 +0200 Subject: [PATCH 15/27] legend keys --- proxy/js/legend.mjs | 48 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/proxy/js/legend.mjs b/proxy/js/legend.mjs index f04b875dc..4ccefff23 100644 --- a/proxy/js/legend.mjs +++ b/proxy/js/legend.mjs @@ -1744,19 +1744,31 @@ const legendData = { 'feature', 'state', 'train_protection0', - 'train_protection_construction0', ], matchKeys: [ [ 'feature', 'state', 'train_protection1', - 'train_protection_construction1', ], [ 'feature', 'state', 'train_protection2', + ], + [ + 'feature', + 'state', + 'train_protection_construction0', + ], + [ + 'feature', + 'state', + 'train_protection_construction1', + ], + [ + 'feature', + 'state', 'train_protection_construction2', ], ], @@ -1783,7 +1795,7 @@ const legendData = { variants: [ { properties: { - train_protection0: null, + train_protection0: 'unknown', train_protection1: null, train_protection2: null, train_protection_rank: 0, @@ -1821,17 +1833,26 @@ const legendData = { key: [ 'state', 'train_protection0', - 'train_protection_construction0', ], matchKeys: [ [ 'state', 'train_protection1', - 'train_protection_construction1', ], [ 'state', 'train_protection2', + ], + [ + 'state', + 'train_protection_construction0', + ], + [ + 'state', + 'train_protection_construction1', + ], + [ + 'state', 'train_protection_construction2', ], ], @@ -1858,7 +1879,7 @@ const legendData = { variants: [ { properties: { - train_protection0: null, + train_protection0: 'unknown', train_protection1: null, train_protection2: null, train_protection_rank: 0, @@ -1896,17 +1917,26 @@ const legendData = { key: [ 'state', 'train_protection0', - 'train_protection_construction0', ], matchKeys: [ [ 'state', 'train_protection1', - 'train_protection_construction1', ], [ 'state', 'train_protection2', + ], + [ + 'state', + 'train_protection_construction0', + ], + [ + 'state', + 'train_protection_construction1', + ], + [ + 'state', 'train_protection_construction2', ], ], @@ -1933,7 +1963,7 @@ const legendData = { variants: [ { properties: { - train_protection0: null, + train_protection0: 'unknown', train_protection1: null, train_protection2: null, train_protection_rank: 0, From fe88b81559ea206ffa710702a9b1a37a7739f792 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 12:11:53 +0200 Subject: [PATCH 16/27] ranks in tests --- import/test/test_import_railway_line.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/import/test/test_import_railway_line.lua b/import/test/test_import_railway_line.lua index 75a1bcd14..eface5af5 100644 --- a/import/test/test_import_railway_line.lua +++ b/import/test/test_import_railway_line.lua @@ -64,6 +64,6 @@ osm2pgsql.process_way({ }) assert.eq(osm2pgsql.get_and_clear_imported_data(), { railway_line = { - { id = '123-0', tunnel = false, bridge = false, highspeed = false, rank = 40, way_length = 1, way = way, feature = 'rail', state = 'present', train_protection = '{"aws","tpws"}', train_protection_rank = 44, train_protection_construction = '{"etcs_2"}', train_protection_construction_rank = 77 }, + { id = '123-0', tunnel = false, bridge = false, highspeed = false, rank = 40, way_length = 1, way = way, feature = 'rail', state = 'present', train_protection = '{"aws","tpws"}', train_protection_rank = 33, train_protection_construction = '{"etcs_2"}', train_protection_construction_rank = 66 }, }, }) From cc4881dfdd8b00cfff2eac33e5510222b7a23e02 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 17:51:21 +0200 Subject: [PATCH 17/27] construction train protection only single value --- import/openrailwaymap.lua | 2 +- import/sql/tile_views.sql | 16 ++--- import/test/test_import_railway_line.lua | 2 +- proxy/js/legend.mjs | 76 +++++------------------- proxy/js/styles.mjs | 16 ++--- 5 files changed, 28 insertions(+), 84 deletions(-) diff --git a/import/openrailwaymap.lua b/import/openrailwaymap.lua index c6747c130..9c981063f 100644 --- a/import/openrailwaymap.lua +++ b/import/openrailwaymap.lua @@ -1389,7 +1389,7 @@ function osm2pgsql.process_way(object) reporting_marks = split_semicolon_to_sql_array(tags['reporting_marks']), train_protection = to_sql_array(railway_train_protection), train_protection_rank = railway_train_protection_rank, - train_protection_construction = to_sql_array(train_protection_construction), + train_protection_construction = train_protection_construction[1], train_protection_construction_rank = train_protection_construction_rank, operator = split_semicolon_to_sql_array(tags['operator']), owner = tags.owner, diff --git a/import/sql/tile_views.sql b/import/sql/tile_views.sql index 8486fcb31..eefd46fda 100644 --- a/import/sql/tile_views.sql +++ b/import/sql/tile_views.sql @@ -159,9 +159,7 @@ RETURN ( train_protection[2] as train_protection1, train_protection[3] as train_protection2, train_protection_construction_rank, - train_protection_construction[1] as train_protection_construction0, - train_protection_construction[2] as train_protection_construction1, - train_protection_construction[3] as train_protection_construction2, + train_protection_construction, electrification_state, voltage, frequency, @@ -258,9 +256,7 @@ DO $do$ BEGIN "train_protection1": "string", "train_protection2": "string", "train_protection_rank": "integer", - "train_protection_construction0": "string", - "train_protection_construction1": "string", - "train_protection_construction2": "string", + "train_protection_construction": "string", "train_protection_construction_rank": "integer", "electrification_state": "string", "frequency": "number", @@ -1263,9 +1259,7 @@ RETURN ( train_protection[2] as train_protection1, train_protection[3] as train_protection2, max(train_protection_construction_rank) as train_protection_construction_rank, - train_protection_construction[1] as train_protection_construction0, - train_protection_construction[2] as train_protection_construction1, - train_protection_construction[3] as train_protection_construction2, + train_protection_construction, max(rank) as rank FROM railway_line_low WHERE way && ST_TileEnvelope(z, x, y) @@ -1296,9 +1290,7 @@ DO $do$ BEGIN "train_protection1": "string", "train_protection2": "string", "train_protection_rank": "integer", - "train_protection_construction0": "string", - "train_protection_construction1": "string", - "train_protection_construction2": "string", + "train_protection_construction": "string", "train_protection_construction_rank": "integer" } } diff --git a/import/test/test_import_railway_line.lua b/import/test/test_import_railway_line.lua index eface5af5..1b239f0db 100644 --- a/import/test/test_import_railway_line.lua +++ b/import/test/test_import_railway_line.lua @@ -64,6 +64,6 @@ osm2pgsql.process_way({ }) assert.eq(osm2pgsql.get_and_clear_imported_data(), { railway_line = { - { id = '123-0', tunnel = false, bridge = false, highspeed = false, rank = 40, way_length = 1, way = way, feature = 'rail', state = 'present', train_protection = '{"aws","tpws"}', train_protection_rank = 33, train_protection_construction = '{"etcs_2"}', train_protection_construction_rank = 66 }, + { id = '123-0', tunnel = false, bridge = false, highspeed = false, rank = 40, way_length = 1, way = way, feature = 'rail', state = 'present', train_protection = '{"aws","tpws"}', train_protection_rank = 33, train_protection_construction = 'etcs_2', train_protection_construction_rank = 66 }, }, }) diff --git a/proxy/js/legend.mjs b/proxy/js/legend.mjs index 4ccefff23..a374ade0d 100644 --- a/proxy/js/legend.mjs +++ b/proxy/js/legend.mjs @@ -1759,17 +1759,7 @@ const legendData = { [ 'feature', 'state', - 'train_protection_construction0', - ], - [ - 'feature', - 'state', - 'train_protection_construction1', - ], - [ - 'feature', - 'state', - 'train_protection_construction2', + 'train_protection_construction', ], ], features: [ @@ -1787,9 +1777,7 @@ const legendData = { train_protection1: null, train_protection2: null, train_protection_rank: 1, - train_protection_construction0: null, - train_protection_construction1: null, - train_protection_construction2: null, + train_protection_construction: null, train_protection_construction_rank: 0, }, variants: [ @@ -1799,9 +1787,7 @@ const legendData = { train_protection1: null, train_protection2: null, train_protection_rank: 0, - train_protection_construction0: train_protection.train_protection, - train_protection_construction1: null, - train_protection_construction2: null, + train_protection_construction: train_protection.train_protection, train_protection_construction_rank: 1, } } @@ -1821,9 +1807,7 @@ const legendData = { train_protection1: null, train_protection2: null, train_protection_rank: 0, - train_protection_construction0: null, - train_protection_construction1: null, - train_protection_construction2: null, + train_protection_construction: null, train_protection_construction_rank: 0, }, }, @@ -1845,15 +1829,7 @@ const legendData = { ], [ 'state', - 'train_protection_construction0', - ], - [ - 'state', - 'train_protection_construction1', - ], - [ - 'state', - 'train_protection_construction2', + 'train_protection_construction', ], ], features: [ @@ -1871,9 +1847,7 @@ const legendData = { train_protection1: null, train_protection2: null, train_protection_rank: 1, - train_protection_construction0: null, - train_protection_construction1: null, - train_protection_construction2: null, + train_protection_construction: null, train_protection_construction_rank: 0, }, variants: [ @@ -1883,9 +1857,7 @@ const legendData = { train_protection1: null, train_protection2: null, train_protection_rank: 0, - train_protection_construction0: train_protection.train_protection, - train_protection_construction1: null, - train_protection_construction2: null, + train_protection_construction: train_protection.train_protection, train_protection_construction_rank: 1, } } @@ -1905,9 +1877,7 @@ const legendData = { train_protection1: null, train_protection2: null, train_protection_rank: 0, - train_protection_construction0: null, - train_protection_construction1: null, - train_protection_construction2: null, + train_protection_construction: null, train_protection_construction_rank: 0, }, }, @@ -1929,15 +1899,7 @@ const legendData = { ], [ 'state', - 'train_protection_construction0', - ], - [ - 'state', - 'train_protection_construction1', - ], - [ - 'state', - 'train_protection_construction2', + 'train_protection_construction', ], ], features: [ @@ -1955,9 +1917,7 @@ const legendData = { train_protection1: null, train_protection2: null, train_protection_rank: 1, - train_protection_construction0: null, - train_protection_construction1: null, - train_protection_construction2: null, + train_protection_construction: null, train_protection_construction_rank: 0, }, variants: [ @@ -1967,9 +1927,7 @@ const legendData = { train_protection1: null, train_protection2: null, train_protection_rank: 0, - train_protection_construction0: train_protection.train_protection, - train_protection_construction1: null, - train_protection_construction2: null, + train_protection_construction: train_protection.train_protection, train_protection_construction_rank: 1, } } @@ -1989,9 +1947,7 @@ const legendData = { train_protection1: null, train_protection2: null, train_protection_rank: 0, - train_protection_construction0: null, - train_protection_construction1: null, - train_protection_construction2: null, + train_protection_construction: null, train_protection_construction_rank: 0, }, }, @@ -2009,9 +1965,7 @@ const legendData = { train_protection1: null, train_protection2: null, train_protection_rank: 1, - train_protection_construction0: null, - train_protection_construction1: null, - train_protection_construction2: null, + train_protection_construction: null, train_protection_construction_rank: 0, }, }, @@ -2029,9 +1983,7 @@ const legendData = { train_protection1: null, train_protection2: null, train_protection_rank: 1, - train_protection_construction0: null, - train_protection_construction1: null, - train_protection_construction2: null, + train_protection_construction: null, train_protection_construction_rank: 0, }, }, diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index b35721722..85f12fe34 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -3744,14 +3744,14 @@ const layers = { }, filter: ['all', ['!=', ['get', 'feature'], 'ferry'], - ['!=', null, ['get', 'train_protection_construction0']], + ['!=', null, ['get', 'train_protection_construction']], ], sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], 0, 2.5, 7, 4, ], - color: trainProtectionColor('train_protection_construction0'), + color: trainProtectionColor('train_protection_construction'), }, { id: 'railway_line_low', @@ -3780,11 +3780,11 @@ const layers = { }, filter: ['all', ['!=', ['get', 'feature'], 'ferry'], - ['!=', null, ['get', 'train_protection_construction0']], + ['!=', null, ['get', 'train_protection_construction']], ], sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], width: 4, - color: trainProtectionColor('train_protection_construction0'), + color: trainProtectionColor('train_protection_construction'), }, { id: 'railway_line_med_construction', @@ -3798,7 +3798,7 @@ const layers = { filter: ['!=', ['get', 'feature'], 'ferry'], sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: 2, - color: trainProtectionColor(['coalesce', 'train_protection_construction0', 'train_protection0']), + color: trainProtectionColor(['coalesce', 'train_protection_construction', 'train_protection0']), }, { id: 'railway_line_med', @@ -3827,7 +3827,7 @@ const layers = { 14, 2, 16, 3, ], - color: trainProtectionColor(['coalesce', 'train_protection_construction0', 'train_protection0']), + color: trainProtectionColor(['coalesce', 'train_protection_construction', 'train_protection0']), }, { id: 'railway_line_high_train_protection_construction', @@ -3838,14 +3838,14 @@ const layers = { }, filter: ['all', ['!=', ['get', 'feature'], 'ferry'], - ['!=', null, ['get', 'train_protection_construction0']], + ['!=', null, ['get', 'train_protection_construction']], ], sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], 14, 4, 16, 5, ], - color: trainProtectionColor('train_protection_construction0'), + color: trainProtectionColor('train_protection_construction'), }, { id: 'railway_line_high', From 31656f04fb32f768771ddc09be53d29f63329b50 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 17:52:08 +0200 Subject: [PATCH 18/27] API test --- api/test/api.hurl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/test/api.hurl b/api/test/api.hurl index a8399bc12..9182c06f1 100644 --- a/api/test/api.hurl +++ b/api/test/api.hurl @@ -807,7 +807,9 @@ jsonpath "$.ref" == "6185" jsonpath "$.name" == "Schnellfahrstrecke Oebisfelde–Spandau" jsonpath "$.speed_label" == "140" jsonpath "$.state" == "present" -jsonpath "$.train_protection" == "lzb" +jsonpath "$.train_protection" count == 2 +jsonpath "$.train_protection[0]" == "lzb" +jsonpath "$.train_protection[1]" == "pzb" jsonpath "$.usage" == "main" jsonpath "$.voltage" == 15000 jsonpath "$.note" == null From a18cdb0e7da72795abbdca1ebc8cbda0a0dd32e5 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 17:59:54 +0200 Subject: [PATCH 19/27] import --- import/openrailwaymap.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/import/openrailwaymap.lua b/import/openrailwaymap.lua index 9c981063f..177b96c40 100644 --- a/import/openrailwaymap.lua +++ b/import/openrailwaymap.lua @@ -207,7 +207,7 @@ local railway_line = osm2pgsql.define_table({ { column = 'reporting_marks', sql_type = 'text[]' }, { column = 'train_protection', sql_type = 'text[]' }, { column = 'train_protection_rank', type = 'smallint' }, - { column = 'train_protection_construction', sql_type = 'text[]' }, + { column = 'train_protection_construction', type = 'text' }, { column = 'train_protection_construction_rank', type = 'smallint' }, { column = 'operator', sql_type = 'text[]' }, { column = 'owner', sql_type = 'text' }, @@ -1389,7 +1389,7 @@ function osm2pgsql.process_way(object) reporting_marks = split_semicolon_to_sql_array(tags['reporting_marks']), train_protection = to_sql_array(railway_train_protection), train_protection_rank = railway_train_protection_rank, - train_protection_construction = train_protection_construction[1], + train_protection_construction = train_protection_construction and train_protection_construction[1] or nil, train_protection_construction_rank = train_protection_construction_rank, operator = split_semicolon_to_sql_array(tags['operator']), owner = tags.owner, From 7566d8f18914eee26f673a745ff53b108286599b Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 18:02:29 +0200 Subject: [PATCH 20/27] remove debug info --- import/tags.lua.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/import/tags.lua.js b/import/tags.lua.js index fa1b28dd4..c62f245b5 100644 --- a/import/tags.lua.js +++ b/import/tags.lua.js @@ -28,10 +28,6 @@ function train_protection(tags, prefix) local any_tag_set_to_no = ${trainProtectionTags.map(tag => `tags[prefix .. '${tag}'] == 'no'`).join(' or ')} local all_tags_set_to_no_or_empty = ${trainProtectionTags.map(tag => `(tags[prefix .. '${tag}'] or 'no') == 'no'`).join(' and ')} - if tags['ref:FR:SNCF_Reseau'] == '229000,1,V2,117.287,140.835' then - print(any_tag_set_to_no, all_tags_set_to_no_or_empty, dump(tags)) - end - if any_tag_set_to_no and all_tags_set_to_no_or_empty then return {'none'}, 1 end From 6a04bb2580124abad41a3e8cb5b47142ef28921f Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 18:39:09 +0200 Subject: [PATCH 21/27] fix missing matching on matching legend keys --- proxy/js/legend.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proxy/js/legend.mjs b/proxy/js/legend.mjs index a374ade0d..3569c5b5f 100644 --- a/proxy/js/legend.mjs +++ b/proxy/js/legend.mjs @@ -3392,8 +3392,9 @@ const legendDataWithKeys = Object.fromEntries( countries, sourceLayers: Object.fromEntries( Object.entries(sourceLayers) - .map(([sourceLayer, {key, features}]) => [sourceLayer, { + .map(([sourceLayer, {key, matchKeys, features}]) => [sourceLayer, { key, + matchKeys, features: features.map(item => { const itemFeatures = [item, ...(item.variants ?? []).map(subItem => ({...item, ...subItem, properties: {...item.properties, ...subItem.properties}}))] const itemFeatureKeys = itemFeatures.map(itemFeature => key.map(keyPart => String(itemFeature.properties[keyPart] ?? '').replace(/\{[^}]+}/, '{}').replace(/@([^|]+|$)/g, '')).join('\u001e')); From 938c482514706c621961ed53109ce23a33ad519a Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 18:40:30 +0200 Subject: [PATCH 22/27] med zooms not showing present lines --- proxy/js/styles.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index 85f12fe34..a2c00791a 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -3806,8 +3806,7 @@ const layers = { maxzoom: 8, source: 'openrailwaymap_low', states: { - construction: construction_dasharray, - proposed: proposed_dasharray, + present: undefined, }, filter: ['!=', ['get', 'feature'], 'ferry'], sort: ['coalesce', ['get', 'train_protection_rank'], 0], From 7cede920787fbded7d8109a0b10622628f1749cc Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 19:51:57 +0200 Subject: [PATCH 23/27] do not render bridge and tunnel casing for construction casing --- proxy/js/styles.mjs | 335 +++++++++++++++++++++++--------------------- 1 file changed, 174 insertions(+), 161 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index a2c00791a..4e14d50d5 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -635,44 +635,46 @@ const railwayLine = (text, layers) => [ // Tunnels - ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, states, sort}) => + ...layers + .filter(({casing}) => !casing) + .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, color, hoverColor, 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, color, hoverColor, states, sort}) => [ - ...Object.entries(states).map(([state, dash]) => ({ id: `${id}_tunnel_fill_${state}`, type: 'line', minzoom, @@ -708,7 +710,7 @@ const railwayLine = (text, layers) => [ 'line-dasharray': dash ?? undefined, }, })), - ]), + ), ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, states, sort}) => ({ id: `${id}_tunnel_cover`, type: 'line', @@ -749,68 +751,72 @@ const railwayLine = (text, layers) => [ 'line-width': width, }, })), - ...layers.flatMap(({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'], - ], - filter ?? true, - ].filter(it => it !== true), - color, - visibility, + ...layers + .filter(({casing}) => !casing) + .flatMap(({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'], + ], + filter ?? true, + ].filter(it => it !== true), + color, + visibility, + ), ), - ), // Ground - ...layers.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], 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 + .filter(({casing}) => !casing) + .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, color, hoverColor, states, sort}) => [ ...Object.entries(states).map(([state, dash]) => ({ id: `${id}_fill_${state}`, @@ -821,7 +827,7 @@ const railwayLine = (text, layers) => [ 'source-layer': sourceLayer || 'railway_line_high', filter: ['all', ['==', ['get', 'state'], state], - ['!=', ['==', ['get', 'bridge'], true], true], + ['!=', ['get', 'bridge'], true], ['!=', ['get', 'tunnel'], true], filter ?? true, ].filter(it => it !== true), @@ -854,6 +860,7 @@ const railwayLine = (text, layers) => [ // Bridges ...layers + .filter(({casing}) => !casing) .filter(({states}) => 'present' in states) .flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, sort}) => [ { @@ -928,8 +935,8 @@ const railwayLine = (text, layers) => [ }, ]), - ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, color, hoverColor, states, sort}) => [ - ...Object.entries(states).map(([state, dash]) => ({ + ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, color, hoverColor, states, sort}) => + Object.entries(states).map(([state, dash]) => ({ id: `${id}_bridge_fill_${state}`, type: 'line', minzoom, @@ -965,14 +972,50 @@ const railwayLine = (text, layers) => [ 'line-dasharray': dash ?? undefined, }, })), - ]), + ), // Preferred direction - ...layers.flatMap(({id, visibility, filter, color, states}) => - preferredDirectionLayer( - `${id}_preferred_direction`, - ['all', + ...layers + .filter(({casing}) => !casing) + .flatMap(({id, visibility, filter, color, states}) => + 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]) + ], + ['!=', ['get', 'tunnel'], true], + ['any', + ['==', ['get', 'preferred_direction'], 'forward'], + ['==', ['get', 'preferred_direction'], 'backward'], + ['==', ['get', 'preferred_direction'], 'both'], + ], + filter ?? true, + ].filter(it => it !== true), + color, + visibility, + ), + ), + + // Text layers + + railwayKmText, + + ...layers + .filter(({casing}) => !casing) + .flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, states}) => ({ + id: `${id}_text`, + type: 'symbol', + minzoom, + maxzoom, + 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]] @@ -980,67 +1023,34 @@ const railwayLine = (text, layers) => [ : state === 'razed' ? ['all', ['global-state', 'showRazedInfrastructure'], ['==', ['get', 'state'], state]] : ['==', ['get', 'state'], state]) ], - ['!=', ['get', 'tunnel'], true], - ['any', - ['==', ['get', 'preferred_direction'], 'forward'], - ['==', ['get', 'preferred_direction'], 'backward'], - ['==', ['get', 'preferred_direction'], 'both'], - ], filter ?? true, ].filter(it => it !== true), - color, - visibility, - ), - ), - - // Text layers - - railwayKmText, - - ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, states}) => ({ - id: `${id}_text`, - type: 'symbol', - minzoom, - maxzoom, - 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]) - ], - filter ?? true, - ].filter(it => it !== true), - paint: { - 'text-color': colors.railwayLine.text, - 'text-halo-color': ['case', - ['boolean', ['feature-state', 'hover'], false], colors.hover.textHalo, - colors.halo, - ], - 'text-halo-width': 2, - }, - layout: { - 'visibility': ['case', - visibility ? ['==', visibility, false] : false, 'none', - ['<', ['global-state', 'date'], defaultDate], 'none', - 'visible', - ], - 'symbol-z-order': 'source', - 'symbol-placement': 'line', - 'text-field': text, - 'text-font': font.bold, - 'text-size': 11, - 'text-padding': 10, - 'text-max-width': 5, - 'symbol-spacing': 200, - }, - })), + paint: { + 'text-color': colors.railwayLine.text, + 'text-halo-color': ['case', + ['boolean', ['feature-state', 'hover'], false], colors.hover.textHalo, + colors.halo, + ], + 'text-halo-width': 2, + }, + layout: { + 'visibility': ['case', + visibility ? ['==', visibility, false] : false, 'none', + ['<', ['global-state', 'date'], defaultDate], 'none', + 'visible', + ], + 'symbol-z-order': 'source', + 'symbol-placement': 'line', + 'text-field': text, + 'text-font': font.bold, + 'text-size': 11, + 'text-padding': 10, + 'text-max-width': 5, + 'symbol-spacing': 200, + }, + })), ]; - const historicalRailwayLine = (text, layers) => [ // Tunnels @@ -3752,6 +3762,7 @@ const layers = { 7, 4, ], color: trainProtectionColor('train_protection_construction'), + casing: true, }, { id: 'railway_line_low', @@ -3785,6 +3796,7 @@ const layers = { sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], width: 4, color: trainProtectionColor('train_protection_construction'), + casing: true, }, { id: 'railway_line_med_construction', @@ -3845,6 +3857,7 @@ const layers = { 16, 5, ], color: trainProtectionColor('train_protection_construction'), + casing: true, }, { id: 'railway_line_high', From ca90c7777ff175e8c58d7d23ae3748f95c9c09ed Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 20:45:27 +0200 Subject: [PATCH 24/27] exclude ETCS from KTCS, exclude PTC from ACSES, ASES, ITCS, ETMS and CBTC --- features/schema/train_protection.yaml | 5 +++++ features/train_protection.yaml | 14 ++++++++++++++ import/tags.lua.js | 3 ++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/features/schema/train_protection.yaml b/features/schema/train_protection.yaml index eff7de59c..b4f11247b 100644 --- a/features/schema/train_protection.yaml +++ b/features/schema/train_protection.yaml @@ -45,6 +45,11 @@ properties: type: string description: A tag value additionalProperties: false + exclude: + type: array + description: Train protection systems to exclude from further detection + items: + type: string additionalProperties: false $schema: type: string diff --git a/features/train_protection.yaml b/features/train_protection.yaml index f304b9d44..22305ea0c 100644 --- a/features/train_protection.yaml +++ b/features/train_protection.yaml @@ -58,6 +58,10 @@ features: - train_protection: ktcs tags: - { tag: 'railway:ktcs', values: ['yes', '1', '2', 'M'] } + exclude: + - etcs + - etcs_1 + - etcs_2 - train_protection: etcs tags: @@ -82,22 +86,32 @@ features: - train_protection: acses tags: - { tag: 'railway:acses', value: 'yes' } + exclude: + - ptc - train_protection: ases tags: - { tag: 'railway:ases', value: 'yes' } + exclude: + - ptc - train_protection: itcs tags: - { tag: 'railway:itcs', value: 'yes' } + exclude: + - ptc - train_protection: etms tags: - { tag: 'railway:etms', value: 'yes' } + exclude: + - ptc - train_protection: cbtc tags: - { tag: 'railway:cbtc', values: ['yes', 'uto', 'sto', 'dto', 'RF', 'IL'] } + exclude: + - ptc - train_protection: ptc tags: diff --git a/import/tags.lua.js b/import/tags.lua.js index c62f245b5..38a7c2e23 100644 --- a/import/tags.lua.js +++ b/import/tags.lua.js @@ -17,8 +17,9 @@ function train_protection(tags, prefix) local systems = {} local rank = 0 local has_systems = false + local excluded = {} ${signals_railway_line.features.map((feature, featureIndex) => ` - if ${feature.tags.map(tag => `${tag.value ? `tags[prefix .. '${tag.tag}'] == '${tag.value}'`: `(${tag.values.map(value => `tags[prefix .. '${tag.tag}'] == '${value}'`).join(' or ')})`}`).join(' and ')} then table.insert(systems, '${feature.train_protection}'); rank = math.max(rank, ${signals_railway_line.features.length - featureIndex + 1}); has_systems = true end`).join('')} + if (not excluded['${feature.train_protection}']) and ${feature.tags.map(tag => `${tag.value ? `tags[prefix .. '${tag.tag}'] == '${tag.value}'`: `(${tag.values.map(value => `tags[prefix .. '${tag.tag}'] == '${value}'`).join(' or ')})`}`).join(' and ')} then table.insert(systems, '${feature.train_protection}'); rank = math.max(rank, ${signals_railway_line.features.length - featureIndex + 1}); has_systems = true${feature.exclude ? `;${feature.exclude.map(exclude => ` excluded['${exclude}'] = true;`).join('')}`: ''} end`).join('')} if has_systems then return systems, rank From 0b84872540559920fb124740d9c55c413901f572 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sat, 30 May 2026 21:02:48 +0200 Subject: [PATCH 25/27] tests --- import/test/test_import_railway_line.lua | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/import/test/test_import_railway_line.lua b/import/test/test_import_railway_line.lua index 1b239f0db..b0e9f26db 100644 --- a/import/test/test_import_railway_line.lua +++ b/import/test/test_import_railway_line.lua @@ -67,3 +67,36 @@ assert.eq(osm2pgsql.get_and_clear_imported_data(), { { id = '123-0', tunnel = false, bridge = false, highspeed = false, rank = 40, way_length = 1, way = way, feature = 'rail', state = 'present', train_protection = '{"aws","tpws"}', train_protection_rank = 33, train_protection_construction = 'etcs_2', train_protection_construction_rank = 66 }, }, }) + +osm2pgsql.process_way({ + id = 123, + type = 'way', + tags = { + ['railway'] = 'rail', + ['railway:ktcs'] = '2', + ['railway:etcs'] = '2', + }, + as_linestring = as_linestring_mock, +}) +assert.eq(osm2pgsql.get_and_clear_imported_data(), { + railway_line = { + { id = '123-0', tunnel = false, bridge = false, highspeed = false, rank = 40, way_length = 1, way = way, feature = 'rail', state = 'present', train_protection = '{"ktcs"}', train_protection_rank = 71 }, + }, +}) + +osm2pgsql.process_way({ + id = 123, + type = 'way', + tags = { + ['railway'] = 'rail', + ['railway:acses'] = 'yes', + ['railway:atc'] = 'yes', + ['railway:ptc'] = 'yes', + }, + as_linestring = as_linestring_mock, +}) +assert.eq(osm2pgsql.get_and_clear_imported_data(), { + railway_line = { + { id = '123-0', tunnel = false, bridge = false, highspeed = false, rank = 40, way_length = 1, way = way, feature = 'rail', state = 'present', train_protection = '{"acses","atc"}', train_protection_rank = 65 }, + }, +}) From 5fbc505962830efc62ad1d206357dcd9d9971549 Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 31 May 2026 17:34:33 +0200 Subject: [PATCH 26/27] split off low zooms into construction layer, tweak dashes --- import/sql/tile_views.sql | 48 ++++++++++++++++++++++++++++- martin/configuration.yml | 5 +++ proxy/js/styles.mjs | 65 ++++++++++++++++++++++----------------- 3 files changed, 88 insertions(+), 30 deletions(-) diff --git a/import/sql/tile_views.sql b/import/sql/tile_views.sql index eefd46fda..1854ed150 100644 --- a/import/sql/tile_views.sql +++ b/import/sql/tile_views.sql @@ -1237,7 +1237,6 @@ END $do$; --- Signals --- - CREATE OR REPLACE FUNCTION signals_railway_line_low(z integer, x integer, y integer) RETURNS bytea LANGUAGE SQL @@ -1299,6 +1298,53 @@ DO $do$ BEGIN $$::json || '$tj$'; END $do$; +CREATE OR REPLACE FUNCTION signals_railway_line_low_construction(z integer, x integer, y integer) + RETURNS bytea + LANGUAGE SQL + IMMUTABLE + STRICT + PARALLEL SAFE +RETURN ( + SELECT + ST_AsMVT(tile, 'signals_railway_line_low_construction', 4096, 'way') + FROM ( + SELECT + min(id) as id, + ST_AsMVTGeom(st_linemerge(st_simplify(st_collect(way), 100000)), ST_TileEnvelope(z, x, y), extent => 4096, buffer => 64, clip_geom => true) AS way, + any_value(state) as state, + max(train_protection_construction_rank) as train_protection_construction_rank, + train_protection_construction + FROM railway_line_low + WHERE way && ST_TileEnvelope(z, x, y) + AND feature != 'ferry' + AND train_protection_construction IS NOT NULL + GROUP BY + ref, + name, + train_protection_construction + ORDER by + train_protection_construction_rank NULLS FIRST + ) as tile + WHERE way IS NOT NULL +); + +DO $do$ BEGIN + EXECUTE 'COMMENT ON FUNCTION signals_railway_line_low_construction IS $tj$' || $$ + { + "vector_layers": [ + { + "id": "signals_railway_line_low_construction", + "fields": { + "id": "string", + "state": "string", + "train_protection_construction": "string" + } + } + ] + } + $$::json || '$tj$'; +END $do$; + --- Signals --- CREATE OR REPLACE VIEW signal_boxes_view AS diff --git a/martin/configuration.yml b/martin/configuration.yml index e07490f15..41f70c600 100644 --- a/martin/configuration.yml +++ b/martin/configuration.yml @@ -122,6 +122,11 @@ postgres: # --- Signals --- # + signals_railway_line_low_construction: + schema: public + function: signals_railway_line_low_construction + maxzoom: 7 + signals_railway_line_low: schema: public function: signals_railway_line_low diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index 4e14d50d5..80edda9a2 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -186,7 +186,7 @@ const construction_dasharray = [4.5, 4.5]; const proposed_dasharray = [1, 4]; const present_dasharray = [1]; -const train_protection_construction_dasharray = [1, 2]; +const train_protection_construction_dasharray = [0, 2, 2, 4]; // Turbo color map // See https://research.google/blog/turbo-an-improved-rainbow-colormap-for-visualization/ @@ -524,7 +524,7 @@ const sources = { }, signals_railway_line_low: { type: 'vector', - url: '/signals_railway_line_low', + url: '/signals_railway_line_low,signals_railway_line_low_construction', promoteId: 'id', }, electrification_railway_line_low: { @@ -817,7 +817,7 @@ const railwayLine = (text, layers) => [ }, })) ), - ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, color, hoverColor, states, sort}) => [ + ...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}`, type: 'line', @@ -852,6 +852,7 @@ const railwayLine = (text, layers) => [ color, ], 'line-width': width, + 'line-gap-width': gapWidth ?? undefined, 'line-dasharray': dash ?? undefined, }, })), @@ -3745,22 +3746,19 @@ const layers = { [ { id: 'railway_line_low_train_protection_construction', - minzoom: 0, + minzoom: 5, // TODO also on source maxzoom: 7, source: 'signals_railway_line_low', - sourceLayer: 'signals_railway_line_low', + sourceLayer: 'signals_railway_line_low_construction', states: { present: train_protection_construction_dasharray, }, - filter: ['all', - ['!=', ['get', 'feature'], 'ferry'], - ['!=', null, ['get', 'train_protection_construction']], - ], sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], - 0, 2.5, - 7, 4, + 0, 0.5, + 7, 2, ], + gapWidth: 2, color: trainProtectionColor('train_protection_construction'), casing: true, }, @@ -3794,7 +3792,8 @@ const layers = { ['!=', null, ['get', 'train_protection_construction']], ], sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], - width: 4, + width: 2, + gapWidth: 2, color: trainProtectionColor('train_protection_construction'), casing: true, }, @@ -3826,11 +3825,12 @@ const layers = { color: trainProtectionColor('train_protection0'), }, { - id: 'railway_line_construction', + id: 'railway_line_construction_proposed', minzoom: 8, source: 'high', states: { construction: construction_dasharray, + proposed: proposed_dasharray, }, filter: ['!=', ['get', 'feature'], 'ferry'], sort: ['coalesce', ['get', 'train_protection_rank'], 0], @@ -3853,36 +3853,40 @@ const layers = { ], sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], - 14, 4, - 16, 5, + 14, 2, + 16, 3, ], + gapWidth: 2, color: trainProtectionColor('train_protection_construction'), casing: true, }, { - id: 'railway_line_high', + id: 'railway_line_high_multi_train_protection', minzoom: 8, source: 'high', states: { present: undefined, - proposed: proposed_dasharray, - disused: disused_dasharray, - preserved: disused_dasharray, }, - filter: ['!=', ['get', 'feature'], 'ferry'], + 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'), + color: trainProtectionColor('train_protection2'), }, { id: 'railway_line_high_dual_train_protection', minzoom: 8, source: 'high', states: { - present: gauge_dual_gauge_dashes, + present: ['case', + ['!=', ['get', 'train_protection2'], null], ['literal', [3, 3]], + ['literal', [100, 0]], + ], }, filter: ['all', ['!=', ['get', 'feature'], 'ferry'], @@ -3896,22 +3900,25 @@ const layers = { color: trainProtectionColor('train_protection1'), }, { - id: 'railway_line_high_multi_train_protection', + id: 'railway_line_high', minzoom: 8, source: 'high', states: { - present: gauge_multi_gauge_dashes, + 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, }, - filter: ['all', - ['!=', ['get', 'feature'], 'ferry'], - ['!=', ['get', 'train_protection2'], null], - ], + filter: ['!=', ['get', 'feature'], 'ferry'], sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], 14, 2, 16, 3, ], - color: trainProtectionColor('train_protection2'), + color: trainProtectionColor('train_protection0'), }, ], ), From 1c6ec54b9b74e46806ca66e483c8ab920f68365c Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Sun, 31 May 2026 17:52:58 +0200 Subject: [PATCH 27/27] bridges and tunnels --- proxy/js/styles.mjs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index 80edda9a2..a3a16e2ea 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -636,7 +636,7 @@ const railwayLine = (text, layers) => [ // Tunnels ...layers - .filter(({casing}) => !casing) + .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}`, @@ -673,7 +673,7 @@ const railwayLine = (text, layers) => [ }, })) ), - ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, color, hoverColor, states, sort}) => + ...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}`, type: 'line', @@ -707,11 +707,12 @@ const railwayLine = (text, layers) => [ color, ], 'line-width': width, + 'line-gap-width': gapWidth ?? undefined, 'line-dasharray': dash ?? undefined, }, })), ), - ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, states, sort}) => ({ + ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, gapWidth, states, sort}) => ({ id: `${id}_tunnel_cover`, type: 'line', minzoom: Math.max(minzoom, 8), @@ -749,10 +750,11 @@ const railwayLine = (text, layers) => [ paint: { 'line-color': colors.styles.standard.tunnelCover, 'line-width': width, + 'line-gap-width': gapWidth ?? undefined, }, })), ...layers - .filter(({casing}) => !casing) + .filter(({gapWidth}) => !gapWidth) .flatMap(({id, visibility, filter, color, states}) => preferredDirectionLayer(`${id}_tunnel_preferred_direction`, ['all', @@ -779,7 +781,7 @@ const railwayLine = (text, layers) => [ // Ground ...layers - .filter(({casing}) => !casing) + .filter(({gapWidth}) => !gapWidth) .flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, states, sort}) => Object.entries(states).map(([state, dash]) => ({ id: `${id}_casing_${state}`, @@ -861,7 +863,7 @@ const railwayLine = (text, layers) => [ // Bridges ...layers - .filter(({casing}) => !casing) + .filter(({gapWidth}) => !gapWidth) .filter(({states}) => 'present' in states) .flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, sort}) => [ { @@ -936,7 +938,7 @@ const railwayLine = (text, layers) => [ }, ]), - ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, color, hoverColor, states, sort}) => + ...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', @@ -970,6 +972,7 @@ const railwayLine = (text, layers) => [ color, ], 'line-width': width, + 'line-gap-width': gapWidth ?? undefined, 'line-dasharray': dash ?? undefined, }, })), @@ -978,7 +981,7 @@ const railwayLine = (text, layers) => [ // Preferred direction ...layers - .filter(({casing}) => !casing) + .filter(({gapWidth}) => !gapWidth) .flatMap(({id, visibility, filter, color, states}) => preferredDirectionLayer( `${id}_preferred_direction`, @@ -1008,7 +1011,7 @@ const railwayLine = (text, layers) => [ railwayKmText, ...layers - .filter(({casing}) => !casing) + .filter(({gapWidth}) => !gapWidth) .flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, states}) => ({ id: `${id}_text`, type: 'symbol', @@ -3760,7 +3763,6 @@ const layers = { ], gapWidth: 2, color: trainProtectionColor('train_protection_construction'), - casing: true, }, { id: 'railway_line_low', @@ -3795,7 +3797,6 @@ const layers = { width: 2, gapWidth: 2, color: trainProtectionColor('train_protection_construction'), - casing: true, }, { id: 'railway_line_med_construction', @@ -3858,7 +3859,6 @@ const layers = { ], gapWidth: 2, color: trainProtectionColor('train_protection_construction'), - casing: true, }, { id: 'railway_line_high_multi_train_protection',