1 /**********************************************************************
2 *
3 * PostGIS - Spatial Types for PostgreSQL
4 * http://postgis.net
5 *
6 * PostGIS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * PostGIS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **********************************************************************
20 *
21 * ^copyright^
22 *
23 **********************************************************************/
24
25 #include "postgres.h"
26 #include "fmgr.h"
27 #include "utils/elog.h"
28 #include "utils/array.h"
29 #include "utils/geo_decls.h"
30 #include "utils/lsyscache.h"
31 #include "catalog/pg_type.h"
32 #include "funcapi.h"
33
34 #include "../postgis_config.h"
35 #include "lwgeom_pg.h"
36
37 #include "access/htup_details.h"
38
39
40 #include "liblwgeom.h"
41
42 /* ST_DumpPoints for postgis.
43 * By Nathan Wagner, copyright disclaimed,
44 * this entire file is in the public domain
45 */
46
47 Datum LWGEOM_dumppoints(PG_FUNCTION_ARGS);
48 Datum LWGEOM_dumpsegments(PG_FUNCTION_ARGS);
49
50 struct dumpnode {
51 LWGEOM *geom;
52 uint32_t idx; /* which member geom we're working on */
53 } ;
54
55 /* 32 is the max depth for st_dump, so it seems reasonable
56 * to use the same here
57 */
58 #define MAXDEPTH 32
59 struct dumpstate {
60 LWGEOM *root;
61 int stacklen; /* collections/geoms on stack */
62 int pathlen; /* polygon rings and such need extra path info */
63 struct dumpnode stack[MAXDEPTH];
64 Datum path[34]; /* two more than max depth, for ring and point */
65
66 /* used to cache the type attributes for integer arrays */
67 int16 typlen;
68 bool byval;
69 char align;
70
71 uint32_t ring; /* ring of top polygon */
72 uint32_t pt; /* point of top geom or current ring */
73 };
74
75 PG_FUNCTION_INFO_V1(LWGEOM_dumppoints);
LWGEOM_dumppoints(PG_FUNCTION_ARGS)76 Datum LWGEOM_dumppoints(PG_FUNCTION_ARGS) {
77 FuncCallContext *funcctx;
78 MemoryContext oldcontext, newcontext;
79
80 GSERIALIZED *pglwgeom;
81 LWCOLLECTION *lwcoll;
82 LWGEOM *lwgeom;
83 struct dumpstate *state;
84 struct dumpnode *node;
85
86 HeapTuple tuple;
87 Datum pathpt[2]; /* used to construct the composite return value */
88 bool isnull[2] = {0,0}; /* needed to say neither value is null */
89 Datum result; /* the actual composite return value */
90
91 if (SRF_IS_FIRSTCALL()) {
92 funcctx = SRF_FIRSTCALL_INIT();
93
94 newcontext = funcctx->multi_call_memory_ctx;
95 oldcontext = MemoryContextSwitchTo(newcontext);
96
97 /* get a local copy of what we're doing a dump points on */
98 pglwgeom = PG_GETARG_GSERIALIZED_P_COPY(0);
99 lwgeom = lwgeom_from_gserialized(pglwgeom);
100
101 /* return early if nothing to do */
102 if (!lwgeom || lwgeom_is_empty(lwgeom)) {
103 MemoryContextSwitchTo(oldcontext);
104 funcctx = SRF_PERCALL_SETUP();
105 SRF_RETURN_DONE(funcctx);
106 }
107
108 /* Create function state */
109 state = lwalloc(sizeof *state);
110 state->root = lwgeom;
111 state->stacklen = 0;
112 state->pathlen = 0;
113 state->pt = 0;
114 state->ring = 0;
115
116 funcctx->user_fctx = state;
117
118 /*
119 * Push a struct dumpnode on the state stack
120 */
121
122 state->stack[0].idx = 0;
123 state->stack[0].geom = lwgeom;
124 state->stacklen++;
125
126 /*
127 * get tuple description for return type
128 */
129 if (get_call_result_type(fcinfo, 0, &funcctx->tuple_desc) != TYPEFUNC_COMPOSITE) {
130 ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
131 errmsg("set-valued function called in context that cannot accept a set")));
132 }
133
134 BlessTupleDesc(funcctx->tuple_desc);
135
136 /* get and cache data for constructing int4 arrays */
137 get_typlenbyvalalign(INT4OID, &state->typlen, &state->byval, &state->align);
138
139 MemoryContextSwitchTo(oldcontext);
140 }
141
142 /* stuff done on every call of the function */
143 funcctx = SRF_PERCALL_SETUP();
144 newcontext = funcctx->multi_call_memory_ctx;
145
146 /* get state */
147 state = funcctx->user_fctx;
148
149 while (1) {
150 node = &state->stack[state->stacklen-1];
151 lwgeom = node->geom;
152
153 /* need to return a point from this geometry */
154 if (!lwgeom_is_collection(lwgeom)) {
155 /* either return a point, or pop the stack */
156 /* TODO use a union? would save a tiny amount of stack space.
157 * probably not worth the bother
158 */
159 LWLINE *line;
160 LWCIRCSTRING *circ;
161 LWPOLY *poly;
162 LWTRIANGLE *tri;
163 LWPOINT *lwpoint = NULL;
164 POINT4D pt;
165
166 /*
167 * net result of switch should be to set lwpoint to the
168 * next point to return, or leave at NULL if there
169 * are no more points in the geometry
170 */
171 switch(lwgeom->type) {
172 case TRIANGLETYPE:
173 tri = lwgeom_as_lwtriangle(lwgeom);
174 if (state->pt == 0) {
175 state->path[state->pathlen++] = Int32GetDatum(state->ring+1);
176 }
177 if (state->pt <= 3) {
178 getPoint4d_p(tri->points, state->pt, &pt);
179 lwpoint = lwpoint_make(tri->srid,
180 lwgeom_has_z(lwgeom),
181 lwgeom_has_m(lwgeom),
182 &pt);
183 }
184 if (state->pt > 3) {
185 state->pathlen--;
186 }
187 break;
188 case POLYGONTYPE:
189 poly = lwgeom_as_lwpoly(lwgeom);
190 if (state->pt == poly->rings[state->ring]->npoints) {
191 state->pt = 0;
192 state->ring++;
193 state->pathlen--;
194 }
195 if (state->pt == 0 && state->ring < poly->nrings) {
196 /* handle new ring */
197 state->path[state->pathlen] = Int32GetDatum(state->ring+1);
198 state->pathlen++;
199 }
200 if (state->ring == poly->nrings) {
201 } else {
202 /* TODO should be able to directly get the point
203 * into the point array of a fixed lwpoint
204 */
205 /* can't get the point directly from the ptarray because
206 * it might be aligned wrong, so at least one memcpy
207 * seems unavoidable
208 * It might be possible to pass it directly to gserialized
209 * depending how that works, it might effectively be gserialized
210 * though a brief look at the code indicates not
211 */
212 getPoint4d_p(poly->rings[state->ring], state->pt, &pt);
213 lwpoint = lwpoint_make(poly->srid,
214 lwgeom_has_z(lwgeom),
215 lwgeom_has_m(lwgeom),
216 &pt);
217 }
218 break;
219 case POINTTYPE:
220 if (state->pt == 0) lwpoint = lwgeom_as_lwpoint(lwgeom);
221 break;
222 case LINETYPE:
223 line = lwgeom_as_lwline(lwgeom);
224 if (line->points && state->pt <= line->points->npoints) {
225 lwpoint = lwline_get_lwpoint((LWLINE*)lwgeom, state->pt);
226 }
227 break;
228 case CIRCSTRINGTYPE:
229 circ = lwgeom_as_lwcircstring(lwgeom);
230 if (circ->points && state->pt <= circ->points->npoints) {
231 lwpoint = lwcircstring_get_lwpoint((LWCIRCSTRING*)lwgeom, state->pt);
232 }
233 break;
234 default:
235 ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION),
236 errmsg("Invalid Geometry type %d passed to ST_DumpPoints()", lwgeom->type)));
237 }
238
239 /*
240 * At this point, lwpoint is either NULL, in which case
241 * we need to pop the geometry stack and get the next
242 * geometry, if amy, or lwpoint is set and we construct
243 * a record type with the integer array of geometry
244 * indexes and the point number, and the actual point
245 * geometry itself
246 */
247
248 if (!lwpoint) {
249 /* no point, so pop the geom and look for more */
250 if (--state->stacklen == 0) SRF_RETURN_DONE(funcctx);
251 state->pathlen--;
252 continue;
253 } else {
254 /* write address of current geom/pt */
255 state->pt++;
256
257 state->path[state->pathlen] = Int32GetDatum(state->pt);
258 pathpt[0] = PointerGetDatum(construct_array(state->path, state->pathlen+1,
259 INT4OID, state->typlen, state->byval, state->align));
260
261 pathpt[1] = PointerGetDatum(geometry_serialize((LWGEOM*)lwpoint));
262
263 tuple = heap_form_tuple(funcctx->tuple_desc, pathpt, isnull);
264 result = HeapTupleGetDatum(tuple);
265 SRF_RETURN_NEXT(funcctx, result);
266 }
267 }
268
269 lwcoll = (LWCOLLECTION*)node->geom;
270
271 /* if a collection and we have more geoms */
272 if (node->idx < lwcoll->ngeoms) {
273 /* push the next geom on the path and the stack */
274 lwgeom = lwcoll->geoms[node->idx++];
275 state->path[state->pathlen++] = Int32GetDatum(node->idx);
276
277 node = &state->stack[state->stacklen++];
278 node->idx = 0;
279 node->geom = lwgeom;
280
281 state->pt = 0;
282 state->ring = 0;
283
284 /* loop back to beginning, which will then check whatever node we just pushed */
285 continue;
286 }
287
288 /* no more geometries in the current collection */
289 if (--state->stacklen == 0) SRF_RETURN_DONE(funcctx);
290 state->pathlen--;
291 }
292 }
293
294 PG_FUNCTION_INFO_V1(LWGEOM_dumpsegments);
LWGEOM_dumpsegments(PG_FUNCTION_ARGS)295 Datum LWGEOM_dumpsegments(PG_FUNCTION_ARGS)
296 {
297 FuncCallContext *funcctx;
298 MemoryContext oldcontext, newcontext;
299
300 GSERIALIZED *pglwgeom;
301 LWCOLLECTION *lwcoll;
302 LWGEOM *lwgeom;
303 struct dumpstate *state;
304 struct dumpnode *node;
305
306 HeapTuple tuple;
307 Datum pathpt[2]; /* used to construct the composite return value */
308 bool isnull[2] = {0, 0}; /* needed to say neither value is null */
309 Datum result; /* the actual composite return value */
310
311 if (SRF_IS_FIRSTCALL())
312 {
313 funcctx = SRF_FIRSTCALL_INIT();
314
315 newcontext = funcctx->multi_call_memory_ctx;
316 oldcontext = MemoryContextSwitchTo(newcontext);
317
318 pglwgeom = PG_GETARG_GSERIALIZED_P_COPY(0);
319 lwgeom = lwgeom_from_gserialized(pglwgeom);
320
321 /* return early if nothing to do */
322 if (!lwgeom || lwgeom_is_empty(lwgeom))
323 {
324 MemoryContextSwitchTo(oldcontext);
325 funcctx = SRF_PERCALL_SETUP();
326 SRF_RETURN_DONE(funcctx);
327 }
328
329 /* Create function state */
330 state = lwalloc(sizeof *state);
331 state->root = lwgeom;
332 state->stacklen = 0;
333 state->pathlen = 0;
334 state->pt = 0;
335 state->ring = 0;
336
337 funcctx->user_fctx = state;
338
339 /*
340 * Push a struct dumpnode on the state stack
341 */
342 state->stack[0].idx = 0;
343 state->stack[0].geom = lwgeom;
344 state->stacklen++;
345
346 /*
347 * get tuple description for return type
348 */
349 if (get_call_result_type(fcinfo, 0, &funcctx->tuple_desc) != TYPEFUNC_COMPOSITE)
350 {
351 ereport(ERROR,
352 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
353 errmsg("set-valued function called in context that cannot accept a set")));
354 }
355
356 BlessTupleDesc(funcctx->tuple_desc);
357
358 /* get and cache data for constructing int4 arrays */
359 get_typlenbyvalalign(INT4OID, &state->typlen, &state->byval, &state->align);
360
361 MemoryContextSwitchTo(oldcontext);
362 }
363
364 /* stuff done on every call of the function */
365 funcctx = SRF_PERCALL_SETUP();
366 newcontext = funcctx->multi_call_memory_ctx;
367
368 /* get state */
369 state = funcctx->user_fctx;
370
371 while (1)
372 {
373 POINTARRAY *points = NULL;
374 LWLINE *line;
375 LWTRIANGLE *tri;
376 LWPOLY *poly;
377 POINT4D pt_start, pt_end;
378 POINTARRAY *segment_pa;
379 LWLINE *segment;
380
381 node = &state->stack[state->stacklen - 1];
382 lwgeom = node->geom;
383
384 if (lwgeom->type == LINETYPE || lwgeom->type == TRIANGLETYPE || lwgeom->type == POLYGONTYPE)
385 {
386 if (lwgeom->type == LINETYPE)
387 {
388 line = (LWLINE *)lwgeom;
389
390 if (state->pt < line->points->npoints - 1)
391 {
392 points = line->points;
393 }
394 }
395 if (lwgeom->type == TRIANGLETYPE)
396 {
397 tri = (LWTRIANGLE *)lwgeom;
398
399 if (state->pt == 0)
400 {
401 state->path[state->pathlen++] = Int32GetDatum(state->ring + 1);
402 }
403
404 if (state->pt < 3)
405 {
406 points = tri->points;
407 }
408 else
409 {
410 state->pathlen--;
411 }
412 }
413 if (lwgeom->type == POLYGONTYPE)
414 {
415 poly = (LWPOLY *)lwgeom;
416
417 if (state->pt == poly->rings[state->ring]->npoints)
418 {
419 state->pt = 0;
420 state->ring++;
421 state->pathlen--;
422 }
423
424 if (state->ring < poly->nrings)
425 {
426 if (state->pt == 0)
427 {
428 state->path[state->pathlen] = Int32GetDatum(state->ring + 1);
429 state->pathlen++;
430 }
431
432 if (state->pt < poly->rings[state->ring]->npoints - 1)
433 {
434 points = poly->rings[state->ring];
435 }
436 else
437 {
438 state->pt++;
439 continue;
440 }
441 }
442 }
443
444 if (points)
445 {
446 getPoint4d_p(points, state->pt, &pt_start);
447 getPoint4d_p(points, state->pt + 1, &pt_end);
448
449 segment_pa = ptarray_construct(lwgeom_has_z(lwgeom), lwgeom_has_m(lwgeom), 2);
450 ptarray_set_point4d(segment_pa, 0, &pt_start);
451 ptarray_set_point4d(segment_pa, 1, &pt_end);
452
453 segment = lwline_construct(lwgeom->srid, NULL, segment_pa);
454
455 state->pt++;
456
457 state->path[state->pathlen] = Int32GetDatum(state->pt);
458 pathpt[0] = PointerGetDatum(construct_array(state->path,
459 state->pathlen + 1,
460 INT4OID,
461 state->typlen,
462 state->byval,
463 state->align));
464 pathpt[1] = PointerGetDatum(geometry_serialize((LWGEOM *)segment));
465
466 tuple = heap_form_tuple(funcctx->tuple_desc, pathpt, isnull);
467 result = HeapTupleGetDatum(tuple);
468 SRF_RETURN_NEXT(funcctx, result);
469 }
470 else
471 {
472 if (--state->stacklen == 0)
473 SRF_RETURN_DONE(funcctx);
474 state->pathlen--;
475 continue;
476 }
477 }
478
479 if (lwgeom->type == COLLECTIONTYPE || lwgeom->type == MULTILINETYPE ||
480 lwgeom->type == MULTIPOLYGONTYPE || lwgeom->type == TINTYPE)
481 {
482 lwcoll = (LWCOLLECTION *)node->geom;
483
484 /* if a collection and we have more geoms */
485 if (node->idx < lwcoll->ngeoms)
486 {
487 /* push the next geom on the path and the stack */
488 lwgeom = lwcoll->geoms[node->idx++];
489 state->path[state->pathlen++] = Int32GetDatum(node->idx);
490
491 node = &state->stack[state->stacklen++];
492 node->idx = 0;
493 node->geom = lwgeom;
494
495 state->pt = 0;
496 state->ring = 0;
497
498 /* loop back to beginning, which will then check whatever node we just pushed */
499 continue;
500 }
501 }
502
503 /* no more geometries in the current collection */
504 if (--state->stacklen == 0)
505 SRF_RETURN_DONE(funcctx);
506 state->pathlen--;
507 }
508 }
509