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 lwgeom_has_z(lwgeom),
180 lwgeom_has_m(lwgeom),
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 lwgeom_has_z(lwgeom),
214 lwgeom_has_m(lwgeom),
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(geometry_serialize((LWGEOM*)lwpoint));
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