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

brick / geo / 13716844819

07 Mar 2025 08:35AM UTC coverage: 47.964% (+3.9%) from 44.086%
13716844819

push

github

BenMorel
Add TypeChecker for engines

8 of 21 new or added lines in 5 files covered. (38.1%)

98 existing lines in 18 files now uncovered.

1684 of 3511 relevant lines covered (47.96%)

944.32 hits per line

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

45.53
/src/Engine/GEOSEngine.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Brick\Geo\Engine;
6

7
use Brick\Geo\Curve;
8
use Brick\Geo\Engine\Internal\TypeChecker;
9
use Brick\Geo\Exception\GeometryEngineException;
10
use Brick\Geo\IO\EWKBReader;
11
use Brick\Geo\IO\EWKBWriter;
12
use Brick\Geo\Geometry;
13
use Brick\Geo\LineString;
14
use Brick\Geo\MultiCurve;
15
use Brick\Geo\MultiPoint;
16
use Brick\Geo\MultiSurface;
17
use Brick\Geo\Point;
18
use Brick\Geo\Surface;
19
use Brick\Geo\MultiPolygon;
20
use Brick\Geo\Polygon;
21
use GEOSWKBReader;
22
use GEOSWKBWriter;
23
use GEOSWKTReader;
24
use GEOSWKTWriter;
25
use Override;
26

27
/**
28
 * GeometryEngine implementation based on the GEOS PHP bindings.
29
 */
30
final class GEOSEngine implements GeometryEngine
31
{
32
    private GEOSWKBReader $wkbReader;
33
    private GEOSWKBWriter $wkbWriter;
34
    private GEOSWKTReader $wktReader;
35
    private GEOSWKTWriter $wktWriter;
36

37
    private EWKBReader $ewkbReader;
38
    private EWKBWriter $ewkbWriter;
39

40
    /**
41
     * Whether the GEOS version in use has support for binary read() and write() methods.
42
     *
43
     * These methods are available since GEOS 3.5.0.
44
     */
45
    private bool $hasBinaryReadWrite;
46

47
    public function __construct()
48
    {
49
        $this->wkbReader = new \GEOSWKBReader();
×
50
        $this->wkbWriter = new \GEOSWKBWriter();
×
51

52
        $this->wktReader = new \GEOSWKTReader();
×
53
        $this->wktWriter = new \GEOSWKTWriter();
×
54

55
        $this->ewkbReader = new EWKBReader();
×
56
        $this->ewkbWriter = new EWKBWriter();
×
57

58
        /** @psalm-suppress RedundantCondition These methods are not available before GEOS 3.5.0 */
59
        $this->hasBinaryReadWrite =
×
60
            method_exists($this->wkbReader, 'read') &&
×
61
            method_exists($this->wkbWriter, 'write');
×
62
    }
63

64
    private function toGEOS(Geometry $geometry) : \GEOSGeometry
65
    {
66
        if ($geometry->isEmpty()) {
184✔
67
            $geosGeometry = $this->wktReader->read($geometry->asText());
18✔
68
            $geosGeometry->setSRID($geometry->SRID());
10✔
69

70
            return $geosGeometry;
10✔
71
        }
72

73
        if ($this->hasBinaryReadWrite) {
168✔
74
            return $this->wkbReader->read($this->ewkbWriter->write($geometry));
168✔
75
        }
76

77
        return $this->wkbReader->readHEX(bin2hex($this->ewkbWriter->write($geometry)));
×
78
    }
79

80
    private function fromGEOS(\GEOSGeometry $geometry) : Geometry
81
    {
82
        if ($geometry->isEmpty()) {
43✔
83
            return Geometry::fromText($this->wktWriter->write($geometry), $geometry->getSRID());
3✔
84
        }
85

86
        if ($this->hasBinaryReadWrite) {
40✔
87
            return $this->ewkbReader->read($this->wkbWriter->write($geometry));
40✔
88
        }
89

90
        $ewkb = hex2bin($this->wkbWriter->writeHEX($geometry));
×
91
        assert($ewkb !== false);
92

93
        return $this->ewkbReader->read($ewkb);
×
94
    }
95

96
    #[Override]
97
    public function union(Geometry $a, Geometry $b) : Geometry
98
    {
99
        try {
100
            return $this->fromGEOS($this->toGEOS($a)->union($this->toGEOS($b)));
4✔
101
        } catch (\Exception $e) {
×
102
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
103
        }
104
    }
105

106
    #[Override]
107
    public function difference(Geometry $a, Geometry $b) : Geometry
108
    {
109
        try {
110
            return $this->fromGEOS($this->toGEOS($a)->difference($this->toGEOS($b)));
2✔
111
        } catch (\Exception $e) {
×
112
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
113
        }
114
    }
115

116
    #[Override]
117
    public function envelope(Geometry $g) : Geometry
118
    {
119
        try {
120
            return $this->fromGEOS($this->toGEOS($g)->envelope());
3✔
121
        } catch (\Exception $e) {
×
122
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
123
        }
124
    }
125

126
    #[Override]
127
    public function length(Curve|MultiCurve $g) : float
128
    {
129
        try {
130
            return $this->toGEOS($g)->length();
16✔
131
        } catch (\Exception $e) {
5✔
132
            throw GeometryEngineException::operationNotSupportedByEngine($e);
5✔
133
        }
134
    }
135

136
    #[Override]
137
    public function area(Surface|MultiSurface $g) : float
138
    {
139
        try {
140
            return $this->toGEOS($g)->area();
11✔
141
        } catch (\Exception $e) {
2✔
142
            throw GeometryEngineException::operationNotSupportedByEngine($e);
2✔
143
        }
144
    }
145

146
    #[Override]
147
    public function azimuth(Point $observer, Point $subject) : float
148
    {
149
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
×
150
    }
151

152
    #[Override]
153
    public function centroid(Geometry $g) : Point
154
    {
155
        try {
NEW
156
            $centroid = $this->fromGEOS($this->toGEOS($g)->centroid());
8✔
157
        } catch (\Exception $e) {
×
158
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
159
        }
160

NEW
161
        TypeChecker::check($centroid, Point::class);
8✔
162

NEW
163
        return $centroid;
8✔
164
    }
165

166
    #[Override]
167
    public function pointOnSurface(Surface|MultiSurface $g) : Point
168
    {
169
        try {
NEW
170
            $pointOnSurface = $this->fromGEOS($this->toGEOS($g)->pointOnSurface());
7✔
171
        } catch (\Exception $e) {
×
172
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
173
        }
174

NEW
175
        TypeChecker::check($pointOnSurface, Point::class);
7✔
176

NEW
177
        return $pointOnSurface;
7✔
178
    }
179

180
    #[Override]
181
    public function boundary(Geometry $g) : Geometry
182
    {
183
        try {
184
            return $this->fromGEOS($this->toGEOS($g)->boundary());
4✔
185
        } catch (\Exception $e) {
×
186
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
187
        }
188
    }
189

190
    #[Override]
191
    public function isValid(Geometry $g) : bool
192
    {
193
        try {
194
            return $this->toGEOS($g)->checkValidity()['valid'];
6✔
195
        } catch (\Exception $e) {
×
196
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
197
        }
198
    }
199

200
    #[Override]
201
    public function isClosed(Geometry $g) : bool
202
    {
203
        try {
204
            return $this->toGEOS($g)->isClosed();
39✔
205
        } catch (\Exception $e) {
12✔
206
            throw GeometryEngineException::operationNotSupportedByEngine($e);
12✔
207
        }
208
    }
209

210
    #[Override]
211
    public function isSimple(Geometry $g) : bool
212
    {
213
        try {
214
            return $this->toGEOS($g)->isSimple();
10✔
215
        } catch (\Exception $e) {
×
216
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
217
        }
218
    }
219

220
    #[Override]
221
    public function isRing(Curve $curve) : bool
222
    {
223
        try {
224
            return $this->toGEOS($curve)->isRing();
11✔
225
        } catch (\Exception $e) {
×
226
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
227
        }
228
    }
229

230
    #[Override]
231
    public function makeValid(Geometry $g): Geometry
232
    {
233
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
×
234
    }
235

236
    #[Override]
237
    public function equals(Geometry $a, Geometry $b) : bool
238
    {
239
        try {
240
            return $this->toGEOS($a)->equals($this->toGEOS($b));
20✔
241
        } catch (\Exception $e) {
×
242
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
243
        }
244
    }
245

246
    #[Override]
247
    public function disjoint(Geometry $a, Geometry $b) : bool
248
    {
249
        try {
250
            return $this->toGEOS($a)->disjoint($this->toGEOS($b));
6✔
251
        } catch (\Exception $e) {
×
252
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
253
        }
254
    }
255

256
    #[Override]
257
    public function intersects(Geometry $a, Geometry $b) : bool
258
    {
259
        try {
260
            return $this->toGEOS($a)->intersects($this->toGEOS($b));
6✔
261
        } catch (\Exception $e) {
×
262
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
263
        }
264
    }
265

266
    #[Override]
267
    public function touches(Geometry $a, Geometry $b) : bool
268
    {
269
        try {
270
            return $this->toGEOS($a)->touches($this->toGEOS($b));
8✔
271
        } catch (\Exception $e) {
×
272
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
273
        }
274
    }
275

276
    #[Override]
277
    public function crosses(Geometry $a, Geometry $b) : bool
278
    {
279
        try {
280
            return $this->toGEOS($a)->crosses($this->toGEOS($b));
8✔
281
        } catch (\Exception $e) {
×
282
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
283
        }
284
    }
285

286
    #[Override]
287
    public function within(Geometry $a, Geometry $b) : bool
288
    {
289
        try {
290
            return $this->toGEOS($a)->within($this->toGEOS($b));
5✔
291
        } catch (\Exception $e) {
×
292
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
293
        }
294
    }
295

296
    #[Override]
297
    public function contains(Geometry $a, Geometry $b) : bool
298
    {
299
        try {
300
            return $this->toGEOS($a)->contains($this->toGEOS($b));
15✔
301
        } catch (\Exception $e) {
×
302
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
303
        }
304
    }
305

306
    #[Override]
307
    public function overlaps(Geometry $a, Geometry $b) : bool
308
    {
309
        try {
310
            return $this->toGEOS($a)->overlaps($this->toGEOS($b));
2✔
311
        } catch (\Exception $e) {
×
312
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
313
        }
314
    }
315

316
    #[Override]
317
    public function relate(Geometry $a, Geometry $b, string $matrix) : bool
318
    {
319
        try {
320
            $result = $this->toGEOS($a)->relate($this->toGEOS($b), $matrix);
4✔
321

322
            // giving a matrix should always return a boolean
323
            assert(is_bool($result));
324

325
            return $result;
4✔
326
        } catch (\Exception $e) {
×
327
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
328
        }
329
    }
330

331
    #[Override]
332
    public function locateAlong(Geometry $g, float $mValue) : Geometry
333
    {
334
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
2✔
335
    }
336

337
    #[Override]
338
    public function locateBetween(Geometry $g, float $mStart, float $mEnd) : Geometry
339
    {
340
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
2✔
341
    }
342

343
    #[Override]
344
    public function distance(Geometry $a, Geometry $b) : float
345
    {
346
        try {
347
            return $this->toGEOS($a)->distance($this->toGEOS($b));
5✔
348
        } catch (\Exception $e) {
×
349
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
350
        }
351
    }
352

353
    #[Override]
354
    public function buffer(Geometry $g, float $distance) : Geometry
355
    {
356
        try {
357
            return $this->fromGEOS($this->toGEOS($g)->buffer($distance));
3✔
358
        } catch (\Exception $e) {
×
359
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
360
        }
361
    }
362

363
    #[Override]
364
    public function convexHull(Geometry $g) : Geometry
365
    {
366
        try {
367
            return $this->fromGEOS($this->toGEOS($g)->convexHull());
3✔
368
        } catch (\Exception $e) {
×
369
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
370
        }
371
    }
372

373
    #[Override]
374
    public function intersection(Geometry $a, Geometry $b) : Geometry
375
    {
376
        try {
377
            return $this->fromGEOS($this->toGEOS($a)->intersection($this->toGEOS($b)));
2✔
378
        } catch (\Exception $e) {
×
379
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
380
        }
381
    }
382

383
    #[Override]
384
    public function symDifference(Geometry $a, Geometry $b) : Geometry
385
    {
386
        try {
387
            return $this->fromGEOS($this->toGEOS($a)->symDifference($this->toGEOS($b)));
1✔
388
        } catch (\Exception $e) {
×
389
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
390
        }
391
    }
392

393
    #[Override]
394
    public function snapToGrid(Geometry $g, float $size) : Geometry
395
    {
396
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
4✔
397
    }
398

399
    #[Override]
400
    public function simplify(Geometry $g, float $tolerance) : Geometry
401
    {
402
        try {
403
            return $this->fromGEOS($this->toGEOS($g)->simplify($tolerance));
2✔
404
        } catch (\Exception $e) {
×
405
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
406
        }
407
    }
408

409
    #[Override]
410
    public function maxDistance(Geometry $a, Geometry $b) : float
411
    {
412
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
3✔
413
    }
414

415
    #[Override]
416
    public function transform(Geometry $g, int $srid) : Geometry
417
    {
418
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
4✔
419
    }
420

421
    #[Override]
422
    public function split(Geometry $g, Geometry $blade) : Geometry
423
    {
424
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
×
425
    }
426

427
    #[Override]
428
    public function lineInterpolatePoint(LineString $lineString, float $fraction) : Point
429
    {
430
        try {
431
            $result = $this->fromGEOS($this->toGEOS($lineString)->interpolate($fraction, true));
4✔
432
        } catch (\Exception $e) {
×
433
            throw GeometryEngineException::operationNotSupportedByEngine($e);
×
434
        }
435

436
        if (! $result instanceof Point) {
4✔
437
            throw new GeometryEngineException('This operation yielded the wrong geometry type: ' . $result::class);
×
438
        }
439

440
        return $result;
4✔
441
    }
442

443
    #[Override]
444
    public function lineInterpolatePoints(LineString $lineString, float $fraction) : MultiPoint
445
    {
446
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
×
447
    }
448
}
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