Coverage Report

Created: 2026-08-14 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}