5
5
namespace Brick \Geo \Engine ;
6
6
7
7
use Brick \Geo \Curve ;
8
+ use Brick \Geo \Engine \Database \DatabasePlatform ;
9
+ use Brick \Geo \Engine \Internal \GeometryParameter ;
8
10
use Brick \Geo \Engine \Internal \TypeChecker ;
9
11
use Brick \Geo \Exception \GeometryEngineException ;
10
12
use Brick \Geo \Geometry ;
11
13
use Brick \Geo \LineString ;
12
14
use Brick \Geo \MultiCurve ;
13
15
use Brick \Geo \MultiPoint ;
14
16
use Brick \Geo \MultiSurface ;
15
- use Brick \Geo \MultiPolygon ;
16
17
use Brick \Geo \Point ;
17
- use Brick \Geo \Polygon ;
18
- use Brick \Geo \Proxy ;
19
- use Brick \Geo \Proxy \ProxyInterface ;
20
18
use Brick \Geo \Surface ;
21
19
use Override ;
22
20
23
21
/**
24
- * Database implementation of the GeometryEngine.
25
- *
26
- * The target database must have support for GIS functions.
22
+ * Base class for database engines.
27
23
*/
28
- abstract class DatabaseEngine implements GeometryEngine
24
+ abstract readonly class DatabaseEngine implements GeometryEngine
29
25
{
30
- private readonly bool $ useProxy ;
31
-
32
- public function __construct (bool $ useProxy )
33
- {
34
- $ this ->useProxy = $ useProxy ;
35
- }
36
-
37
- /**
38
- * Executes a SQL query.
39
- *
40
- * @param string $query The SQL query to execute.
41
- * @param list<GeometryParameter|scalar> $parameters The geometry data or scalar values to pass as parameters.
42
- *
43
- * @return list<mixed> A numeric result array.
44
- *
45
- * @throws GeometryEngineException
46
- */
47
- abstract protected function executeQuery (string $ query , array $ parameters ) : array ;
48
-
49
- /**
50
- * Returns the syntax required to perform a ST_GeomFromText(), together with placeholders.
51
- *
52
- * This method may be overridden if necessary.
53
- */
54
- protected function getGeomFromTextSyntax (): string
55
- {
56
- return 'ST_GeomFromText(?, ?) ' ;
57
- }
58
-
59
- /**
60
- * Returns the syntax required to perform a ST_GeomFromWKB(), together with placeholders.
61
- *
62
- * This method may be overridden if necessary.
63
- */
64
- protected function getGeomFromWkbSyntax (): string
65
- {
66
- return 'ST_GeomFromWKB(?, ?) ' ;
67
- }
68
-
69
- /**
70
- * Returns the placeholder syntax for the given parameter.
71
- *
72
- * This method may be overridden to perform explicit type casts if necessary.
73
- */
74
- protected function getParameterPlaceholder (string |float |int |bool $ parameter ): string
75
- {
76
- return '? ' ;
77
- }
78
-
79
- /**
80
- * Builds and executes a SQL query for a GIS function.
81
- *
82
- * @param string $function The SQL GIS function to execute.
83
- * @param array<Geometry|scalar> $parameters The Geometry objects or scalar values to pass as parameters.
84
- * @param bool $returnsGeometry Whether the GIS function returns a Geometry.
85
- *
86
- * @return list<mixed> A numeric result array.
87
- *
88
- * @throws GeometryEngineException
89
- */
90
- private function query (string $ function , array $ parameters , bool $ returnsGeometry ) : array
91
- {
92
- $ queryParameters = [];
93
- $ queryValues = [];
94
-
95
- foreach ($ parameters as $ parameter ) {
96
- if ($ parameter instanceof Geometry) {
97
- if ($ parameter instanceof Proxy \ProxyInterface) {
98
- $ sendAsBinary = $ parameter ->isProxyBinary ();
99
- } else {
100
- $ sendAsBinary = ! $ parameter ->isEmpty ();
101
- }
102
-
103
- $ queryParameters [] = $ sendAsBinary
104
- ? $ this ->getGeomFromWkbSyntax ()
105
- : $ this ->getGeomFromTextSyntax ();
106
-
107
- $ queryValues [] = new GeometryParameter ($ parameter , $ sendAsBinary );
108
- } else {
109
- $ queryParameters [] = $ this ->getParameterPlaceholder ($ parameter );
110
- $ queryValues [] = $ parameter ;
111
- }
112
- }
113
-
114
- $ query = sprintf ('SELECT %s(%s) ' , $ function , implode (', ' , $ queryParameters ));
115
-
116
- if ($ returnsGeometry ) {
117
- $ query = sprintf ('
118
- SELECT
119
- CASE WHEN ST_IsEmpty(g) THEN ST_AsText(g) ELSE NULL END,
120
- CASE WHEN ST_IsEmpty(g) THEN NULL ELSE ST_AsBinary(g) END,
121
- ST_GeometryType(g),
122
- ST_SRID(g)
123
- FROM (%s AS g) AS q
124
- ' , $ query );
125
- }
126
-
127
- return $ this ->executeQuery ($ query , $ queryValues );
128
- }
129
-
130
26
/**
131
27
* Queries a GIS function returning a boolean value.
132
28
*
@@ -135,21 +31,7 @@ private function query(string $function, array $parameters, bool $returnsGeometr
135
31
*
136
32
* @throws GeometryEngineException
137
33
*/
138
- private function queryBoolean (string $ function , Geometry |string |float |int |bool ...$ parameters ) : bool
139
- {
140
- /** @var array{scalar|null} $result */
141
- $ result = $ this ->query ($ function , $ parameters , false );
142
-
143
- $ value = $ result [0 ];
144
-
145
- // SQLite3 returns -1 when calling a boolean GIS function on a NULL result,
146
- // MariaDB returns -1 when an unsupported operation is performed on a Z/M geometry.
147
- if ($ value === null || $ value === -1 || $ value === '-1 ' ) {
148
- throw GeometryEngineException::operationYieldedNoResult ();
149
- }
150
-
151
- return (bool ) $ value ;
152
- }
34
+ abstract protected function queryBool (string $ function , Geometry |string |float |int |bool ...$ parameters ) : bool ;
153
35
154
36
/**
155
37
* Queries a GIS function returning a floating point value.
@@ -159,19 +41,7 @@ private function queryBoolean(string $function, Geometry|string|float|int|bool .
159
41
*
160
42
* @throws GeometryEngineException
161
43
*/
162
- private function queryFloat (string $ function , Geometry |string |float |int |bool ...$ parameters ) : float
163
- {
164
- /** @var array{scalar|null} $result */
165
- $ result = $ this ->query ($ function , $ parameters , false );
166
-
167
- $ value = $ result [0 ];
168
-
169
- if ($ value === null ) {
170
- throw GeometryEngineException::operationYieldedNoResult ();
171
- }
172
-
173
- return (float ) $ value ;
174
- }
44
+ abstract protected function queryFloat (string $ function , Geometry |string |float |int |bool ...$ parameters ) : float ;
175
45
176
46
/**
177
47
* Queries a GIS function returning a Geometry object.
@@ -181,94 +51,18 @@ private function queryFloat(string $function, Geometry|string|float|int|bool ...
181
51
*
182
52
* @throws GeometryEngineException
183
53
*/
184
- final protected function queryGeometry (string $ function , Geometry |string |float |int |bool ...$ parameters ) : Geometry
185
- {
186
- /** @var array{string|null, string|resource|null, string, int|numeric-string} $result */
187
- $ result = $ this ->query ($ function , $ parameters , true );
188
-
189
- [$ wkt , $ wkb , $ geometryType , $ srid ] = $ result ;
190
-
191
- $ srid = (int ) $ srid ;
192
-
193
- if ($ wkt !== null ) {
194
- if ($ this ->useProxy ) {
195
- $ proxyClassName = $ this ->getProxyClassName ($ geometryType );
196
-
197
- return new $ proxyClassName ($ wkt , false , $ srid );
198
- }
199
-
200
- return Geometry::fromText ($ wkt , $ srid );
201
- }
202
-
203
- if ($ wkb !== null ) {
204
- if (is_resource ($ wkb )) {
205
- $ wkb = stream_get_contents ($ wkb );
206
- }
207
-
208
- if ($ this ->useProxy ) {
209
- $ proxyClassName = $ this ->getProxyClassName ($ geometryType );
210
-
211
- return new $ proxyClassName ($ wkb , true , $ srid );
212
- }
213
-
214
- return Geometry::fromBinary ($ wkb , $ srid );
215
- }
216
-
217
- throw GeometryEngineException::operationYieldedNoResult ();
218
- }
219
-
220
- /**
221
- * @return class-string<Proxy\ProxyInterface&Geometry>
222
- *
223
- * @throws GeometryEngineException
224
- */
225
- private function getProxyClassName (string $ geometryType ) : string
226
- {
227
- $ proxyClasses = [
228
- 'CIRCULARSTRING ' => Proxy \CircularStringProxy::class,
229
- 'COMPOUNDCURVE ' => Proxy \CompoundCurveProxy::class,
230
- 'CURVE ' => Proxy \CurveProxy::class,
231
- 'CURVEPOLYGON ' => Proxy \CurvePolygonProxy::class,
232
- 'GEOMCOLLECTION ' => Proxy \GeometryCollectionProxy::class, /* MySQL 8 - https://github.com/brick/geo/pull/33 */
233
- 'GEOMETRY ' => Proxy \GeometryProxy::class,
234
- 'GEOMETRYCOLLECTION ' => Proxy \GeometryCollectionProxy::class,
235
- 'LINESTRING ' => Proxy \LineStringProxy::class,
236
- 'MULTICURVE ' => Proxy \MultiCurveProxy::class,
237
- 'MULTILINESTRING ' => Proxy \MultiLineStringProxy::class,
238
- 'MULTIPOINT ' => Proxy \MultiPointProxy::class,
239
- 'MULTIPOLYGON ' => Proxy \MultiPolygonProxy::class,
240
- 'MULTISURFACE ' => Proxy \MultiSurfaceProxy::class,
241
- 'POINT ' => Proxy \PointProxy::class,
242
- 'POLYGON ' => Proxy \PolygonProxy::class,
243
- 'POLYHEDRALSURFACE ' => Proxy \PolyhedralSurfaceProxy::class,
244
- 'SURFACE ' => Proxy \SurfaceProxy::class,
245
- 'TIN ' => Proxy \TinProxy::class,
246
- 'TRIANGLE ' => Proxy \TriangleProxy::class
247
- ];
248
-
249
- $ geometryType = strtoupper ($ geometryType );
250
- $ geometryType = preg_replace ('/^ST_/ ' , '' , $ geometryType );
251
- assert ($ geometryType !== null );
252
- $ geometryType = preg_replace ('/ .*/ ' , '' , $ geometryType );
253
- assert ($ geometryType !== null );
254
-
255
- if (! isset ($ proxyClasses [$ geometryType ])) {
256
- throw new GeometryEngineException ('Unknown geometry type: ' . $ geometryType );
257
- }
258
-
259
- return $ proxyClasses [$ geometryType ];
260
- }
54
+ abstract protected function queryGeometry (string $ function , Geometry |string |float |int |bool ...$ parameters ) : Geometry ;
261
55
262
56
#[Override]
263
57
public function contains (Geometry $ a , Geometry $ b ) : bool
264
58
{
265
- return $ this ->queryBoolean ('ST_Contains ' , $ a , $ b );
59
+ return $ this ->queryBool ('ST_Contains ' , $ a , $ b );
266
60
}
267
61
268
62
#[Override]
269
63
public function intersects (Geometry $ a , Geometry $ b ) : bool
270
64
{
271
- return $ this ->queryBoolean ('ST_Intersects ' , $ a , $ b );
65
+ return $ this ->queryBool ('ST_Intersects ' , $ a , $ b );
272
66
}
273
67
274
68
#[Override]
@@ -340,30 +134,25 @@ public function boundary(Geometry $g) : Geometry
340
134
#[Override]
341
135
public function isValid (Geometry $ g ) : bool
342
136
{
343
- return $ this ->queryBoolean ('ST_IsValid ' , $ g );
137
+ return $ this ->queryBool ('ST_IsValid ' , $ g );
344
138
}
345
139
346
140
#[Override]
347
141
public function isClosed (Geometry $ g ) : bool
348
142
{
349
- return $ this ->queryBoolean ('ST_IsClosed ' , $ g );
143
+ return $ this ->queryBool ('ST_IsClosed ' , $ g );
350
144
}
351
145
352
146
#[Override]
353
147
public function isSimple (Geometry $ g ) : bool
354
148
{
355
- return $ this ->queryBoolean ('ST_IsSimple ' , $ g );
149
+ return $ this ->queryBool ('ST_IsSimple ' , $ g );
356
150
}
357
151
358
152
#[Override]
359
153
public function isRing (Curve $ curve ) : bool
360
154
{
361
- try {
362
- return $ this ->queryBoolean ('ST_IsRing ' , $ curve );
363
- } catch (GeometryEngineException ) {
364
- // Not all RDBMS (hello, MySQL) support ST_IsRing(), but we have an easy fallback
365
- return $ this ->isClosed ($ curve ) && $ this ->isSimple ($ curve );
366
- }
155
+ return $ this ->queryBool ('ST_IsRing ' , $ curve );
367
156
}
368
157
369
158
#[Override]
@@ -375,43 +164,43 @@ public function makeValid(Geometry $g) : Geometry
375
164
#[Override]
376
165
public function equals (Geometry $ a , Geometry $ b ) : bool
377
166
{
378
- return $ this ->queryBoolean ('ST_Equals ' , $ a , $ b );
167
+ return $ this ->queryBool ('ST_Equals ' , $ a , $ b );
379
168
}
380
169
381
170
#[Override]
382
171
public function disjoint (Geometry $ a , Geometry $ b ) : bool
383
172
{
384
- return $ this ->queryBoolean ('ST_Disjoint ' , $ a , $ b );
173
+ return $ this ->queryBool ('ST_Disjoint ' , $ a , $ b );
385
174
}
386
175
387
176
#[Override]
388
177
public function touches (Geometry $ a , Geometry $ b ) : bool
389
178
{
390
- return $ this ->queryBoolean ('ST_Touches ' , $ a , $ b );
179
+ return $ this ->queryBool ('ST_Touches ' , $ a , $ b );
391
180
}
392
181
393
182
#[Override]
394
183
public function crosses (Geometry $ a , Geometry $ b ) : bool
395
184
{
396
- return $ this ->queryBoolean ('ST_Crosses ' , $ a , $ b );
185
+ return $ this ->queryBool ('ST_Crosses ' , $ a , $ b );
397
186
}
398
187
399
188
#[Override]
400
189
public function within (Geometry $ a , Geometry $ b ) : bool
401
190
{
402
- return $ this ->queryBoolean ('ST_Within ' , $ a , $ b );
191
+ return $ this ->queryBool ('ST_Within ' , $ a , $ b );
403
192
}
404
193
405
194
#[Override]
406
195
public function overlaps (Geometry $ a , Geometry $ b ) : bool
407
196
{
408
- return $ this ->queryBoolean ('ST_Overlaps ' , $ a , $ b );
197
+ return $ this ->queryBool ('ST_Overlaps ' , $ a , $ b );
409
198
}
410
199
411
200
#[Override]
412
201
public function relate (Geometry $ a , Geometry $ b , string $ matrix ) : bool
413
202
{
414
- return $ this ->queryBoolean ('ST_Relate ' , $ a , $ b , $ matrix );
203
+ return $ this ->queryBool ('ST_Relate ' , $ a , $ b , $ matrix );
415
204
}
416
205
417
206
#[Override]
0 commit comments