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 
49 struct dumpnode {
50 	LWGEOM *geom;
51 	uint32_t idx; /* which member geom we're working on */
52 } ;
53 
54 /* 32 is the max depth for st_dump, so it seems reasonable
55  * to use the same here
56  */
57 #define MAXDEPTH 32
58 struct dumpstate {
59 	LWGEOM	*root;
60 	int	stacklen; /* collections/geoms on stack */
61 	int	pathlen; /* polygon rings and such need extra path info */
62 	struct	dumpnode stack[MAXDEPTH];
63 	Datum	path[34]; /* two more than max depth, for ring and point */
64 
65 	/* used to cache the type attributes for integer arrays */
66 	int16	typlen;
67 	bool	byval;
68 	char	align;
69 
70 	uint32_t ring; /* ring of top polygon */
71 	uint32_t pt; /* point of top geom or current ring */
72 };
73 
74 PG_FUNCTION_INFO_V1(LWGEOM_dumppoints);
LWGEOM_dumppoints(PG_FUNCTION_ARGS)75 Datum LWGEOM_dumppoints(PG_FUNCTION_ARGS) {
76 	FuncCallContext *funcctx;
77 	MemoryContext oldcontext, newcontext;
78 
79 	GSERIALIZED *pglwgeom;
80 	LWCOLLECTION *lwcoll;
81 	LWGEOM *lwgeom;
82 	struct dumpstate *state;
83 	struct dumpnode *node;
84 
85 	HeapTuple tuple;
86 	Datum pathpt[2]; /* used to construct the composite return value */
87 	bool isnull[2] = {0,0}; /* needed to say neither value is null */
88 	Datum result; /* the actual composite return value */
89 
90 	if (SRF_IS_FIRSTCALL()) {
91 		funcctx = SRF_FIRSTCALL_INIT();
92 
93 		newcontext = funcctx->multi_call_memory_ctx;
94 		oldcontext = MemoryContextSwitchTo(newcontext);
95 
96 		/* get a local copy of what we're doing a dump points on */
97 		pglwgeom = PG_GETARG_GSERIALIZED_P_COPY(0);
98 		lwgeom = lwgeom_from_gserialized(pglwgeom);
99 
100 		/* return early if nothing to do */
101 		if (!lwgeom || lwgeom_is_empty(lwgeom)) {
102 			MemoryContextSwitchTo(oldcontext);
103 			funcctx = SRF_PERCALL_SETUP();
104 			SRF_RETURN_DONE(funcctx);
105 		}
106 
107 		/* Create function state */
108 		state = lwalloc(sizeof *state);
109 		state->root = lwgeom;
110 		state->stacklen = 0;
111 		state->pathlen = 0;
112 		state->pt = 0;
113 		state->ring = 0;
114 
115 		funcctx->user_fctx = state;
116 
117 		/*
118 		 * Push a struct dumpnode on the state stack
119 		 */
120 
121 		state->stack[0].idx = 0;
122 		state->stack[0].geom = lwgeom;
123 		state->stacklen++;
124 
125 		/*
126 		 * get tuple description for return type
127 		 */
128 		if (get_call_result_type(fcinfo, 0, &funcctx->tuple_desc) != TYPEFUNC_COMPOSITE) {
129 			ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
130 				errmsg("set-valued function called in context that cannot accept a set")));
131 		}
132 
133 		BlessTupleDesc(funcctx->tuple_desc);
134 
135 		/* get and cache data for constructing int4 arrays */
136 		get_typlenbyvalalign(INT4OID, &state->typlen, &state->byval, &state->align);
137 
138 		MemoryContextSwitchTo(oldcontext);
139 	}
140 
141 	/* stuff done on every call of the function */
142 	funcctx = SRF_PERCALL_SETUP();
143 	newcontext = funcctx->multi_call_memory_ctx;
144 
145 	/* get state */
146 	state = funcctx->user_fctx;
147 
148 	while (1) {
149 		node = &state->stack[state->stacklen-1];
150 		lwgeom = node->geom;
151 
152 		/* need to return a point from this geometry */
153 		if (!lwgeom_is_collection(lwgeom)) {
154 			/* either return a point, or pop the stack */
155 			/* TODO use a union?  would save a tiny amount of stack space.
156 			 * probably not worth the bother
157 			 */
158 			LWLINE	*line;
159 			LWCIRCSTRING *circ;
160 			LWPOLY	*poly;
161 			LWTRIANGLE	*tri;
162 			LWPOINT *lwpoint = NULL;
163 			POINT4D	pt;
164 
165 			/*
166 			 * net result of switch should be to set lwpoint to the
167 			 * next point to return, or leave at NULL if there
168 			 * are no more points in the geometry
169 			 */
170 			switch(lwgeom->type) {
171 				case TRIANGLETYPE:
172 					tri = lwgeom_as_lwtriangle(lwgeom);
173 					if (state->pt == 0) {
174 						state->path[state->pathlen++] = Int32GetDatum(state->ring+1);
175 					}
176 					if (state->pt <= 3) {
177 						getPoint4d_p(tri->points, state->pt, &pt);
178 						lwpoint = lwpoint_make(tri->srid,
179 								FLAGS_GET_Z(tri->points->flags),
180 								FLAGS_GET_M(tri->points->flags),
181 								&pt);
182 					}
183 					if (state->pt > 3) {
184 						state->pathlen--;
185 					}
186 					break;
187 				case POLYGONTYPE:
188 					poly = lwgeom_as_lwpoly(lwgeom);
189 					if (state->pt == poly->rings[state->ring]->npoints) {
190 						state->pt = 0;
191 						state->ring++;
192 						state->pathlen--;
193 					}
194 					if (state->pt == 0 && state->ring < poly->nrings) {
195 						/* handle new ring */
196 						state->path[state->pathlen] = Int32GetDatum(state->ring+1);
197 						state->pathlen++;
198 					}
199 				       	if (state->ring == poly->nrings) {
200 					} else {
201 					/* TODO should be able to directly get the point
202 					 * into the point array of a fixed lwpoint
203 					 */
204 					/* can't get the point directly from the ptarray because
205 					 * it might be aligned wrong, so at least one memcpy
206 					 * seems unavoidable
207 					 * It might be possible to pass it directly to gserialized
208 					 * depending how that works, it might effectively be gserialized
209 					 * though a brief look at the code indicates not
210 					 */
211 						getPoint4d_p(poly->rings[state->ring], state->pt, &pt);
212 						lwpoint = lwpoint_make(poly->srid,
213 								FLAGS_GET_Z(poly->rings[state->ring]->flags),
214 								FLAGS_GET_M(poly->rings[state->ring]->flags),
215 								&pt);
216 					}
217 					break;
218 				case POINTTYPE:
219 					if (state->pt == 0) lwpoint = lwgeom_as_lwpoint(lwgeom);
220 					break;
221 				case LINETYPE:
222 					line = lwgeom_as_lwline(lwgeom);
223 					if (line->points && state->pt <= line->points->npoints) {
224 						lwpoint = lwline_get_lwpoint((LWLINE*)lwgeom, state->pt);
225 					}
226 					break;
227 				case CIRCSTRINGTYPE:
228 					circ = lwgeom_as_lwcircstring(lwgeom);
229 					if (circ->points && state->pt <= circ->points->npoints) {
230 						lwpoint = lwcircstring_get_lwpoint((LWCIRCSTRING*)lwgeom, state->pt);
231 					}
232 					break;
233 				default:
234 					ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION),
235 						errmsg("Invalid Geometry type %d passed to ST_DumpPoints()", lwgeom->type)));
236 			}
237 
238 			/*
239 			 * At this point, lwpoint is either NULL, in which case
240 			 * we need to pop the geometry stack and get the next
241 			 * geometry, if amy, or lwpoint is set and we construct
242 			 * a record type with the integer array of geometry
243 			 * indexes and the point number, and the actual point
244 			 * geometry itself
245 			 */
246 
247 			if (!lwpoint) {
248 				/* no point, so pop the geom and look for more */
249 				if (--state->stacklen == 0) SRF_RETURN_DONE(funcctx);
250 				state->pathlen--;
251 				continue;
252 			} else {
253 				/* write address of current geom/pt */
254 				state->pt++;
255 
256 				state->path[state->pathlen] = Int32GetDatum(state->pt);
257 				pathpt[0] = PointerGetDatum(construct_array(state->path, state->pathlen+1,
258 						INT4OID, state->typlen, state->byval, state->align));
259 
260 				pathpt[1] = PointerGetDatum(gserialized_from_lwgeom((LWGEOM*)lwpoint,0));
261 
262 				tuple = heap_form_tuple(funcctx->tuple_desc, pathpt, isnull);
263 				result = HeapTupleGetDatum(tuple);
264 				SRF_RETURN_NEXT(funcctx, result);
265 			}
266 		}
267 
268 		lwcoll = (LWCOLLECTION*)node->geom;
269 
270 		/* if a collection and we have more geoms */
271 		if (node->idx < lwcoll->ngeoms) {
272 			/* push the next geom on the path and the stack */
273 			lwgeom = lwcoll->geoms[node->idx++];
274 			state->path[state->pathlen++] = Int32GetDatum(node->idx);
275 
276 			node = &state->stack[state->stacklen++];
277 			node->idx = 0;
278 			node->geom = lwgeom;
279 
280 			state->pt = 0;
281 			state->ring = 0;
282 
283 			/* loop back to beginning, which will then check whatever node we just pushed */
284 			continue;
285 		}
286 
287 		/* no more geometries in the current collection */
288 		if (--state->stacklen == 0) SRF_RETURN_DONE(funcctx);
289 		state->pathlen--;
290 		state->stack[state->stacklen-1].idx++;
291 	}
292 }
293 
294 /*
295  * Geometry types of collection types for reference
296  */
297 
298 #if 0
299         case MULTIPOINTTYPE:
300         case MULTILINETYPE:
301         case MULTIPOLYGONTYPE:
302         case COLLECTIONTYPE:
303         case CURVEPOLYTYPE:
304         case COMPOUNDTYPE:
305         case MULTICURVETYPE:
306         case MULTISURFACETYPE:
307         case POLYHEDRALSURFACETYPE:
308         case TINTYPE:
309 
310 #endif
311 
312