diff --git a/.run/Fix formatting.run.xml b/.run/Fix formatting.run.xml new file mode 100644 index 0000000..58c6cbe --- /dev/null +++ b/.run/Fix formatting.run.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.run/Static code analysis.run.xml b/.run/Static code analysis.run.xml new file mode 100644 index 0000000..02486cf --- /dev/null +++ b/.run/Static code analysis.run.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.run/Test.run.xml b/.run/Test.run.xml new file mode 100644 index 0000000..4fc55d4 --- /dev/null +++ b/.run/Test.run.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/API.md b/API.md index 95c0552..c256179 100644 --- a/API.md +++ b/API.md @@ -1,23 +1,28 @@ # API -## Available spatial classes +## Available geometry classes -* `Point(float $latitude, float $longitude)` - [MySQL Point](https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html) -* `MultiPoint(Point[] | Collection)` - [MySQL MultiPoint](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipoint.html) -* `LineString(Point[] | Collection)` - [MySQL LineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-linestring.html) -* `MultiLineString(LineString[] | Collection)` - [MySQL MultiLineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multilinestring.html) -* `Polygon(LineString[] | Collection)` - [MySQL Polygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-polygon.html) -* `MultiPolygon(Polygon[] | Collection)` - [MySQL MultiPolygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipolygon.html) -* `GeometryCollection(Geometry[] | Collection)` - [MySQL GeometryCollection](https://dev.mysql.com/doc/refman/8.0/en/gis-class-geometrycollection.html) +* `Point(float $latitude, float $longitude, int $srid = 0)` - [MySQL Point](https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html) +* `MultiPoint(Point[] | Collection, int $srid = 0)` - [MySQL MultiPoint](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipoint.html) +* `LineString(Point[] | Collection, int $srid = 0)` - [MySQL LineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-linestring.html) +* `MultiLineString(LineString[] | Collection, int $srid = 0)` - [MySQL MultiLineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multilinestring.html) +* `Polygon(LineString[] | Collection, int $srid = 0)` - [MySQL Polygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-polygon.html) +* `MultiPolygon(Polygon[] | Collection, int $srid = 0)` - [MySQL MultiPolygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipolygon.html) +* `GeometryCollection(Geometry[] | Collection, int $srid = 0)` - [MySQL GeometryCollection](https://dev.mysql.com/doc/refman/8.0/en/gis-class-geometrycollection.html) -## Available spatial functions +Geometry classes can be also created by these static methods: -Every geometry class has these functions: +* `fromJson(string $geoJson, int $srid = 0)` - Creates a geometry object from a [GeoJSON](https://en.wikipedia.org/wiki/GeoJSON) string. +* `fromWkt(string $wkt, int $srid = 0)` - Creates a geometry object from a [WKT](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry). +* `fromWkb(string $wkb, int $srid = 0)` - Creates a geometry object from a [WKB](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry#Well-known_binary). + +## Available geometry class methods * `toArray()` - Serializes the geometry object into a GeoJSON associative array. * `toJson()` - Serializes the geometry object into an GeoJSON string. -* `fromJson(string $geoJson)` - Deserializes a geometry object from a GeoJSON string. (static method) * `toFeatureCollectionJson()` - Serializes the geometry object into an GeoJSON's FeatureCollection string. +* `toWkt()` - Serializes the geometry object into a WKT. +* `toWkb()` - Serializes the geometry object into a WKB. * `getCoordinates()` - Returns the coordinates of the geometry object. In addition, `GeometryCollection` also has these functions: @@ -40,7 +45,7 @@ $geometryCollection = new GeometryCollection([ ]); echo $geometryCollection->getGeometries()[1]->latitude; // 180 -// can also access as an array: +// or access as an array: echo $geometryCollection[1]->latitude; // 180 ``` @@ -59,6 +64,7 @@ echo $geometryCollection[1]->latitude; // 180 * [whereCrosses](#whereCrosses) * [whereDisjoint](#whereDisjoint) * [whereEquals](#whereEquals) +* [whereSrid](#whereSrid) ### withDistance @@ -372,3 +378,24 @@ Place::query() ``` +### whereSrid + +Filters records by the [ST_Srid](https://dev.mysql.com/doc/refman/8.0/en/gis-general-property-functions.html#function_st-srid) function. + +| parameter name | type +| ------------------ | -------------------- +| `$column` | `string` +| `$operator` | `string` +| `$value` | `int` + +
Example + +```php +Place::create(['location' => new Point(0, 0, 4326)]); + +Place::query() + ->whereSrid('location', '=', 4326) + ->exists(); // true +``` +
+ diff --git a/README.md b/README.md index 615415e..dd049ab 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,12 @@ use MatanYadaev\EloquentSpatial\Objects\Point; $londonEye = Place::create([ 'name' => 'London Eye', - 'location' => new Point(51.5032973, -0.1195537) + 'location' => new Point(51.5032973, -0.1217424), +]); + +$whiteHouse = Place::create([ + 'name' => 'White House', + 'location' => new Point(38.8976763, -77.0365298, 4326), // with SRID ]); $vaticanCity = Place::create([ @@ -119,8 +124,8 @@ $vaticanCity = Place::create([ new Point(12.457734346389769, 41.905918239316286), new Point(12.45572805404663, 41.90637337450963), new Point(12.455363273620605, 41.90746728266806), - ]) - ]) + ]), + ]), ]) ``` @@ -128,7 +133,9 @@ Retrieve a record with spatial data: ```php echo $londonEye->location->latitude; // 51.5032973 -echo $londonEye->location->longitude; // -0.1195537 +echo $londonEye->location->longitude; // -0.1217424 + +echo $whiteHouse->location->srid; // 4326 echo $vacationCity->area->toJson(); // {"type":"Polygon","coordinates":[[[41.90746728266806,12.455363273620605],[41.906636872349075,12.450309991836548],[41.90197359839437,12.445632219314575],[41.90027269624499,12.447413206100464],[41.90000118654431,12.457906007766724],[41.90281205461268,12.458517551422117],[41.903107507989986,12.457584142684937],[41.905918239316286,12.457734346389769],[41.90637337450963,12.45572805404663],[41.90746728266806,12.455363273620605]]]} ``` @@ -170,13 +177,12 @@ Place::query()->whereDistance(...); // This is IDE-friendly Place::whereDistance(...); // This is not ``` -## Tests +## Development -``` bash -composer phpunit -# or with coverage -composer phpunit-coverage -``` +* Test: `composer pest` +* Test with coverage: `composer pest-coverage` +* Type check: `composer phpstan` +* Format: `composer php-cs-fixer` ## Changelog diff --git a/src/Factory.php b/src/Factory.php index e2dbc15..f946a03 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -25,13 +25,8 @@ class Factory { - public static function parse(string $value, bool $isWkb): Geometry + public static function parse(string $value): Geometry { - if ($isWkb) { - // MySQL adds 4 NULL bytes at the start of the WKB - $value = substr($value, 4); - } - try { /** @var geoPHPGeometry|false $geoPHPGeometry */ $geoPHPGeometry = geoPHP::load($value); @@ -46,14 +41,14 @@ public static function parse(string $value, bool $isWkb): Geometry protected static function createFromGeometry(geoPHPGeometry $geometry): Geometry { + $srid = is_int($geometry->getSRID()) ? $geometry->getSRID() : 0; + if ($geometry instanceof geoPHPPoint) { if ($geometry->coords[0] === null || $geometry->coords[1] === null) { - if (! isset($geoPHPGeometry) || ! $geoPHPGeometry) { - throw new InvalidArgumentException('Invalid spatial value'); - } + throw new InvalidArgumentException('Invalid spatial value'); } - return new Point($geometry->coords[1], $geometry->coords[0]); + return new Point($geometry->coords[1], $geometry->coords[0], $srid); } /** @var geoPHPGeometryCollection $geometry */ @@ -63,25 +58,25 @@ protected static function createFromGeometry(geoPHPGeometry $geometry): Geometry }); if ($geometry::class === geoPHPMultiPoint::class) { - return new MultiPoint($components); + return new MultiPoint($components, $srid); } if ($geometry::class === geoPHPLineString::class) { - return new LineString($components); + return new LineString($components, $srid); } if ($geometry::class === geoPHPPolygon::class) { - return new Polygon($components); + return new Polygon($components, $srid); } if ($geometry::class === geoPHPMultiLineString::class) { - return new MultiLineString($components); + return new MultiLineString($components, $srid); } if ($geometry::class === geoPHPMultiPolygon::class) { - return new MultiPolygon($components); + return new MultiPolygon($components, $srid); } - return new GeometryCollection($components); + return new GeometryCollection($components, $srid); } } diff --git a/src/GeometryCast.php b/src/GeometryCast.php index 6275c5f..9f665cf 100644 --- a/src/GeometryCast.php +++ b/src/GeometryCast.php @@ -27,56 +27,64 @@ public function __construct(string $className) /** * @param Model $model * @param string $key - * @param string|Expression|null $wkbOrWKt + * @param string|Expression|null $value * @param array $attributes * @return Geometry|null */ - public function get($model, string $key, $wkbOrWKt, array $attributes): ?Geometry + public function get($model, string $key, $value, array $attributes): ?Geometry { - if (! $wkbOrWKt) { + if (! $value) { return null; } - if ($wkbOrWKt instanceof Expression) { - $wkt = $this->extractWktFromExpression($wkbOrWKt); + if ($value instanceof Expression) { + $wkt = $this->extractWktFromExpression($value); + $srid = $this->extractSridFromExpression($value); - return $this->className::fromWkt($wkt); + return $this->className::fromWkt($wkt, $srid); } - return $this->className::fromWkb($wkbOrWKt); + return $this->className::fromWkb($value); } /** * @param Model $model * @param string $key - * @param Geometry|mixed|null $geometry + * @param Geometry|mixed|null $value * @param array $attributes * @return Expression|null * * @throws InvalidArgumentException */ - public function set($model, string $key, $geometry, array $attributes): Expression|null + public function set($model, string $key, $value, array $attributes): Expression|null { - if (! $geometry) { + if (! $value) { return null; } - if (! ($geometry instanceof $this->className)) { - $geometryType = is_object($geometry) ? $geometry::class : gettype($geometry); + if (! ($value instanceof $this->className)) { + $geometryType = is_object($value) ? $value::class : gettype($value); throw new InvalidArgumentException( sprintf('Expected %s, %s given.', static::class, $geometryType) ); } - $wkt = $geometry->toWkt(withFunction: true); + $wkt = $value->toWkt(); - return DB::raw("ST_GeomFromText('{$wkt}')"); + return DB::raw("ST_GeomFromText('{$wkt}', {$value->srid})"); } private function extractWktFromExpression(Expression $expression): string { - preg_match('/ST_GeomFromText\(\'(.+)\'\)/', (string) $expression, $match); + preg_match('/ST_GeomFromText\(\'(.+)\', .+\)/', (string) $expression, $match); return $match[1]; } + + private function extractSridFromExpression(Expression $expression): int + { + preg_match('/ST_GeomFromText\(\'.+\', (.+)\)/', (string) $expression, $match); + + return (int) $match[1]; + } } diff --git a/src/Objects/Geometry.php b/src/Objects/Geometry.php index 3f9f167..5434313 100644 --- a/src/Objects/Geometry.php +++ b/src/Objects/Geometry.php @@ -18,6 +18,8 @@ abstract class Geometry implements Castable, Arrayable, Jsonable, JsonSerializable { + public int $srid = 0; + abstract public function toWkt(bool $withFunction = true): string; /** @@ -33,10 +35,14 @@ public function toJson($options = 0): string public function toWkb(): string { - $geoPHPGeometry = geoPHP::load($this->toWkt()); + $geoPHPGeometry = geoPHP::load($this->toJson()); + + $sridInBinary = pack('L', $this->srid); // @phpstan-ignore-next-line - return (new geoPHPWkb)->write($geoPHPGeometry, true); + $wkbWithoutSrid = (new geoPHPWkb)->write($geoPHPGeometry); + + return $sridInBinary.$wkbWithoutSrid; } /** @@ -47,7 +53,14 @@ public function toWkb(): string */ public static function fromWkb(string $wkb): static { - $geometry = Factory::parse($wkb, true); + $srid = substr($wkb, 0, 4); + // @phpstan-ignore-next-line + $srid = unpack('L', $srid)[1]; + + $wkb = substr($wkb, 4); + + $geometry = Factory::parse($wkb); + $geometry->srid = $srid; if (! ($geometry instanceof static)) { throw new InvalidArgumentException( @@ -60,13 +73,15 @@ public static function fromWkb(string $wkb): static /** * @param string $wkt + * @param int $srid * @return static * * @throws InvalidArgumentException */ - public static function fromWkt(string $wkt): static + public static function fromWkt(string $wkt, int $srid = 0): static { - $geometry = Factory::parse($wkt, false); + $geometry = Factory::parse($wkt); + $geometry->srid = $srid; if (! ($geometry instanceof static)) { throw new InvalidArgumentException( @@ -79,13 +94,15 @@ public static function fromWkt(string $wkt): static /** * @param string $geoJson + * @param int $srid * @return static * * @throws InvalidArgumentException */ - public static function fromJson(string $geoJson): static + public static function fromJson(string $geoJson, int $srid = 0): static { - $geometry = Factory::parse($geoJson, false); + $geometry = Factory::parse($geoJson); + $geometry->srid = $srid; if (! ($geometry instanceof static)) { throw new InvalidArgumentException( diff --git a/src/Objects/GeometryCollection.php b/src/Objects/GeometryCollection.php index a15603a..19a77bc 100644 --- a/src/Objects/GeometryCollection.php +++ b/src/Objects/GeometryCollection.php @@ -20,16 +20,18 @@ class GeometryCollection extends Geometry implements ArrayAccess /** * @param Collection|array $geometries + * @param int $srid * * @throws InvalidArgumentException */ - public function __construct(Collection|array $geometries) + public function __construct(Collection|array $geometries, int $srid = 0) { if (is_array($geometries)) { $geometries = collect($geometries); } $this->geometries = $geometries; + $this->srid = $srid; $this->validateGeometriesType(); $this->validateGeometriesCount(); @@ -154,9 +156,9 @@ protected function toCollectionWkt(bool $withFunction): string { return $this->geometries ->map(static function (Geometry $geometry) use ($withFunction): string { - return (string) $geometry->toWkt($withFunction); + return $geometry->toWkt($withFunction); }) - ->join(','); + ->join(', '); } /** diff --git a/src/Objects/MultiLineString.php b/src/Objects/MultiLineString.php index dfc3fba..6d16e49 100644 --- a/src/Objects/MultiLineString.php +++ b/src/Objects/MultiLineString.php @@ -22,13 +22,14 @@ class MultiLineString extends GeometryCollection /** * @param Collection|array $geometries + * @param int $srid * * @throws InvalidArgumentException */ - public function __construct(Collection|array $geometries) + public function __construct(Collection|array $geometries, int $srid = 0) { // @phpstan-ignore-next-line - parent::__construct($geometries); + parent::__construct($geometries, $srid); } public function toWkt(bool $withFunction = true): string diff --git a/src/Objects/MultiPolygon.php b/src/Objects/MultiPolygon.php index a038860..d86fabd 100644 --- a/src/Objects/MultiPolygon.php +++ b/src/Objects/MultiPolygon.php @@ -22,13 +22,14 @@ class MultiPolygon extends GeometryCollection /** * @param Collection|array $geometries + * @param int $srid * * @throws InvalidArgumentException */ - public function __construct(Collection|array $geometries) + public function __construct(Collection|array $geometries, int $srid = 0) { // @phpstan-ignore-next-line - parent::__construct($geometries); + parent::__construct($geometries, $srid); } public function toWkt(bool $withFunction = true): string diff --git a/src/Objects/Point.php b/src/Objects/Point.php index 15a1888..e5d65a1 100644 --- a/src/Objects/Point.php +++ b/src/Objects/Point.php @@ -10,10 +10,11 @@ class Point extends Geometry public float $longitude; - public function __construct(float $latitude, float $longitude) + public function __construct(float $latitude, float $longitude, int $srid = 0) { $this->latitude = $latitude; $this->longitude = $longitude; + $this->srid = $srid; } public function toWkt(bool $withFunction = true): string diff --git a/src/Objects/PointCollection.php b/src/Objects/PointCollection.php index f04a3f9..71a807a 100644 --- a/src/Objects/PointCollection.php +++ b/src/Objects/PointCollection.php @@ -20,12 +20,13 @@ abstract class PointCollection extends GeometryCollection /** * @param Collection|array $geometries + * @param int $srid * * @throws InvalidArgumentException */ - public function __construct(Collection|array $geometries) + public function __construct(Collection|array $geometries, int $srid = 0) { // @phpstan-ignore-next-line - parent::__construct($geometries); + parent::__construct($geometries, $srid); } } diff --git a/src/SpatialBuilder.php b/src/SpatialBuilder.php index 0b316e8..2e1729b 100644 --- a/src/SpatialBuilder.php +++ b/src/SpatialBuilder.php @@ -242,6 +242,24 @@ public function whereEquals(string $column, Geometry|string $geometryOrColumn): return $this; } + public function whereSrid( + string $column, + string $operator, + int|float $value + ): self + { + $this->whereRaw( + sprintf( + 'ST_SRID(%s) %s %s', + "`{$column}`", + $operator, + $value, + ) + ); + + return $this; + } + protected function toExpression(Geometry|string $geometryOrColumn): Expression { if ($geometryOrColumn instanceof Geometry) { diff --git a/tests/GeometryCastTest.php b/tests/GeometryCastTest.php index 43b4d75..b1ebcf4 100644 --- a/tests/GeometryCastTest.php +++ b/tests/GeometryCastTest.php @@ -38,8 +38,8 @@ }); it('gets original geometry field', function (): void { - $point = new Point(180, 0); - $point2 = new Point(0, 0); + $point = new Point(180, 0, 4326); + $point2 = new Point(0, 0, 4326); /** @var TestPlace $testPlace */ $testPlace = TestPlace::factory()->create(['point' => $point]); diff --git a/tests/Objects/GeometryCollectionTest.php b/tests/Objects/GeometryCollectionTest.php index 5ffa774..9ffede0 100644 --- a/tests/Objects/GeometryCollectionTest.php +++ b/tests/Objects/GeometryCollectionTest.php @@ -30,6 +30,26 @@ expect($testPlace->geometry_collection)->toEqual($geometryCollection); }); +it('creates a model record with geometry collection with SRID', function (): void { + $geometryCollection = new GeometryCollection([ + new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]), + new Point(180, 0), + ], 4326); + + /** @var TestPlace $testPlace */ + $testPlace = TestPlace::factory()->create(['geometry_collection' => $geometryCollection]); + + expect($testPlace->geometry_collection->srid)->toBe(4326); +}); + it('creates geometry collection from JSON', function (): void { $geometryCollection = new GeometryCollection([ new Polygon([ @@ -49,6 +69,25 @@ expect($geometryCollectionFromJson)->toEqual($geometryCollection); }); +it('creates geometry collection with SRID from JSON', function (): void { + $geometryCollection = new GeometryCollection([ + new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]), + new Point(180, 0), + ], 4326); + + $geometryCollectionFromJson = GeometryCollection::fromJson('{"type":"GeometryCollection","geometries":[{"type":"Polygon","coordinates":[[[0,180],[1,179],[2,178],[3,177],[0,180]]]},{"type":"Point","coordinates":[0,180]}]}', 4326); + + expect($geometryCollectionFromJson)->toEqual($geometryCollection); +}); + it('creates geometry collection from feature collection JSON', function (): void { $geometryCollection = new GeometryCollection([ new Polygon([ @@ -108,6 +147,82 @@ expect($featureCollectionJson)->toBe($expectedFeatureCollectionJson); }); +it('creates geometry collection from WKT', function (): void { + $geometryCollection = new GeometryCollection([ + new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]), + new Point(180, 0), + ]); + + $geometryCollectionFromWkt = GeometryCollection::fromWkt('GEOMETRYCOLLECTION(POLYGON((0 180, 1 179, 2 178, 3 177, 0 180)),POINT(0 180))'); + + expect($geometryCollectionFromWkt)->toEqual($geometryCollection); +}); + +it('creates geometry collection with SRID from WKT', function (): void { + $geometryCollection = new GeometryCollection([ + new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]), + new Point(180, 0), + ], 4326); + + $geometryCollectionFromWkt = GeometryCollection::fromWkt('GEOMETRYCOLLECTION(POLYGON((0 180, 1 179, 2 178, 3 177, 0 180)),POINT(0 180))', 4326); + + expect($geometryCollectionFromWkt)->toEqual($geometryCollection); +}); + +it('creates geometry collection from WKB', function (): void { + $geometryCollection = new GeometryCollection([ + new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]), + new Point(180, 0), + ]); + + $geometryCollectionFromWkb = GeometryCollection::fromWkb($geometryCollection->toWkb()); + + expect($geometryCollectionFromWkb)->toEqual($geometryCollection); +}); + +it('creates geometry collection with SRID from WKB', function (): void { + $geometryCollection = new GeometryCollection([ + new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]), + new Point(180, 0), + ], 4326); + + $geometryCollectionFromWkb = GeometryCollection::fromWkb($geometryCollection->toWkb()); + + expect($geometryCollectionFromWkb)->toEqual($geometryCollection); +}); + it('does not throw exception when geometry collection has no geometries', function (): void { $geometryCollection = new GeometryCollection([]); diff --git a/tests/Objects/LineStringTest.php b/tests/Objects/LineStringTest.php index 7981c33..a0abe7b 100644 --- a/tests/Objects/LineStringTest.php +++ b/tests/Objects/LineStringTest.php @@ -21,6 +21,18 @@ expect($testPlace->line_string)->toEqual($lineString); }); +it('creates a model record with line string with SRID', function (): void { + $lineString = new LineString([ + new Point(180, 0), + new Point(179, 1), + ], 4326); + + /** @var TestPlace $testPlace */ + $testPlace = TestPlace::factory()->create(['line_string' => $lineString]); + + expect($testPlace->line_string->srid)->toBe(4326); +}); + it('creates line string from JSON', function (): void { $lineString = new LineString([ new Point(180, 0), @@ -32,6 +44,17 @@ expect($lineStringFromJson)->toEqual($lineString); }); +it('creates line string with SRID from JSON', function (): void { + $lineString = new LineString([ + new Point(180, 0), + new Point(179, 1), + ], 4326); + + $lineStringFromJson = LineString::fromJson('{"type":"LineString","coordinates":[[0,180],[1,179]]}', 4326); + + expect($lineStringFromJson)->toEqual($lineString); +}); + it('generates line string JSON', function (): void { $lineString = new LineString([ new Point(180, 0), @@ -56,6 +79,50 @@ expect($featureCollectionJson)->toBe($expectedFeatureCollectionJson); }); +it('creates line string from WKT', function (): void { + $lineString = new LineString([ + new Point(180, 0), + new Point(179, 1), + ]); + + $lineStringFromWkt = LineString::fromWkt('LINESTRING(0 180, 1 179)'); + + expect($lineStringFromWkt)->toEqual($lineString); +}); + +it('creates line string with SRID from WKT', function (): void { + $lineString = new LineString([ + new Point(180, 0), + new Point(179, 1), + ], 4326); + + $lineStringFromWkt = LineString::fromWkt('LINESTRING(0 180, 1 179)', 4326); + + expect($lineStringFromWkt)->toEqual($lineString); +}); + +it('creates line string from WKB', function (): void { + $lineString = new LineString([ + new Point(180, 0), + new Point(179, 1), + ]); + + $lineStringFromWkb = LineString::fromWkb($lineString->toWkb()); + + expect($lineStringFromWkb)->toEqual($lineString); +}); + +it('creates line string with SRID from WKB', function (): void { + $lineString = new LineString([ + new Point(180, 0), + new Point(179, 1), + ], 4326); + + $lineStringFromWkb = LineString::fromWkb($lineString->toWkb()); + + expect($lineStringFromWkb)->toEqual($lineString); +}); + it('throws exception when line string has less than two points', function (): void { expect(function (): void { new LineString([ diff --git a/tests/Objects/MultiLineStringTest.php b/tests/Objects/MultiLineStringTest.php index 6767163..68810d3 100644 --- a/tests/Objects/MultiLineStringTest.php +++ b/tests/Objects/MultiLineStringTest.php @@ -23,6 +23,20 @@ expect($testPlace->multi_line_string)->toEqual($multiLineString); }); +it('creates a model record with multi line string with SRID', function (): void { + $multiLineString = new MultiLineString([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + ]), + ], 4326); + + /** @var TestPlace $testPlace */ + $testPlace = TestPlace::factory()->create(['multi_line_string' => $multiLineString]); + + expect($testPlace->multi_line_string->srid)->toBe(4326); +}); + it('creates multi line string from JSON', function (): void { $multiLineString = new MultiLineString([ new LineString([ @@ -36,6 +50,19 @@ expect($multiLineStringFromJson)->toEqual($multiLineString); }); +it('creates multi line string with SRID from JSON', function (): void { + $multiLineString = new MultiLineString([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + ]), + ], 4326); + + $multiLineStringFromJson = MultiLineString::fromJson('{"type":"MultiLineString","coordinates":[[[0,180],[1,179]]]}', 4326); + + expect($multiLineStringFromJson)->toEqual($multiLineString); +}); + it('generates multi line string JSON', function (): void { $multiLineString = new MultiLineString([ new LineString([ @@ -64,6 +91,58 @@ expect($featureCollectionJson)->toBe($expectedFeatureCollectionJson); }); +it('creates multi line string from WKT', function (): void { + $multiLineString = new MultiLineString([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + ]), + ]); + + $multiLineStringFromWkt = MultiLineString::fromWkt('MULTILINESTRING((0 180, 1 179))', ); + + expect($multiLineStringFromWkt)->toEqual($multiLineString); +}); + +it('creates multi line string with SRID from WKT', function (): void { + $multiLineString = new MultiLineString([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + ]), + ], 4326); + + $multiLineStringFromWkt = MultiLineString::fromWkt('MULTILINESTRING((0 180, 1 179))', 4326); + + expect($multiLineStringFromWkt)->toEqual($multiLineString); +}); + +it('creates multi line string from WKB', function (): void { + $multiLineString = new MultiLineString([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + ]), + ]); + + $multiLineStringFromWkb = MultiLineString::fromWkb($multiLineString->toWkb()); + + expect($multiLineStringFromWkb)->toEqual($multiLineString); +}); + +it('creates multi line string with SRID from WKB', function (): void { + $multiLineString = new MultiLineString([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + ]), + ], 4326); + + $multiLineStringFromWkb = MultiLineString::fromWkb($multiLineString->toWkb()); + + expect($multiLineStringFromWkb)->toEqual($multiLineString); +}); + it('throws exception when multi line string has no line strings', function (): void { expect(function (): void { new MultiLineString([]); diff --git a/tests/Objects/MultiPointTest.php b/tests/Objects/MultiPointTest.php index 7e748da..04dd058 100644 --- a/tests/Objects/MultiPointTest.php +++ b/tests/Objects/MultiPointTest.php @@ -20,6 +20,17 @@ expect($testPlace->multi_point)->toEqual($multiPoint); }); +it('creates a model record with multi point with SRID', function (): void { + $multiPoint = new MultiPoint([ + new Point(180, 0), + ], 4326); + + /** @var TestPlace $testPlace */ + $testPlace = TestPlace::factory()->create(['multi_point' => $multiPoint]); + + expect($testPlace->multi_point->srid)->toBe(4326); +}); + it('creates multi point from JSON', function (): void { $multiPoint = new MultiPoint([ new Point(180, 0), @@ -30,6 +41,16 @@ expect($multiPointFromJson)->toEqual($multiPoint); }); +it('creates multi point with SRID from JSON', function (): void { + $multiPoint = new MultiPoint([ + new Point(180, 0), + ], 4326); + + $multiPointFromJson = MultiPoint::fromJson('{"type":"MultiPoint","coordinates":[[0,180]]}', 4326); + + expect($multiPointFromJson)->toEqual($multiPoint); +}); + it('generates multi point JSON', function (): void { $multiPoint = new MultiPoint([ new Point(180, 0), @@ -52,6 +73,46 @@ expect($multiPointFeatureCollectionJson)->toBe($expectedFeatureCollectionJson); }); +it('creates multi point from WKT', function (): void { + $multiPoint = new MultiPoint([ + new Point(180, 0), + ]); + + $multiPointFromWkt = MultiPoint::fromWkt('MULTIPOINT(0 180)'); + + expect($multiPointFromWkt)->toEqual($multiPoint); +}); + +it('creates multi point with SRID from WKT', function (): void { + $multiPoint = new MultiPoint([ + new Point(180, 0), + ], 4326); + + $multiPointFromWkt = MultiPoint::fromWkt('MULTIPOINT(0 180)', 4326); + + expect($multiPointFromWkt)->toEqual($multiPoint); +}); + +it('creates multi point from WKB', function (): void { + $multiPoint = new MultiPoint([ + new Point(180, 0), + ]); + + $multiPointFromWkb = MultiPoint::fromWkb($multiPoint->toWkb()); + + expect($multiPointFromWkb)->toEqual($multiPoint); +}); + +it('creates multi point with SRID from WKB', function (): void { + $multiPoint = new MultiPoint([ + new Point(180, 0), + ], 4326); + + $multiPointFromWkb = MultiPoint::fromWkb($multiPoint->toWkb()); + + expect($multiPointFromWkb)->toEqual($multiPoint); +}); + it('throws exception when multi point has no points', function (): void { expect(function (): void { new MultiPoint([]); diff --git a/tests/Objects/MultiPolygonTest.php b/tests/Objects/MultiPolygonTest.php index a1d666e..7fc0d4c 100644 --- a/tests/Objects/MultiPolygonTest.php +++ b/tests/Objects/MultiPolygonTest.php @@ -29,6 +29,25 @@ expect($testPlace->multi_polygon)->toEqual($multiPolygon); }); +it('creates a model record with multi polygon with SRID', function (): void { + $multiPolygon = new MultiPolygon([ + new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]), + ], 4326); + + /** @var TestPlace $testPlace */ + $testPlace = TestPlace::factory()->create(['multi_polygon' => $multiPolygon]); + + expect($testPlace->multi_polygon->srid)->toBe(4326); +}); + it('creates multi polygon from JSON', function (): void { $multiPolygon = new MultiPolygon([ new Polygon([ @@ -47,6 +66,24 @@ expect($multiPolygonFromJson)->toEqual($multiPolygon); }); +it('creates multi polygon with SRID from JSON', function (): void { + $multiPolygon = new MultiPolygon([ + new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]), + ], 4326); + + $multiPolygonFromJson = MultiPolygon::fromJson('{"type":"MultiPolygon","coordinates":[[[[0,180],[1,179],[2,178],[3,177],[0,180]]]]}', 4326); + + expect($multiPolygonFromJson)->toEqual($multiPolygon); +}); + it('generates multi polygon JSON', function (): void { $multiPolygon = new MultiPolygon([ new Polygon([ @@ -85,6 +122,78 @@ expect($featureCollectionJson)->toBe($expectedFeatureCollectionJson); }); +it('creates multi polygon from WKT', function (): void { + $multiPolygon = new MultiPolygon([ + new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]), + ]); + + $multiPolygonFromWkt = MultiPolygon::fromWkt('MULTIPOLYGON(((0 180, 1 179, 2 178, 3 177, 0 180)))'); + + expect($multiPolygonFromWkt)->toEqual($multiPolygon); +}); + +it('creates multi polygon with SRID from WKT', function (): void { + $multiPolygon = new MultiPolygon([ + new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]), + ], 4326); + + $multiPolygonFromWkt = MultiPolygon::fromWkt('MULTIPOLYGON(((0 180, 1 179, 2 178, 3 177, 0 180)))', 4326); + + expect($multiPolygonFromWkt)->toEqual($multiPolygon); +}); + +it('creates multi polygon from WKB', function (): void { + $multiPolygon = new MultiPolygon([ + new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]), + ]); + + $multiPolygonFromWkb = MultiPolygon::fromWkb($multiPolygon->toWkb()); + + expect($multiPolygonFromWkb)->toEqual($multiPolygon); +}); + +it('creates multi polygon with SRID from WKB', function (): void { + $multiPolygon = new MultiPolygon([ + new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]), + ], 4326); + + $multiPolygonFromWkb = MultiPolygon::fromWkb($multiPolygon->toWkb()); + + expect($multiPolygonFromWkb)->toEqual($multiPolygon); +}); + it('throws exception when multi polygon has no polygons', function (): void { expect(function (): void { new MultiPolygon([]); diff --git a/tests/Objects/PointTest.php b/tests/Objects/PointTest.php index 4b3c716..d486cd4 100644 --- a/tests/Objects/PointTest.php +++ b/tests/Objects/PointTest.php @@ -16,6 +16,15 @@ expect($testPlace->point)->toEqual($point); }); +it('creates a model record with point with SRID', function (): void { + $point = new Point(180, 0, 4326); + + /** @var TestPlace $testPlace */ + $testPlace = TestPlace::factory()->create(['point' => $point]); + + expect($testPlace->point->srid)->toBe(4326); +}); + it('creates point from JSON', function (): void { $point = new Point(180, 0); @@ -24,6 +33,14 @@ expect($pointFromJson)->toEqual($point); }); +it('creates point with SRID from JSON', function (): void { + $point = new Point(180, 0, 4326); + + $pointFromJson = Point::fromJson('{"type":"Point","coordinates":[0,180]}', 4326); + + expect($pointFromJson)->toEqual($point); +}); + it('generates point json', function (): void { $point = new Point(180, 0); @@ -38,3 +55,35 @@ Point::fromJson('{"type":"Point","coordinates":[]}'); })->toThrow(InvalidArgumentException::class); }); + +it('creates point from WKT', function (): void { + $point = new Point(180, 0); + + $pointFromWkt = Point::fromWkt('POINT(0 180)'); + + expect($pointFromWkt)->toEqual($point); +}); + +it('creates point with SRID from WKT', function (): void { + $point = new Point(180, 0, 4326); + + $pointFromWkt = Point::fromWkt('POINT(0 180)', 4326); + + expect($pointFromWkt)->toEqual($point); +}); + +it('creates point from WKB', function (): void { + $point = new Point(180, 0); + + $pointFromWkb = Point::fromWkb($point->toWkb()); + + expect($pointFromWkb)->toEqual($point); +}); + +it('creates point with SRID from WKB', function (): void { + $point = new Point(180, 0, 4326); + + $pointFromWkb = Point::fromWkb($point->toWkb()); + + expect($pointFromWkb)->toEqual($point); +}); diff --git a/tests/Objects/PolygonTest.php b/tests/Objects/PolygonTest.php index 9b910d6..9923bea 100644 --- a/tests/Objects/PolygonTest.php +++ b/tests/Objects/PolygonTest.php @@ -26,6 +26,23 @@ expect($testPlace->polygon)->toEqual($polygon); }); +it('creates a model record with polygon with SRID', function (): void { + $polygon = new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ], 4326); + + /** @var TestPlace $testPlace */ + $testPlace = TestPlace::factory()->create(['polygon' => $polygon]); + + expect($testPlace->polygon->srid)->toBe(4326); +}); + it('creates polygon from JSON', function (): void { $polygon = new Polygon([ new LineString([ @@ -42,6 +59,22 @@ expect($polygonFromJson)->toEqual($polygon); }); +it('creates polygon with SRID from JSON', function (): void { + $polygon = new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ], 4326); + + $polygonFromJson = Polygon::fromJson('{"type":"Polygon","coordinates":[[[0,180],[1,179],[2,178],[3,177],[0,180]]]}', 4326); + + expect($polygonFromJson)->toEqual($polygon); +}); + it('generates polygon JSON', function (): void { $polygon = new Polygon([ new LineString([ @@ -76,6 +109,70 @@ expect($featureCollectionJson)->toBe($expectedFeatureCollectionJson); }); +it('creates polygon from WKT', function (): void { + $polygon = new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]); + + $polygonFromWkt = Polygon::fromWkt('POLYGON((0 180, 1 179, 2 178, 3 177, 0 180))'); + + expect($polygonFromWkt)->toEqual($polygon); +}); + +it('creates polygon with SRID from WKT', function (): void { + $polygon = new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ], 4326); + + $polygonFromWkt = Polygon::fromWkt('POLYGON((0 180, 1 179, 2 178, 3 177, 0 180))', 4326); + + expect($polygonFromWkt)->toEqual($polygon); +}); + +it('creates polygon from WKB', function (): void { + $polygon = new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ]); + + $polygonFromWkb = Polygon::fromWkb($polygon->toWkb()); + + expect($polygonFromWkb)->toEqual($polygon); +}); + +it('creates polygon with SRID from WKB', function (): void { + $polygon = new Polygon([ + new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]), + ], 4326); + + $polygonFromWkb = Polygon::fromWkb($polygon->toWkb()); + + expect($polygonFromWkb)->toEqual($polygon); +}); + it('throws exception when polygon has no line strings', function (): void { expect(function (): void { new Polygon([]); diff --git a/tests/SpatialBuilderTest.php b/tests/SpatialBuilderTest.php index f5fd43a..214b5f7 100644 --- a/tests/SpatialBuilderTest.php +++ b/tests/SpatialBuilderTest.php @@ -283,3 +283,18 @@ expect($testPlaces)->toHaveCount(1); expect($testPlaces[0]->point)->toEqual($point1); }); + +it('filters by SRID', function (): void { + $point1 = new Point(0, 0, 4326); + $point2 = new Point(50, 50, 0); + TestPlace::factory()->create(['point' => $point1]); + TestPlace::factory()->create(['point' => $point2]); + + /** @var TestPlace[] $testPlaces */ + $testPlaces = TestPlace::query() + ->whereSrid('point', '=', 4326) + ->get(); + + expect($testPlaces)->toHaveCount(1); + expect($testPlaces[0]->point)->toEqual($point1); +});