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

brick / geo / 17456208570

04 Sep 2025 07:10AM UTC coverage: 50.432%. Remained the same
17456208570

push

github

BenMorel
Use @extends and @implements instead of @template-* variants

For consistency with the rest of the project.

1867 of 3702 relevant lines covered (50.43%)

1140.21 hits per line

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

89.76
/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\Geometry;
11
use Brick\Geo\Io\EwkbReader;
12
use Brick\Geo\Io\EwkbWriter;
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 Closure;
20
use Exception;
21
use GEOSGeometry;
22
use GEOSWKBReader;
23
use GEOSWKBWriter;
24
use GEOSWKTReader;
25
use GEOSWKTWriter;
26
use Override;
27

28
use function assert;
29
use function bin2hex;
30
use function hex2bin;
31
use function is_bool;
32
use function method_exists;
33

34
/**
35
 * GeometryEngine implementation based on the GEOS PHP bindings.
36
 */
37
final class GeosEngine implements GeometryEngine
38
{
39
    private readonly GEOSWKBReader $geosWkbReader;
40

41
    private readonly GEOSWKBWriter $geosWkbWriter;
42

43
    private readonly GEOSWKTReader $geosWktReader;
44

45
    private readonly GEOSWKTWriter $geosWktWriter;
46

47
    private readonly EwkbReader $ewkbReader;
48

49
    private readonly EwkbWriter $ewkbWriter;
50

51
    /**
52
     * Whether the GEOS version in use has support for binary read() and write() methods.
53
     *
54
     * These methods are available since GEOS 3.5.0.
55
     */
56
    private readonly bool $hasBinaryReadWrite;
57

58
    public function __construct()
59
    {
60
        $this->geosWkbReader = new GEOSWKBReader();
×
61
        $this->geosWkbWriter = new GEOSWKBWriter();
×
62

63
        $this->geosWktReader = new GEOSWKTReader();
×
64
        $this->geosWktWriter = new GEOSWKTWriter();
×
65

66
        $this->ewkbReader = new EwkbReader();
×
67
        $this->ewkbWriter = new EwkbWriter();
×
68

69
        /** @psalm-suppress RedundantCondition These methods are not available before GEOS 3.5.0 */
70
        $this->hasBinaryReadWrite =
×
71
            method_exists($this->geosWkbReader, 'read') &&
×
72
            method_exists($this->geosWkbWriter, 'write');
×
73
    }
74

75
    #[Override]
76
    public function union(Geometry $a, Geometry $b): Geometry
77
    {
78
        return $this->execute(
4✔
79
            fn () => $this->fromGeos($this->toGeos($a)->union($this->toGeos($b))),
4✔
80
        );
4✔
81
    }
82

83
    #[Override]
84
    public function difference(Geometry $a, Geometry $b): Geometry
85
    {
86
        return $this->execute(
2✔
87
            fn () => $this->fromGeos($this->toGeos($a)->difference($this->toGeos($b))),
2✔
88
        );
2✔
89
    }
90

91
    #[Override]
92
    public function envelope(Geometry $g): Geometry
93
    {
94
        return $this->execute(
3✔
95
            fn () => $this->fromGeos($this->toGeos($g)->envelope()),
3✔
96
        );
3✔
97
    }
98

99
    #[Override]
100
    public function length(Curve|MultiCurve $g): float
101
    {
102
        return $this->execute(
16✔
103
            fn () => $this->toGeos($g)->length(),
16✔
104
        );
16✔
105
    }
106

107
    #[Override]
108
    public function area(Surface|MultiSurface $g): float
109
    {
110
        return $this->execute(
11✔
111
            fn () => $this->toGeos($g)->area(),
11✔
112
        );
11✔
113
    }
114

115
    #[Override]
116
    public function azimuth(Point $observer, Point $subject): float
117
    {
118
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
6✔
119
    }
120

121
    #[Override]
122
    public function centroid(Geometry $g): Point
123
    {
124
        $centroid = $this->execute(
8✔
125
            fn () => $this->fromGeos($this->toGeos($g)->centroid()),
8✔
126
        );
8✔
127

128
        TypeChecker::check($centroid, Point::class);
8✔
129

130
        return $centroid;
8✔
131
    }
132

133
    #[Override]
134
    public function pointOnSurface(Surface|MultiSurface $g): Point
135
    {
136
        $pointOnSurface = $this->execute(
7✔
137
            fn () => $this->fromGeos($this->toGeos($g)->pointOnSurface()),
7✔
138
        );
7✔
139

140
        TypeChecker::check($pointOnSurface, Point::class);
7✔
141

142
        return $pointOnSurface;
7✔
143
    }
144

145
    #[Override]
146
    public function boundary(Geometry $g): Geometry
147
    {
148
        return $this->execute(
4✔
149
            fn () => $this->fromGeos($this->toGeos($g)->boundary()),
4✔
150
        );
4✔
151
    }
152

153
    #[Override]
154
    public function isValid(Geometry $g): bool
155
    {
156
        return $this->execute(
6✔
157
            fn () => $this->toGeos($g)->checkValidity()['valid'],
6✔
158
        );
6✔
159
    }
160

161
    #[Override]
162
    public function isClosed(Geometry $g): bool
163
    {
164
        return $this->execute(
39✔
165
            fn () => $this->toGeos($g)->isClosed(),
39✔
166
        );
39✔
167
    }
168

169
    #[Override]
170
    public function isSimple(Geometry $g): bool
171
    {
172
        return $this->execute(
10✔
173
            fn () => $this->toGeos($g)->isSimple(),
10✔
174
        );
10✔
175
    }
176

177
    #[Override]
178
    public function isRing(Curve $curve): bool
179
    {
180
        return $this->execute(
11✔
181
            fn () => $this->toGeos($curve)->isRing(),
11✔
182
        );
11✔
183
    }
184

185
    #[Override]
186
    public function makeValid(Geometry $g): Geometry
187
    {
188
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
6✔
189
    }
190

191
    #[Override]
192
    public function equals(Geometry $a, Geometry $b): bool
193
    {
194
        return $this->execute(
31✔
195
            fn () => $this->toGeos($a)->equals($this->toGeos($b)),
31✔
196
        );
31✔
197
    }
198

199
    #[Override]
200
    public function disjoint(Geometry $a, Geometry $b): bool
201
    {
202
        return $this->execute(
6✔
203
            fn () => $this->toGeos($a)->disjoint($this->toGeos($b)),
6✔
204
        );
6✔
205
    }
206

207
    #[Override]
208
    public function intersects(Geometry $a, Geometry $b): bool
209
    {
210
        return $this->execute(
6✔
211
            fn () => $this->toGeos($a)->intersects($this->toGeos($b)),
6✔
212
        );
6✔
213
    }
214

215
    #[Override]
216
    public function touches(Geometry $a, Geometry $b): bool
217
    {
218
        return $this->execute(
8✔
219
            fn () => $this->toGeos($a)->touches($this->toGeos($b)),
8✔
220
        );
8✔
221
    }
222

223
    #[Override]
224
    public function crosses(Geometry $a, Geometry $b): bool
225
    {
226
        return $this->execute(
8✔
227
            fn () => $this->toGeos($a)->crosses($this->toGeos($b)),
8✔
228
        );
8✔
229
    }
230

231
    #[Override]
232
    public function within(Geometry $a, Geometry $b): bool
233
    {
234
        return $this->execute(
5✔
235
            fn () => $this->toGeos($a)->within($this->toGeos($b)),
5✔
236
        );
5✔
237
    }
238

239
    #[Override]
240
    public function contains(Geometry $a, Geometry $b): bool
241
    {
242
        return $this->execute(
15✔
243
            fn () => $this->toGeos($a)->contains($this->toGeos($b)),
15✔
244
        );
15✔
245
    }
246

247
    #[Override]
248
    public function overlaps(Geometry $a, Geometry $b): bool
249
    {
250
        return $this->execute(
2✔
251
            fn () => $this->toGeos($a)->overlaps($this->toGeos($b)),
2✔
252
        );
2✔
253
    }
254

255
    #[Override]
256
    public function relate(Geometry $a, Geometry $b, string $matrix): bool
257
    {
258
        $result = $this->execute(
4✔
259
            fn () => $this->toGeos($a)->relate($this->toGeos($b), $matrix),
4✔
260
        );
4✔
261

262
        // giving a matrix should always return a boolean
263
        assert(is_bool($result));
264

265
        return $result;
4✔
266
    }
267

268
    #[Override]
269
    public function locateAlong(Geometry $g, float $mValue): Geometry
270
    {
271
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
2✔
272
    }
273

274
    #[Override]
275
    public function locateBetween(Geometry $g, float $mStart, float $mEnd): Geometry
276
    {
277
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
2✔
278
    }
279

280
    #[Override]
281
    public function distance(Geometry $a, Geometry $b): float
282
    {
283
        return $this->execute(
5✔
284
            fn () => $this->toGeos($a)->distance($this->toGeos($b)),
5✔
285
        );
5✔
286
    }
287

288
    #[Override]
289
    public function buffer(Geometry $g, float $distance): Geometry
290
    {
291
        return $this->execute(
3✔
292
            fn () => $this->fromGeos($this->toGeos($g)->buffer($distance)),
3✔
293
        );
3✔
294
    }
295

296
    #[Override]
297
    public function convexHull(Geometry $g): Geometry
298
    {
299
        return $this->execute(
3✔
300
            fn () => $this->fromGeos($this->toGeos($g)->convexHull()),
3✔
301
        );
3✔
302
    }
303

304
    #[Override]
305
    public function concaveHull(Geometry $g, float $convexity, bool $allowHoles): Geometry
306
    {
307
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
5✔
308
    }
309

310
    #[Override]
311
    public function intersection(Geometry $a, Geometry $b): Geometry
312
    {
313
        return $this->execute(
2✔
314
            fn () => $this->fromGeos($this->toGeos($a)->intersection($this->toGeos($b))),
2✔
315
        );
2✔
316
    }
317

318
    #[Override]
319
    public function symDifference(Geometry $a, Geometry $b): Geometry
320
    {
321
        return $this->execute(
1✔
322
            fn () => $this->fromGeos($this->toGeos($a)->symDifference($this->toGeos($b))),
1✔
323
        );
1✔
324
    }
325

326
    #[Override]
327
    public function snapToGrid(Geometry $g, float $size): Geometry
328
    {
329
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
4✔
330
    }
331

332
    #[Override]
333
    public function simplify(Geometry $g, float $tolerance): Geometry
334
    {
335
        return $this->execute(
2✔
336
            fn () => $this->fromGeos($this->toGeos($g)->simplify($tolerance)),
2✔
337
        );
2✔
338
    }
339

340
    #[Override]
341
    public function maxDistance(Geometry $a, Geometry $b): float
342
    {
343
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
3✔
344
    }
345

346
    #[Override]
347
    public function transform(Geometry $g, int $srid): Geometry
348
    {
349
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
7✔
350
    }
351

352
    #[Override]
353
    public function split(Geometry $g, Geometry $blade): Geometry
354
    {
355
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
4✔
356
    }
357

358
    #[Override]
359
    public function lineInterpolatePoint(LineString $lineString, float $fraction): Point
360
    {
361
        $result = $this->execute(
21✔
362
            fn () => $this->fromGeos($this->toGeos($lineString)->interpolate($fraction, true)),
21✔
363
        );
21✔
364

365
        if (! $result instanceof Point) {
21✔
366
            throw new GeometryEngineException('This operation yielded the wrong geometry type: ' . $result::class);
×
367
        }
368

369
        return $result;
21✔
370
    }
371

372
    #[Override]
373
    public function lineInterpolatePoints(LineString $lineString, float $fraction): MultiPoint
374
    {
375
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
30✔
376
    }
377

378
    private function toGeos(Geometry $geometry): GEOSGeometry
379
    {
380
        if ($geometry->isEmpty()) {
212✔
381
            $geosGeometry = $this->geosWktReader->read($geometry->asText());
18✔
382
            $geosGeometry->setSRID($geometry->srid());
10✔
383

384
            return $geosGeometry;
10✔
385
        }
386

387
        if ($this->hasBinaryReadWrite) {
196✔
388
            return $this->geosWkbReader->read($this->ewkbWriter->write($geometry));
196✔
389
        }
390

391
        return $this->geosWkbReader->readHEX(bin2hex($this->ewkbWriter->write($geometry)));
×
392
    }
393

394
    private function fromGeos(GEOSGeometry $geometry): Geometry
395
    {
396
        if ($geometry->isEmpty()) {
60✔
397
            return Geometry::fromText($this->geosWktWriter->write($geometry), $geometry->getSRID());
3✔
398
        }
399

400
        if ($this->hasBinaryReadWrite) {
57✔
401
            return $this->ewkbReader->read($this->geosWkbWriter->write($geometry));
57✔
402
        }
403

404
        $ewkb = hex2bin($this->geosWkbWriter->writeHEX($geometry));
×
405
        assert($ewkb !== false);
406

407
        return $this->ewkbReader->read($ewkb);
×
408
    }
409

410
    /**
411
     * @template T
412
     *
413
     * @param Closure(): T $action
414
     *
415
     * @return T
416
     *
417
     * @throws GeometryEngineException
418
     */
419
    private function execute(Closure $action): mixed
420
    {
421
        try {
422
            return $action();
212✔
423
        } catch (Exception $e) {
19✔
424
            throw GeometryEngineException::wrap($e);
19✔
425
        }
426
    }
427
}
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