Skip to content

Commit 7c3bf54

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

9 files changed

Lines changed: 188 additions & 62 deletions

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ jobs:
2727
- name: Build Docker image
2828
run: ${{ github.workspace }}/build_docker_image.sh
2929

30+
- name: Fetch tram infrastructure link dump
31+
shell: bash
32+
run: |
33+
curl https://stjore4dev001.blob.core.windows.net/jore4-ui/tram_infraLinks_2026-01-28_2026-06-15.sql -o sql/tram_infraLinks.sql
34+
3035
- name: Import Digiroad shapefiles
3136
run: ${{ github.workspace }}/import_digiroad_shapefiles.sh
3237

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: 80 additions & 50 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,67 +88,79 @@ 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 (
88-
infrastructure_link_id uuid NOT NULL,
89-
vehicle_submode text NOT NULL
90-
);
91-
92-
CREATE TABLE IF NOT EXISTS infrastructure_network.infrastructure_link (
93-
infrastructure_link_id uuid DEFAULT public.gen_random_uuid() NOT NULL,
94-
direction text NOT NULL,
95-
shape public.geography(LineStringZ,4326) NOT NULL,
96-
estimated_length_in_metres double precision,
97-
external_link_id text NOT NULL,
98-
external_link_source text NOT NULL
99-
);
120+
DO $$
121+
BEGIN
122+
IF to_regclass('infrastructure_network.infrastructure_link') IS NULL THEN
123+
CREATE TABLE infrastructure_network.infrastructure_link (
124+
infrastructure_link_id uuid DEFAULT gen_random_uuid() NOT NULL,
125+
direction text NOT NULL,
126+
shape public.geography(LineStringZ,4326) NOT NULL,
127+
estimated_length_in_metres double precision,
128+
external_link_id text NOT NULL,
129+
external_link_source text NOT NULL
130+
);
131+
132+
ALTER TABLE ONLY infrastructure_network.infrastructure_link
133+
ADD CONSTRAINT infrastructure_link_pkey PRIMARY KEY (infrastructure_link_id);
134+
135+
CREATE INDEX infrastructure_link_external_source_link_id_idx ON infrastructure_network.infrastructure_link USING btree (external_link_source, external_link_id);
136+
END IF;
137+
END $$;
138+
139+
DO $$
140+
BEGIN
141+
IF to_regclass('infrastructure_network.vehicle_submode_on_infrastructure_link') IS NULL THEN
142+
CREATE TABLE infrastructure_network.vehicle_submode_on_infrastructure_link (
143+
infrastructure_link_id uuid NOT NULL,
144+
vehicle_submode text NOT NULL
145+
);
146+
147+
ALTER TABLE ONLY infrastructure_network.vehicle_submode_on_infrastructure_link
148+
ADD CONSTRAINT vehicle_submode_on_infrastructure_link_pkey PRIMARY KEY (infrastructure_link_id, vehicle_submode);
149+
150+
ALTER TABLE ONLY infrastructure_network.vehicle_submode_on_infrastructure_link
151+
ADD CONSTRAINT vehicle_submode_on_infrastructure_link_infrastructure_link_id_f FOREIGN KEY (infrastructure_link_id) REFERENCES infrastructure_network.infrastructure_link(infrastructure_link_id) ON UPDATE CASCADE ON DELETE CASCADE;
152+
153+
CREATE INDEX vehicle_submode_on_infrastruc_vehicle_submode_infrastructur_idx ON infrastructure_network.vehicle_submode_on_infrastructure_link USING btree (vehicle_submode, infrastructure_link_id);
154+
END IF;
155+
END $$;
100156
101157
-- 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;
158+
INSERT INTO infrastructure_network.external_source VALUES ({external_link_source_sql}) ON CONFLICT DO NOTHING;
103159
104160
""")
105161

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-
131162
for f in features:
132-
if f['kohdeluokka'] not in (14141, 14142):
163+
if f['kohdeluokka'] not in tram_kohdeluokkat:
133164
non_tram += 1
134165
continue
135166

@@ -189,16 +220,15 @@ def flush_batch():
189220

190221
flush_batch()
191222

192-
193-
lines.append("""
223+
lines.append(f"""
194224
-- Select all infralink ids from 'infrastructure_link' table and insert them to the 'vehicle_submode_on_infrastructure_link' table
195225
-- along with static 'generic_tram' vehicle submode info
196226
197227
INSERT INTO infrastructure_network.vehicle_submode_on_infrastructure_link
198228
SELECT il.infrastructure_link_id,
199229
'generic_tram' AS vehicle_submode
200230
FROM infrastructure_network.infrastructure_link il
201-
WHERE il.external_link_source = 'temp_hsl_tram'
231+
WHERE il.external_link_source = {external_link_source_sql}
202232
ON CONFLICT DO NOTHING;
203233
204234

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.

0 commit comments

Comments
 (0)