Coverage Report

Created: 2026-09-01 06:39

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
3.57k
{
57
3.57k
    parseDepth_ = 0;
58
3.57k
    CLocalizer clocale;
59
3.57k
    StringTokenizer tokenizer(wellKnownText);
60
3.57k
    OrdinateSet ordinateFlags = OrdinateSet::createXY();
61
3.57k
    auto ret = readGeometryTaggedText(&tokenizer, ordinateFlags);
62
63
3.57k
    if (tokenizer.peekNextToken() != StringTokenizer::TT_EOF) {
64
26
        tokenizer.nextToken();
65
26
        throw ParseException("Unexpected text after end of geometry");
66
26
    }
67
68
3.55k
    return ret;
69
3.57k
}
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
1.80k
{
91
1.80k
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
92
1.80k
    if(nextToken == "EMPTY") {
93
1
        return detail::make_unique<CoordinateSequence>(0u, ordinateFlags.hasZ(), ordinateFlags.hasM());
94
1
    }
95
96
1.79k
    CoordinateXYZM coord(0, 0, DoubleNotANumber, DoubleNotANumber);
97
1.79k
    getPreciseCoordinate(tokenizer, ordinateFlags, coord);
98
99
1.79k
    auto coordinates = detail::make_unique<CoordinateSequence>(0u, ordinateFlags.hasZ(), ordinateFlags.hasM());
100
1.79k
    coordinates->add(coord);
101
102
1.79k
    nextToken = getNextCloserOrComma(tokenizer);
103
42.7k
    while(nextToken == ",") {
104
40.9k
        getPreciseCoordinate(tokenizer, ordinateFlags, coord);
105
40.9k
        coordinates->add(coord);
106
40.9k
        nextToken = getNextCloserOrComma(tokenizer);
107
40.9k
    }
108
109
1.79k
    return coordinates;
110
1.80k
}
111
112
void
113
WKTReader::getPreciseCoordinate(StringTokenizer* tokenizer,
114
                                OrdinateSet& ordinateFlags,
115
364k
                                CoordinateXYZM& coord) const {
116
364k
    coord.x = getNextNumber(tokenizer);
117
364k
    coord.y = getNextNumber(tokenizer);
118
119
    // Check for undeclared Z dimension
120
364k
    if (ordinateFlags.changesAllowed() && isNumberNext(tokenizer)) {
121
40
        ordinateFlags.setZ(true);
122
40
    }
123
124
364k
    if (ordinateFlags.hasZ()) {
125
58
        coord.z = getNextNumber(tokenizer);
126
58
    }
127
128
    // Check for undeclared M dimension
129
364k
    if (ordinateFlags.changesAllowed() && ordinateFlags.hasZ() && isNumberNext(tokenizer)) {
130
20
        ordinateFlags.setM(true);
131
20
    }
132
133
364k
    if (ordinateFlags.hasM()) {
134
29
        coord.m = getNextNumber(tokenizer);
135
29
    }
136
137
364k
    ordinateFlags.setChangesAllowed(false); // First coordinate read; future coordinates must be consistent
138
139
364k
    precisionModel->makePrecise(coord);
140
364k
}
141
142
bool
143
WKTReader::isNumberNext(StringTokenizer* tokenizer)
144
3.09k
{
145
3.09k
    return tokenizer->peekNextToken() == StringTokenizer::TT_NUMBER;
146
3.09k
}
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
729k
{
157
729k
    int type = tokenizer->nextToken();
158
729k
    switch(type) {
159
14
    case StringTokenizer::TT_EOF:
160
14
        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
729k
    case StringTokenizer::TT_NUMBER:
164
729k
        return tokenizer->getNVal();
165
8
    case StringTokenizer::TT_WORD:
166
8
        throw  ParseException("Expected number but encountered word", tokenizer->getSVal());
167
1
    case '(':
168
1
        throw  ParseException("Expected number but encountered '('");
169
8
    case ')':
170
8
        throw  ParseException("Expected number but encountered ')'");
171
3
    case ',':
172
3
        throw  ParseException("Expected number but encountered ','");
173
729k
    }
174
729k
    assert(0); // Encountered unexpected StreamTokenizer type
175
0
    return 0;
176
729k
}
177
178
bool
179
24.8k
WKTReader::isTypeName(const std::string & type, const std::string & typeName) {
180
24.8k
    auto l = type.size();
181
24.8k
    auto r = typeName.size();
182
24.8k
    if (l == r && type == typeName) return true;
183
21.2k
    if (l == r + 1 && (type == typeName + 'Z' || type == typeName + 'M')) return true;
184
21.2k
    if (l == r + 2 && type == typeName + "ZM") return true;
185
186
    // take `POINT` as example:
187
    // POI, POINTa, POINTZMx are all invalid
188
21.2k
    return false;
189
21.2k
}
190
191
void
192
4.02k
WKTReader::readOrdinateFlags(const std::string & s, OrdinateSet& ordinateFlags) {
193
4.02k
    if (util::endsWith(s, "ZM")) {
194
15
        ordinateFlags.setM(true);
195
15
        ordinateFlags.setZ(true);
196
15
        ordinateFlags.setChangesAllowed(false);
197
4.01k
    } else if (util::endsWith(s, 'M')) {
198
13
        ordinateFlags.setM(true);
199
13
        ordinateFlags.setChangesAllowed(false);
200
3.99k
    } else if (util::endsWith(s, 'Z')) {
201
38
        ordinateFlags.setZ(true);
202
38
        ordinateFlags.setChangesAllowed(false);
203
38
    }
204
4.02k
}
205
206
std::string
207
WKTReader::getNextEmptyOrOpener(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags)
208
3.72k
{
209
3.72k
    const bool changesAllowed = ordinateFlags.changesAllowed();
210
3.72k
    std::string nextWord = getNextWord(tokenizer);
211
212
3.72k
    bool flagsModified = false;
213
214
    // Skip the Z, M or ZM of an SF1.2 3/4 dim coordinate.
215
3.72k
    if (nextWord == "ZM") {
216
2
        if (!changesAllowed) {
217
1
          throw ParseException("'ZM' found but 'ZM' or 'Z' or 'M' has been specified in previous type");
218
1
        }
219
1
        ordinateFlags.setZ(true);
220
1
        ordinateFlags.setM(true);
221
1
        flagsModified = true;
222
1
        nextWord = getNextWord(tokenizer);
223
3.72k
    } else {
224
3.72k
        if (nextWord == "Z") {
225
6
            if (!changesAllowed) {
226
1
              throw ParseException("'Z' found but 'ZM' or 'Z' or 'M' has been specified in previous type");
227
1
            }
228
5
            ordinateFlags.setZ(true);
229
5
            flagsModified = true;
230
5
            nextWord = getNextWord(tokenizer);
231
5
        }
232
233
3.72k
        if (nextWord == "M") {
234
11
            if (!changesAllowed || flagsModified) {
235
0
              throw ParseException("'M' found but 'ZM' or 'Z' or 'M' has been specified previously");
236
0
            }
237
11
            ordinateFlags.setM(true);
238
11
            flagsModified = true;
239
11
            nextWord = getNextWord(tokenizer);
240
11
        }
241
3.72k
    }
242
243
3.72k
    if (flagsModified) {
244
14
        ordinateFlags.setChangesAllowed(false);
245
14
    }
246
247
3.72k
    if(nextWord == "EMPTY" || nextWord == "(") {
248
3.61k
        return nextWord;
249
3.61k
    }
250
106
    throw ParseException("Expected 'Z', 'M', 'ZM', 'EMPTY' or '(' but encountered ", nextWord);
251
3.72k
}
252
253
std::string
254
WKTReader::getNextCloserOrComma(StringTokenizer* tokenizer)
255
364k
{
256
364k
    std::string nextWord = getNextWord(tokenizer);
257
364k
    if(nextWord == "," || nextWord == ")") {
258
364k
        return nextWord;
259
364k
    }
260
36
    throw  ParseException("Expected ')' or ',' but encountered", nextWord);
261
364k
}
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
372k
{
276
372k
    int type = tokenizer->nextToken();
277
372k
    switch(type) {
278
37
    case StringTokenizer::TT_EOF:
279
37
        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
10
    case StringTokenizer::TT_NUMBER:
283
10
        throw  ParseException("Expected word but encountered number", tokenizer->getNVal());
284
4.16k
    case StringTokenizer::TT_WORD: {
285
4.16k
        std::string word = tokenizer->getSVal();
286
12.7M
        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
12.7M
            c = static_cast<char>(toupper(static_cast<unsigned char>(c)));
290
12.7M
        }
291
4.16k
        return word;
292
0
    }
293
3.61k
    case '(':
294
3.61k
        return "(";
295
3.01k
    case ')':
296
3.01k
        return ")";
297
361k
    case ',':
298
361k
        return ",";
299
372k
    }
300
372k
    assert(0);
301
    //throw  ParseException("Encountered unexpected StreamTokenizer type");
302
0
    return "";
303
372k
}
304
305
std::unique_ptr<Geometry>
306
WKTReader::readGeometryTaggedText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags, const GeometryTypeId* emptyType) const
307
4.05k
{
308
4.05k
    if (parseDepth_ >= MAX_PARSE_DEPTH) {
309
0
        throw ParseException("Input geometry exceeds nesting depth limit");
310
0
    }
311
4.05k
    ++parseDepth_;
312
4.05k
    struct DepthGuard { int& d; ~DepthGuard() { --d; } } guard{parseDepth_};
313
314
4.05k
    std::string type = getNextWord(tokenizer);
315
316
4.05k
    std::unique_ptr<Geometry> geom;
317
4.05k
    OrdinateSet origFlags = ordinateFlags;
318
319
4.05k
    OrdinateSet newFlags = OrdinateSet::createXY();
320
4.05k
    if (type == "EMPTY") {
321
8
        newFlags = origFlags;
322
4.04k
    } else {
323
4.04k
        readOrdinateFlags(type, newFlags);
324
4.04k
    }
325
326
4.05k
    if(isTypeName(type, "POINT")) {
327
12
        geom = readPointText(tokenizer, newFlags);
328
12
    }
329
4.03k
    else if(isTypeName(type, "LINESTRING")) {
330
1.72k
        geom = readLineStringText(tokenizer, newFlags);
331
1.72k
    }
332
2.31k
    else if(isTypeName(type, "LINEARRING")) {
333
2
        geom = readLinearRingText(tokenizer, newFlags);
334
2
    }
335
2.31k
    else if(isTypeName(type, "CIRCULARSTRING")) {
336
1
        geom = readCircularStringText(tokenizer, newFlags);
337
1
    }
338
2.31k
    else if(isTypeName(type, "COMPOUNDCURVE")) {
339
2
        geom = readCompoundCurveText(tokenizer, newFlags);
340
2
    }
341
2.31k
    else if(isTypeName(type, "POLYGON")) {
342
3
        geom = readPolygonText(tokenizer, newFlags);
343
3
    }
344
2.30k
    else if(isTypeName(type, "CURVEPOLYGON")) {
345
7
        geom = readCurvePolygonText(tokenizer, newFlags);
346
7
    }
347
2.30k
    else if(isTypeName(type,  "MULTIPOINT")) {
348
1.41k
        geom = readMultiPointText(tokenizer, newFlags);
349
1.41k
    }
350
884
    else if(isTypeName(type, "MULTILINESTRING")) {
351
1
        geom = readMultiLineStringText(tokenizer, newFlags);
352
1
    }
353
883
    else if(isTypeName(type, "MULTICURVE")) {
354
392
        geom = readMultiCurveText(tokenizer, newFlags);
355
392
    }
356
491
    else if(isTypeName(type, "MULTIPOLYGON")) {
357
3
        geom = readMultiPolygonText(tokenizer, newFlags);
358
3
    }
359
488
    else if(isTypeName(type, "MULTISURFACE")) {
360
93
        geom = readMultiSurfaceText(tokenizer, newFlags);
361
93
    }
362
395
    else if(isTypeName(type, "GEOMETRYCOLLECTION")) {
363
3
        geom = readGeometryCollectionText(tokenizer, newFlags);
364
392
    } else if (type == "EMPTY" && emptyType != nullptr) {
365
7
        return geometryFactory->createEmptyGeometry(*emptyType, newFlags.hasZ(), newFlags.hasM());
366
385
    } else {
367
385
        throw ParseException("Unknown type", type);
368
385
    }
369
370
3.65k
    if (!origFlags.changesAllowed() && newFlags != origFlags) {
371
0
        throw ParseException("Cannot mix dimensionality in a geometry.");
372
0
    }
373
374
3.65k
    return geom;
375
376
3.65k
}
377
378
std::unique_ptr<Point>
379
WKTReader::readPointText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
380
72
{
381
72
    auto&& coords = getCoordinates(tokenizer, ordinateFlags);
382
72
    return geometryFactory->createPoint(std::move(coords));
383
72
}
384
385
std::unique_ptr<LineString>
386
WKTReader::readLineStringText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
387
1.72k
{
388
1.72k
    auto&& coords = getCoordinates(tokenizer, ordinateFlags);
389
1.72k
    return geometryFactory->createLineString(std::move(coords));
390
1.72k
}
391
392
std::unique_ptr<LinearRing>
393
WKTReader::readLinearRingText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
394
5
{
395
5
    auto&& coords = getCoordinates(tokenizer, ordinateFlags);
396
5
    if (fixStructure && !coords->isRing()) {
397
0
        coords->closeRing();
398
0
    }
399
5
    return geometryFactory->createLinearRing(std::move(coords));
400
5
}
401
402
std::unique_ptr<CircularString>
403
WKTReader::readCircularStringText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
404
1
{
405
1
    auto&& coords = getCoordinates(tokenizer, ordinateFlags);
406
1
    return geometryFactory->createCircularString(std::move(coords));
407
1
}
408
409
std::unique_ptr<Curve>
410
WKTReader::readCurveText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
411
386
{
412
386
    int type = tokenizer->peekNextToken();
413
386
    if (type == '(') {
414
0
        return readLineStringText(tokenizer, ordinateFlags);
415
0
    }
416
417
386
    GeometryTypeId defaultType = GEOS_LINESTRING;
418
386
    auto component = readGeometryTaggedText(tokenizer, ordinateFlags, &defaultType);
419
386
    if (dynamic_cast<Curve*>(component.get())) {
420
5
        return std::unique_ptr<Curve>(static_cast<Curve*>(component.release()));
421
5
    }
422
423
381
    throw ParseException("Expected LINESTRING/CIRCULARSTRING/COMPOUNDCURVE but got " + component->getGeometryType());
424
386
}
425
426
std::unique_ptr<Geometry>
427
WKTReader::readSurfaceText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
428
87
{
429
87
    int type = tokenizer->peekNextToken();
430
87
    if (type == '(') {
431
1
        return readPolygonText(tokenizer, ordinateFlags);
432
1
    }
433
434
86
    GeometryTypeId defaultType = GEOS_POLYGON;
435
86
    auto component = readGeometryTaggedText(tokenizer, ordinateFlags, &defaultType);
436
86
    if (dynamic_cast<Surface*>(component.get())) {
437
2
        return component;
438
2
    }
439
440
84
    throw ParseException("Expected POLYGON or CURVEPOLYGON but got " + component->getGeometryType());
441
86
}
442
443
std::unique_ptr<CompoundCurve>
444
WKTReader::readCompoundCurveText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
445
2
{
446
2
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
447
2
    if (nextToken == "EMPTY") {
448
0
        return geometryFactory->createCompoundCurve();
449
0
    }
450
451
2
    std::vector<std::unique_ptr<SimpleCurve>> curves;
452
2
    do {
453
2
        auto curve = readCurveText(tokenizer, ordinateFlags);
454
2
        if (dynamic_cast<SimpleCurve*>(curve.get())) {
455
0
            curves.emplace_back(static_cast<SimpleCurve*>(curve.release()));
456
2
        } else {
457
2
            throw ParseException("Expected LINESTRING or CIRCULARSTRING but got " + curve->getGeometryType());
458
2
        }
459
0
        nextToken = getNextCloserOrComma(tokenizer);
460
0
    } while (nextToken == ",");
461
462
0
    return geometryFactory->createCompoundCurve(std::move(curves));
463
2
}
464
465
std::unique_ptr<MultiPoint>
466
WKTReader::readMultiPointText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
467
1.41k
{
468
1.41k
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
469
1.41k
    if(nextToken == "EMPTY") {
470
0
        return geometryFactory->createMultiPoint();
471
0
    }
472
473
1.41k
    int tok = tokenizer->peekNextToken();
474
475
1.41k
    if(tok == StringTokenizer::TT_NUMBER) {
476
        // Try to parse "MULTIPOINT (0 0, 1 1)"
477
1.34k
        auto coords = detail::make_unique<CoordinateSequence>(0u, ordinateFlags.hasZ(), ordinateFlags.hasM());
478
479
1.34k
        CoordinateXYZM coord(0, 0, DoubleNotANumber, DoubleNotANumber);
480
321k
        do {
481
321k
            getPreciseCoordinate(tokenizer, ordinateFlags, coord);
482
321k
            coords->add(coord);
483
321k
            nextToken = getNextCloserOrComma(tokenizer);
484
321k
        }
485
321k
        while(nextToken == ",");
486
487
1.34k
        return std::unique_ptr<MultiPoint>(geometryFactory->createMultiPoint(*coords));
488
1.34k
    }
489
490
72
    else if(tok == '(' ||        // Try to parse "MULTIPOINT ((0 0), (1 1))"
491
59
            tok == StringTokenizer::TT_WORD)  // "MULTIPOINT (EMPTY, (1 1))"
492
60
    {
493
60
        std::vector<std::unique_ptr<Point>> points;
494
495
60
        do {
496
60
            points.push_back(readPointText(tokenizer, ordinateFlags));
497
60
            nextToken = getNextCloserOrComma(tokenizer);
498
60
        } while(nextToken == ",");
499
500
60
        return geometryFactory->createMultiPoint(std::move(points));
501
60
    }
502
503
12
    else {
504
12
        std::stringstream err;
505
12
        err << "Unexpected token: ";
506
12
        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
1
        case StringTokenizer::TT_EOF:
514
1
        case StringTokenizer::TT_EOL:
515
1
            err << "EOF or EOL";
516
1
            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
12
        }
530
3
        err << std::endl;
531
3
        throw ParseException(err.str());
532
12
    }
533
1.41k
}
534
535
std::unique_ptr<Polygon>
536
WKTReader::readPolygonText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
537
6
{
538
6
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
539
6
    if(nextToken == "EMPTY") {
540
1
        auto coords = detail::make_unique<CoordinateSequence>(0u, ordinateFlags.hasZ(), ordinateFlags.hasM());
541
1
        auto ring = geometryFactory->createLinearRing(std::move(coords));
542
1
        return geometryFactory->createPolygon(std::move(ring));
543
1
    }
544
545
5
    std::vector<std::unique_ptr<LinearRing>> holes;
546
5
    auto shell = readLinearRingText(tokenizer, ordinateFlags);
547
5
    nextToken = getNextCloserOrComma(tokenizer);
548
5
    while(nextToken == ",") {
549
0
        holes.push_back(readLinearRingText(tokenizer, ordinateFlags));
550
0
        nextToken = getNextCloserOrComma(tokenizer);
551
0
    }
552
553
5
    return geometryFactory->createPolygon(std::move(shell), std::move(holes));
554
6
}
555
556
std::unique_ptr<CurvePolygon>
557
WKTReader::readCurvePolygonText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
558
7
{
559
7
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
560
7
    if(nextToken == "EMPTY") {
561
0
        auto coords = detail::make_unique<CoordinateSequence>(0u, ordinateFlags.hasZ(), ordinateFlags.hasM());
562
0
        std::unique_ptr<Curve> ring = geometryFactory->createLinearRing(std::move(coords));
563
0
        return geometryFactory->createCurvePolygon(std::move(ring));
564
0
    }
565
566
7
    std::vector<std::unique_ptr<Curve>> holes;
567
7
    auto shell = readCurveText(tokenizer, ordinateFlags);
568
7
    nextToken = getNextCloserOrComma(tokenizer);
569
8
    while(nextToken == ",") {
570
1
        holes.push_back(readCurveText(tokenizer, ordinateFlags));
571
1
        nextToken = getNextCloserOrComma(tokenizer);
572
1
    }
573
574
7
    return geometryFactory->createCurvePolygon(std::move(shell), std::move(holes));
575
7
}
576
577
std::unique_ptr<MultiLineString>
578
WKTReader::readMultiLineStringText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
579
1
{
580
1
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
581
1
    if(nextToken == "EMPTY") {
582
0
        return geometryFactory->createMultiLineString();
583
0
    }
584
585
1
    std::vector<std::unique_ptr<LineString>> lineStrings;
586
1
    do {
587
1
        lineStrings.push_back(readLineStringText(tokenizer, ordinateFlags));
588
1
        nextToken = getNextCloserOrComma(tokenizer);
589
1
    } while (nextToken == ",");
590
591
1
    return geometryFactory->createMultiLineString(std::move(lineStrings));
592
1
}
593
594
std::unique_ptr<MultiCurve>
595
WKTReader::readMultiCurveText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
596
392
{
597
392
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
598
392
    if(nextToken == "EMPTY") {
599
1
        return geometryFactory->createMultiCurve();
600
1
    }
601
602
391
    std::vector<std::unique_ptr<Curve>> curves;
603
392
    do {
604
392
        curves.push_back(readCurveText(tokenizer, ordinateFlags));
605
392
        nextToken = getNextCloserOrComma(tokenizer);
606
392
    } while(nextToken == ",");
607
608
391
    return geometryFactory->createMultiCurve(std::move(curves));
609
392
}
610
611
std::unique_ptr<MultiPolygon>
612
WKTReader::readMultiPolygonText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
613
3
{
614
3
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
615
3
    if(nextToken == "EMPTY") {
616
0
        return geometryFactory->createMultiPolygon();
617
0
    }
618
619
3
    std::vector<std::unique_ptr<Polygon>> polygons;
620
3
    do {
621
3
        polygons.push_back(readPolygonText(tokenizer, ordinateFlags));
622
3
        nextToken = getNextCloserOrComma(tokenizer);
623
3
    } while(nextToken == ",");
624
625
3
    return geometryFactory->createMultiPolygon(std::move(polygons));
626
3
}
627
628
std::unique_ptr<MultiSurface>
629
WKTReader::readMultiSurfaceText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
630
93
{
631
93
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
632
93
    if(nextToken == "EMPTY") {
633
1
        return geometryFactory->createMultiSurface();
634
1
    }
635
636
92
    std::vector<std::unique_ptr<Geometry>> surfaces;
637
92
    do {
638
92
        surfaces.push_back(readSurfaceText(tokenizer, ordinateFlags));
639
92
        nextToken = getNextCloserOrComma(tokenizer);
640
92
    } while(nextToken == ",");
641
642
92
    return geometryFactory->createMultiSurface(std::move(surfaces));
643
93
}
644
645
std::unique_ptr<GeometryCollection>
646
WKTReader::readGeometryCollectionText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const
647
3
{
648
3
    std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags);
649
3
    if(nextToken == "EMPTY") {
650
0
        return geometryFactory->createGeometryCollection();
651
0
    }
652
653
3
    std::vector<std::unique_ptr<Geometry>> geoms;
654
3
    do {
655
3
        geoms.push_back(readGeometryTaggedText(tokenizer, ordinateFlags));
656
3
        nextToken = getNextCloserOrComma(tokenizer);
657
3
    } while(nextToken == ",");
658
659
3
    return geometryFactory->createGeometryCollection(std::move(geoms));
660
3
}
661
662
} // namespace geos.io
663
} // namespace geos