From 34c021531d0a7a98fd11b93a384ef79fe425ab53 Mon Sep 17 00:00:00 2001 From: Ryan Lambert Date: Fri, 2 Jan 2026 15:02:43 -0700 Subject: [PATCH 1/6] Add column for initial traffic model, seems to be decent average for limited Denver Colorado area testing. Starting point. --- db/data/roads-us.sql | 59 ++++++++++++++++++++++------------------ db/deploy/pgosm_road.sql | 3 ++ 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/db/data/roads-us.sql b/db/data/roads-us.sql index 7fd8812e..b4f9071b 100644 --- a/db/data/roads-us.sql +++ b/db/data/roads-us.sql @@ -1,31 +1,36 @@ -- Road lookup details for generic default- -- Region: United States -INSERT INTO pgosm.road (region, osm_type, route_motor, route_foot, route_cycle, maxspeed) - VALUES ('United States', 'motorway', True, False, False, 104.60736), - ('United States', 'motorway_link', True, False, False, 104.60736), - ('United States', 'trunk', True, False, True, 96.56064), - ('United States', 'trunk_link', True, False, True, 96.56064), - ('United States', 'primary', True, False, True, 96.56064), - ('United States', 'primary_link', True, False, True, 96.56064), - ('United States', 'secondary', True, False, True, 72.42048), - ('United States', 'secondary_link', True, False, True, 72.42048), - ('United States', 'tertiary', True, False, True, 72.42048), - ('United States', 'tertiary_link', True, False, True, 72.42048), - ('United States', 'residential', True, True, True, 40.2336), - ('United States', 'service', True, True, True, 40.2336), - ('United States', 'unclassified', True, True, True, 30), - ('United States', 'proposed', False, False, False, -1), - ('United States', 'planned', False, False, False, -1), - ('United States', 'path', False, True, True, 4), - ('United States', 'footway', False, True, False, 4), - ('United States', 'track', False, True, True, 2), - ('United States', 'pedestrian', False, True, False, 4), - ('United States', 'cycleway', False, True, True, 32), - ('United States', 'crossing', False, True, True, 2), - ('United States', 'platform', False, True, False, 2), - ('United States', 'social_path', False, True, False, 3), - ('United States', 'steps', False, True, False, 2), - ('United States', 'trailhead', False, True, True, 3) +INSERT INTO pgosm.road ( + region, osm_type, route_motor, route_foot, route_cycle, maxspeed + , traffic_penalty_normal + ) + VALUES ('United States', 'motorway', True, False, False, 104.60736, 0.75), + ('United States', 'motorway_link', True, False, False, 104.60736, 0.72), + ('United States', 'trunk', True, False, True, 96.56064, 0.75), + ('United States', 'trunk_link', True, False, True, 96.56064, 0.72), + ('United States', 'primary', True, False, True, 96.56064, 0.6), + ('United States', 'primary_link', True, False, True, 96.56064, 0.6), + ('United States', 'secondary', True, False, True, 72.42048, 0.6), + ('United States', 'secondary_link', True, False, True, 72.42048, 0.6), + ('United States', 'tertiary', True, False, True, 72.42048, 0.6), + ('United States', 'tertiary_link', True, False, True, 72.42048, 0.6), + ('United States', 'residential', True, True, True, 40.2336, 0.95), + ('United States', 'service', True, True, True, 40.2336, 0.95), + ('United States', 'unclassified', True, True, True, 30, 0.95), + ('United States', 'proposed', False, False, False, -1, 1.0), + ('United States', 'planned', False, False, False, -1, 1.0), + ('United States', 'path', False, True, True, 4, 1.0), + ('United States', 'footway', False, True, False, 4, 1.0), + ('United States', 'track', False, True, True, 2, 1.0), + ('United States', 'pedestrian', False, True, False, 4, 1.0), + ('United States', 'cycleway', False, True, True, 32, 0.95), + ('United States', 'crossing', False, True, True, 2, 0.3), + ('United States', 'platform', False, True, False, 2, 0.3), + ('United States', 'social_path', False, True, False, 3, 0.7), + ('United States', 'steps', False, True, False, 2, 0.9), + ('United States', 'trailhead', False, True, True, 3, 0.9) -- Doing nothing allows users to safely customize this table - ON CONFLICT DO NOTHING + ON CONFLICT (region, osm_type) DO UPDATE + SET maxspeed = EXCLUDED.maxspeed + , traffic_penalty_normal = EXCLUDED.traffic_penalty_normal ; diff --git a/db/deploy/pgosm_road.sql b/db/deploy/pgosm_road.sql index 9ebe169e..3b45214a 100644 --- a/db/deploy/pgosm_road.sql +++ b/db/deploy/pgosm_road.sql @@ -19,6 +19,9 @@ CREATE TABLE IF NOT EXISTS pgosm.road CONSTRAINT uq_pgosm_routable_code UNIQUE (region, osm_type) ); +ALTER TABLE pgosm.road + ADD COLUMN IF NOT EXISTS traffic_penalty_normal NUMERIC(3,2) NOT NULL DEFAULT 1.0 +; COMMENT ON TABLE pgosm.road IS 'Provides lookup information for road layers, generally related to routing use cases.'; COMMENT ON COLUMN pgosm.road.region IS 'Allows defining different definitions based on region. Can be custom defined.'; From 4cebacb75a8f9e8f5c257a41f68d84583a9765de Mon Sep 17 00:00:00 2001 From: Ryan Lambert Date: Sat, 3 Jan 2026 10:00:02 -0700 Subject: [PATCH 2/6] Expand pre-computed costs and add function for basic travel time routing. --- Dockerfile | 7 +- db/deploy/pgosm_road.sql | 5 +- db/deploy/routing_functions.sql | 173 +++++++++++++++++++++++++------- docs/src/routing-4.md | 4 +- 4 files changed, 144 insertions(+), 45 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7f2c8fc2..398b664b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -58,10 +58,9 @@ RUN git clone --depth 1 --branch $OSM2PGSQL_BRANCH $OSM2PGSQL_REPO \ RUN wget https://github.com/rustprooflabs/pgdd/releases/download/0.6.1/pgdd_0.6.1_postgis_pg18_amd64.deb \ && dpkg -i ./pgdd_0.6.1_postgis_pg18_amd64.deb \ && rm ./pgdd_0.6.1_postgis_pg18_amd64.deb \ - && wget https://github.com/rustprooflabs/convert/releases/download/0.0.5/convert_0.0.5_postgis_pg18_amd64.deb \ - && dpkg -i ./convert_0.0.5_postgis_pg18_amd64.deb \ - && rm ./convert_0.0.5_postgis_pg18_amd64.deb - + && wget https://github.com/rustprooflabs/convert/releases/download/0.1.0/convert_0.1.0_postgis_pg18_amd64.deb \ + && dpkg -i ./convert_0.1.0_postgis_pg18_amd64.deb \ + && rm ./convert_0.1.0_postgis_pg18_amd64.deb WORKDIR /app diff --git a/db/deploy/pgosm_road.sql b/db/deploy/pgosm_road.sql index 3b45214a..1dd70d4c 100644 --- a/db/deploy/pgosm_road.sql +++ b/db/deploy/pgosm_road.sql @@ -23,13 +23,14 @@ ALTER TABLE pgosm.road ADD COLUMN IF NOT EXISTS traffic_penalty_normal NUMERIC(3,2) NOT NULL DEFAULT 1.0 ; + COMMENT ON TABLE pgosm.road IS 'Provides lookup information for road layers, generally related to routing use cases.'; COMMENT ON COLUMN pgosm.road.region IS 'Allows defining different definitions based on region. Can be custom defined.'; COMMENT ON COLUMN pgosm.road.osm_type IS 'Value from highway tags.'; COMMENT ON COLUMN pgosm.road.route_motor IS 'Used to filter for classifications that typically allow motorized traffic.'; COMMENT ON COLUMN pgosm.road.route_foot IS 'Used to filter for classifications that typically allow foot traffic.'; COMMENT ON COLUMN pgosm.road.route_cycle IS 'Used to filter for classifications that typically allow bicycle traffic.'; -COMMENT ON COLUMN pgosm.road.maxspeed IS 'Maxspeed in km/hr'; -COMMENT ON COLUMN pgosm.road.maxspeed_mph IS 'Maxspeed in mph'; +COMMENT ON COLUMN pgosm.road.maxspeed IS 'Max speed in km/hr'; +COMMENT ON COLUMN pgosm.road.maxspeed_mph IS 'Max speed in mph. Calculated from max speed in km/hr.'; COMMIT; diff --git a/db/deploy/routing_functions.sql b/db/deploy/routing_functions.sql index 8f0058ec..1a9f303e 100644 --- a/db/deploy/routing_functions.sql +++ b/db/deploy/routing_functions.sql @@ -1,7 +1,8 @@ -CREATE OR REPLACE PROCEDURE {schema_name}.pgrouting_version_check() +CREATE OR REPLACE PROCEDURE {schema_name}.extension_version_check() LANGUAGE plpgsql AS $$ DECLARE pgr_ver TEXT; + DECLARE convert_version TEXT; BEGIN -- Ensure pgRouting extension exists with at least pgRouting 4.0 IF NOT EXISTS ( @@ -21,10 +22,28 @@ BEGIN pgr_ver; END IF; + -- Ensure convert extension exists with at least convert 0.1.0 + IF NOT EXISTS ( + SELECT 1 FROM pg_extension WHERE extname = 'convert' + ) THEN + RAISE EXCEPTION + 'The convert extension is not installed. Version >= 0.1.0 required.'; + END IF; + + SELECT extversion INTO convert_version FROM pg_extension WHERE extname = 'convert' + ; + + -- Enforce minimum version + IF string_to_array(convert_version, '.')::INT[] < ARRAY[0,1,0] THEN + RAISE EXCEPTION + 'Convert version % detected. Version >= 0.1.0 required.', + convert_version; + END IF; + END $$; -COMMENT ON PROCEDURE {schema_name}.pgrouting_version_check IS 'Ensures appropriate pgRouting extension is installed with an appropriate version.'; +COMMENT ON PROCEDURE {schema_name}.extension_version_check IS 'Ensures pgRouting and convert extensions are installed with appropriate versions.'; @@ -229,7 +248,7 @@ LANGUAGE plpgsql AS $$ BEGIN - CALL {schema_name}.pgrouting_version_check(); + CALL {schema_name}.extension_version_check(); --Create edges table for input to routing_prepare_edge_network procedure DROP TABLE IF EXISTS route_edge_input; @@ -263,23 +282,58 @@ BEGIN , tunnel TEXT , bridge TEXT , access TEXT - , geom GEOMETRY(LINESTRING) + , cost_length DOUBLE PRECISION + , cost_length_forward DOUBLE PRECISION NULL + , cost_length_reverse DOUBLE PRECISION NULL + , cost_motor_forward_s DOUBLE PRECISION NULL + , cost_motor_reverse_s DOUBLE PRECISION NULL + , geom GEOMETRY(LINESTRING) NOT NULL --, UNIQUE (osm_id, sub_id) -- Currently not enforceable... dups exist... ); INSERT INTO {schema_name}.routing_road_edge ( osm_id, sub_id, osm_type, name, ref, maxspeed, oneway, layer, tunnel, bridge, major , route_foot, route_cycle, route_motor, access - , geom + , cost_length + , geom -- Through geom is in initial query + -- Forward/reverse added next + , cost_length_forward, cost_length_reverse + -- Travel times added last. + , cost_motor_forward_s, cost_motor_reverse_s ) + WITH add_cost AS ( SELECT re.osm_id, re.sub_id , r.osm_type, r.name, r.ref, r.maxspeed , r.oneway, re.layer, r.tunnel, r.bridge, r.major , r.route_foot, r.route_cycle, r.route_motor, r.access + , ST_Length(ST_Transform(re.geom, 4326)::GEOGRAPHY) AS cost_length , re.geom FROM route_edges_output re INNER JOIN {schema_name}.road_line r ON re.osm_id = r.osm_id - ORDER BY re.geom + ), add_forward_reverse AS ( + SELECT a.* + , CASE WHEN a.oneway IN (0, 1) OR a.oneway IS NULL + THEN a.cost_length + WHEN a.oneway = -1 + THEN -1 * a.cost_length + END AS cost_length_forward + , CASE WHEN a.oneway IN (0, -1) OR a.oneway IS NULL + THEN a.cost_length + WHEN a.oneway = 1 + THEN -1 * a.cost_length + END AS cost_length_reverse + FROM add_cost a + ) + SELECT a.* + , convert.ttt_meters_km_hr_to_seconds( + a.cost_length_forward, COALESCE(a.maxspeed, r.maxspeed) + ) AS cost_motor_forward_s + , convert.ttt_meters_km_hr_to_seconds( + a.cost_length_forward, COALESCE(a.maxspeed, r.maxspeed) + ) AS cost_motor_reverse_s + FROM add_forward_reverse a + INNER JOIN pgosm.road r ON a.osm_type = r.osm_type + ORDER BY a.geom ; CREATE INDEX gix_{schema_name}_routing_road_edge @@ -289,41 +343,34 @@ BEGIN RAISE NOTICE 'Created table {schema_name}.routing_road_edge'; - - ALTER TABLE {schema_name}.routing_road_edge - ADD cost_length DOUBLE PRECISION NULL; - - UPDATE {schema_name}.routing_road_edge - SET cost_length = ST_Length(ST_Transform(geom, 4326)::GEOGRAPHY) - ; - COMMENT ON COLUMN {schema_name}.routing_road_edge.cost_length IS 'Length based cost calculated using GEOGRAPHY for accurate length.'; - - -- Add forward cost column, enforcing oneway restrictions - ALTER TABLE {schema_name}.routing_road_edge - ADD cost_length_forward NUMERIC - GENERATED ALWAYS AS ( - CASE WHEN oneway IN (0, 1) OR oneway IS NULL - THEN cost_length - WHEN oneway = -1 - THEN -1 * cost_length - END - ) - STORED + UPDATE {schema_name}.routing_road_edge + SET cost_length_forward = + CASE WHEN oneway IN (0, 1) OR oneway IS NULL + THEN cost_length + WHEN oneway = -1 + THEN -1 * cost_length + END + , cost_length_reverse = + CASE WHEN oneway IN (0, -1) OR oneway IS NULL + THEN cost_length + WHEN oneway = 1 + THEN -1 * cost_length + END ; - -- Add reverse cost column, enforcing oneway restrictions - ALTER TABLE {schema_name}.routing_road_edge - ADD cost_length_reverse NUMERIC - GENERATED ALWAYS AS ( - CASE WHEN oneway IN (0, -1) OR oneway IS NULL - THEN cost_length - WHEN oneway = 1 - THEN -1 * cost_length - END - ) - STORED + UPDATE {schema_name}.routing_road_edge e + SET cost_motor_forward_s = + convert.ttt_meters_km_hr_to_seconds( + cost_length_forward, COALESCE(e.maxspeed, r.maxspeed) + ) + , cost_motor_reverse_s = + convert.ttt_meters_km_hr_to_seconds( + cost_length_reverse, COALESCE(e.maxspeed, r.maxspeed) + ) + FROM pgosm.road r + WHERE e.osm_type = r.osm_type ; COMMENT ON COLUMN {schema_name}.routing_road_edge.cost_length_forward IS 'Length based cost for forward travel with directed routing. Based on cost_length value.'; @@ -392,7 +439,7 @@ LANGUAGE plpgsql AS $$ BEGIN - CALL {schema_name}.pgrouting_version_check(); + CALL {schema_name}.extension_version_check(); --Create edges table for input to routing_prepare_edge_network procedure DROP TABLE IF EXISTS route_edge_input; @@ -520,3 +567,53 @@ END $$; COMMENT ON PROCEDURE {schema_name}.routing_prepare_water_network IS 'Creates the {schema_name}.routing_water_edge and {schema_name}.routing_water_vertex from the {schema_name}.water_line input data'; + + + + +CREATE OR REPLACE FUNCTION {schema_name}.route_motor_travel_time( + route_vertex_id_start BIGINT, route_vertex_id_end BIGINT +) +RETURNS TABLE (segments BIGINT, vertex_ids BIGINT[], edge_ids BIGINT[] + , total_cost_seconds DOUBLE PRECISION, geom GEOMETRY +) +LANGUAGE plpgsql +ROWS 5 +AS $function$ + +BEGIN + + RETURN QUERY + WITH route_steps AS ( + SELECT d.node AS vertex_id + , d.edge AS edge_id + , d.cost + , n.geom AS node_geom, e.geom AS edge_geom + FROM pgr_dijkstra( + 'SELECT e.edge_id AS id + , e.vertex_id_source AS source + , e.vertex_id_target AS target + , e.cost_motor_forward_s AS cost + , e.cost_motor_reverse_s AS reverse_cost + , e.geom + FROM {schema_name}.routing_road_edge e + WHERE e.route_motor + ', + route_vertex_id_start, route_vertex_id_end, directed := True + ) d + INNER JOIN {schema_name}.routing_road_vertex n ON d.node = n.id + LEFT JOIN {schema_name}.routing_road_edge e ON d.edge = e.edge_id + ) + SELECT COUNT(*) AS segments + , ARRAY_AGG(vertex_id) AS vertex_ids + , ARRAY_AGG(edge_id) AS edge_ids + , SUM(cost) AS total_cost_seconds + , ST_Collect(edge_geom) AS geom + FROM route_steps +; +END +$function$ +; + +COMMENT ON FUNCTION {schema_name}.route_motor_travel_time IS 'Computes best route using ideal travel time costs. Does not account for traffic.'; + diff --git a/docs/src/routing-4.md b/docs/src/routing-4.md index 56ef4dcf..06400c43 100644 --- a/docs/src/routing-4.md +++ b/docs/src/routing-4.md @@ -2,10 +2,12 @@ > If you are using a pgRouting prior to 4.0 see [Routing with pgRouting 3](./routing-3.md). -Create the `pgRouting` extension. +Create the `pgRouting` and `convert extensions. + ```sql CREATE EXTENSION IF NOT EXISTS pgrouting; +CREATE EXTENSION IF NOT EXISTS convert; ``` From 95142bac8df69bbb3e5d3a6fac6c6f04906e4500 Mon Sep 17 00:00:00 2001 From: Ryan Lambert Date: Sun, 4 Jan 2026 08:00:32 -0700 Subject: [PATCH 3/6] Improving costs with traffic adjustment. Reworking routing docs to make the most common need (routing on roads) the easiest to get to. --- db/deploy/routing_functions.sql | 32 +-- docs/src/SUMMARY.md | 7 +- docs/src/routing-3.md | 2 +- docs/src/routing-4.md | 375 -------------------------------- docs/src/routing-process.md | 65 ++++++ docs/src/routing-road.md | 190 ++++++++++++++++ docs/src/routing-water.md | 105 +++++++++ docs/src/routing.md | 74 +++---- 8 files changed, 399 insertions(+), 451 deletions(-) delete mode 100644 docs/src/routing-4.md create mode 100644 docs/src/routing-process.md create mode 100644 docs/src/routing-road.md create mode 100644 docs/src/routing-water.md diff --git a/db/deploy/routing_functions.sql b/db/deploy/routing_functions.sql index 1a9f303e..e10ca483 100644 --- a/db/deploy/routing_functions.sql +++ b/db/deploy/routing_functions.sql @@ -326,10 +326,10 @@ BEGIN ) SELECT a.* , convert.ttt_meters_km_hr_to_seconds( - a.cost_length_forward, COALESCE(a.maxspeed, r.maxspeed) + a.cost_length_forward, COALESCE(a.maxspeed, r.maxspeed) * r.traffic_penalty_normal ) AS cost_motor_forward_s , convert.ttt_meters_km_hr_to_seconds( - a.cost_length_forward, COALESCE(a.maxspeed, r.maxspeed) + a.cost_length_reverse, COALESCE(a.maxspeed, r.maxspeed) * r.traffic_penalty_normal ) AS cost_motor_reverse_s FROM add_forward_reverse a INNER JOIN pgosm.road r ON a.osm_type = r.osm_type @@ -345,34 +345,6 @@ BEGIN COMMENT ON COLUMN {schema_name}.routing_road_edge.cost_length IS 'Length based cost calculated using GEOGRAPHY for accurate length.'; - UPDATE {schema_name}.routing_road_edge - SET cost_length_forward = - CASE WHEN oneway IN (0, 1) OR oneway IS NULL - THEN cost_length - WHEN oneway = -1 - THEN -1 * cost_length - END - , cost_length_reverse = - CASE WHEN oneway IN (0, -1) OR oneway IS NULL - THEN cost_length - WHEN oneway = 1 - THEN -1 * cost_length - END - ; - - UPDATE {schema_name}.routing_road_edge e - SET cost_motor_forward_s = - convert.ttt_meters_km_hr_to_seconds( - cost_length_forward, COALESCE(e.maxspeed, r.maxspeed) - ) - , cost_motor_reverse_s = - convert.ttt_meters_km_hr_to_seconds( - cost_length_reverse, COALESCE(e.maxspeed, r.maxspeed) - ) - FROM pgosm.road r - WHERE e.osm_type = r.osm_type - ; - COMMENT ON COLUMN {schema_name}.routing_road_edge.cost_length_forward IS 'Length based cost for forward travel with directed routing. Based on cost_length value.'; COMMENT ON COLUMN {schema_name}.routing_road_edge.cost_length_reverse IS 'Length based cost for reverse travel with directed routing. Based on cost_length value.'; diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index a3ff1bf1..45b6c2be 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -16,8 +16,11 @@ - [Data Files](./data-files.md) - [Query examples](./query.md) - [Routing](./routing.md) - - [pgRouting 3](./routing-3.md) - - [pgRouting 4](./routing-4.md) + - [Routing Roads](./routing-road.md) + - [Routing Water](./routing-water.md) + - [Routing Process](./routing-process.md) + - [Routing Roads: Legacy (pgRouting 3)](./routing-3.md) + - [Processing Time](./performance.md) # Production Use diff --git a/docs/src/routing-3.md b/docs/src/routing-3.md index 6b1c3ea3..b10fb5a0 100644 --- a/docs/src/routing-3.md +++ b/docs/src/routing-3.md @@ -1,4 +1,4 @@ -# Routing with pgRouting 3 +# Routing Roads: Legacy (pgRouting 3) If you are using a pgRouting 4.0 or later see [Routing with pgRouting 4](./routing-4.md). diff --git a/docs/src/routing-4.md b/docs/src/routing-4.md deleted file mode 100644 index 06400c43..00000000 --- a/docs/src/routing-4.md +++ /dev/null @@ -1,375 +0,0 @@ -# Routing with pgRouting 4 - -> If you are using a pgRouting prior to 4.0 see [Routing with pgRouting 3](./routing-3.md). - -Create the `pgRouting` and `convert extensions. - - -```sql -CREATE EXTENSION IF NOT EXISTS pgrouting; -CREATE EXTENSION IF NOT EXISTS convert; -``` - - -# Process the OpenStreetMap Roads - -For routing on `osm.road_line` data use the `osm.routing_prepare_road_network` -procedure to prepare the edge and vertex data used for routing. - -```sql -CALL osm.routing_prepare_road_network(); -``` - -The `osm.routing_prepare_road_network` procedure focuses on the most common use -cases of routing with the `osm.road_line` layer. It generates the edge/vertex -network for all data in the `osm.road_line` table. It generates accurate `cost_length` -by casting data to `GEOGRAPHY` and generates `cost_length_forward` -and `cost_length_reverse` to natively support directionally-enforced routing -without additional steps. - - -> ⚠️ The `osm.routing_prepare_road_network` procedure was added in PgOSM Flex 1.1.2 -> and is a significant deviation in routing preparation along with pgRouting 4.0. -> This procedure should be treated as a new feature with potential bugs lurking. - -This procedure was created as part of the migration to pgRouting 4.0, see -[#408](https://github.com/rustprooflabs/pgosm-flex/pull/408) for notes about -this. - - -## Timing for data preparation - -This section outlines a few timing references to help gauge how long this might -take for your region's data. Note these are generic timings using the built-in -database in the PgOSM Flex Docker image, without any tuning from default -configuration. Your tuning and your hardware will influence these timings. - -* D.C.: 18 seconds -* Colorado: 11.5 minutes - -The Colorado data set has 1.2M input roads resulting in 2.6M edges after splitting. - -``` -┌────────┬───────────────────┬─────────┐ -│ s_name │ t_name │ rows │ -╞════════╪═══════════════════╪═════════╡ -│ osm │ routing_road_edge │ 2560998 │ -│ osm │ road_line │ 1189678 │ -└────────┴───────────────────┴─────────┘ -``` - - -# Costs for Routing - -Routing uses a concept of cost to determine the best route to traverse. Cost can -be defined in a variety of methods. This section explores a few options. - -* `cost_length` -* `cost_length_forward` -* `cost_length_reverse` - - - -The procedure automatically generates a `cost_length` column using `GEOGRAPHY` -for accurate calculations in meters. - -The forward/reverse costs start with `cost_length` calculation and apply the -OpenStreetMap `oneway` restrictions. - - -## Costs Including One Way Restrictions - -Many real-world routing examples need to be aware of one-way travel restrictions. -The `oneway` column in PgOSM Flex's road tables uses -[osm2pgsql's `direction` data type](https://osm2pgsql.org/doc/manual.html#type-conversions). -This direction data type resolves to `int2` in Postgres. Valid values are: - -* `0`: Not one way -* `1`: One way, forward travel allowed -* `-1`: One way, reverse travel allowed -* `NULL`: It's complicated. See [#172](https://github.com/rustprooflabs/pgosm-flex/issues/172). - -> Forward and reverse cost columns are calculated in the `cost_length_forward` -> and `cost_length_reverse` columns within the `osm.routing_prepare_road_network()` procedure. - - -## Travel Time Costs - -With lengths and one-way already calculated per edge, speed limits can be used -to compute travel time costs. - -The `maxspeed` data in `pgosm.road` can be used to generate travel time costs -per segment. -[Chapter 16 of Mastering PostGIS and OpenStreetMap](https://book.postgis-osm.com/ch_pgrouting/improve-specific-routing.html) (paid content) provides examples of this approach. - - -# Determine route start and end - -The following query identifies the vertex IDs for a start and end point -to use for later queries. The query uses an input set of points -created from specific longitude/latitude values. -Use the `start_id` and `end_id` values from this query -in subsequent queries through the `:start_id` and `:end_id` variables -via DBeaver. - -> This query simulates a GUI allowing user to click on start/end points on a map, -> resulting in longitude and latitude values. - - -```sql -WITH s_point AS ( -SELECT v.id AS start_id, v.geom - FROM osm.routing_road_vertex v - INNER JOIN (SELECT - ST_Transform(ST_SetSRID(ST_MakePoint(-77.0211, 38.92255), 4326), 3857) - AS geom - ) p ON v.geom <-> p.geom < 20 - ORDER BY v.geom <-> p.geom - LIMIT 1 -), e_point AS ( -SELECT v.id AS end_id, v.geom - FROM osm.routing_road_vertex v - INNER JOIN (SELECT - ST_Transform(ST_SetSRID(ST_MakePoint(-77.0183, 38.9227), 4326), 3857) - AS geom - ) p ON v.geom <-> p.geom < 20 - ORDER BY v.geom <-> p.geom - LIMIT 1 -) -SELECT s_point.start_id, e_point.end_id - , s_point.geom AS geom_start - , e_point.geom AS geom_end - FROM s_point, e_point -; -``` - -```bash -┌──────────┬────────┐ -│ start_id │ end_id │ -╞══════════╪════════╡ -│ 14630 │ 14686 │ -└──────────┴────────┘ -``` - - -> Warning: The vertex IDs returned by the above query will vary. The pgRouting functions that generate this data do not guarantee data will always be generated in precisely the same order, causing these IDs to be different. - - -The vertex IDs returned were `14630` and `14686`. These points -span a particular segment of road (`osm_id = 6062791`) that is tagged -as `highway=residential` and `access=private`. -This segment is used to illustrate how the calculated access -control columns, `route_motor`, `route_cycle` and `route_foot`, -can influence route selection. - - - -```sql -SELECT * - FROM routing.road_line - WHERE osm_id = 6062791 -; -``` - -![Screenshot from QGIS showing two labeled points, 14630 and 14686. The road between the two points is shown with a light gray dash indicating the access tag indicates non-public travel.](dc-example-route-start-end-vertices.png) - -> See `flex-config/helpers.lua` functions (e.g. `routable_motor()`) for logic behind access control columns. - - - -# Route! - - -Using `pgr_dijkstra()` and no additional filters will -use all roads from OpenStreetMap without regard to mode of travel -or access rules. -This query picks a route that traverses sidewalks and -a section of road with the -[`access=private` tag from OpenStreetMap](https://wiki.openstreetmap.org/wiki/Tag:access%3Dprivate). -The key details to focus on in the following queries -is the string containing a SQL query passed into the `pgr_dijkstra()` -function. This first example is a simple query from the -`osm.routing_road_edge` table. - -> Note: These queries are intended to be ran using DBeaver. The `:start_id` and `:end_id` variables work within DBeaver, but not via `psql` or QGIS. Support in other GUIs is unknown at this time (PRs welcome!). - - -```sql -SELECT d.*, n.geom AS node_geom, e.geom AS edge_geom - FROM pgr_dijkstra( - 'SELECT e.edge_id AS id - , e.vertex_id_source AS source - , e.vertex_id_target AS target - , e.cost_length AS cost - , e.geom - FROM osm.routing_road_edge e - ', - :start_id, :end_id, directed := False - ) d - INNER JOIN osm.routing_road_vertex n ON d.node = n.id - LEFT JOIN osm.routing_road_edge e ON d.edge = e.edge_id -; -``` - - -![Screenshot from DBeaver showing the route generated with all roads and no access control. The route is direct, traversing the road marked access=private.](dc-example-route-start-no-access-control.png) - - - - -# Route motorized - -The following query modifies the query passed in to `pgr_dijkstra()` -to join the `osm.routing_road_edge` table to the -`routing.road_line` table. This allows using attributes available -in the upstream table for additional routing logic. -The join clause includes a filter on the `route_motor` column. - -From the comment on the `osm.road_line.route_motor` column: - -> "Best guess if the segment is route-able for motorized traffic. If access is no or private, set to false. WARNING: This does not indicate that this method of travel is safe OR allowed!" - -Based on this comment, we can expect that adding `AND route_motor` -into the filter will ensure the road type is suitable for motorized -traffic, and it excludes routes marked private. - - -```sql -SELECT d.*, n.geom AS node_geom, e.geom AS edge_geom - FROM pgr_dijkstra( - 'SELECT e.edge_id AS id - , e.vertex_id_source AS source - , e.vertex_id_target AS target - , e.cost_length AS cost, - e.geom - FROM osm.routing_road_edge e - WHERE e.route_motor - ', - :start_id, :end_id, directed := False - ) d - INNER JOIN osm.routing_road_vertex n ON d.node = n.id - LEFT JOIN osm.routing_road_edge e ON d.edge = e.edge_id -; -``` - - -![Screenshot from DBeaver showing the route generated with all roads and limiting based on route_motor. The route bypasses the road(s) marked access=no and access=private.](dc-example-route-start-motor-access-control.png) - -# One-way Aware Routing - -The route shown in the previous example now respects the -access control and limits to routes suitable for motorized traffic. -It, however, **did not** respect the one-way access control. -The very first segment (top-left corner of screenshot) went -the wrong way on a one-way street. -This behavior is a result of the simple length-based cost model. - - -This query uses the new reverse cost column, and changes -`directed` from `False` to `True`. -If you do not see the route shown in the screenshot below, try switching the -`:start_id` and `:end_id` values. - - -```sql -SELECT d.*, n.geom AS node_geom, e.geom AS edge_geom - FROM pgr_dijkstra( - 'SELECT e.edge_id AS id - , e.vertex_id_source AS source - , e.vertex_id_target AS target - , e.cost_length_forward AS cost - , e.cost_length_reverse AS reverse_cost - , e.geom - FROM osm.routing_road_edge e - WHERE e.route_motor - ', - :start_id, :end_id, directed := True - ) d - INNER JOIN osm.routing_road_vertex n ON d.node = n.id - LEFT JOIN osm.routing_road_edge e ON d.edge = e.edge_id -; -``` - -![Screenshot from DBeaver showing the route generated with all roads and limiting based on route_motor and using the improved cost model including forward and reverse costs. The route bypasses the road(s) marked access=no and access=private, as well as respects the one-way access controls.](dc-example-route-start-motor-access-control-oneway.png) - - -# Routing with Water - -PgOSM Flex also includes a procedure to prepare a routing network using -the `osm.water_line` table. - -```sql -CALL osm.routing_prepare_water_network(); -``` - -Find the `vertex_id` for start and end nodes, similar to approach above -with roads. - -```sql - -WITH s_point AS ( -SELECT v.id AS start_id, v.geom - FROM osm.routing_water_vertex v - INNER JOIN (SELECT - ST_Transform(ST_SetSRID(ST_MakePoint(-77.050625, 38.908953), 4326), 3857) - AS geom - ) p ON v.geom <-> p.geom < 200 - ORDER BY v.geom <-> p.geom - LIMIT 1 -), e_point AS ( -SELECT v.id AS end_id, v.geom - FROM osm.routing_water_vertex v - INNER JOIN (SELECT - ST_Transform(ST_SetSRID(ST_MakePoint(-77.055645, 38.888747), 4326), 3857) - AS geom - ) p ON v.geom <-> p.geom < 200 - ORDER BY v.geom <-> p.geom - LIMIT 1 -) -SELECT s_point.start_id, e_point.end_id - , s_point.geom AS geom_start - , e_point.geom AS geom_end - FROM s_point, e_point -; -``` - - -Route, using the directional approach. - -```sql -SELECT d.*, n.geom AS node_geom, e.geom AS edge_geom - FROM pgr_dijkstra( - 'SELECT e.edge_id AS id - , e.vertex_id_source AS source - , e.vertex_id_target AS target - , e.cost_length_forward AS cost - , e.cost_length_reverse AS reverse_cost - , e.geom - FROM osm.routing_water_edge e - ', - :start_id, :end_id, directed := True - ) d - INNER JOIN osm.routing_water_vertex n ON d.node = n.id - LEFT JOIN osm.routing_water_edge e ON d.edge = e.edge_id -; -``` - -![Example route using D.C. water layer.](dc-water-route-example.png) - -## Challenge: Polygons with Water Routing - -Waterway routing using lines only is often complicated by the nature of waterways -and the way routes flow through steams and rivers (lines) and also through ponds -and lakes (polygons). The data prepared by the above procedure only provides -the line-based functionality. - -The following image ([source](https://blog.rustprooflabs.com/2022/10/pgrouting-lines-through-polygons)) -visualizes the impact polygons can have on a line-only routing network for water routes. - -![Image alternates between showing / hiding water polygons, creating significant gaps in the routing network.](https://blog.rustprooflabs.com/static/images/water-polygons-are-important-for-routing.gif) - - - -See the [Routing with Lines through Polygons](https://blog.rustprooflabs.com/2022/10/pgrouting-lines-through-polygons) -blog post to explore one possible approach to solving this problem. - diff --git a/docs/src/routing-process.md b/docs/src/routing-process.md new file mode 100644 index 00000000..d191a0b2 --- /dev/null +++ b/docs/src/routing-process.md @@ -0,0 +1,65 @@ +# Routing Data and Process + +This page describes some of the processes involved in the routing edge network. + +## Length Based Costs + +The `osm.routing_prepare_road_network` procedure generates accurate `cost_length` +by casting data to `GEOGRAPHY` and generates `cost_length_forward` +and `cost_length_reverse` to natively support directionally-enforced routing +without additional steps. + +This procedure was created as part of the migration to pgRouting 4.0, see +[#408](https://github.com/rustprooflabs/pgosm-flex/pull/408) for notes about +the initial migration. + +> ⚠️ The routing procedures began to be added in PgOSM Flex 1.1.2 and continue to evolve. +> These procedures should be treated as a new feature with potential bugs lurking. + + + +## Costs Including One Way Restrictions + +Most real-world routing examples need to be aware of one-way travel restrictions. +The `oneway` column in PgOSM Flex's road tables (e.g. `osm.road_line`) uses +[osm2pgsql's `direction` data type](https://osm2pgsql.org/doc/manual.html#type-conversions). +This direction data type resolves to `int2` in Postgres. Valid values are: + +* `0`: Not one way +* `1`: One way, forward travel allowed +* `-1`: One way, reverse travel allowed +* `NULL`: It's complicated. See [#172](https://github.com/rustprooflabs/pgosm-flex/issues/172). + + +Forward and reverse cost columns are calculated in the `cost_length_forward` +and `cost_length_reverse` columns within the `osm.routing_prepare_road_network()` procedure. + + +## Travel Time Costs + +With lengths and one-way already calculated per edge, speed limits can be used +to compute travel time costs. The `osm.routing_prepare_road_network` procedure +calculate travel times in seconds into two `motor` travel focused columns. +The `osm.route_motor_travel_time()` function uses these costs to compute travel times. + +* `cost_motor_forward_s` +* `cost_motor_reverse_s` + + +The calculations use two sources of `maxspeed` to drive this logic. +The first source is the `maxspeed` value from each road segment from OpenStreetMap. +When that value is not set, the `maxspeed` value is used from the `pgosm.road` +lookup table based on `osm_type` (e.g. 'primary', 'residential') +packaged with PgOSM Flex. + +The `maxspeed` is multipled by `traffic_penalty_normal` to calculate a more realistic +travel time. The `traffic_penalty_normal` values can be between `0.0` (block routing entirely) +to `1.0` (no penalty). These values are pre-set in the `pgosm.road` table and can +be adjusted **before** running the `osm.routing_prepare_road_network` procedure +to use your adjusted values. + + +In most common routing scenarios this will under-report travel times due +to not considering for traffic signals, slowing down for corners, and traffic in +general. + diff --git a/docs/src/routing-road.md b/docs/src/routing-road.md new file mode 100644 index 00000000..d9a9ecd5 --- /dev/null +++ b/docs/src/routing-road.md @@ -0,0 +1,190 @@ +# Routing Roads + +PgOSM Flex makes it easy to get started with routing with OpenStreetMap data +and pgRouting. The best support is available with pgRouting 4.0 and newer. + +> If you are using a pgRouting prior to 4.0 see [Routing Roads: Legacy (pgRouting 3)](./routing-3.md). + + +## Prepare routing edge networks + +You should have ran the steps in [Prepare for Routing](routing.md#prepare-for-routing) +before continuing. + +---- + + +PgOSM Flex includes functions to prepare routing edge networks for data in +`osm.road_line` by running the appropriate procedure. +This procedure can take a while to run on larger regions, see the Timing section +below for more details. + +```sql +CALL osm.routing_prepare_road_network(); +``` + +You should see output similar to below. This example is to prepare the data for +Colorado and took 8 minutes in this example. This step takes roughly 20 seconds +to process for Washington D.C. + + +``` +NOTICE: Edge table table created +NOTICE: Nearby table created +NOTICE: Intersection table created +NOTICE: Blades created +NOTICE: Split edges created +NOTICE: Edge data in route_edges_output temp table. +NOTICE: Created table osm.routing_road_edge +NOTICE: Created table osm.routing_road_vertex from edges. +NOTICE: Edge table updated with vertex source/target details. +CALL +Time: 484294.179 ms (08:04.294) +``` + +The procedure creates two tables: + +* `osm.routing_road_edge` +* `osm.routing_road_vertex` + +These tables make up the routing network, built from data in `osm.road_line`. +The `_vertex` table has the points that can be used as start/end points for routes. +The `_edge` table has the edges (lines) that can be routed, along with their +costs and other access control measures. + + +> Do not make customizations to the tables generated by the procedures without +> renaming the tables to avoid data loss. +> +> Changes to OpenStreetMap data will not be reflected in the routing network +> until the procedure is ran again. This is not automated along with `--replication`. + + +# Routing Examples + +The data preparation procedures above handle the steps to create a routable edge +network with usable costs. + + +## Determine route start and end + +The following query identifies the vertex IDs from the `osm.routing_road_vertex` +table to use for start and end points. + +The query uses an input set of points +created from specific longitude/latitude values. +Make note of the `start_id` and `end_id` values from this query +to use in subsequent queries. The following queries are setup to run +within DBeaver using `:start_id` and `:end_id` variables for dynamic querying. + +> The query with Longitude/Latitude simulates a user clicking in a GUI map to set +> start and end points. This type of interaction typically results in +> longitude and latitude values. + + +```sql +WITH s_point AS ( +SELECT v.id AS start_id, v.geom + FROM osm.routing_road_vertex v + INNER JOIN (SELECT + ST_Transform(ST_SetSRID(ST_MakePoint(-77.0211, 38.92255), 4326), 3857) + AS geom + ) p ON v.geom <-> p.geom < 20 + ORDER BY v.geom <-> p.geom + LIMIT 1 +), e_point AS ( +SELECT v.id AS end_id, v.geom + FROM osm.routing_road_vertex v + INNER JOIN (SELECT + ST_Transform(ST_SetSRID(ST_MakePoint(-77.0183, 38.9227), 4326), 3857) + AS geom + ) p ON v.geom <-> p.geom < 20 + ORDER BY v.geom <-> p.geom + LIMIT 1 +) +SELECT s_point.start_id, e_point.end_id + , s_point.geom AS geom_start + , e_point.geom AS geom_end + FROM s_point, e_point +; +``` + +```bash +┌──────────┬────────┐ +│ start_id │ end_id │ +╞══════════╪════════╡ +│ 14630 │ 14686 │ +└──────────┴────────┘ +``` + + +> Warning: The vertex IDs returned by the above query will vary. The pgRouting functions that generate this data do not guarantee data will always be generated in precisely the same order, causing these IDs to be different. + + +## Route with Vertex Start/End IDs + +Routing using PgOSM Flex's built-in travel-time calculations is as simple +as running a function, passing in an ID for the start and end vertices. + +```sql +SELECT * + FROM osm.route_motor_travel_time(:vertex_start_id, :vertex_end_id) +; +``` + + +![Screenshot from DBeaver showing the route generated with all roads and limiting based on route_motor and using the improved cost model including forward and reverse costs. The route bypasses the road(s) marked access=no and access=private, as well as respects the one-way access controls.](dc-example-route-start-motor-access-control-oneway.png) + + +Beyond the route shown in the screenshot above the table below shows the route has +13 total segments and will take an estimated 73 seconds (1 minute, 13 seconds) +to travel. + + +``` +┌──────────┬────────────────────┐ +│ segments │ total_cost_seconds │ +╞══════════╪════════════════════╡ +│ 13 │ 73.00033290095476 │ +└──────────┴────────────────────┘ +``` + +This routing function uses the `cost_motor_forward_s` +and `cost_motor_reverse_s` columns, combined with +the `pgosm.road.traffic_penalty_normal` column, to compute travel time. + +> The `pgosm.road.traffic_penalty_normal` column can be adjusted to influence the +> timing model. Other penalty / cost models can also be investigated. + + + +---- + +# OLD NOTES + + + +# Routing Data Preparation Timing + + +This section outlines a few timing references for routing preparation +to help gauge how long this might take for your region's data. +Note these are generic timings using the built-in +database in the PgOSM Flex Docker image, without any tuning from default +configuration. Your tuning and your hardware will influence these timings. + +> Note: Testing was done with PgOSM Flex 1.2.1 on a personal laptop. + +* D.C.: 18 seconds +* Colorado: 11.5 minutes + +The Colorado data set has 1.2M input roads resulting in 2.6M edges after splitting. + +``` +┌────────┬───────────────────┬─────────┐ +│ s_name │ t_name │ rows │ +╞════════╪═══════════════════╪═════════╡ +│ osm │ routing_road_edge │ 2560998 │ +│ osm │ road_line │ 1189678 │ +└────────┴───────────────────┴─────────┘ +``` \ No newline at end of file diff --git a/docs/src/routing-water.md b/docs/src/routing-water.md new file mode 100644 index 00000000..a2571d66 --- /dev/null +++ b/docs/src/routing-water.md @@ -0,0 +1,105 @@ +# Routing with Water + +PgOSM Flex makes it easy to get started with routing with OpenStreetMap data +and pgRouting. The most support is available with pgRouting 4.0 and newer. + +> If you are using a pgRouting prior to 4.0 see [Routing Roads: Legacy (pgRouting 3)](./routing-3.md). + +## Prepare routing edge networks + +You should have ran the steps in [Prepare for Routing](routing.md#prepare-for-routing) +before continuing. + +---- + + +PgOSM Flex includes functions to prepare routing edge networks for data in +`osm.water_line` by running the appropriate procedure. +These procedures can take a while to run on larger regions, see the Timing section +below for more details. + +```sql +CALL osm.routing_prepare_water_network(); +``` + + +# Routing with Water + +PgOSM Flex also includes a procedure to prepare a routing network using +the `osm.water_line` table. + +```sql +CALL osm.routing_prepare_water_network(); +``` + +Find the `vertex_id` for start and end nodes, similar to approach above +with roads. + +```sql + +WITH s_point AS ( +SELECT v.id AS start_id, v.geom + FROM osm.routing_water_vertex v + INNER JOIN (SELECT + ST_Transform(ST_SetSRID(ST_MakePoint(-77.050625, 38.908953), 4326), 3857) + AS geom + ) p ON v.geom <-> p.geom < 200 + ORDER BY v.geom <-> p.geom + LIMIT 1 +), e_point AS ( +SELECT v.id AS end_id, v.geom + FROM osm.routing_water_vertex v + INNER JOIN (SELECT + ST_Transform(ST_SetSRID(ST_MakePoint(-77.055645, 38.888747), 4326), 3857) + AS geom + ) p ON v.geom <-> p.geom < 200 + ORDER BY v.geom <-> p.geom + LIMIT 1 +) +SELECT s_point.start_id, e_point.end_id + , s_point.geom AS geom_start + , e_point.geom AS geom_end + FROM s_point, e_point +; +``` + + +Route, using the directional approach. + +```sql +SELECT d.*, n.geom AS node_geom, e.geom AS edge_geom + FROM pgr_dijkstra( + 'SELECT e.edge_id AS id + , e.vertex_id_source AS source + , e.vertex_id_target AS target + , e.cost_length_forward AS cost + , e.cost_length_reverse AS reverse_cost + , e.geom + FROM osm.routing_water_edge e + ', + :start_id, :end_id, directed := True + ) d + INNER JOIN osm.routing_water_vertex n ON d.node = n.id + LEFT JOIN osm.routing_water_edge e ON d.edge = e.edge_id +; +``` + +![Example route using D.C. water layer.](dc-water-route-example.png) + +## Challenge: Polygons with Water Routing + +Waterway routing using lines only is often complicated by the nature of waterways +and the way routes flow through steams and rivers (lines) and also through ponds +and lakes (polygons). The data prepared by the above procedure only provides +the line-based functionality. + +The following image ([source](https://blog.rustprooflabs.com/2022/10/pgrouting-lines-through-polygons)) +visualizes the impact polygons can have on a line-only routing network for water routes. + +![Image alternates between showing / hiding water polygons, creating significant gaps in the routing network.](https://blog.rustprooflabs.com/static/images/water-polygons-are-important-for-routing.gif) + + + +See the [Routing with Lines through Polygons](https://blog.rustprooflabs.com/2022/10/pgrouting-lines-through-polygons) +blog post to explore one possible approach to solving this problem. + diff --git a/docs/src/routing.md b/docs/src/routing.md index acd09154..731584c1 100644 --- a/docs/src/routing.md +++ b/docs/src/routing.md @@ -1,5 +1,24 @@ # Routing with PgOSM Flex +This section provides details about routing with OpenStreetMap data loaded by +PgOSM Flex. The primary focus of this documentation supports pgRouting 4.0 +and newer, with legacy documentation available for older versions. + +## Prepare for Routing + +The Postgres database needs to have both [`pgrouting`](https://pgrouting.org/) +and [`convert`](https://github.com/rustprooflabs/convert) extensions installed. +These extensions are both available in the PgOSM Flex Docker image, they are your +responsibility to install in external Postgres instances. + +```sql +CREATE EXTENSION IF NOT EXISTS pgrouting; +CREATE EXTENSION IF NOT EXISTS convert; +``` + + +## Data File for Examples + This page provides a simple example of using OpenStreetMap roads loaded with PgOSM Flex for routing. The example uses the D.C. PBF included under `tests/data/`. @@ -28,55 +47,24 @@ docker exec -it \ ``` -## Prepare for routing - -Create the `pgrouting` extension if it does not already exist. -Also create the `routing` schema to store the data used in this -example. - - -```sql -CREATE EXTENSION IF NOT EXISTS pgrouting SCHEMA public; -CREATE SCHEMA IF NOT EXISTS routing; -``` - -> This command explicitly specifies the `public` schema to enforce the expected default -> and avoid unexpected behavior with custom `search_path` settings. - -### Prepare data for routing - -The [pgRouting 4.0 release](https://github.com/pgRouting/pgrouting/releases/tag/v4.0.0) -removed functions previously used for data preparation in the original documentation. - -The routing setup instructions are now scoped to which version of pgRouting you are -using. You can check your version with `pgr_version()`. - -```sql -SELECT * FROM pgr_version(); -``` - -``` -pgr_version| ------------+ -4.0.0 | -``` - +# Prepare Data and Route -Follow the instructions for your version of pgRouting. +It is highly recommended to use [Routing with pgRouting 4.0](./routing-4.md). +Not all steps are backward compatible with older versions of +pgRouting. Table names, column names, and more have changed in recent versions. -* [Routing with pgRouting 3](./routing-3.md) -* [Routing with pgRouting 4](./routing-4.md) +The goal with the rewritten docs is improved understanding and usability. -> PgOSM Flex 1.1.1 and later packages `pgRouting` 4.0. -> If you are using external Postgres -> as the target for your data, the pgRouting version you have installed is in -> your control. +## Legacy Routing Instructions +If you must use an older version of pgRouting, see +[Routing with pgRouting 3](./routing-3.md). +These are the legacy procedures that used pgRouting functions removed in pgRouting 4.0. +> The significnat improvements with routing in PgOSM Flex are focused on +> pgRouting 4.0 and newer. The queries used in the latest versions are not +> fully backward compatible to older version of pgRouting. -The 4.0 instructions have been rewritten to improve naming conventions and reduce -artifacts left behind from the process. The goal with the rewritten docs is improved -understanding and usability. The pre-4.0 documentation used naming conventions aimed at conforming to pgRouting's naming conventions surrounding the legacy functions. From 66fae3417d50ed34d49f7495cbdcab50d8d4cbdc Mon Sep 17 00:00:00 2001 From: Ryan Lambert Date: Sun, 4 Jan 2026 08:25:52 -0700 Subject: [PATCH 4/6] More docs cleanup in routing sections --- docs/src/readme.md | 14 +++++--------- docs/src/routing-3.md | 20 +++++++++++++------- docs/src/routing-process.md | 17 ++++++++++++++++- docs/src/routing-road.md | 6 +++--- docs/src/routing-water.md | 5 +++-- 5 files changed, 40 insertions(+), 22 deletions(-) diff --git a/docs/src/readme.md b/docs/src/readme.md index a8f57214..7dba0a73 100644 --- a/docs/src/readme.md +++ b/docs/src/readme.md @@ -40,20 +40,16 @@ passed along to osm2pgsql, with post-processing steps creating indexes, constraints and comments. -## Versions Supported +## Minimum Versions Supported -Minimum versions supported: - -* Postgres 12 -* PostGIS 3.0 - -This project will attempt, but not guarantee, to support PostgreSQL 12 until it -reaches it EOL support. +This project will attempt, but not guarantee, to support each major PostgreSQL version +until it reaches it EOL support. The Docker image is pinned to osm2pgsql's `master` branch. Users of the Docker image naturally use the latest version of osm2pgsql at the time the Docker image was created. -This project runs entirely in Docker, optionally connecting to an external Postgres instance. +This project runs entirely in Docker, optionally connecting to an external +Postgres instance at runtime. It should work on any typical OS able to run Docker. diff --git a/docs/src/routing-3.md b/docs/src/routing-3.md index b10fb5a0..a9798294 100644 --- a/docs/src/routing-3.md +++ b/docs/src/routing-3.md @@ -1,16 +1,22 @@ # Routing Roads: Legacy (pgRouting 3) -If you are using a pgRouting 4.0 or later see [Routing with pgRouting 4](./routing-4.md). +## Plan your Upgrade! -New development in PgOSM Flex will focus support on pgRouting 4.0 support -per the Versions Supported section [in the About page](./readme.md). -[PgOSM Flex 1.1.2](https://github.com/rustprooflabs/pgosm-flex/releases/tag/1.1.2) -simplified and improved performance in the routing data preparation. +This page is a legacy documentation page for versions of pgRouting +older than 4.0. This process requires more manual effort to setup and +results in lower-quality routing networks compared to the +[latest procedures](./routing-road.md). +It is recommended to use pgRouting 4.0 or later, see [the latest Routing Roads](./routing-road.md) +documentation. -> ⚠️ This page will remain in the PgOSM documentation through at least 2026 to ensure +> ⚠️ This page is no longer maintained. +> +> This page will remain in the PgOSM documentation for the foreseeable future to ensure > continuity for a transition to pgRouting 4.0. -> There will not be improvements made to these legacy instructions. + + +## Getting Started Create the `pgRouting` extension. diff --git a/docs/src/routing-process.md b/docs/src/routing-process.md index d191a0b2..8b00291d 100644 --- a/docs/src/routing-process.md +++ b/docs/src/routing-process.md @@ -17,7 +17,6 @@ the initial migration. > These procedures should be treated as a new feature with potential bugs lurking. - ## Costs Including One Way Restrictions Most real-world routing examples need to be aware of one-way travel restrictions. @@ -63,3 +62,19 @@ In most common routing scenarios this will under-report travel times due to not considering for traffic signals, slowing down for corners, and traffic in general. + +## Customize the Edge Network Generation + +The `osm.routing_prepare_road_network` and `osm.routing_prepare_water_network` +procedures are bundled in PgOSM Flex as a convenient and easy starting point +for realistic routing. They are not expected to be "perfect" or meet every routing +use case. Feel free to script out those procedures to modify for your own needs. +The source SQL for these procedures can be found +in the [`db/deploy`](https://github.com/rustprooflabs/pgosm-flex/tree/main/db/deploy) +folder in the repository. + +The main procedures to prepare the road and water networks leverage a common +procedure `osm.routing_prepare_edge_network()`. You most likely want to use this +procedure within your custom logic, it handles the nitty-gritty details of preparing +the edges/vertices. The outer procedures handle layer-specific columns, cost models, +and other minutia. diff --git a/docs/src/routing-road.md b/docs/src/routing-road.md index d9a9ecd5..ecb49efc 100644 --- a/docs/src/routing-road.md +++ b/docs/src/routing-road.md @@ -1,9 +1,9 @@ # Routing Roads PgOSM Flex makes it easy to get started with routing with OpenStreetMap data -and pgRouting. The best support is available with pgRouting 4.0 and newer. - -> If you are using a pgRouting prior to 4.0 see [Routing Roads: Legacy (pgRouting 3)](./routing-3.md). +and pgRouting. The best experience is with pgRouting 4.0 and newer. +If you are using a pgRouting prior to 4.0 see +[Routing Roads: Legacy (pgRouting 3)](./routing-3.md). ## Prepare routing edge networks diff --git a/docs/src/routing-water.md b/docs/src/routing-water.md index a2571d66..fefac2c4 100644 --- a/docs/src/routing-water.md +++ b/docs/src/routing-water.md @@ -1,9 +1,10 @@ # Routing with Water PgOSM Flex makes it easy to get started with routing with OpenStreetMap data -and pgRouting. The most support is available with pgRouting 4.0 and newer. +and pgRouting. The best experience is with pgRouting 4.0 and newer. +If you are using a pgRouting prior to 4.0 see +[Routing Roads: Legacy (pgRouting 3)](./routing-3.md). -> If you are using a pgRouting prior to 4.0 see [Routing Roads: Legacy (pgRouting 3)](./routing-3.md). ## Prepare routing edge networks From d36d677370e4fa7dfe8a930c7bcdbd9bad787754 Mon Sep 17 00:00:00 2001 From: Ryan Lambert Date: Sun, 4 Jan 2026 08:49:23 -0700 Subject: [PATCH 5/6] Add indexes on source/target vertex --- db/deploy/routing_functions.sql | 9 ++++++++- docs/src/routing-road.md | 6 ------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/db/deploy/routing_functions.sql b/db/deploy/routing_functions.sql index e10ca483..5eb2b330 100644 --- a/db/deploy/routing_functions.sql +++ b/db/deploy/routing_functions.sql @@ -336,10 +336,17 @@ BEGIN ORDER BY a.geom ; - CREATE INDEX gix_{schema_name}_routing_road_edge + CREATE INDEX gix_{schema_name}_routing_road_edge_geom ON {schema_name}.routing_road_edge USING GIST (geom) ; + CREATE INDEX ix_{schema_name}_routing_road_edge_vertex_id_source + ON {schema_name}.routing_road_edge (vertex_id_source) + ; + CREATE INDEX ix_{schema_name}_routing_road_edge_vertex_id_target + ON {schema_name}.routing_road_edge (vertex_id_target) + ; + RAISE NOTICE 'Created table {schema_name}.routing_road_edge'; diff --git a/docs/src/routing-road.md b/docs/src/routing-road.md index ecb49efc..806ccb91 100644 --- a/docs/src/routing-road.md +++ b/docs/src/routing-road.md @@ -158,12 +158,6 @@ the `pgosm.road.traffic_penalty_normal` column, to compute travel time. ----- - -# OLD NOTES - - - # Routing Data Preparation Timing From be611e12d7f1f70a869572e34bab6a09c67c2eb8 Mon Sep 17 00:00:00 2001 From: Ryan Lambert Date: Sun, 4 Jan 2026 09:44:28 -0700 Subject: [PATCH 6/6] Example of routing through a join --- docs/src/routing-road.md | 77 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/docs/src/routing-road.md b/docs/src/routing-road.md index 806ccb91..75246d6a 100644 --- a/docs/src/routing-road.md +++ b/docs/src/routing-road.md @@ -157,6 +157,83 @@ the `pgosm.road.traffic_penalty_normal` column, to compute travel time. > timing model. Other penalty / cost models can also be investigated. +# Routing by Joining to Inputs + +This next example expands beyond using singular routes to generating routes +based on a table of inputs. The `osm.route_motor_travel_time()` function is +a set-returning function and can be used in a lateral join. + +## Example Table - Random Points + +For the purpose of this example, create a temp table `route_vertex_combos` +with a few start/end points to route between. The `LIMIT 4` sets the number +of vertices to select. The final output is `(N * N) - N` potential routes +to generate. + +```sql +DROP TABLE IF EXISTS route_vertex_combos; +CREATE TEMP TABLE route_vertex_combos AS +WITH vertices AS ( +SELECT v.id AS vertex_id, v.geom + FROM osm.routing_road_vertex v + INNER JOIN osm.routing_road_edge e + ON v.id IN (e.vertex_id_source, e.vertex_id_target) + AND e.route_motor + ORDER BY random() + LIMIT 4 -- results in row count: (N * N) - N +) +SELECT a.vertex_id AS vertex_id_start + , b.vertex_id AS vertex_id_end + FROM vertices a + CROSS JOIN vertices b + -- Don't route to yourself :) + WHERE a.vertex_id <> b.vertex_id +; +``` + +## Generate the Routes + +The table if start/end points can be joined to the `osm.route_motor_travel_time()` +function. + +```sql +DROP TABLE IF EXISTS public.my_random_routes; +CREATE TABLE public.my_random_routes AS +SELECT v.*, rte.* + FROM route_vertex_combos v + CROSS JOIN LATERAL osm.route_motor_travel_time(v.vertex_id_start, v.vertex_id_end) rte +; +``` + +> Warning: The above query can take a long time to execute, depending on the number of inputs and the +> size of your routing network. It is often best to calculate routes in batches +> instead of a full join like shown in this simple example. + + +Routes can now be examined with costs often desired to be converted to minutes +or hours. The example here shows an example of a long route taking a few hours. + +```sql +SELECT vertex_id_start, vertex_id_end, segments + , total_cost_seconds / 60 AS total_cost_minutes + , total_cost_seconds / 60 / 60 AS total_cost_hours + , geom + FROM public.my_routes + WHERE vertex_id_start = 1817591 + AND vertex_id_end = 17109 +; +``` + + +``` +┌─────────────────┬───────────────┬──────────┬────────────────────┬───────────────────┐ +│ vertex_id_start │ vertex_id_end │ segments │ total_cost_minutes │ total_cost_hours │ +╞═════════════════╪═══════════════╪══════════╪════════════════════╪═══════════════════╡ +│ 1817591 │ 17109 │ 783 │ 350.379710135436 │ 5.839661835590601 │ +└─────────────────┴───────────────┴──────────┴────────────────────┴───────────────────┘ +``` + + # Routing Data Preparation Timing