Skip to content

Commit 71b68e3

Browse files
committed
Add procedure and docs for water routing prep.
1 parent 6099358 commit 71b68e3

File tree

3 files changed

+226
-14
lines changed

3 files changed

+226
-14
lines changed

db/deploy/routing_functions.sql

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,8 @@ BEGIN
317317
STORED
318318
;
319319

320-
COMMENT ON COLUMN {schema_name}.routing_road_edge.cost_length_forward IS 'Length based cost for forward travel when using directed travel graphs.';
321-
COMMENT ON COLUMN {schema_name}.routing_road_edge.cost_length_reverse IS 'Length based cost for reverse travel when using directed travel graphs.';
320+
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.';
321+
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.';
322322

323323

324324
DROP TABLE IF EXISTS {schema_name}.routing_road_vertex;
@@ -369,3 +369,143 @@ END $$;
369369

370370

371371
COMMENT ON PROCEDURE {schema_name}.routing_prepare_roads IS 'Creates the {schema_name}.routing_road_edge and {schema_name}.routing_road_vertex from the {schema_name}.road_line input data';
372+
373+
374+
375+
--------------------------------------------------
376+
-- Waterway routing prep
377+
--------------------------------------------------
378+
379+
380+
CREATE OR REPLACE PROCEDURE {schema_name}.routing_prepare_water()
381+
LANGUAGE plpgsql
382+
AS $$
383+
BEGIN
384+
385+
CALL {schema_name}.pgrouting_version_check();
386+
387+
--Create edges table for input to routing_prepare_edges procedure
388+
DROP TABLE IF EXISTS route_edge_input;
389+
CREATE TEMP TABLE route_edge_input AS
390+
SELECT osm_id, layer, geom
391+
FROM {schema_name}.water_line
392+
;
393+
394+
-- Creates the `route_edges_output` table.
395+
CALL {schema_name}.routing_prepare_edges();
396+
397+
398+
DROP TABLE IF EXISTS {schema_name}.routing_water_edge;
399+
CREATE TABLE {schema_name}.routing_water_edge
400+
(
401+
edge_id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY
402+
, osm_id BIGINT NOT NULL
403+
, sub_id BIGINT NOT NULL
404+
, vertex_id_source BIGINT
405+
, vertex_id_target BIGINT
406+
, layer INT
407+
, osm_type TEXT NOT NULL
408+
, name TEXT
409+
, tunnel TEXT
410+
, bridge TEXT
411+
, geom GEOMETRY(LINESTRING)
412+
);
413+
414+
INSERT INTO {schema_name}.routing_water_edge (
415+
osm_id, sub_id, osm_type, name, layer, tunnel, bridge
416+
, geom
417+
)
418+
SELECT re.osm_id, re.sub_id
419+
, r.osm_type, r.name
420+
, re.layer, r.tunnel, r.bridge
421+
, re.geom
422+
FROM route_edges_output re
423+
INNER JOIN {schema_name}.water_line r ON re.osm_id = r.osm_id
424+
ORDER BY re.geom
425+
;
426+
427+
CREATE INDEX gix_{schema_name}_routing_water_edge
428+
ON {schema_name}.routing_water_edge
429+
USING GIST (geom)
430+
;
431+
432+
RAISE NOTICE 'Created table {schema_name}.routing_water_edge with edge data';
433+
434+
435+
ALTER TABLE {schema_name}.routing_water_edge
436+
ADD cost_length DOUBLE PRECISION NULL;
437+
438+
UPDATE {schema_name}.routing_water_edge
439+
SET cost_length = ST_Length(ST_Transform(geom, 4326)::GEOGRAPHY)
440+
;
441+
442+
COMMENT ON COLUMN {schema_name}.routing_water_edge.cost_length IS 'Length based cost calculated using GEOGRAPHY for accurate length.';
443+
444+
445+
-- Add forward cost column, enforcing oneway restrictions
446+
ALTER TABLE {schema_name}.routing_water_edge
447+
ADD cost_length_forward NUMERIC
448+
GENERATED ALWAYS AS (cost_length)
449+
STORED
450+
;
451+
452+
-- Add reverse cost column, enforcing oneway restrictions
453+
ALTER TABLE {schema_name}.routing_water_edge
454+
ADD cost_length_reverse NUMERIC
455+
GENERATED ALWAYS AS (-1 * cost_length)
456+
STORED
457+
;
458+
459+
COMMENT ON COLUMN {schema_name}.routing_water_edge.cost_length_forward IS 'Length based cost for forward travel with directed routing. Based on cost_length value.';
460+
COMMENT ON COLUMN {schema_name}.routing_water_edge.cost_length_reverse IS 'Length based cost for reverse travel with directed routing. Based on cost_length value.';
461+
462+
463+
DROP TABLE IF EXISTS {schema_name}.routing_water_vertex;
464+
CREATE TABLE {schema_name}.routing_water_vertex AS
465+
SELECT * FROM pgr_extractVertices(
466+
'SELECT edge_id AS id, geom FROM {schema_name}.routing_water_edge')
467+
;
468+
RAISE NOTICE 'Created table {schema_name}.routing_water_vertex from edges.';
469+
470+
CREATE INDEX gix_{schema_name}_routing_water_vertex
471+
ON {schema_name}.routing_water_vertex
472+
USING GIST (geom)
473+
;
474+
475+
-- Update source column from out_edges
476+
WITH outgoing AS (
477+
SELECT id AS vertex_id_source
478+
, unnest(out_edges) AS edge_id
479+
FROM {schema_name}.routing_water_vertex
480+
)
481+
UPDATE {schema_name}.routing_water_edge e
482+
SET vertex_id_source = o.vertex_id_source
483+
FROM outgoing o
484+
WHERE e.edge_id = o.edge_id
485+
AND e.vertex_id_source IS NULL
486+
;
487+
488+
-- Update target column from in_edges
489+
WITH incoming AS (
490+
SELECT id AS vertex_id_target
491+
, unnest(in_edges) AS edge_id
492+
FROM {schema_name}.routing_water_vertex
493+
)
494+
UPDATE {schema_name}.routing_water_edge e
495+
SET vertex_id_target = i.vertex_id_target
496+
FROM incoming i
497+
WHERE e.edge_id = i.edge_id
498+
AND e.vertex_id_target IS NULL
499+
;
500+
501+
502+
ANALYZE {schema_name}.routing_water_edge;
503+
ANALYZE {schema_name}.routing_water_vertex;
504+
505+
COMMENT ON TABLE {schema_name}.routing_water_vertex IS 'Routing vertex data. These points can be used as the start/end points for routing the edge network in {schema_name}.routing_water_edge..';
506+
507+
508+
END $$;
509+
510+
511+
COMMENT ON PROCEDURE {schema_name}.routing_prepare_water IS 'Creates the {schema_name}.routing_water_edge and {schema_name}.routing_water_vertex from the {schema_name}.water_line input data';
321 KB
Loading

docs/src/routing-4.md

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,8 @@ This direction data type resolves to `int2` in Postgres. Valid values are:
8787
* `-1`: One way, reverse travel allowed
8888
* `NULL`: It's complicated. See [#172](https://github.com/rustprooflabs/pgosm-flex/issues/172).
8989

90-
The `osm.routing_road_edge` table has the `oneway` column from the
91-
`osm.road_line` table used as the source.
92-
93-
94-
Forward and reverse cost columns are calculated within the
95-
`osm.routing_prepare_roads()` procedure.
96-
97-
98-
Calculate forward and reverse costs using the `oneway` column. This still provides
99-
a length-based cost. The change is to also enforce direction restrictions within
100-
the cost model.
90+
> Forward and reverse cost columns are calculated in the `cost_length_forward`
91+
> and `cost_length_reverse` columns within the `osm.routing_prepare_roads()` procedure.
10192
10293

10394
## Travel Time Costs
@@ -262,7 +253,7 @@ SELECT d.*, n.geom AS node_geom, e.geom AS edge_geom
262253

263254
![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)
264255

265-
# Oneway-aware routing
256+
# One-way Aware Routing
266257

267258
The route shown in the previous example now respects the
268259
access control and limits to routes suitable for motorized traffic.
@@ -299,3 +290,84 @@ SELECT d.*, n.geom AS node_geom, e.geom AS edge_geom
299290

300291
![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)
301292

293+
294+
# Routing with Water
295+
296+
PgOSM Flex also includes a procedure to prepare a routing network using
297+
the `osm.water_line` table.
298+
299+
```sql
300+
CALL osm.routing_prepare_water();
301+
```
302+
303+
Find the `vertex_id` for start and end nodes, similar to approach above
304+
with roads.
305+
306+
```sql
307+
308+
WITH s_point AS (
309+
SELECT v.id AS start_id, v.geom
310+
FROM osm.routing_water_vertex v
311+
INNER JOIN (SELECT
312+
ST_Transform(ST_SetSRID(ST_MakePoint(-77.050625, 38.908953), 4326), 3857)
313+
AS geom
314+
) p ON v.geom <-> p.geom < 200
315+
ORDER BY v.geom <-> p.geom
316+
LIMIT 1
317+
), e_point AS (
318+
SELECT v.id AS end_id, v.geom
319+
FROM osm.routing_water_vertex v
320+
INNER JOIN (SELECT
321+
ST_Transform(ST_SetSRID(ST_MakePoint(-77.055645, 38.888747), 4326), 3857)
322+
AS geom
323+
) p ON v.geom <-> p.geom < 200
324+
ORDER BY v.geom <-> p.geom
325+
LIMIT 1
326+
)
327+
SELECT s_point.start_id, e_point.end_id
328+
, s_point.geom AS geom_start
329+
, e_point.geom AS geom_end
330+
FROM s_point, e_point
331+
;
332+
```
333+
334+
335+
Route, using the directional approach.
336+
337+
```sql
338+
SELECT d.*, n.geom AS node_geom, e.geom AS edge_geom
339+
FROM pgr_dijkstra(
340+
'SELECT e.edge_id AS id
341+
, e.vertex_id_source AS source
342+
, e.vertex_id_target AS target
343+
, e.cost_length_forward AS cost
344+
, e.cost_length_reverse AS reverse_cost
345+
, e.geom
346+
FROM osm.routing_water_edge e
347+
',
348+
:start_id, :end_id, directed := True
349+
) d
350+
INNER JOIN osm.routing_water_vertex n ON d.node = n.id
351+
LEFT JOIN osm.routing_water_edge e ON d.edge = e.edge_id
352+
;
353+
```
354+
355+
![Example route using D.C. water layer.](dc-water-route-example.png)
356+
357+
## Challenge: Polygons with Water Routing
358+
359+
Waterway routing using lines only is often complicated by the nature of waterways
360+
and the way routes flow through steams and rivers (lines) and also through ponds
361+
and lakes (polygons). The data prepared by the above procedure only provides
362+
the line-based functionality.
363+
364+
The following image ([source](https://blog.rustprooflabs.com/2022/10/pgrouting-lines-through-polygons))
365+
visualizes the impact polygons can have on a line-only routing network for water routes.
366+
367+
![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)
368+
369+
370+
371+
See the [Routing with Lines through Polygons](https://blog.rustprooflabs.com/2022/10/pgrouting-lines-through-polygons)
372+
blog post to explore one possible approach to solving this problem.
373+

0 commit comments

Comments
 (0)