/src/postgis/liblwgeom/lwout_wkt.c
Line | Count | Source |
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 (C) 2009 Paul Ramsey <pramsey@cleverelephant.ca> |
22 | | * Copyright (C) 2026 Darafei Praliaskouski <me@komzpa.net> |
23 | | * |
24 | | **********************************************************************/ |
25 | | |
26 | | #include "liblwgeom_internal.h" |
27 | | #include "lwgeom_log.h" |
28 | | #include "stringbuffer.h" |
29 | | #include <stdbool.h> |
30 | | #include <math.h> |
31 | | |
32 | | static void lwgeom_to_wkt_sb(const LWGEOM *geom, stringbuffer_t *sb, int precision, uint8_t variant); |
33 | | static void lwnurbscurve_to_wkt_sb(const LWNURBSCURVE *curve, stringbuffer_t *sb, int precision, uint8_t variant); |
34 | | |
35 | | #define buffer_size 128 |
36 | | |
37 | | /* |
38 | | * ISO format uses both Z and M qualifiers. |
39 | | * Extended format only uses an M qualifier for 3DM variants, where it is not |
40 | | * clear what the third dimension represents. |
41 | | * SFSQL format never has more than two dimensions, so no qualifiers. |
42 | | */ |
43 | | static void dimension_qualifiers_to_wkt_sb(const LWGEOM *geom, stringbuffer_t *sb, uint8_t variant) |
44 | 0 | { |
45 | |
|
46 | 0 | if ((variant & WKT_EXTENDED) && geom->type == TINTYPE && FLAGS_GET_Z(geom->flags)) |
47 | 0 | { |
48 | 0 | stringbuffer_append_len(sb, " Z", 2); |
49 | 0 | if (FLAGS_GET_M(geom->flags)) |
50 | 0 | stringbuffer_append_len(sb, "M", 1); |
51 | 0 | stringbuffer_append_len(sb, " ", 1); |
52 | 0 | return; |
53 | 0 | } |
54 | | |
55 | | /* Extended WKT: POINTM(0 0 0) */ |
56 | | #if 0 |
57 | | if ( (variant & WKT_EXTENDED) && ! (variant & WKT_IS_CHILD) && FLAGS_GET_M(geom->flags) && (!FLAGS_GET_Z(geom->flags)) ) |
58 | | #else |
59 | 0 | if ( (variant & WKT_EXTENDED) && FLAGS_GET_M(geom->flags) && (!FLAGS_GET_Z(geom->flags)) ) |
60 | 0 | #endif |
61 | 0 | { |
62 | 0 | stringbuffer_append_len(sb, "M", 1); /* "M" */ |
63 | 0 | return; |
64 | 0 | } |
65 | | |
66 | | /* ISO WKT: POINT ZM (0 0 0 0) */ |
67 | 0 | if ( (variant & WKT_ISO) && (FLAGS_NDIMS(geom->flags) > 2) ) |
68 | 0 | { |
69 | 0 | stringbuffer_append_len(sb, " ", 1); |
70 | 0 | if ( FLAGS_GET_Z(geom->flags) ) |
71 | 0 | stringbuffer_append_len(sb, "Z", 1); |
72 | 0 | if ( FLAGS_GET_M(geom->flags) ) |
73 | 0 | stringbuffer_append_len(sb, "M", 1); |
74 | 0 | stringbuffer_append_len(sb, " ", 1); |
75 | 0 | } |
76 | 0 | } |
77 | | |
78 | | /* |
79 | | * Write an empty token out, padding with a space if |
80 | | * necessary. |
81 | | */ |
82 | | static void empty_to_wkt_sb(stringbuffer_t *sb) |
83 | 0 | { |
84 | 0 | if ( ! strchr(" ,(", stringbuffer_lastchar(sb)) ) /* "EMPTY" */ |
85 | 0 | { |
86 | 0 | stringbuffer_append_len(sb, " ", 1); |
87 | 0 | } |
88 | 0 | stringbuffer_append_len(sb, "EMPTY", 5); |
89 | 0 | } |
90 | | |
91 | | inline static void |
92 | | coordinate_to_wkt_sb(double *coords, stringbuffer_t *sb, uint32_t dimensions, int precision) |
93 | 0 | { |
94 | 0 | uint32_t d = 0; |
95 | 0 | stringbuffer_append_double(sb, coords[d], precision); |
96 | |
|
97 | 0 | for (d = 1; d < dimensions; d++) |
98 | 0 | { |
99 | 0 | stringbuffer_append_len(sb, " ", 1); |
100 | 0 | stringbuffer_append_double(sb, coords[d], precision); |
101 | 0 | } |
102 | 0 | } |
103 | | |
104 | | /* |
105 | | * Point array is a list of coordinates. Depending on output mode, |
106 | | * we may suppress some dimensions. ISO and Extended formats include |
107 | | * all dimensions. Standard OGC output only includes X/Y coordinates. |
108 | | */ |
109 | | static void ptarray_to_wkt_sb(const POINTARRAY *ptarray, stringbuffer_t *sb, int precision, uint8_t variant) |
110 | 0 | { |
111 | | /* OGC only includes X/Y */ |
112 | 0 | uint32_t dimensions = 2; |
113 | | |
114 | | /* ISO and extended formats include all dimensions */ |
115 | 0 | if ( variant & ( WKT_ISO | WKT_EXTENDED ) ) |
116 | 0 | dimensions = FLAGS_NDIMS(ptarray->flags); |
117 | |
|
118 | 0 | stringbuffer_makeroom(sb, 2 + ((OUT_MAX_BYTES_DOUBLE + 1) * dimensions * ptarray->npoints)); |
119 | | /* Opening paren? */ |
120 | 0 | if ( ! (variant & WKT_NO_PARENS) ) |
121 | 0 | stringbuffer_append_len(sb, "(", 1); |
122 | | |
123 | | /* Digits and commas */ |
124 | 0 | if (ptarray->npoints) |
125 | 0 | { |
126 | 0 | uint32_t i = 0; |
127 | |
|
128 | 0 | double *dbl_ptr = (double *)getPoint_internal(ptarray, i); |
129 | 0 | coordinate_to_wkt_sb(dbl_ptr, sb, dimensions, precision); |
130 | |
|
131 | 0 | for (i = 1; i < ptarray->npoints; i++) |
132 | 0 | { |
133 | 0 | stringbuffer_append_len(sb, ",", 1); |
134 | 0 | dbl_ptr = (double *)getPoint_internal(ptarray, i); |
135 | 0 | coordinate_to_wkt_sb(dbl_ptr, sb, dimensions, precision); |
136 | 0 | } |
137 | 0 | } |
138 | | |
139 | | /* Closing paren? */ |
140 | 0 | if ( ! (variant & WKT_NO_PARENS) ) |
141 | 0 | stringbuffer_append_len(sb, ")", 1); |
142 | 0 | } |
143 | | |
144 | | /* |
145 | | * A four-dimensional point will have different outputs depending on variant. |
146 | | * ISO: POINT ZM (0 0 0 0) |
147 | | * Extended: POINT(0 0 0 0) |
148 | | * OGC: POINT(0 0) |
149 | | * A three-dimensional m-point will have different outputs too. |
150 | | * ISO: POINT M (0 0 0) |
151 | | * Extended: POINTM(0 0 0) |
152 | | * OGC: POINT(0 0) |
153 | | */ |
154 | | static void lwpoint_to_wkt_sb(const LWPOINT *pt, stringbuffer_t *sb, int precision, uint8_t variant) |
155 | 0 | { |
156 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
157 | 0 | { |
158 | 0 | stringbuffer_append_len(sb, "POINT", 5); /* "POINT" */ |
159 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)pt, sb, variant); |
160 | 0 | } |
161 | |
|
162 | 0 | if ( lwpoint_is_empty(pt) ) |
163 | 0 | { |
164 | 0 | empty_to_wkt_sb(sb); |
165 | 0 | return; |
166 | 0 | } |
167 | | |
168 | 0 | ptarray_to_wkt_sb(pt->point, sb, precision, variant); |
169 | 0 | } |
170 | | |
171 | | /* |
172 | | * LINESTRING(0 0 0, 1 1 1) |
173 | | */ |
174 | | static void lwline_to_wkt_sb(const LWLINE *line, stringbuffer_t *sb, int precision, uint8_t variant) |
175 | 0 | { |
176 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
177 | 0 | { |
178 | 0 | stringbuffer_append_len(sb, "LINESTRING", 10); /* "LINESTRING" */ |
179 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)line, sb, variant); |
180 | 0 | } |
181 | 0 | if ( lwline_is_empty(line) ) |
182 | 0 | { |
183 | 0 | empty_to_wkt_sb(sb); |
184 | 0 | return; |
185 | 0 | } |
186 | | |
187 | 0 | ptarray_to_wkt_sb(line->points, sb, precision, variant); |
188 | 0 | } |
189 | | |
190 | | /* |
191 | | * POLYGON(0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1) |
192 | | */ |
193 | | static void lwpoly_to_wkt_sb(const LWPOLY *poly, stringbuffer_t *sb, int precision, uint8_t variant) |
194 | 0 | { |
195 | 0 | uint32_t i = 0; |
196 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
197 | 0 | { |
198 | 0 | stringbuffer_append_len(sb, "POLYGON", 7); /* "POLYGON" */ |
199 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)poly, sb, variant); |
200 | 0 | } |
201 | 0 | if ( lwpoly_is_empty(poly) ) |
202 | 0 | { |
203 | 0 | empty_to_wkt_sb(sb); |
204 | 0 | return; |
205 | 0 | } |
206 | | |
207 | 0 | stringbuffer_append_len(sb, "(", 1); |
208 | 0 | for ( i = 0; i < poly->nrings; i++ ) |
209 | 0 | { |
210 | 0 | if ( i > 0 ) |
211 | 0 | stringbuffer_append_len(sb, ",", 1); |
212 | 0 | ptarray_to_wkt_sb(poly->rings[i], sb, precision, variant); |
213 | 0 | } |
214 | 0 | stringbuffer_append_len(sb, ")", 1); |
215 | 0 | } |
216 | | |
217 | | /* |
218 | | * CIRCULARSTRING |
219 | | */ |
220 | | static void lwcircstring_to_wkt_sb(const LWCIRCSTRING *circ, stringbuffer_t *sb, int precision, uint8_t variant) |
221 | 0 | { |
222 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
223 | 0 | { |
224 | 0 | stringbuffer_append_len(sb, "CIRCULARSTRING", 14); /* "CIRCULARSTRING" */ |
225 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)circ, sb, variant); |
226 | 0 | } |
227 | 0 | if ( lwcircstring_is_empty(circ) ) |
228 | 0 | { |
229 | 0 | empty_to_wkt_sb(sb); |
230 | 0 | return; |
231 | 0 | } |
232 | 0 | ptarray_to_wkt_sb(circ->points, sb, precision, variant); |
233 | 0 | } |
234 | | |
235 | | |
236 | | /* |
237 | | * Multi-points, in non-ISO format, do not wrap their sub-members in parens, unlike other multi-geometries. |
238 | | * MULTPOINT(0 0, 1 1) instead of MULTIPOINT((0 0),(1 1)) |
239 | | * Strictly speaking, the SFA spec also mandates use of parens in sub-members, but |
240 | | * use the old non-parens interpretation for WKT_SFSQL |
241 | | */ |
242 | | static void lwmpoint_to_wkt_sb(const LWMPOINT *mpoint, stringbuffer_t *sb, int precision, uint8_t variant) |
243 | 0 | { |
244 | 0 | uint32_t i = 0; |
245 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
246 | 0 | { |
247 | 0 | stringbuffer_append_len(sb, "MULTIPOINT", 10); /* "MULTIPOINT" */ |
248 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)mpoint, sb, variant); |
249 | 0 | } |
250 | 0 | if ( mpoint->ngeoms < 1 ) |
251 | 0 | { |
252 | 0 | empty_to_wkt_sb(sb); |
253 | 0 | return; |
254 | 0 | } |
255 | 0 | stringbuffer_append_len(sb, "(", 1); |
256 | 0 | variant = variant | WKT_IS_CHILD | WKT_NO_TYPE; /* Inform the sub-geometries they are children */ |
257 | 0 | if ( !(variant & WKT_ISO) ) |
258 | 0 | variant = variant | WKT_NO_PARENS; |
259 | 0 | for ( i = 0; i < mpoint->ngeoms; i++ ) |
260 | 0 | { |
261 | 0 | if ( i > 0 ) |
262 | 0 | stringbuffer_append_len(sb, ",", 1); |
263 | | /* We don't want type strings or parens on our subgeoms */ |
264 | 0 | lwpoint_to_wkt_sb(mpoint->geoms[i], sb, precision, variant); |
265 | 0 | } |
266 | 0 | stringbuffer_append_len(sb, ")", 1); |
267 | 0 | } |
268 | | |
269 | | /* |
270 | | * MULTILINESTRING |
271 | | */ |
272 | | static void lwmline_to_wkt_sb(const LWMLINE *mline, stringbuffer_t *sb, int precision, uint8_t variant) |
273 | 0 | { |
274 | 0 | uint32_t i = 0; |
275 | |
|
276 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
277 | 0 | { |
278 | 0 | stringbuffer_append_len(sb, "MULTILINESTRING", 15); /* "MULTILINESTRING" */ |
279 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)mline, sb, variant); |
280 | 0 | } |
281 | 0 | if ( mline->ngeoms < 1 ) |
282 | 0 | { |
283 | 0 | empty_to_wkt_sb(sb); |
284 | 0 | return; |
285 | 0 | } |
286 | | |
287 | 0 | stringbuffer_append_len(sb, "(", 1); |
288 | 0 | variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ |
289 | 0 | for ( i = 0; i < mline->ngeoms; i++ ) |
290 | 0 | { |
291 | 0 | if ( i > 0 ) |
292 | 0 | stringbuffer_append_len(sb, ",", 1); |
293 | | /* We don't want type strings on our subgeoms */ |
294 | 0 | lwline_to_wkt_sb(mline->geoms[i], sb, precision, variant | WKT_NO_TYPE ); |
295 | 0 | } |
296 | 0 | stringbuffer_append_len(sb, ")", 1); |
297 | 0 | } |
298 | | |
299 | | /* |
300 | | * MULTIPOLYGON |
301 | | */ |
302 | | static void lwmpoly_to_wkt_sb(const LWMPOLY *mpoly, stringbuffer_t *sb, int precision, uint8_t variant) |
303 | 0 | { |
304 | 0 | uint32_t i = 0; |
305 | |
|
306 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
307 | 0 | { |
308 | 0 | stringbuffer_append_len(sb, "MULTIPOLYGON", 12); /* "MULTIPOLYGON" */ |
309 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)mpoly, sb, variant); |
310 | 0 | } |
311 | 0 | if ( mpoly->ngeoms < 1 ) |
312 | 0 | { |
313 | 0 | empty_to_wkt_sb(sb); |
314 | 0 | return; |
315 | 0 | } |
316 | | |
317 | 0 | stringbuffer_append_len(sb, "(", 1); |
318 | 0 | variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ |
319 | 0 | for ( i = 0; i < mpoly->ngeoms; i++ ) |
320 | 0 | { |
321 | 0 | if ( i > 0 ) |
322 | 0 | stringbuffer_append_len(sb, ",", 1); |
323 | | /* We don't want type strings on our subgeoms */ |
324 | 0 | lwpoly_to_wkt_sb(mpoly->geoms[i], sb, precision, variant | WKT_NO_TYPE ); |
325 | 0 | } |
326 | 0 | stringbuffer_append_len(sb, ")", 1); |
327 | 0 | } |
328 | | |
329 | | /* |
330 | | * Compound curves provide type information for their curved sub-geometries |
331 | | * but not their linestring sub-geometries. |
332 | | * COMPOUNDCURVE((0 0, 1 1), CURVESTRING(1 1, 2 2, 3 3)) |
333 | | */ |
334 | | static void lwcompound_to_wkt_sb(const LWCOMPOUND *comp, stringbuffer_t *sb, int precision, uint8_t variant) |
335 | 0 | { |
336 | 0 | uint32_t i = 0; |
337 | |
|
338 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
339 | 0 | { |
340 | 0 | stringbuffer_append_len(sb, "COMPOUNDCURVE", 13); /* "COMPOUNDCURVE" */ |
341 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)comp, sb, variant); |
342 | 0 | } |
343 | 0 | if ( comp->ngeoms < 1 ) |
344 | 0 | { |
345 | 0 | empty_to_wkt_sb(sb); |
346 | 0 | return; |
347 | 0 | } |
348 | | |
349 | 0 | stringbuffer_append_len(sb, "(", 1); |
350 | 0 | variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ |
351 | 0 | for ( i = 0; i < comp->ngeoms; i++ ) |
352 | 0 | { |
353 | 0 | int type = comp->geoms[i]->type; |
354 | 0 | if ( i > 0 ) |
355 | 0 | stringbuffer_append_len(sb, ",", 1); |
356 | | /* Linestring subgeoms don't get type identifiers */ |
357 | 0 | if ( type == LINETYPE ) |
358 | 0 | { |
359 | 0 | lwline_to_wkt_sb((LWLINE*)comp->geoms[i], sb, precision, variant | WKT_NO_TYPE ); |
360 | 0 | } |
361 | | /* But circstring subgeoms *do* get type identifiers */ |
362 | 0 | else if ( type == CIRCSTRINGTYPE ) |
363 | 0 | { |
364 | 0 | lwcircstring_to_wkt_sb((LWCIRCSTRING*)comp->geoms[i], sb, precision, variant ); |
365 | 0 | } |
366 | 0 | else if ( type == NURBSCURVETYPE ) |
367 | 0 | { |
368 | 0 | lwnurbscurve_to_wkt_sb((LWNURBSCURVE*)comp->geoms[i], sb, precision, variant ); |
369 | 0 | } |
370 | 0 | else |
371 | 0 | { |
372 | 0 | lwerror("lwcompound_to_wkt_sb: Unknown type received %d - %s", type, lwtype_name(type)); |
373 | 0 | } |
374 | 0 | } |
375 | 0 | stringbuffer_append_len(sb, ")", 1); |
376 | 0 | } |
377 | | |
378 | | /* |
379 | | * Curve polygons provide type information for their curved rings |
380 | | * but not their linestring rings. |
381 | | * CURVEPOLYGON((0 0, 1 1, 0 1, 0 0), CURVESTRING(0 0, 1 1, 0 1, 0.5 1, 0 0)) |
382 | | */ |
383 | | static void lwcurvepoly_to_wkt_sb(const LWCURVEPOLY *cpoly, stringbuffer_t *sb, int precision, uint8_t variant) |
384 | 0 | { |
385 | 0 | uint32_t i = 0; |
386 | |
|
387 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
388 | 0 | { |
389 | 0 | stringbuffer_append_len(sb, "CURVEPOLYGON", 12); /* "CURVEPOLYGON" */ |
390 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)cpoly, sb, variant); |
391 | 0 | } |
392 | 0 | if ( cpoly->nrings < 1 ) |
393 | 0 | { |
394 | 0 | empty_to_wkt_sb(sb); |
395 | 0 | return; |
396 | 0 | } |
397 | 0 | stringbuffer_append_len(sb, "(", 1); |
398 | 0 | variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ |
399 | 0 | for ( i = 0; i < cpoly->nrings; i++ ) |
400 | 0 | { |
401 | 0 | int type = cpoly->rings[i]->type; |
402 | 0 | if ( i > 0 ) |
403 | 0 | stringbuffer_append_len(sb, ",", 1); |
404 | 0 | switch (type) |
405 | 0 | { |
406 | 0 | case LINETYPE: |
407 | | /* Linestring subgeoms don't get type identifiers */ |
408 | 0 | lwline_to_wkt_sb((LWLINE*)cpoly->rings[i], sb, precision, variant | WKT_NO_TYPE ); |
409 | 0 | break; |
410 | 0 | case CIRCSTRINGTYPE: |
411 | | /* But circstring subgeoms *do* get type identifiers */ |
412 | 0 | lwcircstring_to_wkt_sb((LWCIRCSTRING*)cpoly->rings[i], sb, precision, variant ); |
413 | 0 | break; |
414 | 0 | case COMPOUNDTYPE: |
415 | | /* And compoundcurve subgeoms *do* get type identifiers */ |
416 | 0 | lwcompound_to_wkt_sb((LWCOMPOUND*)cpoly->rings[i], sb, precision, variant ); |
417 | 0 | break; |
418 | 0 | case NURBSCURVETYPE: |
419 | 0 | lwnurbscurve_to_wkt_sb((LWNURBSCURVE*)cpoly->rings[i], sb, precision, variant ); |
420 | 0 | break; |
421 | 0 | default: |
422 | 0 | lwerror("lwcurvepoly_to_wkt_sb: Unknown type received %d - %s", type, lwtype_name(type)); |
423 | 0 | } |
424 | 0 | } |
425 | 0 | stringbuffer_append_len(sb, ")", 1); |
426 | 0 | } |
427 | | |
428 | | |
429 | | /* |
430 | | * Multi-curves provide type information for their curved sub-geometries |
431 | | * but not their linear sub-geometries. |
432 | | * MULTICURVE((0 0, 1 1), CURVESTRING(0 0, 1 1, 2 2)) |
433 | | */ |
434 | | static void lwmcurve_to_wkt_sb(const LWMCURVE *mcurv, stringbuffer_t *sb, int precision, uint8_t variant) |
435 | 0 | { |
436 | 0 | uint32_t i = 0; |
437 | |
|
438 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
439 | 0 | { |
440 | 0 | stringbuffer_append_len(sb, "MULTICURVE", 10); /* "MULTICURVE" */ |
441 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)mcurv, sb, variant); |
442 | 0 | } |
443 | 0 | if ( mcurv->ngeoms < 1 ) |
444 | 0 | { |
445 | 0 | empty_to_wkt_sb(sb); |
446 | 0 | return; |
447 | 0 | } |
448 | 0 | stringbuffer_append_len(sb, "(", 1); |
449 | 0 | variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ |
450 | 0 | for ( i = 0; i < mcurv->ngeoms; i++ ) |
451 | 0 | { |
452 | 0 | int type = mcurv->geoms[i]->type; |
453 | 0 | if ( i > 0 ) |
454 | 0 | stringbuffer_append_len(sb, ",", 1); |
455 | 0 | switch (type) |
456 | 0 | { |
457 | 0 | case LINETYPE: |
458 | | /* Linestring subgeoms don't get type identifiers */ |
459 | 0 | lwline_to_wkt_sb((LWLINE*)mcurv->geoms[i], sb, precision, variant | WKT_NO_TYPE ); |
460 | 0 | break; |
461 | 0 | case CIRCSTRINGTYPE: |
462 | | /* But circstring subgeoms *do* get type identifiers */ |
463 | 0 | lwcircstring_to_wkt_sb((LWCIRCSTRING*)mcurv->geoms[i], sb, precision, variant ); |
464 | 0 | break; |
465 | 0 | case COMPOUNDTYPE: |
466 | | /* And compoundcurve subgeoms *do* get type identifiers */ |
467 | 0 | lwcompound_to_wkt_sb((LWCOMPOUND*)mcurv->geoms[i], sb, precision, variant ); |
468 | 0 | break; |
469 | 0 | case NURBSCURVETYPE: |
470 | 0 | lwnurbscurve_to_wkt_sb((LWNURBSCURVE*)mcurv->geoms[i], sb, precision, variant ); |
471 | 0 | break; |
472 | 0 | default: |
473 | 0 | lwerror("lwmcurve_to_wkt_sb: Unknown type received %d - %s", type, lwtype_name(type)); |
474 | 0 | } |
475 | 0 | } |
476 | 0 | stringbuffer_append_len(sb, ")", 1); |
477 | 0 | } |
478 | | |
479 | | |
480 | | /* |
481 | | * Multi-surfaces provide type information for their curved sub-geometries |
482 | | * but not their linear sub-geometries. |
483 | | * MULTISURFACE(((0 0, 1 1, 1 0, 0 0)), CURVEPOLYGON(CURVESTRING(0 0, 1 1, 2 2, 0 1, 0 0))) |
484 | | */ |
485 | | static void lwmsurface_to_wkt_sb(const LWMSURFACE *msurf, stringbuffer_t *sb, int precision, uint8_t variant) |
486 | 0 | { |
487 | 0 | uint32_t i = 0; |
488 | |
|
489 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
490 | 0 | { |
491 | 0 | stringbuffer_append_len(sb, "MULTISURFACE", 12); /* "MULTISURFACE" */ |
492 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)msurf, sb, variant); |
493 | 0 | } |
494 | 0 | if ( msurf->ngeoms < 1 ) |
495 | 0 | { |
496 | 0 | empty_to_wkt_sb(sb); |
497 | 0 | return; |
498 | 0 | } |
499 | 0 | stringbuffer_append_len(sb, "(", 1); |
500 | 0 | variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ |
501 | 0 | for ( i = 0; i < msurf->ngeoms; i++ ) |
502 | 0 | { |
503 | 0 | int type = msurf->geoms[i]->type; |
504 | 0 | if ( i > 0 ) |
505 | 0 | stringbuffer_append_len(sb, ",", 1); |
506 | 0 | switch (type) |
507 | 0 | { |
508 | 0 | case POLYGONTYPE: |
509 | | /* Linestring subgeoms don't get type identifiers */ |
510 | 0 | lwpoly_to_wkt_sb((LWPOLY*)msurf->geoms[i], sb, precision, variant | WKT_NO_TYPE ); |
511 | 0 | break; |
512 | 0 | case CURVEPOLYTYPE: |
513 | | /* But circstring subgeoms *do* get type identifiers */ |
514 | 0 | lwcurvepoly_to_wkt_sb((LWCURVEPOLY*)msurf->geoms[i], sb, precision, variant); |
515 | 0 | break; |
516 | 0 | default: |
517 | 0 | lwerror("lwmsurface_to_wkt_sb: Unknown type received %d - %s", type, lwtype_name(type)); |
518 | 0 | } |
519 | 0 | } |
520 | 0 | stringbuffer_append_len(sb, ")", 1); |
521 | 0 | } |
522 | | |
523 | | /* |
524 | | * Geometry collections provide type information for all their curved sub-geometries |
525 | | * but not their linear sub-geometries. |
526 | | * GEOMETRYCOLLECTION(POLYGON((0 0, 1 1, 1 0, 0 0)), CURVEPOLYGON(CURVESTRING(0 0, 1 1, 2 2, 0 1, 0 0))) |
527 | | */ |
528 | | static void lwcollection_to_wkt_sb(const LWCOLLECTION *collection, stringbuffer_t *sb, int precision, uint8_t variant) |
529 | 0 | { |
530 | 0 | uint32_t i = 0; |
531 | |
|
532 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
533 | 0 | { |
534 | 0 | stringbuffer_append_len(sb, "GEOMETRYCOLLECTION", 18); /* "GEOMETRYCOLLECTION" */ |
535 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)collection, sb, variant); |
536 | 0 | } |
537 | 0 | if ( collection->ngeoms < 1 ) |
538 | 0 | { |
539 | 0 | empty_to_wkt_sb(sb); |
540 | 0 | return; |
541 | 0 | } |
542 | 0 | stringbuffer_append_len(sb, "(", 1); |
543 | 0 | variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are children */ |
544 | 0 | for ( i = 0; i < collection->ngeoms; i++ ) |
545 | 0 | { |
546 | 0 | if ( i > 0 ) |
547 | 0 | stringbuffer_append_len(sb, ",", 1); |
548 | 0 | lwgeom_to_wkt_sb((LWGEOM*)collection->geoms[i], sb, precision, variant ); |
549 | 0 | } |
550 | 0 | stringbuffer_append_len(sb, ")", 1); |
551 | 0 | } |
552 | | |
553 | | /* |
554 | | * TRIANGLE |
555 | | */ |
556 | | static void lwtriangle_to_wkt_sb(const LWTRIANGLE *tri, stringbuffer_t *sb, int precision, uint8_t variant) |
557 | 0 | { |
558 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
559 | 0 | { |
560 | 0 | stringbuffer_append_len(sb, "TRIANGLE", 8); /* "TRIANGLE" */ |
561 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)tri, sb, variant); |
562 | 0 | } |
563 | 0 | if ( lwtriangle_is_empty(tri) ) |
564 | 0 | { |
565 | 0 | empty_to_wkt_sb(sb); |
566 | 0 | return; |
567 | 0 | } |
568 | | |
569 | 0 | stringbuffer_append_len(sb, "(", 1); /* Triangles have extraneous brackets */ |
570 | 0 | ptarray_to_wkt_sb(tri->points, sb, precision, variant); |
571 | 0 | stringbuffer_append_len(sb, ")", 1); |
572 | 0 | } |
573 | | |
574 | | /* |
575 | | * TIN |
576 | | */ |
577 | | static void lwtin_to_wkt_sb(const LWTIN *tin, stringbuffer_t *sb, int precision, uint8_t variant) |
578 | 0 | { |
579 | 0 | uint32_t i = 0; |
580 | |
|
581 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
582 | 0 | { |
583 | 0 | stringbuffer_append_len(sb, "TIN", 3); /* "TIN" */ |
584 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)tin, sb, variant); |
585 | 0 | } |
586 | 0 | if ( tin->ngeoms < 1 ) |
587 | 0 | { |
588 | 0 | empty_to_wkt_sb(sb); |
589 | 0 | return; |
590 | 0 | } |
591 | | |
592 | 0 | stringbuffer_append_len(sb, "(", 1); |
593 | 0 | for ( i = 0; i < tin->ngeoms; i++ ) |
594 | 0 | { |
595 | 0 | if ( i > 0 ) |
596 | 0 | stringbuffer_append_len(sb, ",", 1); |
597 | | /* We don't want type strings on our subgeoms */ |
598 | 0 | lwtriangle_to_wkt_sb(tin->geoms[i], sb, precision, variant | WKT_NO_TYPE ); |
599 | 0 | } |
600 | 0 | stringbuffer_append_len(sb, ")", 1); |
601 | 0 | } |
602 | | |
603 | | /** |
604 | | * Emit a POLYHEDRALSURFACE WKT representation for the given LWPSURFACE. |
605 | | * |
606 | | * Writes the WKT form (e.g. `POLYHEDRALSURFACE ...`) into the provided string buffer, |
607 | | * including dimension qualifiers unless suppressed by the variant flags. If the surface |
608 | | * contains no polygons, emits the `EMPTY` token. Sub-geometries are emitted as child |
609 | | * geometries (their type tokens are suppressed). |
610 | | * |
611 | | * @param psurf Polyhedral surface to convert to WKT. |
612 | | * @param sb String buffer to append the generated WKT to. |
613 | | * @param precision Number of decimal digits to use when formatting coordinates. |
614 | | * @param variant Bitmask of WKT emission options (e.g. WKT_NO_TYPE, WKT_IS_CHILD, WKT_EXTENDED). |
615 | | */ |
616 | | static void lwpsurface_to_wkt_sb(const LWPSURFACE *psurf, stringbuffer_t *sb, int precision, uint8_t variant) |
617 | 0 | { |
618 | 0 | uint32_t i = 0; |
619 | |
|
620 | 0 | if ( ! (variant & WKT_NO_TYPE) ) |
621 | 0 | { |
622 | 0 | stringbuffer_append_len(sb, "POLYHEDRALSURFACE", 17); /* "POLYHEDRALSURFACE" */ |
623 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)psurf, sb, variant); |
624 | 0 | } |
625 | 0 | if ( psurf->ngeoms < 1 ) |
626 | 0 | { |
627 | 0 | empty_to_wkt_sb(sb); |
628 | 0 | return; |
629 | 0 | } |
630 | | |
631 | 0 | variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ |
632 | |
|
633 | 0 | stringbuffer_append_len(sb, "(", 1); |
634 | 0 | for ( i = 0; i < psurf->ngeoms; i++ ) |
635 | 0 | { |
636 | 0 | if ( i > 0 ) |
637 | 0 | stringbuffer_append_len(sb, ",", 1); |
638 | | /* We don't want type strings on our subgeoms */ |
639 | 0 | lwpoly_to_wkt_sb(psurf->geoms[i], sb, precision, variant | WKT_NO_TYPE ); |
640 | 0 | } |
641 | 0 | stringbuffer_append_len(sb, ")", 1); |
642 | 0 | } |
643 | | |
644 | | /** |
645 | | * Emit a NURBSCURVE as WKT into the provided stringbuffer. |
646 | | * |
647 | | * Produces the WKT representation for the given NURBSCURVE and appends it to |
648 | | * sb. If the variant does not include WKT_NO_TYPE, the leading type token |
649 | | * "NURBSCURVE" (with appropriate dimension qualifiers) is written. If the |
650 | | * curve has no control points the token "EMPTY" is emitted. |
651 | | * |
652 | | * The WKT payload follows the ISO form: DEGREE, CONTROLPOINTS containing |
653 | | * NURBSPOINT/WEIGHTEDPOINT entries, and KNOTS containing collapsed knot |
654 | | * multiplicities. Default uniform weights and knots are emitted to preserve |
655 | | * round-trip consistency. |
656 | | * |
657 | | * Numeric values (coordinates, weights, knots) are formatted using the given |
658 | | * precision (passed to printf-style formatting). |
659 | | * |
660 | | * @param curve NURBSCURVE to format. |
661 | | * @param sb Destination stringbuffer; WKT text is appended to it. |
662 | | * @param precision Number of significant digits used when formatting numeric values. |
663 | | * @param variant Bitmask of WKT emission flags (e.g., WKT_NO_TYPE to suppress the type token). |
664 | | */ |
665 | | static void lwnurbscurve_to_wkt_sb(const LWNURBSCURVE *curve, stringbuffer_t *sb, int precision, uint8_t variant) |
666 | 0 | { |
667 | 0 | uint32_t dimensions; |
668 | 0 | uint8_t output_variant = variant | WKT_ISO; |
669 | 0 | uint32_t nknots = 0; |
670 | 0 | double *knots = NULL; |
671 | |
|
672 | 0 | if (!(variant & WKT_NO_TYPE)) |
673 | 0 | { |
674 | 0 | stringbuffer_append_len(sb, "NURBSCURVE", 10); |
675 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)curve, sb, output_variant); |
676 | 0 | } |
677 | |
|
678 | 0 | if (!curve->points || curve->points->npoints == 0) |
679 | 0 | { |
680 | 0 | empty_to_wkt_sb(sb); |
681 | 0 | return; |
682 | 0 | } |
683 | | |
684 | 0 | stringbuffer_append_len(sb, "(", 1); |
685 | 0 | stringbuffer_aprintf(sb, "DEGREE %d,CONTROLPOINTS", curve->degree); |
686 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)curve, sb, output_variant); |
687 | 0 | stringbuffer_append_len(sb, "(", 1); |
688 | |
|
689 | 0 | dimensions = (output_variant & (WKT_ISO | WKT_EXTENDED)) ? FLAGS_NDIMS(curve->points->flags) : 2; |
690 | 0 | for (uint32_t i = 0; i < curve->points->npoints; i++) |
691 | 0 | { |
692 | 0 | double *coords = (double *)getPoint_internal(curve->points, i); |
693 | 0 | double weight = (curve->weights && i < curve->nweights) ? curve->weights[i] : 1.0; |
694 | |
|
695 | 0 | if (i > 0) |
696 | 0 | stringbuffer_append_len(sb, ",", 1); |
697 | |
|
698 | 0 | stringbuffer_append_len(sb, "NURBSPOINT(WEIGHTEDPOINT", 24); |
699 | 0 | dimension_qualifiers_to_wkt_sb((LWGEOM*)curve, sb, output_variant); |
700 | 0 | stringbuffer_append_len(sb, "(", 1); |
701 | 0 | coordinate_to_wkt_sb(coords, sb, dimensions, precision); |
702 | 0 | stringbuffer_append_len(sb, "),WEIGHT ", strlen("),WEIGHT ")); |
703 | 0 | stringbuffer_append_double(sb, weight, precision); |
704 | 0 | stringbuffer_append_len(sb, ")", 1); |
705 | 0 | } |
706 | 0 | stringbuffer_append_len(sb, ")", 1); |
707 | |
|
708 | 0 | knots = lwnurbscurve_get_or_generate_knots(curve, &nknots); |
709 | 0 | if (knots && nknots > 0) |
710 | 0 | { |
711 | 0 | uint32_t knot_count = 0; |
712 | 0 | stringbuffer_append_len(sb, ",KNOTS (", 8); |
713 | 0 | for (uint32_t i = 0; i < nknots; i += knot_count) |
714 | 0 | { |
715 | 0 | knot_count = 1; |
716 | 0 | while (i + knot_count < nknots && fabs(knots[i + knot_count] - knots[i]) <= FP_TOLERANCE) |
717 | 0 | knot_count++; |
718 | |
|
719 | 0 | if (i > 0) |
720 | 0 | stringbuffer_append_len(sb, ",", 1); |
721 | 0 | stringbuffer_append_len(sb, "KNOT(", 5); |
722 | 0 | stringbuffer_append_double(sb, knots[i], precision); |
723 | 0 | stringbuffer_append_len(sb, ",", 1); |
724 | 0 | stringbuffer_aprintf(sb, "%d", knot_count); |
725 | 0 | stringbuffer_append_len(sb, ")", 1); |
726 | 0 | } |
727 | 0 | stringbuffer_append_len(sb, ")", 1); |
728 | 0 | lwfree(knots); |
729 | 0 | } |
730 | |
|
731 | 0 | stringbuffer_append_len(sb, ")", 1); |
732 | 0 | } |
733 | | |
734 | | /** |
735 | | * Dispatches a geometry to the appropriate WKT emitter and appends result to a string buffer. |
736 | | * |
737 | | * Given a concrete LWGEOM subclass, selects the matching type-specific |
738 | | * writer (POINT, LINESTRING, POLYGON, MULTI*, CURVE, etc.) and emits its |
739 | | * Well-Known Text representation into the provided stringbuffer. |
740 | | * |
741 | | * @param geom Input geometry (may not be NULL). |
742 | | * @param sb Destination stringbuffer to which WKT text is appended. |
743 | | * @param precision Number of decimal digits to use when formatting coordinates. |
744 | | * @param variant Bitmask selecting WKT variant/formatting flags (e.g., ISO/Extended/SFSQL and child/parenthesis behaviors). |
745 | | * |
746 | | * Note: Unsupported geometry types are reported via lwerror; this function itself does not return an error code. It has the side effect of modifying the supplied stringbuffer. |
747 | | */ |
748 | | static void lwgeom_to_wkt_sb(const LWGEOM *geom, stringbuffer_t *sb, int precision, uint8_t variant) |
749 | 0 | { |
750 | 0 | LWDEBUGF(4, "lwgeom_to_wkt_sb: type %s, hasz %d, hasm %d", |
751 | 0 | lwtype_name(geom->type), |
752 | 0 | FLAGS_GET_Z(geom->flags)?1:0, FLAGS_GET_M(geom->flags)?1:0); |
753 | |
|
754 | 0 | switch (geom->type) |
755 | 0 | { |
756 | 0 | case POINTTYPE: |
757 | 0 | lwpoint_to_wkt_sb((LWPOINT*)geom, sb, precision, variant); |
758 | 0 | break; |
759 | 0 | case LINETYPE: |
760 | 0 | lwline_to_wkt_sb((LWLINE*)geom, sb, precision, variant); |
761 | 0 | break; |
762 | 0 | case POLYGONTYPE: |
763 | 0 | lwpoly_to_wkt_sb((LWPOLY*)geom, sb, precision, variant); |
764 | 0 | break; |
765 | 0 | case MULTIPOINTTYPE: |
766 | 0 | lwmpoint_to_wkt_sb((LWMPOINT*)geom, sb, precision, variant); |
767 | 0 | break; |
768 | 0 | case MULTILINETYPE: |
769 | 0 | lwmline_to_wkt_sb((LWMLINE*)geom, sb, precision, variant); |
770 | 0 | break; |
771 | 0 | case MULTIPOLYGONTYPE: |
772 | 0 | lwmpoly_to_wkt_sb((LWMPOLY*)geom, sb, precision, variant); |
773 | 0 | break; |
774 | 0 | case COLLECTIONTYPE: |
775 | 0 | lwcollection_to_wkt_sb((LWCOLLECTION*)geom, sb, precision, variant); |
776 | 0 | break; |
777 | 0 | case CIRCSTRINGTYPE: |
778 | 0 | lwcircstring_to_wkt_sb((LWCIRCSTRING*)geom, sb, precision, variant); |
779 | 0 | break; |
780 | 0 | case COMPOUNDTYPE: |
781 | 0 | lwcompound_to_wkt_sb((LWCOMPOUND*)geom, sb, precision, variant); |
782 | 0 | break; |
783 | 0 | case CURVEPOLYTYPE: |
784 | 0 | lwcurvepoly_to_wkt_sb((LWCURVEPOLY*)geom, sb, precision, variant); |
785 | 0 | break; |
786 | 0 | case MULTICURVETYPE: |
787 | 0 | lwmcurve_to_wkt_sb((LWMCURVE*)geom, sb, precision, variant); |
788 | 0 | break; |
789 | 0 | case MULTISURFACETYPE: |
790 | 0 | lwmsurface_to_wkt_sb((LWMSURFACE*)geom, sb, precision, variant); |
791 | 0 | break; |
792 | 0 | case TRIANGLETYPE: |
793 | 0 | lwtriangle_to_wkt_sb((LWTRIANGLE*)geom, sb, precision, variant); |
794 | 0 | break; |
795 | 0 | case TINTYPE: |
796 | 0 | lwtin_to_wkt_sb((LWTIN*)geom, sb, precision, variant); |
797 | 0 | break; |
798 | 0 | case POLYHEDRALSURFACETYPE: |
799 | 0 | lwpsurface_to_wkt_sb((LWPSURFACE*)geom, sb, precision, variant); |
800 | 0 | break; |
801 | 0 | case NURBSCURVETYPE: |
802 | 0 | lwnurbscurve_to_wkt_sb((LWNURBSCURVE*)geom, sb, precision, variant); |
803 | 0 | break; |
804 | 0 | default: |
805 | 0 | lwerror("lwgeom_to_wkt_sb: Type %d - %s unsupported.", |
806 | 0 | geom->type, lwtype_name(geom->type)); |
807 | 0 | } |
808 | 0 | } |
809 | | |
810 | | static stringbuffer_t * |
811 | | lwgeom_to_wkt_internal(const LWGEOM *geom, uint8_t variant, int precision) |
812 | 0 | { |
813 | 0 | stringbuffer_t *sb; |
814 | 0 | if ( geom == NULL ) |
815 | 0 | return NULL; |
816 | 0 | sb = stringbuffer_create(); |
817 | | /* Extended mode starts with an "SRID=" section for geoms that have one */ |
818 | 0 | if ( (variant & WKT_EXTENDED) && lwgeom_has_srid(geom) ) |
819 | 0 | { |
820 | 0 | stringbuffer_aprintf(sb, "SRID=%d;", geom->srid); |
821 | 0 | } |
822 | 0 | lwgeom_to_wkt_sb(geom, sb, precision, variant); |
823 | 0 | if ( stringbuffer_getstring(sb) == NULL ) |
824 | 0 | { |
825 | 0 | lwerror("Uh oh"); |
826 | 0 | return NULL; |
827 | 0 | } |
828 | 0 | return sb; |
829 | 0 | } |
830 | | |
831 | | /** |
832 | | * WKT emitter function. Allocates a new *char and fills it with the WKT |
833 | | * representation. If size_out is not NULL, it will be set to the size of the |
834 | | * allocated *char. |
835 | | * |
836 | | * @param variant Bitmasked value, accepts one of WKT_ISO, WKT_SFSQL, |
837 | | * WKT_EXTENDED. |
838 | | * @param precision Maximal number of digits after comma in the output doubles. |
839 | | * @param size_out If supplied, will return the size of the returned string, |
840 | | * including the null terminator. |
841 | | */ |
842 | | char * |
843 | | lwgeom_to_wkt(const LWGEOM *geom, uint8_t variant, int precision, size_t *size_out) |
844 | 0 | { |
845 | 0 | stringbuffer_t *sb = lwgeom_to_wkt_internal(geom, variant, precision); |
846 | 0 | if (!sb) |
847 | 0 | return NULL; |
848 | 0 | char *str = stringbuffer_getstringcopy(sb); |
849 | 0 | if ( size_out ) |
850 | 0 | *size_out = stringbuffer_getlength(sb) + 1; |
851 | 0 | stringbuffer_destroy(sb); |
852 | 0 | return str; |
853 | 0 | } |
854 | | |
855 | | lwvarlena_t * |
856 | | lwgeom_to_wkt_varlena(const LWGEOM *geom, uint8_t variant, int precision) |
857 | 0 | { |
858 | 0 | stringbuffer_t *sb = lwgeom_to_wkt_internal(geom, variant, precision); |
859 | 0 | if (!sb) |
860 | 0 | return NULL; |
861 | 0 | lwvarlena_t *output = stringbuffer_getvarlenacopy(sb); |
862 | 0 | stringbuffer_destroy(sb); |
863 | 0 | return output; |
864 | 0 | } |