-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathn50_path.sql
227 lines (192 loc) · 6.75 KB
/
n50_path.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
-- Shortest path routing function for N50 vegsti data
CREATE OR REPLACE FUNCTION path(
IN x1 double precision,
IN y1 double precision,
IN x2 double precision,
IN y2 double precision,
IN path_buffer double precision DEFAULT 2000,
IN point_buffer double precision DEFAULT 10,
IN targets integer DEFAULT 1,
IN srid_in integer DEFAULT 4326,
IN srid_db integer DEFAULT 25833,
IN bbox double precision ARRAY[4] DEFAULT '{}',
OUT path_id integer,
OUT cost double precision,
OUT geom geometry
) RETURNS SETOF record AS
$BODY$
DECLARE
sql text;
point1 geometry;
point2 geometry;
bounds text;
rec record;
source record;
target record;
prec1 double precision;
prec2 double precision;
BEGIN
-- Find the closest edge (source) near the start (x1, y1)
point1 := ST_Transform(ST_SetSRID(ST_MakePoint(x1, y1), srid_in), srid_db);
EXECUTE 'SELECT
ogc_fid AS id,
ST_LineLocatePoint(geometri, $1) AS prec
FROM n50.n50_vegsti
WHERE geometri && ST_Buffer($1, ' || point_buffer || ')
ORDER BY ST_Distance(geometri, $1)
LIMIT 1'
INTO source
USING point1;
-- Find the closest edge (target) near the end (x2, y2)
point2 := ST_Transform(ST_SetSRID(ST_MakePoint(x2, y2), srid_in), srid_db);
EXECUTE 'SELECT
ogc_fid AS id,
ST_LineLocatePoint(geometri, $1) AS prec
FROM n50.n50_vegsti
WHERE geometri && ST_Buffer($1, ' || point_buffer || ')
ORDER BY ST_Distance(geometri, $1)
LIMIT 1'
INTO target
USING point2;
RAISE NOTICE '[ROUTER] source.id=% source.prec=%', source.id, source.prec;
RAISE NOTICE '[ROUTER] target.id=% target.prec=%', target.id, target.prec;
-- Return if we could not find source or target edges
IF source.id IS null OR target.id IS null THEN
RAISE NOTICE '[ROUTER] source or target is NULL; returning';
RETURN;
END IF;
-- Since there are millions of edges in the database we need to limit the
-- shortes path search to only the subset of edges that can reasonably match.
IF array_length(bbox, 1) != 0 THEN
RAISE NOTICE '[ROUTER] creating bounds based on bbox';
bounds := 'ST_Transform(ST_MakeEnvelope(
' || bbox[1] || ', ' || bbox[2] || ',
' || bbox[3] || ', ' || bbox[4] || ',
' || srid_in || '
), ' || srid_db || ')';
ELSE
RAISE NOTICE '[ROUTER] creating bounds based on path_buffer';
bounds := 'ST_Buffer(ST_Transform(ST_MakeEnvelope(
' || x1 || ', ' || y1 || ', ' || x2 || ', ' || y2 || ', ' || srid_in || '
), ' || srid_db || '), ' || path_buffer || ')';
END IF;
-- Construct a query to find all edges that should be routed for shortest
-- path. This is not the shortest path routing but a set of edges that the
-- router will work on.
sql := 'SELECT
ogc_fid as id,
source::int,
target::int,
cost::float
FROM n50.n50_vegsti
WHERE geometri && ' || bounds || '
UNION SELECT
ogc_fid AS id,
source::int,
-888::int AS target,
cost::float * ' || source.prec || ' AS cost
FROM n50.n50_vegsti
WHERE ogc_fid = ' || source.id || '
UNION SELECT
ogc_fid AS id,
-888::int AS source,
target::int,
cost::float * (1 - ' || source.prec || ') AS cost
FROM n50.n50_vegsti
WHERE ogc_fid = ' || source.id || '
UNION SELECT
ogc_fid AS id,
source::int,
-999::int AS target,
cost::float * ' || target.prec || ' AS cost
FROM n50.n50_vegsti
WHERE ogc_fid = ' || target.id || '
UNION SELECT
ogc_fid AS id,
-999::int AS source,
target::int,
cost::float * (1 - ' || target.prec || ') AS cost
FROM n50.n50_vegsti
WHERE ogc_fid = ' || target.id;
-- If the edge is long or the route is short we could end up with a shortest
-- route consisting of a single edge or a subset of it. We handle that by
-- inserting dummy edges into the pool of edges.
IF (source.id = target.id) THEN
RAISE NOTICE '[ROUTER] source.id = target.id';
sql := sql || 'UNION SELECT
ogc_fid AS id,
-888::int AS source,
-999::int AS target,
cost::float * ' || source.prec || ' * ' || target.prec || ' AS cost
FROM n50.n50_vegsti
WHERE ogc_fid = ' || source.id || '
UNION SELECT
ogc_fid AS id,
-999::int AS source,
-888::int AS target,
cost::float * ' || target.prec || ' * ' || source.prec || ' AS cost
FROM n50.n50_vegsti
WHERE ogc_fid = ' || target.id;
END IF;
-- This is the actual SQL query that takes the edges and applies the
-- K-Shortest Path routing algorithm implemeted by `pgr_ksp`.
sql := 'SELECT
path.path_id,
ST_LineMerge(ST_Collect(vegsti.geometri)) AS geom,
SUM(path.cost) AS cost
FROM
pgr_ksp(
''' || sql || ''',
-888,
-999,
' || targets || ',
directed:=false
) AS path,
n50.n50_vegsti AS vegsti
WHERE path.edge = vegsti.ogc_fid
GROUP BY path.path_id
ORDER BY SUM(path.cost)';
-- Now, get the shortest paths and process them before returning
FOR rec IN EXECUTE sql
LOOP
-- Return if no route was found
IF rec.geom IS NULL THEN
RAISE NOTICE '[ROUTER] route geometry is NULL; returning';
RETURN;
END IF;
-- If the geometry is not a `LINESTRING` it means `ST_LineMerge` was not
-- able to merge path geometry to a contiguous line.
IF GeometryType(rec.geom) != 'LINESTRING' THEN
RAISE NOTICE '[ROUTER] route geometry is MULTILINE; returning';
RETURN;
END IF;
-- Ok, so `rec.geom` is the combined geometry of the all edges in the
-- shortes path. However, since we rarly route from the exact beginning of
-- an edge we need to trim the route in both ends to the points where we
-- want the route to source and target.
-- Locate the point on the route closest to the source point
prec1 := ST_LineLocatePoint(rec.geom, point1);
-- Locate the point on the route closest to the target point
prec2 := ST_LineLocatePoint(rec.geom, point2);
RAISE NOTICE '[ROUTING] path_id=%, start=%, end=%', rec.path_id, prec1, prec2;
-- Now that we have the closest points in both ends we just need to trim the
-- route in order to fit our source and target requirements. Since the route
-- can be returned source to target or target to source we check reverse the
-- route when necessary before trimming.
IF (prec2 < prec1) THEN
rec.geom := ST_Reverse(ST_LineSubstring(rec.geom, prec2, prec1));
ELSE
rec.geom := ST_LineSubstring(rec.geom, prec1, prec2);
END IF;
RAISE NOTICE '[ROUTING] path_id=%, cost=%', rec.path_id, rec.cost;
-- Return the resulting record as defined in our function definition
path_id := rec.path_id;
cost := rec.cost;
geom := rec.geom;
-- Continue processing the next shortest route
RETURN NEXT;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE STRICT;