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