diff --git a/api/test/api.hurl b/api/test/api.hurl index c9200eb29..06763e7fc 100644 --- a/api/test/api.hurl +++ b/api/test/api.hurl @@ -808,7 +808,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 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 d77140cbf..22305ea0c 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: @@ -57,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: @@ -81,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: @@ -333,60 +348,3 @@ features: - train_protection: twc tags: - { tag: 'railway:twc', value: 'yes' } - - - 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/openrailwaymap.lua b/import/openrailwaymap.lua index 76b8b9057..c3353068a 100644 --- a/import/openrailwaymap.lua +++ b/import/openrailwaymap.lua @@ -205,7 +205,7 @@ 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_rank', type = 'smallint' }, @@ -1402,9 +1402,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 = 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, diff --git a/import/sql/tile_views.sql b/import/sql/tile_views.sql index e92f39fe7..bf69bbcf6 100644 --- a/import/sql/tile_views.sql +++ b/import/sql/tile_views.sql @@ -155,7 +155,9 @@ 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, electrification_state, @@ -250,7 +252,9 @@ 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_construction_rank": "integer", @@ -1244,7 +1248,6 @@ END $do$; --- Signals --- - CREATE OR REPLACE FUNCTION signals_railway_line_low(z integer, x integer, y integer) RETURNS bytea LANGUAGE SQL @@ -1261,9 +1264,11 @@ RETURN ( feature, any_value(state) as state, any_value(usage) as usage, - train_protection_rank, - train_protection, - train_protection_construction_rank, + 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, max(rank) as rank FROM railway_line_low @@ -1272,9 +1277,7 @@ RETURN ( feature, ref, name, - train_protection_rank, train_protection, - train_protection_construction_rank, train_protection_construction ORDER by rank NULLS LAST @@ -1293,7 +1296,9 @@ 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_construction_rank": "integer" @@ -1304,6 +1309,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/import/tags.lua.js b/import/tags.lua.js index d700773dc..38a7c2e23 100644 --- a/import/tags.lua.js +++ b/import/tags.lua.js @@ -6,18 +6,39 @@ 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)${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) + -- Match a known system + local systems = {} + local rank = 0 + local has_systems = false + local excluded = {} + ${signals_railway_line.features.map((feature, featureIndex) => ` + 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 + 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 ')} - return nil, 0 + 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 => ` - { 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 => ` diff --git a/import/test/test_import_railway_line.lua b/import/test/test_import_railway_line.lua index 6c10011f8..b0e9f26db 100644 --- a/import/test/test_import_railway_line.lua +++ b/import/test/test_import_railway_line.lua @@ -47,6 +47,56 @@ 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 = 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 }, }, }) 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/legend.mjs b/proxy/js/legend.mjs index fafc0f108..3569c5b5f 100644 --- a/proxy/js/legend.mjs +++ b/proxy/js/legend.mjs @@ -1743,8 +1743,24 @@ const legendData = { key: [ 'feature', 'state', - 'train_protection', - 'train_protection_construction', + 'train_protection0', + ], + matchKeys: [ + [ + 'feature', + 'state', + 'train_protection1', + ], + [ + 'feature', + 'state', + 'train_protection2', + ], + [ + 'feature', + 'state', + 'train_protection_construction', + ], ], features: [ ...signals_railway_line.train_protections.map(train_protection => ({ @@ -1757,7 +1773,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: train_protection.train_protection, + train_protection0: train_protection.train_protection, + train_protection1: null, + train_protection2: null, train_protection_rank: 1, train_protection_construction: null, train_protection_construction_rank: 0, @@ -1765,7 +1783,9 @@ const legendData = { variants: [ { properties: { - train_protection: null, + train_protection0: 'unknown', + train_protection1: null, + train_protection2: null, train_protection_rank: 0, train_protection_construction: train_protection.train_protection, train_protection_construction_rank: 1, @@ -1783,7 +1803,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: null, + train_protection0: null, + train_protection1: null, + train_protection2: null, train_protection_rank: 0, train_protection_construction: null, train_protection_construction_rank: 0, @@ -1794,8 +1816,21 @@ const legendData = { 'openrailwaymap_low-railway_line_high': { key: [ 'state', - 'train_protection', - 'train_protection_construction', + 'train_protection0', + ], + matchKeys: [ + [ + 'state', + 'train_protection1', + ], + [ + 'state', + 'train_protection2', + ], + [ + 'state', + 'train_protection_construction', + ], ], features: [ ...signals_railway_line.train_protections.map(train_protection => ({ @@ -1808,7 +1843,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: train_protection.train_protection, + train_protection0: train_protection.train_protection, + train_protection1: null, + train_protection2: null, train_protection_rank: 1, train_protection_construction: null, train_protection_construction_rank: 0, @@ -1816,7 +1853,9 @@ const legendData = { variants: [ { properties: { - train_protection: null, + train_protection0: 'unknown', + train_protection1: null, + train_protection2: null, train_protection_rank: 0, train_protection_construction: train_protection.train_protection, train_protection_construction_rank: 1, @@ -1834,7 +1873,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: null, + train_protection0: null, + train_protection1: null, + train_protection2: null, train_protection_rank: 0, train_protection_construction: null, train_protection_construction_rank: 0, @@ -1845,8 +1886,21 @@ const legendData = { 'high-railway_line_high': { key: [ 'state', - 'train_protection', - 'train_protection_construction', + 'train_protection0', + ], + matchKeys: [ + [ + 'state', + 'train_protection1', + ], + [ + 'state', + 'train_protection2', + ], + [ + 'state', + 'train_protection_construction', + ], ], features: [ ...signals_railway_line.train_protections.map(train_protection => ({ @@ -1859,7 +1913,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: train_protection.train_protection, + train_protection0: train_protection.train_protection, + train_protection1: null, + train_protection2: null, train_protection_rank: 1, train_protection_construction: null, train_protection_construction_rank: 0, @@ -1867,7 +1923,9 @@ const legendData = { variants: [ { properties: { - train_protection: null, + train_protection0: 'unknown', + train_protection1: null, + train_protection2: null, train_protection_rank: 0, train_protection_construction: train_protection.train_protection, train_protection_construction_rank: 1, @@ -1885,7 +1943,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: null, + train_protection0: null, + train_protection1: null, + train_protection2: null, train_protection_rank: 0, train_protection_construction: null, train_protection_construction_rank: 0, @@ -1901,7 +1961,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: 'etcs', + train_protection0: 'etcs', + train_protection1: null, + train_protection2: null, train_protection_rank: 1, train_protection_construction: null, train_protection_construction_rank: 0, @@ -1917,7 +1979,9 @@ const legendData = { service: null, bridge: false, tunnel: false, - train_protection: 'etcs', + train_protection0: 'etcs', + train_protection1: null, + train_protection2: null, train_protection_rank: 1, train_protection_construction: null, train_protection_construction_rank: 0, @@ -3328,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')); diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index a7a6b9c3d..a3a16e2ea 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 = [2, 8]; +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: { @@ -635,44 +635,46 @@ const railwayLine = (text, layers) => [ // Tunnels - ...layers.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, states, sort}) => + ...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_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, @@ -705,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), @@ -747,71 +750,76 @@ const railwayLine = (text, layers) => [ paint: { 'line-color': colors.styles.standard.tunnelCover, 'line-width': width, + 'line-gap-width': gapWidth ?? undefined, }, })), - ...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(({gapWidth}) => !gapWidth) + .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.flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, color, hoverColor, states, sort}) => [ + ...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}`, type: 'line', @@ -821,7 +829,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), @@ -846,6 +854,7 @@ const railwayLine = (text, layers) => [ color, ], 'line-width': width, + 'line-gap-width': gapWidth ?? undefined, 'line-dasharray': dash ?? undefined, }, })), @@ -854,6 +863,7 @@ const railwayLine = (text, layers) => [ // Bridges ...layers + .filter(({gapWidth}) => !gapWidth) .filter(({states}) => 'present' in states) .flatMap(({id, minzoom, maxzoom, source, sourceLayer, visibility, filter, width, sort}) => [ { @@ -928,8 +938,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, gapWidth, color, hoverColor, states, sort}) => + Object.entries(states).map(([state, dash]) => ({ id: `${id}_bridge_fill_${state}`, type: 'line', minzoom, @@ -962,17 +972,54 @@ const railwayLine = (text, layers) => [ color, ], 'line-width': width, + 'line-gap-width': gapWidth ?? undefined, 'line-dasharray': dash ?? undefined, }, })), - ]), + ), // Preferred direction - ...layers.flatMap(({id, visibility, filter, color, states}) => - preferredDirectionLayer( - `${id}_preferred_direction`, - ['all', + ...layers + .filter(({gapWidth}) => !gapWidth) + .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(({gapWidth}) => !gapWidth) + .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 +1027,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 @@ -3733,6 +3747,23 @@ const layers = { ...railwayLine( '', [ + { + id: 'railway_line_low_train_protection_construction', + minzoom: 5, // TODO also on source + maxzoom: 7, + source: 'signals_railway_line_low', + sourceLayer: 'signals_railway_line_low_construction', + states: { + present: train_protection_construction_dasharray, + }, + sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], + width: ["interpolate", ["exponential", 1.2], ["zoom"], + 0, 0.5, + 7, 2, + ], + gapWidth: 2, + color: trainProtectionColor('train_protection_construction'), + }, { id: 'railway_line_low', minzoom: 0, @@ -3743,19 +3774,18 @@ 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, ], - color: trainProtectionColor('train_protection'), + 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, }, @@ -3763,87 +3793,133 @@ const layers = { ['!=', ['get', 'feature'], 'ferry'], ['!=', null, ['get', 'train_protection_construction']], ], - sort: ['get', 'train_protection_construction_rank'], - width: ["interpolate", ["exponential", 1.2], ["zoom"], - 0, 0.5, - 7, 2, - ], + sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], + width: 2, + gapWidth: 2, color: trainProtectionColor('train_protection_construction'), }, { - 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: ['get', 'train_protection_rank'], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: 2, - color: trainProtectionColor('train_protection'), + color: trainProtectionColor(['coalesce', 'train_protection_construction', '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, + present: undefined, }, - filter: ['all', - ['!=', ['get', 'feature'], 'ferry'], - ['!=', null, ['get', 'train_protection_construction']], - ], - sort: ['get', 'train_protection_construction_rank'], + filter: ['!=', ['get', 'feature'], 'ferry'], + sort: ['coalesce', ['get', 'train_protection_rank'], 0], width: 2, - color: trainProtectionColor('train_protection_construction'), + color: trainProtectionColor('train_protection0'), }, { - id: 'railway_line_high', + id: 'railway_line_construction_proposed', minzoom: 8, source: 'high', states: { - present: undefined, construction: construction_dasharray, proposed: proposed_dasharray, - disused: disused_dasharray, - 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, ], - color: trainProtectionColor('train_protection'), + color: trainProtectionColor(['coalesce', 'train_protection_construction', 'train_protection0']), }, { - id: 'railway_line_high_construction', + id: 'railway_line_high_train_protection_construction', 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, }, filter: ['all', ['!=', ['get', 'feature'], 'ferry'], ['!=', null, ['get', 'train_protection_construction']], ], - sort: ['get', 'train_protection_construction_rank'], + sort: ['coalesce', ['get', 'train_protection_construction_rank'], 0], width: ["interpolate", ["exponential", 1.2], ["zoom"], 14, 2, 16, 3, ], + gapWidth: 2, color: trainProtectionColor('train_protection_construction'), }, + { + id: 'railway_line_high_multi_train_protection', + minzoom: 8, + source: 'high', + states: { + present: undefined, + }, + 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_protection2'), + }, + { + id: 'railway_line_high_dual_train_protection', + minzoom: 8, + source: 'high', + states: { + present: ['case', + ['!=', ['get', 'train_protection2'], null], ['literal', [3, 3]], + ['literal', [100, 0]], + ], + }, + filter: ['all', + ['!=', ['get', 'feature'], 'ferry'], + ['!=', ['get', 'train_protection1'], 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', + 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, + }, + 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'), + }, ], ), route, 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);