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

brick / geo / 14087467812

26 Mar 2025 03:39PM UTC coverage: 63.783%. Remained the same
14087467812

push

github

BenMorel
Test with all 3 PDO errmodes

1939 of 3040 relevant lines covered (63.78%)

2122.25 hits per line

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

89.31
/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 GEOSWKBReader;
20
use GEOSWKBWriter;
21
use GEOSWKTReader;
22
use GEOSWKTWriter;
23
use Override;
24

25
/**
26
 * GeometryEngine implementation based on the GEOS PHP bindings.
27
 */
28
final readonly class GeosEngine implements GeometryEngine
29
{
30
    private GEOSWKBReader $geosWkbReader;
31
    private GEOSWKBWriter $geosWkbWriter;
32
    private GEOSWKTReader $geosWktReader;
33
    private GEOSWKTWriter $geosWktWriter;
34

35
    private EwkbReader $ewkbReader;
36
    private EwkbWriter $ewkbWriter;
37

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

45
    public function __construct()
46
    {
47
        $this->geosWkbReader = new \GEOSWKBReader();
×
48
        $this->geosWkbWriter = new \GEOSWKBWriter();
×
49

50
        $this->geosWktReader = new \GEOSWKTReader();
×
51
        $this->geosWktWriter = new \GEOSWKTWriter();
×
52

53
        $this->ewkbReader = new EwkbReader();
×
54
        $this->ewkbWriter = new EwkbWriter();
×
55

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

62
    private function toGeos(Geometry $geometry) : \GEOSGeometry
63
    {
64
        if ($geometry->isEmpty()) {
212✔
65
            $geosGeometry = $this->geosWktReader->read($geometry->asText());
18✔
66
            $geosGeometry->setSRID($geometry->srid());
10✔
67

68
            return $geosGeometry;
10✔
69
        }
70

71
        if ($this->hasBinaryReadWrite) {
196✔
72
            return $this->geosWkbReader->read($this->ewkbWriter->write($geometry));
196✔
73
        }
74

75
        return $this->geosWkbReader->readHEX(bin2hex($this->ewkbWriter->write($geometry)));
×
76
    }
77

78
    private function fromGeos(\GEOSGeometry $geometry) : Geometry
79
    {
80
        if ($geometry->isEmpty()) {
60✔
81
            return Geometry::fromText($this->geosWktWriter->write($geometry), $geometry->getSRID());
3✔
82
        }
83

84
        if ($this->hasBinaryReadWrite) {
57✔
85
            return $this->ewkbReader->read($this->geosWkbWriter->write($geometry));
57✔
86
        }
87

88
        $ewkb = hex2bin($this->geosWkbWriter->writeHEX($geometry));
×
89
        assert($ewkb !== false);
90

91
        return $this->ewkbReader->read($ewkb);
×
92
    }
93

94
    #[Override]
95
    public function union(Geometry $a, Geometry $b) : Geometry
96
    {
97
        return $this->execute(
4✔
98
            fn() => $this->fromGeos($this->toGeos($a)->union($this->toGeos($b))),
4✔
99
        );
4✔
100
    }
101

102
    #[Override]
103
    public function difference(Geometry $a, Geometry $b) : Geometry
104
    {
105
        return $this->execute(
2✔
106
            fn() => $this->fromGeos($this->toGeos($a)->difference($this->toGeos($b))),
2✔
107
        );
2✔
108
    }
109

110
    #[Override]
111
    public function envelope(Geometry $g) : Geometry
112
    {
113
        return $this->execute(
3✔
114
            fn() => $this->fromGeos($this->toGeos($g)->envelope()),
3✔
115
        );
3✔
116
    }
117

118
    #[Override]
119
    public function length(Curve|MultiCurve $g) : float
120
    {
121
        return $this->execute(
16✔
122
            fn() => $this->toGeos($g)->length(),
16✔
123
        );
16✔
124
    }
125

126
    #[Override]
127
    public function area(Surface|MultiSurface $g) : float
128
    {
129
        return $this->execute(
11✔
130
            fn() => $this->toGeos($g)->area(),
11✔
131
        );
11✔
132
    }
133

134
    #[Override]
135
    public function azimuth(Point $observer, Point $subject) : float
136
    {
137
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
6✔
138
    }
139

140
    #[Override]
141
    public function centroid(Geometry $g) : Point
142
    {
143
        $centroid = $this->execute(
8✔
144
            fn() => $this->fromGeos($this->toGeos($g)->centroid()),
8✔
145
        );
8✔
146

147
        TypeChecker::check($centroid, Point::class);
8✔
148

149
        return $centroid;
8✔
150
    }
151

152
    #[Override]
153
    public function pointOnSurface(Surface|MultiSurface $g) : Point
154
    {
155
        $pointOnSurface = $this->execute(
7✔
156
            fn() => $this->fromGeos($this->toGeos($g)->pointOnSurface()),
7✔
157
        );
7✔
158

159
        TypeChecker::check($pointOnSurface, Point::class);
7✔
160

161
        return $pointOnSurface;
7✔
162
    }
163

164
    #[Override]
165
    public function boundary(Geometry $g) : Geometry
166
    {
167
        return $this->execute(
4✔
168
            fn() => $this->fromGeos($this->toGeos($g)->boundary()),
4✔
169
        );
4✔
170
    }
171

172
    #[Override]
173
    public function isValid(Geometry $g) : bool
174
    {
175
        return $this->execute(
6✔
176
            fn() => $this->toGeos($g)->checkValidity()['valid'],
6✔
177
        );
6✔
178
    }
179

180
    #[Override]
181
    public function isClosed(Geometry $g) : bool
182
    {
183
        return $this->execute(
39✔
184
            fn() => $this->toGeos($g)->isClosed(),
39✔
185
        );
39✔
186
    }
187

188
    #[Override]
189
    public function isSimple(Geometry $g) : bool
190
    {
191
        return $this->execute(
10✔
192
            fn() => $this->toGeos($g)->isSimple(),
10✔
193
        );
10✔
194
    }
195

196
    #[Override]
197
    public function isRing(Curve $curve) : bool
198
    {
199
        return $this->execute(
11✔
200
            fn() => $this->toGeos($curve)->isRing(),
11✔
201
        );
11✔
202
    }
203

204
    #[Override]
205
    public function makeValid(Geometry $g): Geometry
206
    {
207
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
6✔
208
    }
209

210
    #[Override]
211
    public function equals(Geometry $a, Geometry $b) : bool
212
    {
213
        return $this->execute(
31✔
214
            fn() => $this->toGeos($a)->equals($this->toGeos($b)),
31✔
215
        );
31✔
216
    }
217

218
    #[Override]
219
    public function disjoint(Geometry $a, Geometry $b) : bool
220
    {
221
        return $this->execute(
6✔
222
            fn() => $this->toGeos($a)->disjoint($this->toGeos($b)),
6✔
223
        );
6✔
224
    }
225

226
    #[Override]
227
    public function intersects(Geometry $a, Geometry $b) : bool
228
    {
229
        return $this->execute(
6✔
230
            fn() => $this->toGeos($a)->intersects($this->toGeos($b)),
6✔
231
        );
6✔
232
    }
233

234
    #[Override]
235
    public function touches(Geometry $a, Geometry $b) : bool
236
    {
237
        return $this->execute(
8✔
238
            fn() => $this->toGeos($a)->touches($this->toGeos($b)),
8✔
239
        );
8✔
240
    }
241

242
    #[Override]
243
    public function crosses(Geometry $a, Geometry $b) : bool
244
    {
245
        return $this->execute(
8✔
246
            fn() => $this->toGeos($a)->crosses($this->toGeos($b)),
8✔
247
        );
8✔
248
    }
249

250
    #[Override]
251
    public function within(Geometry $a, Geometry $b) : bool
252
    {
253
        return $this->execute(
5✔
254
            fn() => $this->toGeos($a)->within($this->toGeos($b)),
5✔
255
        );
5✔
256
    }
257

258
    #[Override]
259
    public function contains(Geometry $a, Geometry $b) : bool
260
    {
261
        return $this->execute(
15✔
262
            fn() => $this->toGeos($a)->contains($this->toGeos($b)),
15✔
263
        );
15✔
264
    }
265

266
    #[Override]
267
    public function overlaps(Geometry $a, Geometry $b) : bool
268
    {
269
        return $this->execute(
2✔
270
            fn() => $this->toGeos($a)->overlaps($this->toGeos($b)),
2✔
271
        );
2✔
272
    }
273

274
    #[Override]
275
    public function relate(Geometry $a, Geometry $b, string $matrix) : bool
276
    {
277
        $result = $this->execute(
4✔
278
            fn() => $this->toGeos($a)->relate($this->toGeos($b), $matrix),
4✔
279
        );
4✔
280

281
        // giving a matrix should always return a boolean
282
        assert(is_bool($result));
283

284
        return $result;
4✔
285
    }
286

287
    #[Override]
288
    public function locateAlong(Geometry $g, float $mValue) : Geometry
289
    {
290
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
2✔
291
    }
292

293
    #[Override]
294
    public function locateBetween(Geometry $g, float $mStart, float $mEnd) : Geometry
295
    {
296
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
2✔
297
    }
298

299
    #[Override]
300
    public function distance(Geometry $a, Geometry $b) : float
301
    {
302
        return $this->execute(
5✔
303
            fn() => $this->toGeos($a)->distance($this->toGeos($b)),
5✔
304
        );
5✔
305
    }
306

307
    #[Override]
308
    public function buffer(Geometry $g, float $distance) : Geometry
309
    {
310
        return $this->execute(
3✔
311
            fn() => $this->fromGeos($this->toGeos($g)->buffer($distance)),
3✔
312
        );
3✔
313
    }
314

315
    #[Override]
316
    public function convexHull(Geometry $g) : Geometry
317
    {
318
        return $this->execute(
3✔
319
            fn() => $this->fromGeos($this->toGeos($g)->convexHull()),
3✔
320
        );
3✔
321
    }
322

323
    #[Override]
324
    public function concaveHull(Geometry $g, float $convexity, bool $allowHoles): Geometry
325
    {
326
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
5✔
327
    }
328

329
    #[Override]
330
    public function intersection(Geometry $a, Geometry $b) : Geometry
331
    {
332
        return $this->execute(
2✔
333
            fn() => $this->fromGeos($this->toGeos($a)->intersection($this->toGeos($b))),
2✔
334
        );
2✔
335
    }
336

337
    #[Override]
338
    public function symDifference(Geometry $a, Geometry $b) : Geometry
339
    {
340
        return $this->execute(
1✔
341
            fn() => $this->fromGeos($this->toGeos($a)->symDifference($this->toGeos($b))),
1✔
342
        );
1✔
343
    }
344

345
    #[Override]
346
    public function snapToGrid(Geometry $g, float $size) : Geometry
347
    {
348
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
4✔
349
    }
350

351
    #[Override]
352
    public function simplify(Geometry $g, float $tolerance) : Geometry
353
    {
354
        return $this->execute(
2✔
355
            fn() => $this->fromGeos($this->toGeos($g)->simplify($tolerance)),
2✔
356
        );
2✔
357
    }
358

359
    #[Override]
360
    public function maxDistance(Geometry $a, Geometry $b) : float
361
    {
362
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
3✔
363
    }
364

365
    #[Override]
366
    public function transform(Geometry $g, int $srid) : Geometry
367
    {
368
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
7✔
369
    }
370

371
    #[Override]
372
    public function split(Geometry $g, Geometry $blade) : Geometry
373
    {
374
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
4✔
375
    }
376

377
    #[Override]
378
    public function lineInterpolatePoint(LineString $lineString, float $fraction) : Point
379
    {
380
        $result = $this->execute(
21✔
381
            fn() => $this->fromGeos($this->toGeos($lineString)->interpolate($fraction, true)),
21✔
382
        );
21✔
383

384
        if (! $result instanceof Point) {
21✔
385
            throw new GeometryEngineException('This operation yielded the wrong geometry type: ' . $result::class);
×
386
        }
387

388
        return $result;
21✔
389
    }
390

391
    #[Override]
392
    public function lineInterpolatePoints(LineString $lineString, float $fraction) : MultiPoint
393
    {
394
        throw GeometryEngineException::unimplementedMethod(__METHOD__);
30✔
395
    }
396

397
    public function getGeosVersion() : string
398
    {
399
        $version = GEOSVersion();
9✔
400

401
        if (false !== $pos = strpos($version, '-')) {
9✔
402
            return substr($version, 0, $pos);
9✔
403
        }
404

405
        return $version;
×
406
    }
407

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