Coverage Report

Created: 2026-05-30 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/io/WKTReader.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2005-2006 Refractions Research Inc.
7
 * Copyright (C) 2001-2002 Vivid Solutions Inc.
8
 *
9
 * This is free software; you can redistribute and/or modify it under
10
 * the terms of the GNU Lesser General Public Licence as published
11
 * by the Free Software Foundation.
12
 * See the COPYING file for more information.
13
 *
14
 **********************************************************************
15
 *
16
 * Last port: io/WKTReader.java rev. 1.1 (JTS-1.7)
17
 *
18
 **********************************************************************/
19
20
#include <geos/io/WKTReader.h>
21
#include <geos/io/StringTokenizer.h>
22
#include <geos/io/ParseException.h>
23
#include <geos/io/CLocalizer.h>
24
#include <geos/geom/Coordinate.h>
25
#include <geos/geom/CircularString.h>
26
#include <geos/geom/CompoundCurve.h>
27
#include <geos/geom/Point.h>
28
#include <geos/geom/LinearRing.h>
29
#include <geos/geom/LineString.h>
30
#include <geos/geom/Polygon.h>
31
#include <geos/geom/CurvePolygon.h>
32
#include <geos/geom/MultiPoint.h>
33
#include <geos/geom/MultiLineString.h>
34
#include <geos/geom/MultiCurve.h>
35
#include <geos/geom/MultiPolygon.h>
36
#include <geos/geom/MultiSurface.h>
37
#include <geos/geom/CoordinateSequence.h>
38
#include <geos/geom/Surface.h>
39
#include <geos/util.h>
40
#include <geos/util/string.h>
41
42
#include <sstream>
43
#include <string>
44
#include <cassert>
45
46
47
using namespace geos::geom;
48
49
namespace geos {
50
namespace io { // geos.io
51
52
static constexpr int MAX_PARSE_DEPTH = 100;
53
54
std::unique_ptr<Geometry>
55
WKTReader::read(const std::string& wellKnownText) const
56
9.03k
{
57
9.03k
    parseDepth_ = 0;
58
9.03k
    CLocalizer clocale;
59
9.03k
    StringTokenizer tokenizer(wellKnownText);
60
9.03k
    OrdinateSet ordinateFlags = OrdinateSet::createXY();
61
9.03k
    auto ret = readGeometryTaggedText(&tokenizer, ordinateFlags);
62
63
9.03k
    if (tokenizer.peekNextToken() != StringTokenizer::TT_EOF) {
64
59
        tokenizer.nextToken();
65
59
        throw ParseException("Unexpected text after end of geometry");
66
59
    }
67
68
8.97k
    return ret;
69
9.03k
}
70
71
72
std::unique_ptr<CoordinateSequence>
73
WKTReader::readCoordinates(const std::string& wellKnownText) const
74
0
{
75
0
    CLocalizer clocale;
76
0
    StringTokenizer tokenizer(wellKnownText);
77
0
    OrdinateSet ordinateFlags = OrdinateSet::createXY();
78
0
    auto ret = getCoordinates(&tokenizer, ordinateFlags);
79
80
0
    if (tokenizer.peekNextToken() != StringTokenizer::TT_EOF) {
81
0
        tokenizer.nextToken();
82
0
        throw ParseException("Unexpected text after end of geometry");
83
0
    }
84
85
0
    return ret;
86
0
}
87
88
std::unique_ptr<CoordinateSequence>
89
WKTReader::getCoordinates(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
90
2.23M
{
91
2.23M
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
92
2.23M
    if(nextToken == "EMPTY") {
93
4.07k
        return detail::make_unique<CoordinateSequence>(0u, ordinateFlags.hasZ(), ordinateFlags.hasM());
94
4.07k
    }
95
96
2.23M
    CoordinateXYZM coord(0, 0, DoubleNotANumber, DoubleNotANumber);
97
2.23M
    getPreciseCoordinate(tokenizer, ordinateFlags, coord);
98
99
2.23M
    auto coordinates = detail::make_unique<CoordinateSequence>(0u, ordinateFlags.hasZ(), ordinateFlags.hasM());
100
2.23M
    coordinates->add(coord);
101
102
2.23M
    nextToken = getNextCloserOrComma(tokenizer);
103
13.3M
    while(nextToken == ",") {
104
11.1M
        getPreciseCoordinate(tokenizer, ordinateFlags, coord);
105
11.1M
        coordinates->add(coord);
106
11.1M
        nextToken = getNextCloserOrComma(tokenizer);
107
11.1M
    }
108
109
2.23M
    return coordinates;
110
2.23M
}
111
112
void
113
WKTReader::getPreciseCoordinate(StringTokenizer* tokenizer,
114
                                OrdinateSet& ordinateFlags,
115
17.3M
                                CoordinateXYZM& coord) const {
116
17.3M
    coord.x = getNextNumber(tokenizer);
117
17.3M
    coord.y = getNextNumber(tokenizer);
118
119
    // Check for undeclared Z dimension
120
17.3M
    if (ordinateFlags.changesAllowed() && isNumberNext(tokenizer)) {
121
3.54k
        ordinateFlags.setZ(true);
122
3.54k
    }
123
124
17.3M
    if (ordinateFlags.hasZ()) {
125
9.89k
        coord.z = getNextNumber(tokenizer);
126
9.89k
    }
127
128
    // Check for undeclared M dimension
129
17.3M
    if (ordinateFlags.changesAllowed() && ordinateFlags.hasZ() && isNumberNext(tokenizer)) {
130
1.40k
        ordinateFlags.setM(true);
131
1.40k
    }
132
133
17.3M
    if (ordinateFlags.hasM()) {
134
35.4k
        coord.m = getNextNumber(tokenizer);
135
35.4k
    }
136
137
17.3M
    ordinateFlags.setChangesAllowed(false); // First coordinate read; future coordinates must be consistent
138
139
17.3M
    precisionModel->makePrecise(coord);
140
17.3M
}
141
142
bool
143
WKTReader::isNumberNext(StringTokenizer* tokenizer)
144
33.9k
{
145
33.9k
    return tokenizer->peekNextToken() == StringTokenizer::TT_NUMBER;
146
33.9k
}
147
148
bool
149
WKTReader::isOpenerNext(StringTokenizer* tokenizer)
150
0
{
151
0
    return tokenizer->peekNextToken() == '(';
152
0
}
153
154
double
155
WKTReader::getNextNumber(StringTokenizer* tokenizer)
156
34.6M
{
157
34.6M
    int type = tokenizer->nextToken();
158
34.6M
    switch(type) {
159
22
    case StringTokenizer::TT_EOF:
160
22
        throw  ParseException("Expected number but encountered end of stream");
161
0
    case StringTokenizer::TT_EOL:
162
0
        throw  ParseException("Expected number but encountered end of line");
163
34.6M
    case StringTokenizer::TT_NUMBER:
164
34.6M
        return tokenizer->getNVal();
165
41
    case StringTokenizer::TT_WORD:
166
41
        throw  ParseException("Expected number but encountered word", tokenizer->getSVal());
167
4
    case '(':
168
4
        throw  ParseException("Expected number but encountered '('");
169
5
    case ')':
170
5
        throw  ParseException("Expected number but encountered ')'");
171
13
    case ',':
172
13
        throw  ParseException("Expected number but encountered ','");
173
34.6M
    }
174
34.6M
    assert(0); // Encountered unexpected StreamTokenizer type
175
0
    return 0;
176
34.6M
}
177
178
bool
179
308k
WKTReader::isTypeName(const std::string & type, const std::string & typeName) {
180
308k
    auto l = type.size();
181
308k
    auto r = typeName.size();
182
308k
    if (l == r && type == typeName) return true;
183
272k
    if (l == r + 1 && (type == typeName + 'Z' || type == typeName + 'M')) return true;
184
270k
    if (l == r + 2 && type == typeName + "ZM") return true;
185
186
    // take `POINT` as example:
187
    // POI, POINTa, POINTZMx are all invalid
188
269k
    return false;
189
270k
}
190
191
void
192
38.7k
WKTReader::readOrdinateFlags(const std::string & s, OrdinateSet& ordinateFlags) {
193
38.7k
    if (util::endsWith(s, "ZM")) {
194
564
        ordinateFlags.setM(true);
195
564
        ordinateFlags.setZ(true);
196
564
        ordinateFlags.setChangesAllowed(false);
197
38.1k
    } else if (util::endsWith(s, 'M')) {
198
2.30k
        ordinateFlags.setM(true);
199
2.30k
        ordinateFlags.setChangesAllowed(false);
200
35.8k
    } else if (util::endsWith(s, 'Z')) {
201
275
        ordinateFlags.setZ(true);
202
275
        ordinateFlags.setChangesAllowed(false);
203
275
    }
204
38.7k
}
205
206
std::string
207
WKTReader::getNextEmptyOrOpener(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags)
208
3.96M
{
209
3.96M
    const bool changesAllowed = ordinateFlags.changesAllowed();
210
3.96M
    std::string nextWord = getNextWord(tokenizer);
211
212
3.96M
    bool flagsModified = false;
213
214
    // Skip the Z, M or ZM of an SF1.2 3/4 dim coordinate.
215
3.96M
    if (nextWord == "ZM") {
216
172
        if (!changesAllowed) {
217
2
          throw ParseException("'ZM' found but 'ZM' or 'Z' or 'M' has been specified in previous type");
218
2
        }
219
170
        ordinateFlags.setZ(true);
220
170
        ordinateFlags.setM(true);
221
170
        flagsModified = true;
222
170
        nextWord = getNextWord(tokenizer);
223
3.96M
    } else {
224
3.96M
        if (nextWord == "Z") {
225
636
            if (!changesAllowed) {
226
1
              throw ParseException("'Z' found but 'ZM' or 'Z' or 'M' has been specified in previous type");
227
1
            }
228
635
            ordinateFlags.setZ(true);
229
635
            flagsModified = true;
230
635
            nextWord = getNextWord(tokenizer);
231
635
        }
232
233
3.96M
        if (nextWord == "M") {
234
91
            if (!changesAllowed || flagsModified) {
235
2
              throw ParseException("'M' found but 'ZM' or 'Z' or 'M' has been specified previously");
236
2
            }
237
89
            ordinateFlags.setM(true);
238
89
            flagsModified = true;
239
89
            nextWord = getNextWord(tokenizer);
240
89
        }
241
3.96M
    }
242
243
3.96M
    if (flagsModified) {
244
888
        ordinateFlags.setChangesAllowed(false);
245
888
    }
246
247
3.96M
    if(nextWord == "EMPTY" || nextWord == "(") {
248
3.96M
        return nextWord;
249
3.96M
    }
250
150
    throw ParseException("Expected 'Z', 'M', 'ZM', 'EMPTY' or '(' but encountered ", nextWord);
251
3.96M
}
252
253
std::string
254
WKTReader::getNextCloserOrComma(StringTokenizer* tokenizer)
255
21.2M
{
256
21.2M
    std::string nextWord = getNextWord(tokenizer);
257
21.2M
    if(nextWord == "," || nextWord == ")") {
258
21.2M
        return nextWord;
259
21.2M
    }
260
117
    throw  ParseException("Expected ')' or ',' but encountered", nextWord);
261
21.2M
}
262
263
std::string
264
WKTReader::getNextCloser(StringTokenizer* tokenizer)
265
0
{
266
0
    std::string nextWord = getNextWord(tokenizer);
267
0
    if(nextWord == ")") {
268
0
        return nextWord;
269
0
    }
270
0
    throw  ParseException("Expected ')' but encountered", nextWord);
271
0
}
272
273
std::string
274
WKTReader::getNextWord(StringTokenizer* tokenizer)
275
25.2M
{
276
25.2M
    int type = tokenizer->nextToken();
277
25.2M
    switch(type) {
278
66
    case StringTokenizer::TT_EOF:
279
66
        throw  ParseException("Expected word but encountered end of stream");
280
0
    case StringTokenizer::TT_EOL:
281
0
        throw  ParseException("Expected word but encountered end of line");
282
25
    case StringTokenizer::TT_NUMBER:
283
25
        throw  ParseException("Expected word but encountered number", tokenizer->getNVal());
284
49.4k
    case StringTokenizer::TT_WORD: {
285
49.4k
        std::string word = tokenizer->getSVal();
286
21.1M
        for (char& c : word) {
287
            // Avoid UB if c is not representable as unsigned char
288
            // https://en.cppreference.com/w/cpp/string/byte/toupper
289
21.1M
            c = static_cast<char>(toupper(static_cast<unsigned char>(c)));
290
21.1M
        }
291
49.4k
        return word;
292
0
    }
293
3.96M
    case '(':
294
3.96M
        return "(";
295
3.95M
    case ')':
296
3.95M
        return ")";
297
17.3M
    case ',':
298
17.3M
        return ",";
299
25.2M
    }
300
25.2M
    assert(0);
301
    //throw  ParseException("Encountered unexpected StreamTokenizer type");
302
0
    return "";
303
25.2M
}
304
305
std::unique_ptr<Geometry>
306
WKTReader::readGeometryTaggedText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags, const GeometryTypeId* emptyType) const
307
41.8k
{
308
41.8k
    if (parseDepth_ >= MAX_PARSE_DEPTH) {
309
7
        throw ParseException("Input geometry exceeds nesting depth limit");
310
7
    }
311
41.8k
    ++parseDepth_;
312
41.8k
    struct DepthGuard { int& d; ~DepthGuard() { --d; } } guard{parseDepth_};
313
314
41.8k
    std::string type = getNextWord(tokenizer);
315
316
41.8k
    std::unique_ptr<Geometry> geom;
317
41.8k
    OrdinateSet origFlags = ordinateFlags;
318
319
41.8k
    OrdinateSet newFlags = OrdinateSet::createXY();
320
41.8k
    if (type == "EMPTY") {
321
3.07k
        newFlags = origFlags;
322
38.7k
    } else {
323
38.7k
        readOrdinateFlags(type, newFlags);
324
38.7k
    }
325
326
41.8k
    if(isTypeName(type, "POINT")) {
327
2.73k
        geom = readPointText(tokenizer, newFlags);
328
2.73k
    }
329
39.0k
    else if(isTypeName(type, "LINESTRING")) {
330
1.94k
        geom = readLineStringText(tokenizer, newFlags);
331
1.94k
    }
332
37.1k
    else if(isTypeName(type, "LINEARRING")) {
333
1.76k
        geom = readLinearRingText(tokenizer, newFlags);
334
1.76k
    }
335
35.3k
    else if(isTypeName(type, "CIRCULARSTRING")) {
336
3.65k
        geom = readCircularStringText(tokenizer, newFlags);
337
3.65k
    }
338
31.7k
    else if(isTypeName(type, "COMPOUNDCURVE")) {
339
1.16k
        geom = readCompoundCurveText(tokenizer, newFlags);
340
1.16k
    }
341
30.5k
    else if(isTypeName(type, "POLYGON")) {
342
2.77k
        geom = readPolygonText(tokenizer, newFlags);
343
2.77k
    }
344
27.8k
    else if(isTypeName(type, "CURVEPOLYGON")) {
345
611
        geom = readCurvePolygonText(tokenizer, newFlags);
346
611
    }
347
27.1k
    else if(isTypeName(type,  "MULTIPOINT")) {
348
18.6k
        geom = readMultiPointText(tokenizer, newFlags);
349
18.6k
    }
350
8.52k
    else if(isTypeName(type, "MULTILINESTRING")) {
351
418
        geom = readMultiLineStringText(tokenizer, newFlags);
352
418
    }
353
8.11k
    else if(isTypeName(type, "MULTICURVE")) {
354
374
        geom = readMultiCurveText(tokenizer, newFlags);
355
374
    }
356
7.73k
    else if(isTypeName(type, "MULTIPOLYGON")) {
357
1.06k
        geom = readMultiPolygonText(tokenizer, newFlags);
358
1.06k
    }
359
6.67k
    else if(isTypeName(type, "MULTISURFACE")) {
360
237
        geom = readMultiSurfaceText(tokenizer, newFlags);
361
237
    }
362
6.43k
    else if(isTypeName(type, "GEOMETRYCOLLECTION")) {
363
2.98k
        geom = readGeometryCollectionText(tokenizer, newFlags);
364
3.44k
    } else if (type == "EMPTY" && emptyType != nullptr) {
365
3.07k
        return geometryFactory->createEmptyGeometry(*emptyType, newFlags.hasZ(), newFlags.hasM());
366
3.07k
    } else {
367
374
        throw ParseException("Unknown type", type);
368
374
    }
369
370
38.3k
    if (!origFlags.changesAllowed() && newFlags != origFlags) {
371
12
        throw ParseException("Cannot mix dimensionality in a geometry.");
372
12
    }
373
374
38.3k
    return geom;
375
376
38.3k
}
377
378
std::unique_ptr<Point>
379
WKTReader::readPointText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
380
5.49k
{
381
5.49k
    auto&& coords = getCoordinates(tokenizer, ordinateFlags);
382
5.49k
    return geometryFactory->createPoint(std::move(coords));
383
5.49k
}
384
385
std::unique_ptr<LineString>
386
WKTReader::readLineStringText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
387
8.64k
{
388
8.64k
    auto&& coords = getCoordinates(tokenizer, ordinateFlags);
389
8.64k
    return geometryFactory->createLineString(std::move(coords));
390
8.64k
}
391
392
std::unique_ptr<LinearRing>
393
WKTReader::readLinearRingText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
394
2.21M
{
395
2.21M
    auto&& coords = getCoordinates(tokenizer, ordinateFlags);
396
2.21M
    if (fixStructure && !coords->isRing()) {
397
0
        coords->closeRing();
398
0
    }
399
2.21M
    return geometryFactory->createLinearRing(std::move(coords));
400
2.21M
}
401
402
std::unique_ptr<CircularString>
403
WKTReader::readCircularStringText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
404
3.65k
{
405
3.65k
    auto&& coords = getCoordinates(tokenizer, ordinateFlags);
406
3.65k
    return geometryFactory->createCircularString(std::move(coords));
407
3.65k
}
408
409
std::unique_ptr<Curve>
410
WKTReader::readCurveText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
411
10.0k
{
412
10.0k
    int type = tokenizer->peekNextToken();
413
10.0k
    if (type == '(') {
414
4.15k
        return readLineStringText(tokenizer, ordinateFlags);
415
4.15k
    }
416
417
5.89k
    GeometryTypeId defaultType = GEOS_LINESTRING;
418
5.89k
    auto component = readGeometryTaggedText(tokenizer, ordinateFlags, &defaultType);
419
5.89k
    if (dynamic_cast<Curve*>(component.get())) {
420
4.95k
        return std::unique_ptr<Curve>(static_cast<Curve*>(component.release()));
421
4.95k
    }
422
423
942
    throw ParseException("Expected LINESTRING/CIRCULARSTRING/COMPOUNDCURVE but got " + component->getGeometryType());
424
5.89k
}
425
426
std::unique_ptr<Geometry>
427
WKTReader::readSurfaceText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
428
1.53k
{
429
1.53k
    int type = tokenizer->peekNextToken();
430
1.53k
    if (type == '(') {
431
753
        return readPolygonText(tokenizer, ordinateFlags);
432
753
    }
433
434
784
    GeometryTypeId defaultType = GEOS_POLYGON;
435
784
    auto component = readGeometryTaggedText(tokenizer, ordinateFlags, &defaultType);
436
784
    if (dynamic_cast<Surface*>(component.get())) {
437
595
        return component;
438
595
    }
439
440
189
    throw ParseException("Expected POLYGON or CURVEPOLYGON but got " + component->getGeometryType());
441
784
}
442
443
std::unique_ptr<CompoundCurve>
444
WKTReader::readCompoundCurveText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
445
1.16k
{
446
1.16k
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
447
1.16k
    if (nextToken == "EMPTY") {
448
5
        return geometryFactory->createCompoundCurve();
449
5
    }
450
451
1.15k
    std::vector<std::unique_ptr<SimpleCurve>> curves;
452
2.61k
    do {
453
2.61k
        auto curve = readCurveText(tokenizer, ordinateFlags);
454
2.61k
        if (dynamic_cast<SimpleCurve*>(curve.get())) {
455
2.48k
            curves.emplace_back(static_cast<SimpleCurve*>(curve.release()));
456
2.48k
        } else {
457
135
            throw ParseException("Expected LINESTRING or CIRCULARSTRING but got " + curve->getGeometryType());
458
135
        }
459
2.48k
        nextToken = getNextCloserOrComma(tokenizer);
460
2.48k
    } while (nextToken == ",");
461
462
1.02k
    return geometryFactory->createCompoundCurve(std::move(curves));
463
1.15k
}
464
465
std::unique_ptr<MultiPoint>
466
WKTReader::readMultiPointText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
467
18.6k
{
468
18.6k
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
469
18.6k
    if(nextToken == "EMPTY") {
470
690
        return geometryFactory->createMultiPoint();
471
690
    }
472
473
17.9k
    int tok = tokenizer->peekNextToken();
474
475
17.9k
    if(tok == StringTokenizer::TT_NUMBER) {
476
        // Try to parse "MULTIPOINT (0 0, 1 1)"
477
16.3k
        auto coords = detail::make_unique<CoordinateSequence>(0u, ordinateFlags.hasZ(), ordinateFlags.hasM());
478
479
16.3k
        CoordinateXYZM coord(0, 0, DoubleNotANumber, DoubleNotANumber);
480
3.95M
        do {
481
3.95M
            getPreciseCoordinate(tokenizer, ordinateFlags, coord);
482
3.95M
            coords->add(coord);
483
3.95M
            nextToken = getNextCloserOrComma(tokenizer);
484
3.95M
        }
485
3.95M
        while(nextToken == ",");
486
487
16.3k
        return std::unique_ptr<MultiPoint>(geometryFactory->createMultiPoint(*coords));
488
16.3k
    }
489
490
1.61k
    else if(tok == '(' ||        // Try to parse "MULTIPOINT ((0 0), (1 1))"
491
92
            tok == StringTokenizer::TT_WORD)  // "MULTIPOINT (EMPTY, (1 1))"
492
1.60k
    {
493
1.60k
        std::vector<std::unique_ptr<Point>> points;
494
495
2.75k
        do {
496
2.75k
            points.push_back(readPointText(tokenizer, ordinateFlags));
497
2.75k
            nextToken = getNextCloserOrComma(tokenizer);
498
2.75k
        } while(nextToken == ",");
499
500
1.60k
        return geometryFactory->createMultiPoint(std::move(points));
501
1.60k
    }
502
503
6
    else {
504
6
        std::stringstream err;
505
6
        err << "Unexpected token: ";
506
6
        switch(tok) {
507
0
        case StringTokenizer::TT_WORD:
508
0
            err << "WORD " << tokenizer->getSVal();
509
0
            break;
510
0
        case StringTokenizer::TT_NUMBER:
511
0
            err << "NUMBER " << tokenizer->getNVal();
512
0
            break;
513
2
        case StringTokenizer::TT_EOF:
514
2
        case StringTokenizer::TT_EOL:
515
2
            err << "EOF or EOL";
516
2
            break;
517
0
        case '(':
518
0
            err << "(";
519
0
            break;
520
1
        case ')':
521
1
            err << ")";
522
1
            break;
523
1
        case ',':
524
1
            err << ",";
525
1
            break;
526
0
        default:
527
0
            err << "??";
528
0
            break;
529
6
        }
530
4
        err << std::endl;
531
4
        throw ParseException(err.str());
532
6
    }
533
17.9k
}
534
535
std::unique_ptr<Polygon>
536
WKTReader::readPolygonText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
537
1.70M
{
538
1.70M
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
539
1.70M
    if(nextToken == "EMPTY") {
540
996
        auto coords = detail::make_unique<CoordinateSequence>(0u, ordinateFlags.hasZ(), ordinateFlags.hasM());
541
996
        auto ring = geometryFactory->createLinearRing(std::move(coords));
542
996
        return geometryFactory->createPolygon(std::move(ring));
543
996
    }
544
545
1.70M
    std::vector<std::unique_ptr<LinearRing>> holes;
546
1.70M
    auto shell = readLinearRingText(tokenizer, ordinateFlags);
547
1.70M
    nextToken = getNextCloserOrComma(tokenizer);
548
2.21M
    while(nextToken == ",") {
549
513k
        holes.push_back(readLinearRingText(tokenizer, ordinateFlags));
550
513k
        nextToken = getNextCloserOrComma(tokenizer);
551
513k
    }
552
553
1.70M
    return geometryFactory->createPolygon(std::move(shell), std::move(holes));
554
1.70M
}
555
556
std::unique_ptr<CurvePolygon>
557
WKTReader::readCurvePolygonText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
558
611
{
559
611
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
560
611
    if(nextToken == "EMPTY") {
561
9
        auto coords = detail::make_unique<CoordinateSequence>(0u, ordinateFlags.hasZ(), ordinateFlags.hasM());
562
9
        std::unique_ptr<Curve> ring = geometryFactory->createLinearRing(std::move(coords));
563
9
        return geometryFactory->createCurvePolygon(std::move(ring));
564
9
    }
565
566
602
    std::vector<std::unique_ptr<Curve>> holes;
567
602
    auto shell = readCurveText(tokenizer, ordinateFlags);
568
602
    nextToken = getNextCloserOrComma(tokenizer);
569
4.61k
    while(nextToken == ",") {
570
4.00k
        holes.push_back(readCurveText(tokenizer, ordinateFlags));
571
4.00k
        nextToken = getNextCloserOrComma(tokenizer);
572
4.00k
    }
573
574
602
    return geometryFactory->createCurvePolygon(std::move(shell), std::move(holes));
575
611
}
576
577
std::unique_ptr<MultiLineString>
578
WKTReader::readMultiLineStringText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
579
418
{
580
418
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
581
418
    if(nextToken == "EMPTY") {
582
6
        return geometryFactory->createMultiLineString();
583
6
    }
584
585
412
    std::vector<std::unique_ptr<LineString>> lineStrings;
586
2.55k
    do {
587
2.55k
        lineStrings.push_back(readLineStringText(tokenizer, ordinateFlags));
588
2.55k
        nextToken = getNextCloserOrComma(tokenizer);
589
2.55k
    } while (nextToken == ",");
590
591
412
    return geometryFactory->createMultiLineString(std::move(lineStrings));
592
418
}
593
594
std::unique_ptr<MultiCurve>
595
WKTReader::readMultiCurveText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
596
374
{
597
374
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
598
374
    if(nextToken == "EMPTY") {
599
1
        return geometryFactory->createMultiCurve();
600
1
    }
601
602
373
    std::vector<std::unique_ptr<Curve>> curves;
603
2.82k
    do {
604
2.82k
        curves.push_back(readCurveText(tokenizer, ordinateFlags));
605
2.82k
        nextToken = getNextCloserOrComma(tokenizer);
606
2.82k
    } while(nextToken == ",");
607
608
373
    return geometryFactory->createMultiCurve(std::move(curves));
609
374
}
610
611
std::unique_ptr<MultiPolygon>
612
WKTReader::readMultiPolygonText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
613
1.06k
{
614
1.06k
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
615
1.06k
    if(nextToken == "EMPTY") {
616
96
        return geometryFactory->createMultiPolygon();
617
96
    }
618
619
970
    std::vector<std::unique_ptr<Polygon>> polygons;
620
1.70M
    do {
621
1.70M
        polygons.push_back(readPolygonText(tokenizer, ordinateFlags));
622
1.70M
        nextToken = getNextCloserOrComma(tokenizer);
623
1.70M
    } while(nextToken == ",");
624
625
970
    return geometryFactory->createMultiPolygon(std::move(polygons));
626
1.06k
}
627
628
std::unique_ptr<MultiSurface>
629
WKTReader::readMultiSurfaceText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
630
237
{
631
237
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
632
237
    if(nextToken == "EMPTY") {
633
1
        return geometryFactory->createMultiSurface();
634
1
    }
635
636
236
    std::vector<std::unique_ptr<Geometry>> surfaces;
637
1.53k
    do {
638
1.53k
        surfaces.push_back(readSurfaceText(tokenizer, ordinateFlags));
639
1.53k
        nextToken = getNextCloserOrComma(tokenizer);
640
1.53k
    } while(nextToken == ",");
641
642
236
    return geometryFactory->createMultiSurface(std::move(surfaces));
643
237
}
644
645
std::unique_ptr<GeometryCollection>
646
WKTReader::readGeometryCollectionText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
647
2.98k
{
648
2.98k
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
649
2.98k
    if(nextToken == "EMPTY") {
650
648
        return geometryFactory->createGeometryCollection();
651
648
    }
652
653
2.33k
    std::vector<std::unique_ptr<Geometry>> geoms;
654
26.1k
    do {
655
26.1k
        geoms.push_back(readGeometryTaggedText(tokenizer, ordinateFlags));
656
26.1k
        nextToken = getNextCloserOrComma(tokenizer);
657
26.1k
    } while(nextToken == ",");
658
659
2.33k
    return geometryFactory->createGeometryCollection(std::move(geoms));
660
2.98k
}
661
662
} // namespace geos.io
663
} // namespace geos