Skip to content

Commit c4b4609

Browse files
feat(#305): add support for PostGIS operators
1 parent d4f6794 commit c4b4609

File tree

12 files changed

+281
-0
lines changed

12 files changed

+281
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Fixtures\MartinGeorgiev\Doctrine\Entity;
6+
7+
use Doctrine\ORM\Mapping as ORM;
8+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\WktSpatialData;
9+
10+
#[ORM\Entity()]
11+
class ContainsGeometries extends Entity
12+
{
13+
#[ORM\Column(type: 'geometry')]
14+
public ?WktSpatialData $geometry1 = null;
15+
16+
#[ORM\Column(type: 'geometry')]
17+
public ?WktSpatialData $geometry2 = null;
18+
19+
#[ORM\Column(type: 'geography')]
20+
public ?WktSpatialData $geography1 = null;
21+
22+
#[ORM\Column(type: 'geography')]
23+
public ?WktSpatialData $geography2 = null;
24+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
/**
8+
* Implementation of PostGIS bounding box overlaps or is above operator (using |&>).
9+
*
10+
* Returns TRUE if A's bounding box overlaps or is above B's.
11+
*
12+
* @see https://postgis.net/docs/reference.html#Operators_Geometry
13+
* @since 3.5
14+
*
15+
* @author Martin Georgiev <martin.georgiev@gmail.com>
16+
*
17+
* @example Using it in DQL with boolean comparison: "WHERE OVERLAPS_ABOVE(g1.geometry, g2.geometry) = TRUE"
18+
* Returns boolean, must be used with "= TRUE" or "= FALSE" in DQL.
19+
*/
20+
class OverlapsAbove extends BaseFunction
21+
{
22+
protected function customizeFunction(): void
23+
{
24+
$this->setFunctionPrototype('(%s |&> %s)');
25+
$this->addNodeMapping('StringPrimary');
26+
$this->addNodeMapping('StringPrimary');
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
/**
8+
* Implementation of PostGIS bounding box overlaps or is below operator (using &<|).
9+
*
10+
* Returns TRUE if A's bounding box overlaps or is below B's.
11+
*
12+
* @see https://postgis.net/docs/reference.html#Operators_Geometry
13+
* @since 3.5
14+
*
15+
* @author Martin Georgiev <martin.georgiev@gmail.com>
16+
*
17+
* @example Using it in DQL with boolean comparison: "WHERE OVERLAPS_BELOW(g1.geometry, g2.geometry) = TRUE"
18+
* Returns boolean, must be used with "= TRUE" or "= FALSE" in DQL.
19+
*/
20+
class OverlapsBelow extends BaseFunction
21+
{
22+
protected function customizeFunction(): void
23+
{
24+
$this->setFunctionPrototype('(%s &<| %s)');
25+
$this->addNodeMapping('StringPrimary');
26+
$this->addNodeMapping('StringPrimary');
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
/**
8+
* Implementation of PostGIS bounding box overlaps or is to the left operator (using &<).
9+
*
10+
* Returns TRUE if A's bounding box overlaps or is to the left of B's.
11+
*
12+
* @see https://postgis.net/docs/reference.html#Operators_Geometry
13+
* @since 3.5
14+
*
15+
* @author Martin Georgiev <martin.georgiev@gmail.com>
16+
*
17+
* @example Using it in DQL with boolean comparison: "WHERE OVERLAPS_LEFT(g1.geometry, g2.geometry) = TRUE"
18+
* Returns boolean, must be used with "= TRUE" or "= FALSE" in DQL.
19+
*/
20+
class OverlapsLeft extends BaseFunction
21+
{
22+
protected function customizeFunction(): void
23+
{
24+
$this->setFunctionPrototype('(%s &< %s)');
25+
$this->addNodeMapping('StringPrimary');
26+
$this->addNodeMapping('StringPrimary');
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
/**
8+
* Implementation of PostGIS bounding box overlaps or is to the right operator (using &>).
9+
*
10+
* Returns TRUE if A's bounding box overlaps or is to the right of B's.
11+
*
12+
* @see https://postgis.net/docs/reference.html#Operators_Geometry
13+
* @since 3.5
14+
*
15+
* @author Martin Georgiev <martin.georgiev@gmail.com>
16+
*
17+
* @example Using it in DQL with boolean comparison: "WHERE OVERLAPS_RIGHT(g1.geometry, g2.geometry) = TRUE"
18+
* Returns boolean, must be used with "= TRUE" or "= FALSE" in DQL.
19+
*/
20+
class OverlapsRight extends BaseFunction
21+
{
22+
protected function customizeFunction(): void
23+
{
24+
$this->setFunctionPrototype('(%s &> %s)');
25+
$this->addNodeMapping('StringPrimary');
26+
$this->addNodeMapping('StringPrimary');
27+
}
28+
}

tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ContainsTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
66

77
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsArrays;
8+
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries;
89
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Contains;
910

1011
class ContainsTest extends TestCase
@@ -23,6 +24,8 @@ protected function getExpectedSqlStatements(): array
2324
'contains array of strings' => "SELECT (c0_.textArray @> '{\"foo\",\"bar\"}') AS sclr_0 FROM ContainsArrays c0_",
2425
'contains single element' => "SELECT (c0_.textArray @> '{42}') AS sclr_0 FROM ContainsArrays c0_",
2526
'contains using parameter' => 'SELECT (c0_.textArray @> ?) AS sclr_0 FROM ContainsArrays c0_',
27+
'contains with spatial geometries' => 'SELECT (c0_.geometry1 @> c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_',
28+
'contains with spatial literal' => "SELECT (c0_.geometry1 @> 'POINT(1 2)') AS sclr_0 FROM ContainsGeometries c0_",
2629
];
2730
}
2831

@@ -33,6 +36,8 @@ protected function getDqlStatements(): array
3336
'contains array of strings' => \sprintf("SELECT CONTAINS(e.textArray, '{\"foo\",\"bar\"}') FROM %s e", ContainsArrays::class),
3437
'contains single element' => \sprintf("SELECT CONTAINS(e.textArray, '{42}') FROM %s e", ContainsArrays::class),
3538
'contains using parameter' => \sprintf('SELECT CONTAINS(e.textArray, :parameter) FROM %s e', ContainsArrays::class),
39+
'contains with spatial geometries' => \sprintf('SELECT CONTAINS(e.geometry1, e.geometry2) FROM %s e', ContainsGeometries::class),
40+
'contains with spatial literal' => \sprintf("SELECT CONTAINS(e.geometry1, 'POINT(1 2)') FROM %s e", ContainsGeometries::class),
3641
];
3742
}
3843
}

tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/IsContainedByTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
66

77
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsArrays;
8+
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries;
89
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\IsContainedBy;
910

1011
class IsContainedByTest extends TestCase
@@ -20,13 +21,17 @@ protected function getExpectedSqlStatements(): array
2021
{
2122
return [
2223
'checks if left array is contained by right array' => "SELECT (c0_.textArray <@ '{681,1185,1878}') AS sclr_0 FROM ContainsArrays c0_",
24+
'checks if geometry is contained by another' => 'SELECT (c0_.geometry1 <@ c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_',
25+
'checks if geometry is contained by literal' => "SELECT (c0_.geometry1 <@ 'POLYGON((0 0, 2 2, 4 4, 0 0))') AS sclr_0 FROM ContainsGeometries c0_",
2326
];
2427
}
2528

2629
protected function getDqlStatements(): array
2730
{
2831
return [
2932
'checks if left array is contained by right array' => \sprintf("SELECT IS_CONTAINED_BY(e.textArray, '{681,1185,1878}') FROM %s e", ContainsArrays::class),
33+
'checks if geometry is contained by another' => \sprintf('SELECT IS_CONTAINED_BY(e.geometry1, e.geometry2) FROM %s e', ContainsGeometries::class),
34+
'checks if geometry is contained by literal' => \sprintf("SELECT IS_CONTAINED_BY(e.geometry1, 'POLYGON((0 0, 2 2, 4 4, 0 0))') FROM %s e", ContainsGeometries::class),
3035
];
3136
}
3237
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries;
8+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\OverlapsAbove;
9+
10+
class OverlapsAboveTest extends TestCase
11+
{
12+
protected function getStringFunctions(): array
13+
{
14+
return [
15+
'OVERLAPS_ABOVE' => OverlapsAbove::class,
16+
];
17+
}
18+
19+
protected function getExpectedSqlStatements(): array
20+
{
21+
return [
22+
'checks if geometry overlaps or is above' => 'SELECT (c0_.geometry1 |&> c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_',
23+
];
24+
}
25+
26+
protected function getDqlStatements(): array
27+
{
28+
return [
29+
'checks if geometry overlaps or is above' => \sprintf('SELECT OVERLAPS_ABOVE(e.geometry1, e.geometry2) FROM %s e', ContainsGeometries::class),
30+
];
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries;
8+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\OverlapsBelow;
9+
10+
class OverlapsBelowTest extends TestCase
11+
{
12+
protected function getStringFunctions(): array
13+
{
14+
return [
15+
'OVERLAPS_BELOW' => OverlapsBelow::class,
16+
];
17+
}
18+
19+
protected function getExpectedSqlStatements(): array
20+
{
21+
return [
22+
'checks if geometry overlaps or is below' => 'SELECT (c0_.geometry1 &<| c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_',
23+
];
24+
}
25+
26+
protected function getDqlStatements(): array
27+
{
28+
return [
29+
'checks if geometry overlaps or is below' => \sprintf('SELECT OVERLAPS_BELOW(e.geometry1, e.geometry2) FROM %s e', ContainsGeometries::class),
30+
];
31+
}
32+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries;
8+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\OverlapsLeft;
9+
10+
class OverlapsLeftTest extends TestCase
11+
{
12+
protected function getStringFunctions(): array
13+
{
14+
return [
15+
'OVERLAPS_LEFT' => OverlapsLeft::class,
16+
];
17+
}
18+
19+
protected function getExpectedSqlStatements(): array
20+
{
21+
return [
22+
'checks if geometry overlaps or is to the left' => 'SELECT (c0_.geometry1 &< c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_',
23+
'checks if geometry overlaps or is to the left of literal' => "SELECT (c0_.geometry1 &< 'POINT(1 2)') AS sclr_0 FROM ContainsGeometries c0_",
24+
];
25+
}
26+
27+
protected function getDqlStatements(): array
28+
{
29+
return [
30+
'checks if geometry overlaps or is to the left' => \sprintf('SELECT OVERLAPS_LEFT(e.geometry1, e.geometry2) FROM %s e', ContainsGeometries::class),
31+
'checks if geometry overlaps or is to the left of literal' => \sprintf("SELECT OVERLAPS_LEFT(e.geometry1, 'POINT(1 2)') FROM %s e", ContainsGeometries::class),
32+
];
33+
}
34+
}

0 commit comments

Comments
 (0)