• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

brick / geo / 13655507172

04 Mar 2025 02:07PM UTC coverage: 84.44% (+0.04%) from 84.402%
13655507172

push

github

BenMorel
Restrict CompoundCurve to contain LineString|CircularString

It looks like nested CompoundCurves are, in fact, forbidden.

3 of 5 new or added lines in 3 files covered. (60.0%)

88 existing lines in 4 files now uncovered.

1552 of 1838 relevant lines covered (84.44%)

1985.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.77
/src/IO/AbstractWKTReader.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Brick\Geo\IO;
6

7
use Brick\Geo\Curve;
8
use Brick\Geo\Geometry;
9
use Brick\Geo\Point;
10
use Brick\Geo\LineString;
11
use Brick\Geo\CircularString;
12
use Brick\Geo\CompoundCurve;
13
use Brick\Geo\Polygon;
14
use Brick\Geo\CurvePolygon;
15
use Brick\Geo\MultiPoint;
16
use Brick\Geo\MultiLineString;
17
use Brick\Geo\MultiPolygon;
18
use Brick\Geo\GeometryCollection;
19
use Brick\Geo\PolyhedralSurface;
20
use Brick\Geo\TIN;
21
use Brick\Geo\Triangle;
22
use Brick\Geo\CoordinateSystem;
23
use Brick\Geo\Exception\GeometryIOException;
24

25
/**
26
 * Base class for WKTReader and EWKTReader.
27
 */
28
abstract class AbstractWKTReader
29
{
30
    /**
31
     * @throws GeometryIOException
32
     */
33
    protected function readGeometry(WKTParser $parser, int $srid) : Geometry
34
    {
35
        $geometryType = $parser->getNextWord();
14,513✔
36
        $word = $parser->getOptionalNextWord();
14,513✔
37

38
        $hasZ = false;
14,513✔
39
        $hasM = false;
14,513✔
40
        $isEmpty    = false;
14,513✔
41

42
        if ($word !== null) {
14,513✔
43
            if ($word === 'Z') {
10,971✔
44
                $hasZ = true;
3,340✔
45
            } elseif ($word === 'M') {
7,660✔
46
                $hasM = true;
3,054✔
47
            } elseif ($word === 'ZM') {
4,613✔
48
                $hasZ = true;
2,928✔
49
                $hasM = true;
2,928✔
50
            } elseif ($word === 'EMPTY') {
1,685✔
51
                $isEmpty = true;
1,685✔
52
            } else {
53
                throw new GeometryIOException('Unexpected word in WKT: ' . $word);
×
54
            }
55

56
            if (! $isEmpty) {
10,971✔
57
                $word = $parser->getOptionalNextWord();
9,301✔
58

59
                if ($word === 'EMPTY') {
9,301✔
60
                    $isEmpty = true;
3,849✔
61
                } elseif ($word !== null) {
5,497✔
62
                    throw new GeometryIOException('Unexpected word in WKT: ' . $word);
×
63
                }
64
            }
65
        }
66

67
        $cs = new CoordinateSystem($hasZ, $hasM, $srid);
14,513✔
68

69
        switch ($geometryType) {
70
            case 'POINT':
14,513✔
71
                if ($isEmpty) {
1,906✔
72
                    return new Point($cs);
422✔
73
                }
74

75
                return $this->readPointText($parser, $cs);
1,508✔
76

77
            case 'LINESTRING':
13,381✔
78
                if ($isEmpty) {
2,599✔
79
                    return new LineString($cs);
570✔
80
                }
81

82
                return $this->readLineStringText($parser, $cs);
2,035✔
83

84
            case 'CIRCULARSTRING':
11,472✔
85
                if ($isEmpty) {
2,268✔
86
                    return new CircularString($cs);
468✔
87
                }
88

89
                return $this->readCircularStringText($parser, $cs);
1,800✔
90

91
            case 'COMPOUNDCURVE':
10,394✔
92
                if ($isEmpty) {
1,462✔
93
                    return new CompoundCurve($cs);
580✔
94
                }
95

96
                return $this->readCompoundCurveText($parser, $cs);
882✔
97

98
            case 'POLYGON':
9,142✔
99
                if ($isEmpty) {
1,860✔
100
                    return new Polygon($cs);
426✔
101
                }
102

103
                return $this->readPolygonText($parser, $cs);
1,434✔
104

105
            case 'CURVEPOLYGON':
7,415✔
106
                if ($isEmpty) {
1,113✔
107
                    return new CurvePolygon($cs);
462✔
108
                }
109

110
                return $this->readCurvePolygonText($parser, $cs);
651✔
111

112
            case 'MULTIPOINT':
6,302✔
113
                if ($isEmpty) {
939✔
114
                    return new MultiPoint($cs);
357✔
115
                }
116

117
                return $this->readMultiPointText($parser, $cs);
582✔
118

119
            case 'MULTILINESTRING':
5,363✔
120
                if ($isEmpty) {
1,050✔
121
                    return new MultiLineString($cs);
350✔
122
                }
123

124
                return $this->readMultiLineStringText($parser, $cs);
700✔
125

126
            case 'MULTIPOLYGON':
4,320✔
127
                if ($isEmpty) {
1,049✔
128
                    return new MultiPolygon($cs);
357✔
129
                }
130

131
                return $this->readMultiPolygonText($parser, $cs);
692✔
132

133
            case 'GEOMETRYCOLLECTION':
3,271✔
134
                if ($isEmpty) {
975✔
135
                    return new GeometryCollection($cs);
443✔
136
                }
137

138
                return $this->readGeometryCollectionText($parser, $cs);
532✔
139

140
            case 'POLYHEDRALSURFACE':
2,296✔
141
                if ($isEmpty) {
1,057✔
142
                    return new PolyhedralSurface($cs);
483✔
143
                }
144

145
                return $this->readPolyhedralSurfaceText($parser, $cs);
574✔
146

147
            case 'TIN':
1,239✔
148
                if ($isEmpty) {
637✔
149
                    return new TIN($cs);
322✔
150
                }
151

152
                return $this->readTINText($parser, $cs);
315✔
153

154
            case 'TRIANGLE':
637✔
155
                if ($isEmpty) {
637✔
156
                    return new Triangle($cs);
308✔
157
                }
158

159
            return $this->readTriangleText($parser, $cs);
329✔
160
        }
161

162
        throw new GeometryIOException('Unknown geometry type: ' . $geometryType);
×
163
    }
164

165
    /**
166
     * x y
167
     */
168
    private function readPoint(WKTParser $parser, CoordinateSystem $cs) : Point
169
    {
170
        $dim = $cs->coordinateDimension();
9,172✔
171
        $coords = [];
9,172✔
172

173
        for ($i = 0; $i < $dim; $i++) {
9,172✔
174
            $coords[] = $parser->getNextNumber();
9,172✔
175
        }
176

177
        return new Point($cs, ...$coords);
9,172✔
178
    }
179

180
    /**
181
     * (x y)
182
     */
183
    private function readPointText(WKTParser $parser, CoordinateSystem $cs) : Point
184
    {
185
        $parser->matchOpener();
1,508✔
186
        $point = $this->readPoint($parser, $cs);
1,508✔
187
        $parser->matchCloser();
1,508✔
188

189
        return $point;
1,508✔
190
    }
191

192
    /**
193
     * (x y, ...)
194
     *
195
     * @return Point[]
196
     */
197
    private function readMultiPoint(WKTParser $parser, CoordinateSystem $cs) : array
198
    {
199
        $parser->matchOpener();
8,275✔
200
        $points = [];
8,275✔
201

202
        do {
203
            $points[] = $this->readPoint($parser, $cs);
8,275✔
204
            $nextToken = $parser->getNextCloserOrComma();
8,275✔
205
        } while ($nextToken === ',');
8,275✔
206

207
        return $points;
8,275✔
208
    }
209

210
    /**
211
     * (x y, ...)
212
     */
213
    private function readLineStringText(WKTParser $parser, CoordinateSystem $cs) : LineString
214
    {
215
        $points = $this->readMultiPoint($parser, $cs);
7,104✔
216

217
        return new LineString($cs, ...$points);
7,104✔
218
    }
219

220
    /**
221
     * (x y, ...)
222
     */
223
    private function readCircularStringText(WKTParser $parser, CoordinateSystem $cs) : CircularString
224
    {
225
        $points = $this->readMultiPoint($parser, $cs);
1,800✔
226

227
        return new CircularString($cs, ...$points);
1,800✔
228
    }
229

230
    /**
231
     * @throws GeometryIOException
232
     */
233
    private function readCompoundCurveText(WKTParser $parser, CoordinateSystem $cs) : CompoundCurve
234
    {
235
        $parser->matchOpener();
882✔
236
        $curves = [];
882✔
237

238
        do {
239
            if ($parser->isNextOpenerOrWord()) {
882✔
240
                $curves[] = $this->readLineStringText($parser, $cs);
861✔
241
            } else {
242
                $curve = $this->readGeometry($parser, $cs->SRID());
819✔
243

244
                if (! $curve instanceof LineString && ! $curve instanceof CircularString) {
812✔
NEW
245
                    throw new GeometryIOException('Expected LineString|CircularString, got ' . $curve->geometryType());
×
246
                }
247

248
                $curves[] = $curve;
812✔
249
            }
250

251
            $nextToken = $parser->getNextCloserOrComma();
868✔
252
        } while ($nextToken === ',');
868✔
253

254
        return new CompoundCurve($cs, ...$curves);
868✔
255
    }
256

257
    /**
258
     * (x y, ...)
259
     */
260
    private function readMultiPointText(WKTParser $parser, CoordinateSystem $cs) : MultiPoint
261
    {
262
        $points = $this->readMultiPoint($parser, $cs);
582✔
263

264
        return new MultiPoint($cs, ...$points);
582✔
265
    }
266

267
    /**
268
     * ((x y, ...), ...)
269
     *
270
     * @return LineString[]
271
     */
272
    private function readMultiLineString(WKTParser $parser, CoordinateSystem $cs) : array
273
    {
274
        $parser->matchOpener();
3,988✔
275
        $lineStrings = [];
3,988✔
276

277
        do {
278
            $lineStrings[] = $this->readLineStringText($parser, $cs);
3,988✔
279
            $nextToken = $parser->getNextCloserOrComma();
3,988✔
280
        } while ($nextToken === ',');
3,988✔
281

282
        return $lineStrings;
3,988✔
283
    }
284

285
    /**
286
     * ((x y, ...), ...)
287
     */
288
    private function readPolygonText(WKTParser $parser, CoordinateSystem $cs) : Polygon
289
    {
290
        $rings = $this->readMultiLineString($parser, $cs);
2,665✔
291

292
        return new Polygon($cs, ...$rings);
2,665✔
293
    }
294

295
    /**
296
     * @throws GeometryIOException
297
     */
298
    private function readCurvePolygonText(WKTParser $parser, CoordinateSystem $cs) : CurvePolygon
299
    {
300
        $parser->matchOpener();
651✔
301
        $curves = [];
651✔
302

303
        do {
304
            if ($parser->isNextOpenerOrWord()) {
651✔
305
                $curves[] = $this->readLineStringText($parser, $cs);
616✔
306
            } else {
307
                $curve = $this->readGeometry($parser, $cs->SRID());
518✔
308

309
                if (! $curve instanceof Curve) {
518✔
310
                    throw new GeometryIOException('Expected Curve, got ' . $curve->geometryType());
×
311
                }
312

313
                $curves[] = $curve;
518✔
314
            }
315

316
            $nextToken = $parser->getNextCloserOrComma();
651✔
317
        } while ($nextToken === ',');
651✔
318

319
        return new CurvePolygon($cs, ...$curves);
651✔
320
    }
321

322
    /**
323
     * ((x y, ...), ...)
324
     */
325
    private function readTriangleText(WKTParser $parser, CoordinateSystem $cs) : Triangle
326
    {
327
        $rings = $this->readMultiLineString($parser, $cs);
623✔
328

329
        return new Triangle($cs, ...$rings);
623✔
330
    }
331

332
    /**
333
     * ((x y, ...), ...)
334
     */
335
    private function readMultiLineStringText(WKTParser $parser, CoordinateSystem $cs) : MultiLineString
336
    {
337
        $lineStrings = $this->readMultiLineString($parser, $cs);
700✔
338

339
        return new MultiLineString($cs, ...$lineStrings);
700✔
340
    }
341

342
    /**
343
     * (((x y, ...), ...), ...)
344
     */
345
    private function readMultiPolygonText(WKTParser $parser, CoordinateSystem $cs) : MultiPolygon
346
    {
347
        $parser->matchOpener();
692✔
348
        $polygons = [];
692✔
349

350
        do {
351
            $polygons[] = $this->readPolygonText($parser, $cs);
692✔
352
            $nextToken = $parser->getNextCloserOrComma();
692✔
353
        } while ($nextToken === ',');
692✔
354

355
        return new MultiPolygon($cs, ...$polygons);
692✔
356
    }
357

358
    private function readGeometryCollectionText(WKTParser $parser, CoordinateSystem $cs) : GeometryCollection
359
    {
360
        $parser->matchOpener();
532✔
361
        $geometries = [];
532✔
362

363
        do {
364
            $geometries[] = $this->readGeometry($parser, $cs->SRID());
532✔
365
            $nextToken = $parser->getNextCloserOrComma();
532✔
366
        } while ($nextToken === ',');
532✔
367

368
        return new GeometryCollection($cs, ...$geometries);
532✔
369
    }
370

371
    private function readPolyhedralSurfaceText(WKTParser $parser, CoordinateSystem $cs) : PolyhedralSurface
372
    {
373
        $parser->matchOpener();
574✔
374
        $patches = [];
574✔
375

376
        do {
377
            $patches[] = $this->readPolygonText($parser, $cs);
574✔
378
            $nextToken = $parser->getNextCloserOrComma();
574✔
379
        } while ($nextToken === ',');
574✔
380

381
        return new PolyhedralSurface($cs, ...$patches);
574✔
382
    }
383

384
    private function readTINText(WKTParser $parser, CoordinateSystem $cs) : TIN
385
    {
386
        $parser->matchOpener();
315✔
387
        $patches = [];
315✔
388

389
        do {
390
            $patches[] = $this->readTriangleText($parser, $cs);
315✔
391
            $nextToken = $parser->getNextCloserOrComma();
315✔
392
        } while ($nextToken === ',');
315✔
393

394
        return new TIN($cs, ...$patches);
315✔
395
    }
396
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc