Skip to content

Commit 7cedc12

Browse files
committed
Tram infralinks to routing schema
1 parent f2d8e5b commit 7cedc12

9 files changed

Lines changed: 154 additions & 51 deletions

.github/workflows/test-import-export.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ jobs:
2424
- name: Checkout code
2525
uses: actions/checkout@v6
2626

27+
- name: Fetch tram infrastructure link dump
28+
shell: bash
29+
run: |
30+
curl https://stjore4dev001.blob.core.windows.net/jore4-ui/tram_infraLinks_2026-01-28.sql -o sql/tram_infraLinks.sql
31+
2732
- name: Build Docker image
2833
run: ${{ github.workspace }}/build_docker_image.sh
2934

export_mbtiles_tram_links.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ time docker_exec "$CURRUSER" \
7272
external_link_id AS link_id,\
7373
ST_Force2D(shape::geometry) AS geom \
7474
FROM infrastructure_network.infrastructure_link \
75-
WHERE external_link_source = 'temp_hsl_tram'\" \
75+
WHERE external_link_source = 'hsl_tram'\" \
7676
-nln $MBTILES_LAYER_NAME"
7777

7878
# Convert from GeoJSON to MBTiles.

generate-tram-infralinks-from-qgis.py

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,42 @@
3030
output_path = '/tmp/tram_infraLinks.sql'
3131
table_name = 'infrastructure_network.infrastructure_link'
3232
direction = 'bidirectional'
33-
external_link_source = 'temp_hsl_tram'
33+
# Use temp_hsl_tram for tram_infraLinks.sql generation, hsl_tram for dev database imports.
34+
external_link_source = 'hsl_tram'
35+
# 14111, 14112 - Rautatie
36+
# 14121 - Kapearaiteinen rautatie
37+
# 14131 - Metro
38+
# 14141 - Raitiotie, erillinen raitioliikenteen väylä
39+
# 14142 - Raitiotie, yhdistetty raide- ja ajoneuvoliikenteen väylä
40+
# 14151 - Pikaraitiotie, erillinen raitioliikenteen väylä
41+
# 14152 - Pikaraitiotie, yhdistetty raide- ja ajoneuvoliikenteen väylä
42+
tram_kohdeluokkat = (14141, 14142, 14151, 14152)
3443
id_field = 'mtk_id'
3544
batch_size = 50
3645

3746
layer = iface.activeLayer()
3847
if layer is None:
3948
raise RuntimeError('No active layer.')
49+
field_names = layer.fields().names()
50+
51+
def sql_quote(val):
52+
if val is None:
53+
return 'NULL'
54+
return "'" + str(val).replace("'", "''") + "'"
4055

4156
proj = QgsProject.instance()
4257
src_crs = layer.crs()
4358
dst_crs = QgsCoordinateReferenceSystem('EPSG:4326')
4459
transform = QgsCoordinateTransform(src_crs, dst_crs, proj)
60+
external_link_source_sql = sql_quote(external_link_source)
61+
62+
# Diagnostics
63+
written = 0 # rows added to VALUES
64+
seen = 0 # features iterated
65+
failed_transform = 0
66+
skipped_empty = 0
67+
skipped_non_line = 0
68+
non_tram = 0
4569

4670
# Ellipsoidal length calculator on GRS80 (EPSG:7019)
4771
dist = QgsDistanceArea()
@@ -54,11 +78,6 @@
5478
if not ok:
5579
dist.setEllipsoid('GRS80') # fallback
5680

57-
def sql_quote(val):
58-
if val is None:
59-
return 'NULL'
60-
return "'" + str(val).replace("'", "''") + "'"
61-
6281
# Determine feature source: selected or full layer
6382
selected_count = layer.selectedFeatureCount()
6483
if selected_count > 0:
@@ -69,28 +88,42 @@ def sql_quote(val):
6988
features = list(layer.getFeatures())
7089
export_count = layer.featureCount()
7190

91+
batch_values = []
92+
93+
def flush_batch():
94+
if not batch_values:
95+
return
96+
values_sql = ',\n '.join(batch_values)
97+
insert_sql = (
98+
f"INSERT INTO {table_name} "
99+
"(infrastructure_link_id, direction, shape, estimated_length_in_metres, external_link_id, external_link_source)\n"
100+
f"VALUES\n {values_sql};"
101+
)
102+
lines.append(insert_sql)
103+
batch_values.clear()
104+
72105
lines = []
73106
lines.append('-- SQL export generated by QGIS Python console')
74107
lines.append(f'-- Source layer: {layer.name()}')
75108
lines.append(f'-- Exported features (source): {export_count}')
76109
lines.append('BEGIN;')
77110

78111
# Add infralink type
79-
lines.append("""
112+
lines.append(f"""
80113
-- Create schema and tables if they don't exist. Needed in digiroad-import repo when generating mbtiles.
81114
CREATE SCHEMA IF NOT EXISTS infrastructure_network;
82115
83116
CREATE TABLE IF NOT EXISTS infrastructure_network.external_source (
84117
value text NOT NULL
85118
);
86119
87-
CREATE TABLE infrastructure_network.vehicle_submode_on_infrastructure_link (
120+
CREATE TABLE IF NOT EXISTS infrastructure_network.vehicle_submode_on_infrastructure_link (
88121
infrastructure_link_id uuid NOT NULL,
89122
vehicle_submode text NOT NULL
90123
);
91124
92125
CREATE TABLE IF NOT EXISTS infrastructure_network.infrastructure_link (
93-
infrastructure_link_id uuid DEFAULT public.gen_random_uuid() NOT NULL,
126+
infrastructure_link_id uuid DEFAULT gen_random_uuid() NOT NULL,
94127
direction text NOT NULL,
95128
shape public.geography(LineStringZ,4326) NOT NULL,
96129
estimated_length_in_metres double precision,
@@ -99,37 +132,12 @@ def sql_quote(val):
99132
);
100133
101134
-- Add the temporary link external source type is it doesn't exist
102-
INSERT INTO infrastructure_network.external_source VALUES ('temp_hsl_tram') ON CONFLICT DO NOTHING;
135+
INSERT INTO infrastructure_network.external_source VALUES ({external_link_source_sql}) ON CONFLICT DO NOTHING;
103136
104137
""")
105138

106-
107-
batch_values = []
108-
109-
# Diagnostics
110-
written = 0 # rows added to VALUES
111-
seen = 0 # features iterated
112-
failed_transform = 0
113-
skipped_empty = 0
114-
skipped_non_line = 0
115-
non_tram = 0
116-
117-
def flush_batch():
118-
if not batch_values:
119-
return
120-
values_sql = ',\n '.join(batch_values)
121-
insert_sql = (
122-
f"INSERT INTO {table_name} "
123-
"(infrastructure_link_id, direction, shape, estimated_length_in_metres, external_link_id, external_link_source)\n"
124-
f"VALUES\n {values_sql};"
125-
)
126-
lines.append(insert_sql)
127-
batch_values.clear()
128-
129-
field_names = layer.fields().names()
130-
131139
for f in features:
132-
if f['kohdeluokka'] not in (14141, 14142):
140+
if f['kohdeluokka'] not in tram_kohdeluokkat:
133141
non_tram += 1
134142
continue
135143

@@ -189,16 +197,15 @@ def flush_batch():
189197

190198
flush_batch()
191199

192-
193-
lines.append("""
200+
lines.append(f"""
194201
-- Select all infralink ids from 'infrastructure_link' table and insert them to the 'vehicle_submode_on_infrastructure_link' table
195202
-- along with static 'generic_tram' vehicle submode info
196203
197204
INSERT INTO infrastructure_network.vehicle_submode_on_infrastructure_link
198205
SELECT il.infrastructure_link_id,
199206
'generic_tram' AS vehicle_submode
200207
FROM infrastructure_network.infrastructure_link il
201-
WHERE il.external_link_source = 'temp_hsl_tram'
208+
WHERE il.external_link_source = {external_link_source_sql}
202209
ON CONFLICT DO NOTHING;
203210
204211

import_digiroad_shapefiles.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ set -euo pipefail
66
# Source common environment variables and functions.
77
source "$(dirname "$0")/set_env.sh"
88

9+
TRAM_INFRALINKS_SQL="sql/tram_infraLinks.sql"
10+
TRAM_INFRALINKS_SQL_LOCAL="${CWD}/${TRAM_INFRALINKS_SQL}"
11+
if [[ ! -f "$TRAM_INFRALINKS_SQL_LOCAL" ]]; then
12+
echo "Required tram infralink SQL file does not exist: $TRAM_INFRALINKS_SQL_LOCAL" >&2
13+
exit 1
14+
fi
15+
TRAM_INFRALINKS_SQL_DOCKER="/tmp/$TRAM_INFRALINKS_SQL"
16+
917
AREA="UUSIMAA"
1018

1119
SHP_URL="https://aineistot.vayla.fi/?path=ava/Tie/Digiroad/Aineistojulkaisut/latest/Maakuntajako_digiroad_R/${AREA}.zip"
@@ -107,6 +115,15 @@ docker_exec postgres "exec $PSQL -v ON_ERROR_STOP=1 -f /tmp/sql/transform_dr_pys
107115
# Create SQL views combining Digiroad links and public transport stops with fixup layers from GeoPackage file.
108116
docker_exec postgres "exec $PSQL -v ON_ERROR_STOP=1 -f /tmp/sql/apply_fixup_layer.sql -v schema=$DB_SCHEMA_NAME_DIGIROAD"
109117

118+
# Import HSL tram infrastructure links. These are loaded separately from Digiroad
119+
# links so that the GeoPackage fixup layer applies to Digiroad (bus) links only.
120+
# `pgcrypto` provides `gen_random_uuid()` referenced by the staging table.
121+
docker_exec postgres "exec $PSQL -v ON_ERROR_STOP=1 -c 'CREATE EXTENSION IF NOT EXISTS pgcrypto;'"
122+
docker_exec postgres "exec $PSQL -v ON_ERROR_STOP=1 -f $TRAM_INFRALINKS_SQL_DOCKER"
123+
124+
# Transform tram links into the Digiroad schema (reproject to EPSG:3067).
125+
docker_exec postgres "exec $PSQL -v ON_ERROR_STOP=1 -f /tmp/sql/transform_tram_links.sql -v schema=$DB_SCHEMA_NAME_DIGIROAD"
126+
110127
# Process turn restrictions and filter properties in database.
111128
docker_exec postgres "exec $PSQL -v ON_ERROR_STOP=1 -f /tmp/sql/transform_dr_kaantymisrajoitus.sql -v schema=$DB_SCHEMA_NAME_DIGIROAD"
112129

sql/apply_fixup_layer.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-- Add internal ID for SQL view. Values will be derived from the primary key of GeoPackage layer
22
-- (`fid`).
3-
ALTER TABLE :schema.fix_layer_link ADD COLUMN internal_id int;
3+
ALTER TABLE :schema.fix_layer_link ADD COLUMN internal_id bigint;
44

55
-- Force separate ID value spaces for custom fixup links.
66
--

sql/routing/create_routing_schema.sql

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ COMMENT ON COLUMN :schema.infrastructure_source.infrastructure_source_name IS
4646
INSERT INTO :schema.infrastructure_source (infrastructure_source_id, infrastructure_source_name, description) VALUES
4747
(1, 'digiroad_r_mml', 'Infrastructure links from the MML Maastotietokanta. The data is from the Digiroad R export and has been made available by Finnish Transport Infrastructure Agency (https://vayla.fi)'),
4848
(2, 'digiroad_r_supplementary', 'Digiroad''s supplementary infrastructure links. The data is from the Digiroad R export and has been made available by Finnish Transport Infrastructure Agency (https://vayla.fi)'),
49-
(100, 'hsl_fixup', 'HSL''s infrastructure link and stop point customisations on top of Digiroad infrastructure network');
49+
(100, 'hsl_fixup', 'HSL''s infrastructure link and stop point customisations on top of Digiroad infrastructure network'),
50+
(200, 'hsl_tram', 'HSL''s tram infrastructure links derived from the MML tram network');
5051

5152
--
5253
-- Import infrastructure links
@@ -88,7 +89,24 @@ WHERE
8889
-- 15, -- Erikoiskuljetusyhteys puomilla
8990
21, -- Lossi
9091
99 -- Ei tietoa (esiintyy vain rakenteilla olevilla tielinkeillä)
91-
);
92+
)
93+
UNION ALL
94+
-- Tram infrastructure links from the HSL tram network. These are imported
95+
-- separately from Digiroad links and are therefore not affected by the
96+
-- GeoPackage fixup layer, which applies to Digiroad (bus) links only.
97+
SELECT
98+
t.id::bigint AS infrastructure_link_id,
99+
isrc.infrastructure_source_id,
100+
t.link_id::text AS external_link_id,
101+
dir.traffic_flow_direction_type,
102+
NULL AS municipality_code,
103+
NULL AS external_link_type,
104+
3 AS external_link_state,
105+
NULL AS name,
106+
ST_Force3D(t.geom) AS geom_3d
107+
FROM :source_schema.hsl_tram_linkki t
108+
INNER JOIN :schema.traffic_flow_direction dir ON dir.traffic_flow_direction_type = t.ajosuunta
109+
INNER JOIN :schema.infrastructure_source isrc ON isrc.infrastructure_source_name = 'hsl_tram';
92110

93111
COMMENT ON TABLE :schema.infrastructure_link IS
94112
'The infrastructure links, e.g. road or rail elements: https://www.transmodel-cen.eu/model/index.htm?goto=2:1:1:1:453';
@@ -181,32 +199,38 @@ CREATE INDEX ON :schema.infrastructure_link_safely_traversed_by_vehicle_type (ve
181199
INSERT INTO :schema.infrastructure_link_safely_traversed_by_vehicle_type (infrastructure_link_id, vehicle_type)
182200
SELECT lnk.infrastructure_link_id, 'generic_bus'
183201
FROM :schema.infrastructure_link lnk
184-
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id::int
202+
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id
185203
WHERE src_lnk.is_generic_bus = true
186204
UNION
187205
SELECT lnk.infrastructure_link_id, 'tall_electric_bus'
188206
FROM :schema.infrastructure_link lnk
189-
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id::int
207+
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id
190208
WHERE src_lnk.is_tall_electric_bus = true
191209
UNION
192210
SELECT lnk.infrastructure_link_id, 'generic_tram'
193211
FROM :schema.infrastructure_link lnk
194-
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id::int
212+
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id
195213
WHERE src_lnk.is_tram = true
214+
UNION
215+
-- Tram links imported from the HSL tram network are traversable by trams.
216+
SELECT lnk.infrastructure_link_id, 'generic_tram'
217+
FROM :schema.infrastructure_link lnk
218+
INNER JOIN :schema.infrastructure_source isrc ON isrc.infrastructure_source_id = lnk.infrastructure_source_id
219+
WHERE isrc.infrastructure_source_name = 'hsl_tram'
196220
UNION
197221
SELECT lnk.infrastructure_link_id, 'generic_train'
198222
FROM :schema.infrastructure_link lnk
199-
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id::int
223+
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id
200224
WHERE src_lnk.is_train = true
201225
UNION
202226
SELECT lnk.infrastructure_link_id, 'generic_metro'
203227
FROM :schema.infrastructure_link lnk
204-
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id::int
228+
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id
205229
WHERE src_lnk.is_metro = true
206230
UNION
207231
SELECT lnk.infrastructure_link_id, 'generic_ferry'
208232
FROM :schema.infrastructure_link lnk
209-
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id::int
233+
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id
210234
WHERE src_lnk.is_ferry = true;
211235

212236
--

sql/transform_dr_linkki.sql

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ WHERE
4747
ALTER TABLE :schema.dr_linkki_out RENAME column gid TO id;
4848

4949
-- Replace input table with transformed output.
50-
DROP TABLE :schema.dr_linkki;
50+
--DROP TABLE :schema.dr_linkki;
51+
DROP TABLE IF EXISTS :schema.dr_linkki_orig;
52+
ALTER TABLE :schema.dr_linkki RENAME TO dr_linkki_orig;
53+
ALTER TABLE :schema.dr_linkki_orig DROP CONSTRAINT IF EXISTS dr_linkki_pkey;
54+
ALTER TABLE :schema.dr_linkki_orig ADD CONSTRAINT dr_linkki_orig_pkey PRIMARY KEY (gid);
5155
ALTER TABLE :schema.dr_linkki_out RENAME TO dr_linkki;
5256

5357
-- Add data integrity constraints.

sql/transform_dr_pysakki.sql

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ ALTER TABLE :schema.dr_pysakki_out
2727
ALTER TABLE :schema.dr_pysakki_out RENAME COLUMN gid TO id;
2828

2929
-- Replace input table with transformed output.
30-
DROP TABLE :schema.dr_pysakki;
30+
DROP TABLE IF EXISTS :schema.dr_pysakki_orig;
31+
-- Drop the primary key created by shp2pgsql so its index name does not
32+
-- conflict with the constraint we add below after the rename.
33+
ALTER TABLE :schema.dr_pysakki DROP CONSTRAINT IF EXISTS dr_pysakki_pkey;
34+
ALTER TABLE :schema.dr_pysakki RENAME TO dr_pysakki_orig;
35+
ALTER TABLE :schema.dr_pysakki_orig ADD CONSTRAINT dr_pysakki_orig_pkey PRIMARY KEY (gid);
3136
ALTER TABLE :schema.dr_pysakki_out RENAME TO dr_pysakki;
3237

3338
-- Add data integrity constraints.

sql/transform_tram_links.sql

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
-- Transform tram infrastructure links into the Digiroad schema.
2+
--
3+
-- The tram links are loaded by `tram_infraLinks.sql` into the staging table
4+
-- `infrastructure_network.infrastructure_link` as EPSG:4326 geography. Here they
5+
-- are projected to EPSG:3067 (the coordinate system used by Digiroad) and given
6+
-- integer IDs so that they can be consumed by the routing schema the same way as
7+
-- Digiroad links.
8+
--
9+
-- These links are intentionally kept separate from the Digiroad `dr_linkki`
10+
-- table so that the GeoPackage fixup layer (which applies to Digiroad bus links
11+
-- only) does not affect tram links.
12+
13+
DROP TABLE IF EXISTS :schema.hsl_tram_linkki;
14+
15+
CREATE TABLE :schema.hsl_tram_linkki AS
16+
SELECT
17+
-- Tram link IDs from MML are > 2_000_000_000.
18+
-- This keeps tram link IDs apart from Digiroad-originated IDs (< 1_000_000_000)
19+
-- and HSL fixup IDs (>= 1_000_000_000).
20+
src.external_link_id::bigint AS id,
21+
src.external_link_id::text AS link_id,
22+
CASE
23+
WHEN src.direction = 'bidirectional' THEN 2
24+
WHEN src.direction = 'backward' THEN 3
25+
WHEN src.direction = 'forward' THEN 4
26+
END AS ajosuunta,
27+
ST_Transform(src.shape::geometry, 3067) AS geom
28+
FROM infrastructure_network.infrastructure_link src
29+
WHERE src.external_link_source IN ('temp_hsl_tram', 'hsl_tram');
30+
31+
-- Add data integrity constraints.
32+
ALTER TABLE :schema.hsl_tram_linkki
33+
ALTER COLUMN id SET NOT NULL,
34+
ALTER COLUMN link_id SET NOT NULL,
35+
ALTER COLUMN ajosuunta SET NOT NULL,
36+
ALTER COLUMN geom SET NOT NULL,
37+
38+
ADD CONSTRAINT hsl_tram_linkki_pkey PRIMARY KEY (id),
39+
ADD CONSTRAINT uk_hsl_tram_linkki_link_id UNIQUE (link_id);
40+
41+
CREATE INDEX hsl_tram_linkki_geom_idx ON :schema.hsl_tram_linkki USING gist (geom);

0 commit comments

Comments
 (0)