Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
dec7a52
Import multiple train protection systems
hiddewie May 29, 2026
f587f66
Test for railway line import
hiddewie May 29, 2026
cc5ce85
tiles
hiddewie May 29, 2026
1827b7d
features
hiddewie May 29, 2026
1ea2882
high zooms
hiddewie May 29, 2026
26ff477
TODO
hiddewie May 29, 2026
0005833
lookup array
hiddewie May 29, 2026
06bc47f
match explicit no train protection system
hiddewie May 29, 2026
07e5c30
no rank
hiddewie May 29, 2026
789a6b8
rank 1 for none
hiddewie May 29, 2026
f350eb7
default type
hiddewie May 29, 2026
603f2ec
none matching
hiddewie May 29, 2026
208c6e2
construction dasharray around line
hiddewie May 30, 2026
9351cbd
low/med zooms construction train protection
hiddewie May 30, 2026
e4cc960
legend keys
hiddewie May 30, 2026
fe88b81
ranks in tests
hiddewie May 30, 2026
cc4881d
construction train protection only single value
hiddewie May 30, 2026
31656f0
API test
hiddewie May 30, 2026
a18cdb0
import
hiddewie May 30, 2026
7566d8f
remove debug info
hiddewie May 30, 2026
6a04bb2
fix missing matching on matching legend keys
hiddewie May 30, 2026
938c482
med zooms not showing present lines
hiddewie May 30, 2026
7cede92
do not render bridge and tunnel casing for construction casing
hiddewie May 30, 2026
ca90c77
exclude ETCS from KTCS, exclude PTC from ACSES, ASES, ITCS, ETMS and …
hiddewie May 30, 2026
0b84872
tests
hiddewie May 30, 2026
5fbc505
split off low zooms into construction layer, tweak dashes
hiddewie May 31, 2026
1c6ec54
bridges and tunnels
hiddewie May 31, 2026
10931bf
Merge branch 'master' into multiple-train-protection-systems
hiddewie Jun 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion api/test/api.hurl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions features/schema/train_protection.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 15 additions & 57 deletions features/train_protection.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,18 @@ 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:

- train_protection: ktcs
tags:
- { tag: 'railway:ktcs', values: ['yes', '1', '2', 'M'] }
exclude:
- etcs
- etcs_1
- etcs_2

- train_protection: etcs
tags:
Expand All @@ -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:
Expand Down Expand Up @@ -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' }
6 changes: 3 additions & 3 deletions import/openrailwaymap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down Expand Up @@ -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,
Expand Down
70 changes: 61 additions & 9 deletions import/sql/tile_views.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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"
Expand All @@ -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
Expand Down
29 changes: 25 additions & 4 deletions import/tags.lua.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => `
Expand Down
52 changes: 51 additions & 1 deletion import/test/test_import_railway_line.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
},
})
5 changes: 5 additions & 0 deletions martin/configuration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading