Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .github/workflows/test-import-export.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ jobs:
- name: Build Docker image
run: ${{ github.workspace }}/build_docker_image.sh

- name: Fetch tram infrastructure link dump
shell: bash
run: |
curl https://stjore4dev001.blob.core.windows.net/jore4-ui/tram_infraLinks_2026-01-28_2026-06-15.sql -o sql/tram_infraLinks.sql

- name: Import Digiroad shapefiles
run: ${{ github.workspace }}/import_digiroad_shapefiles.sh

Expand Down
2 changes: 1 addition & 1 deletion export_mbtiles_tram_links.sh
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ time docker_exec "$CURRUSER" \
external_link_id AS link_id,\
ST_Force2D(shape::geometry) AS geom \
FROM infrastructure_network.infrastructure_link \
WHERE external_link_source = 'temp_hsl_tram'\" \
WHERE external_link_source = 'hsl_tram'\" \
-nln $MBTILES_LAYER_NAME"

# Convert from GeoJSON to MBTiles.
Expand Down
130 changes: 80 additions & 50 deletions generate-tram-infralinks-from-qgis.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,42 @@
output_path = '/tmp/tram_infraLinks.sql'
table_name = 'infrastructure_network.infrastructure_link'
direction = 'bidirectional'
external_link_source = 'temp_hsl_tram'
# Use temp_hsl_tram for tram_infraLinks.sql generation, hsl_tram for dev database imports.
external_link_source = 'hsl_tram'
# 14111, 14112 - Rautatie
# 14121 - Kapearaiteinen rautatie
# 14131 - Metro
# 14141 - Raitiotie, erillinen raitioliikenteen väylä
# 14142 - Raitiotie, yhdistetty raide- ja ajoneuvoliikenteen väylä
# 14151 - Pikaraitiotie, erillinen raitioliikenteen väylä
# 14152 - Pikaraitiotie, yhdistetty raide- ja ajoneuvoliikenteen väylä
tram_kohdeluokkat = (14141, 14142, 14151, 14152)
id_field = 'mtk_id'
batch_size = 50

layer = iface.activeLayer()
if layer is None:
raise RuntimeError('No active layer.')
field_names = layer.fields().names()

def sql_quote(val):
if val is None:
return 'NULL'
return "'" + str(val).replace("'", "''") + "'"

proj = QgsProject.instance()
src_crs = layer.crs()
dst_crs = QgsCoordinateReferenceSystem('EPSG:4326')
transform = QgsCoordinateTransform(src_crs, dst_crs, proj)
external_link_source_sql = sql_quote(external_link_source)

# Diagnostics
written = 0 # rows added to VALUES
seen = 0 # features iterated
failed_transform = 0
skipped_empty = 0
skipped_non_line = 0
non_tram = 0

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

def sql_quote(val):
if val is None:
return 'NULL'
return "'" + str(val).replace("'", "''") + "'"

# Determine feature source: selected or full layer
selected_count = layer.selectedFeatureCount()
if selected_count > 0:
Expand All @@ -69,67 +88,79 @@ def sql_quote(val):
features = list(layer.getFeatures())
export_count = layer.featureCount()

batch_values = []

def flush_batch():
if not batch_values:
return
values_sql = ',\n '.join(batch_values)
insert_sql = (
f"INSERT INTO {table_name} "
"(infrastructure_link_id, direction, shape, estimated_length_in_metres, external_link_id, external_link_source)\n"
f"VALUES\n {values_sql};"
)
lines.append(insert_sql)
batch_values.clear()

lines = []
lines.append('-- SQL export generated by QGIS Python console')
lines.append(f'-- Source layer: {layer.name()}')
lines.append(f'-- Exported features (source): {export_count}')
lines.append('BEGIN;')

# Add infralink type
lines.append("""
lines.append(f"""
-- Create schema and tables if they don't exist. Needed in digiroad-import repo when generating mbtiles.
CREATE SCHEMA IF NOT EXISTS infrastructure_network;

CREATE TABLE IF NOT EXISTS infrastructure_network.external_source (
value text NOT NULL
);

CREATE TABLE infrastructure_network.vehicle_submode_on_infrastructure_link (
infrastructure_link_id uuid NOT NULL,
vehicle_submode text NOT NULL
);

CREATE TABLE IF NOT EXISTS infrastructure_network.infrastructure_link (
infrastructure_link_id uuid DEFAULT public.gen_random_uuid() NOT NULL,
direction text NOT NULL,
shape public.geography(LineStringZ,4326) NOT NULL,
estimated_length_in_metres double precision,
external_link_id text NOT NULL,
external_link_source text NOT NULL
);
DO $$
BEGIN
IF to_regclass('infrastructure_network.infrastructure_link') IS NULL THEN
CREATE TABLE infrastructure_network.infrastructure_link (
infrastructure_link_id uuid DEFAULT gen_random_uuid() NOT NULL,
direction text NOT NULL,
shape public.geography(LineStringZ,4326) NOT NULL,
estimated_length_in_metres double precision,
external_link_id text NOT NULL,
external_link_source text NOT NULL
);

ALTER TABLE ONLY infrastructure_network.infrastructure_link
ADD CONSTRAINT infrastructure_link_pkey PRIMARY KEY (infrastructure_link_id);

CREATE INDEX infrastructure_link_external_source_link_id_idx ON infrastructure_network.infrastructure_link USING btree (external_link_source, external_link_id);
END IF;
END $$;

DO $$
BEGIN
IF to_regclass('infrastructure_network.vehicle_submode_on_infrastructure_link') IS NULL THEN
CREATE TABLE infrastructure_network.vehicle_submode_on_infrastructure_link (
infrastructure_link_id uuid NOT NULL,
vehicle_submode text NOT NULL
);

ALTER TABLE ONLY infrastructure_network.vehicle_submode_on_infrastructure_link
ADD CONSTRAINT vehicle_submode_on_infrastructure_link_pkey PRIMARY KEY (infrastructure_link_id, vehicle_submode);

ALTER TABLE ONLY infrastructure_network.vehicle_submode_on_infrastructure_link
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;

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);
END IF;
END $$;

-- Add the temporary link external source type is it doesn't exist
INSERT INTO infrastructure_network.external_source VALUES ('temp_hsl_tram') ON CONFLICT DO NOTHING;
INSERT INTO infrastructure_network.external_source VALUES ({external_link_source_sql}) ON CONFLICT DO NOTHING;

""")


batch_values = []

# Diagnostics
written = 0 # rows added to VALUES
seen = 0 # features iterated
failed_transform = 0
skipped_empty = 0
skipped_non_line = 0
non_tram = 0

def flush_batch():
if not batch_values:
return
values_sql = ',\n '.join(batch_values)
insert_sql = (
f"INSERT INTO {table_name} "
"(infrastructure_link_id, direction, shape, estimated_length_in_metres, external_link_id, external_link_source)\n"
f"VALUES\n {values_sql};"
)
lines.append(insert_sql)
batch_values.clear()

field_names = layer.fields().names()

for f in features:
if f['kohdeluokka'] not in (14141, 14142):
if f['kohdeluokka'] not in tram_kohdeluokkat:
non_tram += 1
continue

Expand Down Expand Up @@ -189,16 +220,15 @@ def flush_batch():

flush_batch()


lines.append("""
lines.append(f"""
-- Select all infralink ids from 'infrastructure_link' table and insert them to the 'vehicle_submode_on_infrastructure_link' table
-- along with static 'generic_tram' vehicle submode info

INSERT INTO infrastructure_network.vehicle_submode_on_infrastructure_link
SELECT il.infrastructure_link_id,
'generic_tram' AS vehicle_submode
FROM infrastructure_network.infrastructure_link il
WHERE il.external_link_source = 'temp_hsl_tram'
WHERE il.external_link_source = {external_link_source_sql}
ON CONFLICT DO NOTHING;


Expand Down
17 changes: 17 additions & 0 deletions import_digiroad_shapefiles.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ set -euo pipefail
# Source common environment variables and functions.
source "$(dirname "$0")/set_env.sh"

TRAM_INFRALINKS_SQL="sql/tram_infraLinks.sql"
TRAM_INFRALINKS_SQL_LOCAL="${CWD}/${TRAM_INFRALINKS_SQL}"
if [[ ! -f "$TRAM_INFRALINKS_SQL_LOCAL" ]]; then
echo "Required tram infralink SQL file does not exist: $TRAM_INFRALINKS_SQL_LOCAL" >&2
exit 1
fi
TRAM_INFRALINKS_SQL_DOCKER="/tmp/$TRAM_INFRALINKS_SQL"

AREA="UUSIMAA"

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

# Import HSL tram infrastructure links. These are loaded separately from Digiroad
# links so that the GeoPackage fixup layer applies to Digiroad (bus) links only.
# `pgcrypto` provides `gen_random_uuid()` referenced by the staging table.
docker_exec postgres "exec $PSQL -v ON_ERROR_STOP=1 -c 'CREATE EXTENSION IF NOT EXISTS pgcrypto;'"
docker_exec postgres "exec $PSQL -v ON_ERROR_STOP=1 -f $TRAM_INFRALINKS_SQL_DOCKER"

# Transform tram links into the Digiroad schema (reproject to EPSG:3067).
docker_exec postgres "exec $PSQL -v ON_ERROR_STOP=1 -f /tmp/sql/transform_tram_links.sql -v schema=$DB_SCHEMA_NAME_DIGIROAD"

# Process turn restrictions and filter properties in database.
docker_exec postgres "exec $PSQL -v ON_ERROR_STOP=1 -f /tmp/sql/transform_dr_kaantymisrajoitus.sql -v schema=$DB_SCHEMA_NAME_DIGIROAD"

Expand Down
2 changes: 1 addition & 1 deletion sql/apply_fixup_layer.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-- Add internal ID for SQL view. Values will be derived from the primary key of GeoPackage layer
-- (`fid`).
ALTER TABLE :schema.fix_layer_link ADD COLUMN internal_id int;
ALTER TABLE :schema.fix_layer_link ADD COLUMN internal_id bigint;

-- Force separate ID value spaces for custom fixup links.
--
Expand Down
40 changes: 32 additions & 8 deletions sql/routing/create_routing_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ COMMENT ON COLUMN :schema.infrastructure_source.infrastructure_source_name IS
INSERT INTO :schema.infrastructure_source (infrastructure_source_id, infrastructure_source_name, description) VALUES
(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)'),
(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)'),
(100, 'hsl_fixup', 'HSL''s infrastructure link and stop point customisations on top of Digiroad infrastructure network');
(100, 'hsl_fixup', 'HSL''s infrastructure link and stop point customisations on top of Digiroad infrastructure network'),
(200, 'hsl_tram', 'HSL''s tram infrastructure links derived from the MML tram network');

--
-- Import infrastructure links
Expand Down Expand Up @@ -88,7 +89,24 @@ WHERE
-- 15, -- Erikoiskuljetusyhteys puomilla
21, -- Lossi
99 -- Ei tietoa (esiintyy vain rakenteilla olevilla tielinkeillä)
);
)
UNION ALL
-- Tram infrastructure links from the HSL tram network. These are imported
-- separately from Digiroad links and are therefore not affected by the
-- GeoPackage fixup layer, which applies to Digiroad (bus) links only.
SELECT
t.id::bigint AS infrastructure_link_id,
isrc.infrastructure_source_id,
t.link_id::text AS external_link_id,
dir.traffic_flow_direction_type,
NULL AS municipality_code,
NULL AS external_link_type,
3 AS external_link_state,
NULL AS name,
ST_Force3D(t.geom) AS geom_3d
FROM :source_schema.hsl_tram_linkki t
INNER JOIN :schema.traffic_flow_direction dir ON dir.traffic_flow_direction_type = t.ajosuunta
INNER JOIN :schema.infrastructure_source isrc ON isrc.infrastructure_source_name = 'hsl_tram';

COMMENT ON TABLE :schema.infrastructure_link IS
'The infrastructure links, e.g. road or rail elements: https://www.transmodel-cen.eu/model/index.htm?goto=2:1:1:1:453';
Expand Down Expand Up @@ -181,32 +199,38 @@ CREATE INDEX ON :schema.infrastructure_link_safely_traversed_by_vehicle_type (ve
INSERT INTO :schema.infrastructure_link_safely_traversed_by_vehicle_type (infrastructure_link_id, vehicle_type)
SELECT lnk.infrastructure_link_id, 'generic_bus'
FROM :schema.infrastructure_link lnk
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id::int
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id
WHERE src_lnk.is_generic_bus = true
UNION
SELECT lnk.infrastructure_link_id, 'tall_electric_bus'
FROM :schema.infrastructure_link lnk
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id::int
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id
WHERE src_lnk.is_tall_electric_bus = true
UNION
SELECT lnk.infrastructure_link_id, 'generic_tram'
FROM :schema.infrastructure_link lnk
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id::int
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id
WHERE src_lnk.is_tram = true
UNION
-- Tram links imported from the HSL tram network are traversable by trams.
SELECT lnk.infrastructure_link_id, 'generic_tram'
FROM :schema.infrastructure_link lnk
INNER JOIN :schema.infrastructure_source isrc ON isrc.infrastructure_source_id = lnk.infrastructure_source_id
WHERE isrc.infrastructure_source_name = 'hsl_tram'
UNION
SELECT lnk.infrastructure_link_id, 'generic_train'
FROM :schema.infrastructure_link lnk
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id::int
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id
WHERE src_lnk.is_train = true
UNION
SELECT lnk.infrastructure_link_id, 'generic_metro'
FROM :schema.infrastructure_link lnk
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id::int
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id
WHERE src_lnk.is_metro = true
UNION
SELECT lnk.infrastructure_link_id, 'generic_ferry'
FROM :schema.infrastructure_link lnk
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id::int
INNER JOIN :source_schema.dr_linkki_fixup src_lnk ON src_lnk.id = lnk.infrastructure_link_id
WHERE src_lnk.is_ferry = true;

--
Expand Down
6 changes: 5 additions & 1 deletion sql/transform_dr_linkki.sql
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ WHERE
ALTER TABLE :schema.dr_linkki_out RENAME column gid TO id;

-- Replace input table with transformed output.
DROP TABLE :schema.dr_linkki;
--DROP TABLE :schema.dr_linkki;
DROP TABLE IF EXISTS :schema.dr_linkki_orig;
ALTER TABLE :schema.dr_linkki RENAME TO dr_linkki_orig;
ALTER TABLE :schema.dr_linkki_orig DROP CONSTRAINT IF EXISTS dr_linkki_pkey;
ALTER TABLE :schema.dr_linkki_orig ADD CONSTRAINT dr_linkki_orig_pkey PRIMARY KEY (gid);
ALTER TABLE :schema.dr_linkki_out RENAME TO dr_linkki;

-- Add data integrity constraints.
Expand Down
7 changes: 6 additions & 1 deletion sql/transform_dr_pysakki.sql
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ ALTER TABLE :schema.dr_pysakki_out
ALTER TABLE :schema.dr_pysakki_out RENAME COLUMN gid TO id;

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

-- Add data integrity constraints.
Expand Down
Loading
Loading