From 855b1993f8981f67fd70d603a35737f803b6071c Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Wed, 5 Jun 2024 21:34:22 +0200 Subject: [PATCH 1/2] Add GeometryEngine::split() --- CHANGELOG.md | 10 +++++ README.md | 69 ++++++++++++++++++----------------- src/Engine/DatabaseEngine.php | 5 +++ src/Engine/GEOSEngine.php | 5 +++ src/Engine/GeometryEngine.php | 5 +++ tests/GeometryEngineTest.php | 27 ++++++++++++++ 6 files changed, 87 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b51839e..6cc5341 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## UNRELEASED (0.11.0) + +💥 **Breaking changes** + +- interface `GeometryEngine` has a new method: `split()` + +✨ **New features** + +- New engine method: `GeometryEngine::split()` + ## [0.10.0](https://github.com/brick/geo/releases/tag/0.10.0) - 2024-01-23 💥 **Breaking changes** diff --git a/README.md b/README.md index 2378498..b543402 100644 --- a/README.md +++ b/README.md @@ -272,46 +272,47 @@ Here is a list of all exceptions: - `UnexpectedGeometryException` is thrown when a geometry is not an instance of the expected sub-type, for example when calling `Point::fromText()` with a `LineString` WKT. -Spatial Function Reference --------------------------- +GeometryEngine methods reference +-------------------------------- -This is a list of all functions which are currently implemented in the geo project. Some functions are only available +This is a list of all methods available in the `GeometryEngine` interface. Some methods are only available if you use a specific geometry engine, sometimes with a minimum version. This table also shows which functions are part of the OpenGIS standard. | Function Name | GEOS | PostGIS | MySQL | MariaDB | SpatiaLite | OpenGIS standard | |------------------|------|---------|--------|---------|------------|------------------| -| `area` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `azimuth` | | ✓ | | | ✓ | | -| `boundary` | ✓ | ✓ | | | ✓ | ✓ | -| `buffer` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `centroid` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `contains` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `convexHull` | ✓ | ✓ | 5.7.6 | | ✓ | ✓ | -| `crosses` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `difference` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `disjoint` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `distance` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `envelope` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `equals` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `intersects` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `intersection` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `isSimple` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `isValid` | ✓ | ✓ | 5.7.6 | | ✓ | | -| `length` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `locateAlong` | | ✓ | | | ✓ | | -| `locateBetween` | | ✓ | | | ✓ | | -| `makeValid` | | ✓ | | | ✓ | | -| `maxDistance` | | ✓ | | | ✓ | | -| `overlaps` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `pointOnSurface` | ✓ | ✓ | | | ✓ | ✓ | -| `relate` | ✓ | ✓ | | | ✓ | ✓ | -| `simplify` | ✓ | ✓ | 5.7.6 | | 4.1.0 | | -| `snapToGrid` | | ✓ | | | ✓ | | -| `symDifference` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `touches` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `union` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| `within` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `area` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `azimuth` | | ✓ | | | ✓ | | +| `boundary` | ✓ | ✓ | | | ✓ | ✓ | +| `buffer` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `centroid` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `contains` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `convexHull` | ✓ | ✓ | 5.7.6 | | ✓ | ✓ | +| `crosses` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `difference` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `disjoint` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `distance` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `envelope` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `equals` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `intersection` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `intersects` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `isSimple` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `isValid` | ✓ | ✓ | 5.7.6 | | ✓ | | +| `length` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `locateAlong` | | ✓ | | | ✓ | | +| `locateBetween` | | ✓ | | | ✓ | | +| `makeValid` | | ✓ | | | ✓ | | +| `maxDistance` | | ✓ | | | ✓ | | +| `overlaps` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `pointOnSurface` | ✓ | ✓ | | | ✓ | ✓ | +| `relate` | ✓ | ✓ | | | ✓ | ✓ | +| `simplify` | ✓ | ✓ | 5.7.6 | | 4.1.0 | | +| `snapToGrid` | | ✓ | | | ✓ | | +| `split` | | ✓ | | | ✓ | | +| `symDifference` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `touches` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `union` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| `within` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Importing and exporting geometries ---------------------------------- diff --git a/src/Engine/DatabaseEngine.php b/src/Engine/DatabaseEngine.php index 08227ef..fc3796c 100644 --- a/src/Engine/DatabaseEngine.php +++ b/src/Engine/DatabaseEngine.php @@ -433,4 +433,9 @@ public function transform(Geometry $g, int $srid) : Geometry { return $this->queryGeometry('ST_Transform', $g, $srid); } + + public function split(Geometry $g, Geometry $blade) : Geometry + { + return $this->queryGeometry('ST_Split', $g, $blade); + } } diff --git a/src/Engine/GEOSEngine.php b/src/Engine/GEOSEngine.php index 8665ffd..b747df5 100644 --- a/src/Engine/GEOSEngine.php +++ b/src/Engine/GEOSEngine.php @@ -375,4 +375,9 @@ public function transform(Geometry $g, int $srid) : Geometry { throw GeometryEngineException::unimplementedMethod(__METHOD__); } + + public function split(Geometry $g, Geometry $blade) : Geometry + { + throw GeometryEngineException::unimplementedMethod(__METHOD__); + } } diff --git a/src/Engine/GeometryEngine.php b/src/Engine/GeometryEngine.php index ed721ea..60aa034 100644 --- a/src/Engine/GeometryEngine.php +++ b/src/Engine/GeometryEngine.php @@ -486,4 +486,9 @@ public function boundingPolygons(Polygon $p) : MultiPolygon; * Returns a new geometry with its coordinates transformed to a different spatial reference system. */ public function transform(Geometry $g, int $srid) : Geometry; + + /** + * Splits a geometry into several geometries using a blade. + */ + public function split(Geometry $g, Geometry $blade) : Geometry; } diff --git a/tests/GeometryEngineTest.php b/tests/GeometryEngineTest.php index bfe5ac1..5537e28 100644 --- a/tests/GeometryEngineTest.php +++ b/tests/GeometryEngineTest.php @@ -1164,6 +1164,33 @@ public static function providerTransform() : array ]; } + #[DataProvider('providerSplit')] + public function testSplit(string $originalWKT, string $bladeWKT, string $expectedWKT) : void + { + $geometryEngine = $this->getGeometryEngine(); + + if (! $this->isPostGIS() && ! $this->isSpatiaLite()) { + self::markTestSkipped('This test currently runs on PostGIS & SpatiaLite only.'); + } + + $originalGeometry = Geometry::fromText($originalWKT); + $bladeGeometry = Geometry::fromText($bladeWKT); + + $splitGeometry = $geometryEngine->split($originalGeometry, $bladeGeometry); + + $this->assertSame($expectedWKT, $splitGeometry->asText()); + } + + public static function providerSplit() : array + { + return [ + ['LINESTRING (1 1, 3 3)', 'POINT (2 2)', 'GEOMETRYCOLLECTION (LINESTRING (1 1, 2 2), LINESTRING (2 2, 3 3))'], + ['LINESTRING (1 1, 1 2, 2 2, 2 1, 1 1)', 'LINESTRING (0 0, 3 3)', 'GEOMETRYCOLLECTION (LINESTRING (1 1, 1 2, 2 2), LINESTRING (2 2, 2 1, 1 1))'], + ['POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1))', 'LINESTRING (0 0, 3 3)', 'GEOMETRYCOLLECTION (POLYGON ((1 1, 1 2, 2 2, 1 1)), POLYGON ((2 2, 2 1, 1 1, 2 2)))'], + ['POLYGON ((1 1, 1 2, 3 2, 3 1, 1 1))', 'LINESTRING (1 1, 2 2, 3 1)', 'GEOMETRYCOLLECTION (POLYGON ((1 1, 1 2, 2 2, 1 1)), POLYGON ((1 1, 2 2, 3 1, 1 1)), POLYGON ((3 1, 2 2, 3 2, 3 1)))'], + ]; + } + private function getGeometryEngine(): GeometryEngine { if (! isset($GLOBALS['GEOMETRY_ENGINE'])) { From 83c5bdd1f80d0c03a2805c7f9be8ba087734a2bc Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Wed, 5 Jun 2024 22:02:02 +0200 Subject: [PATCH 2/2] Fix split test for SpatiaLite --- tests/GeometryEngineTest.php | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/tests/GeometryEngineTest.php b/tests/GeometryEngineTest.php index 5537e28..909264c 100644 --- a/tests/GeometryEngineTest.php +++ b/tests/GeometryEngineTest.php @@ -1164,8 +1164,11 @@ public static function providerTransform() : array ]; } + /** + * @param string|string[] $expectedWKT + */ #[DataProvider('providerSplit')] - public function testSplit(string $originalWKT, string $bladeWKT, string $expectedWKT) : void + public function testSplit(string $originalWKT, string $bladeWKT, string|array $expectedWKT) : void { $geometryEngine = $this->getGeometryEngine(); @@ -1178,16 +1181,32 @@ public function testSplit(string $originalWKT, string $bladeWKT, string $expecte $splitGeometry = $geometryEngine->split($originalGeometry, $bladeGeometry); - $this->assertSame($expectedWKT, $splitGeometry->asText()); + if (is_array($expectedWKT)) { + self::assertContains($splitGeometry->asText(), $expectedWKT); + } else { + $this->assertSame($expectedWKT, $splitGeometry->asText()); + } } public static function providerSplit() : array { return [ - ['LINESTRING (1 1, 3 3)', 'POINT (2 2)', 'GEOMETRYCOLLECTION (LINESTRING (1 1, 2 2), LINESTRING (2 2, 3 3))'], - ['LINESTRING (1 1, 1 2, 2 2, 2 1, 1 1)', 'LINESTRING (0 0, 3 3)', 'GEOMETRYCOLLECTION (LINESTRING (1 1, 1 2, 2 2), LINESTRING (2 2, 2 1, 1 1))'], - ['POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1))', 'LINESTRING (0 0, 3 3)', 'GEOMETRYCOLLECTION (POLYGON ((1 1, 1 2, 2 2, 1 1)), POLYGON ((2 2, 2 1, 1 1, 2 2)))'], - ['POLYGON ((1 1, 1 2, 3 2, 3 1, 1 1))', 'LINESTRING (1 1, 2 2, 3 1)', 'GEOMETRYCOLLECTION (POLYGON ((1 1, 1 2, 2 2, 1 1)), POLYGON ((1 1, 2 2, 3 1, 1 1)), POLYGON ((3 1, 2 2, 3 2, 3 1)))'], + ['LINESTRING (1 1, 3 3)', 'POINT (2 2)', [ + 'MULTILINESTRING ((1 1, 2 2), (2 2, 3 3))', + 'GEOMETRYCOLLECTION (LINESTRING (1 1, 2 2), LINESTRING (2 2, 3 3))', + ]], + ['LINESTRING (1 1, 1 2, 2 2, 2 1, 1 1)', 'LINESTRING (0 0, 3 3)', [ + 'MULTILINESTRING ((1 1, 1 2, 2 2), (2 2, 2 1, 1 1))', + 'GEOMETRYCOLLECTION (LINESTRING (1 1, 1 2, 2 2), LINESTRING (2 2, 2 1, 1 1))', + ]], + ['POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1))', 'LINESTRING (0 0, 3 3)', [ + 'MULTIPOLYGON (((1 1, 1 2, 2 2, 1 1)), ((2 2, 2 1, 1 1, 2 2)))', + 'GEOMETRYCOLLECTION (POLYGON ((1 1, 1 2, 2 2, 1 1)), POLYGON ((2 2, 2 1, 1 1, 2 2)))', + ]], + ['POLYGON ((1 1, 1 2, 3 2, 3 1, 1 1))', 'LINESTRING (1 1, 2 2, 3 1)', [ + 'MULTIPOLYGON (((1 1, 1 2, 2 2, 1 1)), ((2 2, 3 2, 3 1, 2 2)), ((3 1, 1 1, 2 2, 3 1)))', + 'GEOMETRYCOLLECTION (POLYGON ((1 1, 1 2, 2 2, 1 1)), POLYGON ((1 1, 2 2, 3 1, 1 1)), POLYGON ((3 1, 2 2, 3 2, 3 1)))', + ]], ]; }