diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml index b94a72a..2fb01e7 100644 --- a/.github/workflows/php-cs-fixer.yml +++ b/.github/workflows/php-cs-fixer.yml @@ -20,7 +20,7 @@ jobs: coverage: none - name: Install dependencies - run: composer install + run: composer install --prefer-dist --no-interaction - name: Run PHP CS Fixer run: ./vendor/bin/php-cs-fixer fix --allow-risky=yes --diff --dry-run diff --git a/API.md b/API.md new file mode 100644 index 0000000..987339c --- /dev/null +++ b/API.md @@ -0,0 +1,374 @@ +# API + +## Available spatial 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[] | Collectiogit n)` - [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) + +## Available spatial functions + +Every geometry class has these functions: + +* `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. +* `getCoordinates()` - Returns the coordinates of the geometry object. + +In addition, `GeometryCollection` also has these functions: + +* `getGeometries()` - Returns a geometry array. Can be used with `ArrayAccess` as well. + +```php +$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), + ]), +]); + +echo $geometryCollection->getGeometries()[1]->latitude; // 180 +// can also access as an array: +echo $geometryCollection[1]->latitude; // 180 +``` + +## Available spatial scopes + +* [withDistance](#withDistance) +* [whereDistance](#whereDistance) +* [orderByDistance](#orderByDistance) +* [withDistanceSphere](#withDistanceSphere) +* [whereDistanceSphere](#whereDistanceSphere) +* [orderByDistanceSphere](#orderByDistanceSphere) +* [whereWithin](#whereWithin) +* [whereContains](#whereContains) +* [whereTouches](#whereTouches) +* [whereIntersects](#whereIntersects) +* [whereCrosses](#whereCrosses) +* [whereDisjoint](#whereDisjoint) +* [whereEquals](#whereEquals) + +### withDistance + +Retrieves the distance between 2 geometry objects. Uses [ST_Distance](https://dev.mysql.com/doc/refman/8.0/en/spatial-relation-functions-object-shapes.html#function_st-distance). + +| parameter name | type | default | +| ------------------ | -------------------- | ------- | +| `$column` | `string` | +| `$geometryOrColumn` | `Geometry \| string` | +| `$alias` | `string` | `'distance'` + +
Example + +```php +Place::create(['location' => new Point(0, 0)]); + +$placeWithDistance = Place::query() + ->withDistance('location', new Point(1, 1)) + ->first(); + +echo $placeWithDistance->distance; // 1.4142135623731 + +// when using alias: +$placeWithDistance = Place::query() + ->withDistance('location', new Point(1, 1), 'distance_in_meters') + ->first(); + +echo $placeWithDistance->distance_in_meters; // 1.4142135623731 +``` +
+ +### whereDistance + +Filters records by distance. Uses [ST_Distance](https://dev.mysql.com/doc/refman/8.0/en/spatial-relation-functions-object-shapes.html#function_st-distance). + +| parameter name | type +| ------------------ | -------------------- +| `$column` | `string` +| `$geometryOrColumn` | `Geometry \| string` +| `$operator` | `string` +| `$value` | `int \| float` + +
Example + +```php +Place::create(['location' => new Point(0, 0)]); +Place::create(['location' => new Point(100, 100)]); + +$placesCountWithinDistance = Place::query() + ->whereDistance('location', new Point(1, 1), '<', 1.5) + ->count(); + +echo $placesCountWithinDistance; // 1 +``` +
+ +### orderByDistance + +Orders records by distance. Uses [ST_Distance](https://dev.mysql.com/doc/refman/8.0/en/spatial-relation-functions-object-shapes.html#function_st-distance). + +| parameter name | type | default | +| ------------------ | -------------------- | ------- | +| `$column` | `string` | +| `$geometryOrColumn` | `Geometry \| string` | +| `$direction` | `string` | `'asc'` + +
Example + +```php +Place::create([ + 'name' => 'first', + 'location' => new Point(0, 0), +]); +Place::create([ + 'name' => 'second', + 'location' => new Point(100, 100), +]); + +$places = Place::query() + ->orderByDistance('location', new Point(1, 1), 'desc') + ->get(); + +echo $places[0]->name; // second +echo $places[1]->name; // first +``` +
+ +### withDistanceSphere + +Retrieves the spherical distance between 2 geometry objects. Uses [ST_Distance_Sphere](https://dev.mysql.com/doc/refman/8.0/en/spatial-convenience-functions.html#function_st-distance-sphere). + +| parameter name | type | default | +| ------------------ | -------------------- | ------- | +| `$column` | `string` | +| `$geometryOrColumn` | `Geometry \| string` | +| `$alias` | `string` | `'distance'` + +
Example + +```php +Place::create(['location' => new Point(0, 0)]); + +$placeWithDistance = Place::query() + ->withDistanceSphere('location', new Point(1, 1)) + ->first(); + +echo $placeWithDistance->distance; // 157249.0357231545 + +// when using alias: +$placeWithDistance = Place::query() + ->withDistanceSphere('location', new Point(1, 1), 'distance_in_meters') + ->first(); + +echo $placeWithDistance->distance_in_meters; // 157249.0357231545 +``` +
+ +### whereDistanceSphere + +Filters records by spherical distance. Uses [ST_Distance_Sphere](https://dev.mysql.com/doc/refman/8.0/en/spatial-convenience-functions.html#function_st-distance-sphere). + +| parameter name | type +| ------------------ | -------------------- +| `$column` | `string` +| `$geometryOrColumn` | `Geometry \| string` +| `$operator` | `string` +| `$value` | `int \| float` + +
Example + +```php +Place::create(['location' => new Point(0, 0)]); +Place::create(['location' => new Point(100, 100)]); + +$placesCountWithinDistance = Place::query() + ->whereDistanceSphere('location', new Point(1, 1), '<', 160000) + ->count(); + +echo $placesCountWithinDistance; // 1 +``` +
+ +### orderByDistanceSphere + +Orders records by spherical distance. Uses [ST_Distance_Sphere](https://dev.mysql.com/doc/refman/8.0/en/spatial-convenience-functions.html#function_st-distance-sphere). + +| parameter name | type | default | +| ------------------ | -------------------- | ------- | +| `$column` | `string` | +| `$geometryOrColumn` | `Geometry \| string` | +| `$direction` | `string` | `'asc'` + +
Example + +```php +Place::create([ + 'name' => 'first', + 'location' => new Point(0, 0), +]); +Place::create([ + 'name' => 'second', + 'location' => new Point(100, 100), +]); + +$places = Place::query() + ->orderByDistanceSphere('location', new Point(1, 1), 'desc') + ->get(); + +echo $places[0]->name; // second +echo $places[1]->name; // first +``` +
+ +### whereWithin + +Filters records by the [ST_Within](https://dev.mysql.com/doc/refman/8.0/en/spatial-relation-functions-object-shapes.html#function_st-within) function. + +| parameter name | type +| ------------------ | -------------------- +| `$column` | `string` +| `$geometryOrColumn` | `Geometry \| string` + +
Example + +```php +Place::create(['location' => new Point(0, 0)]); + +Place::query() + ->whereWithin('location', Polygon::fromJson('{"type":"Polygon","coordinates":[[[-1,-1],[1,-1],[1,1],[-1,1],[-1,-1]]]}')) + ->exists(); // true +``` +
+ +### whereContains + +Filters records by the [ST_Contains](https://dev.mysql.com/doc/refman/8.0/en/spatial-relation-functions-object-shapes.html#function_st-contains) function. + +| parameter name | type +| ------------------ | -------------------- +| `$column` | `string` +| `$geometryOrColumn` | `Geometry \| string` + +
Example + +```php +Place::create(['area' => Polygon::fromJson('{"type":"Polygon","coordinates":[[[-1,-1],[1,-1],[1,1],[-1,1],[-1,-1]]]}'),]); + +Place::query() + ->whereContains('area', new Point(0, 0)) + ->exists(); // true +``` +
+ +### whereTouches + +Filters records by the [ST_Touches](https://dev.mysql.com/doc/refman/8.0/en/spatial-relation-functions-object-shapes.html#function_st-touches) function. + +| parameter name | type +| ------------------ | -------------------- +| `$column` | `string` +| `$geometryOrColumn` | `Geometry \| string` + +
Example + +```php +Place::create(['location' => new Point(0, 0)]); + +Place::query() + ->whereTouches('location', Polygon::fromJson('{"type":"Polygon","coordinates":[[[-1,-1],[0,-1],[0,0],[-1,0],[-1,-1]]]}')) + ->exists(); // true +``` +
+ +### whereIntersects + +Filters records by the [ST_Intersects](https://dev.mysql.com/doc/refman/8.0/en/spatial-relation-functions-object-shapes.html#function_st-intersects) function. + +| parameter name | type +| ------------------ | -------------------- +| `$column` | `string` +| `$geometryOrColumn` | `Geometry \| string` + +
Example + +```php +Place::create(['location' => new Point(0, 0)]); + +Place::query() + ->whereIntersects('location', Polygon::fromJson('{"type":"Polygon","coordinates":[[[-1,-1],[1,-1],[1,1],[-1,1],[-1,-1]]]}')) + ->exists(); // true +``` +
+ +### whereCrosses + +Filters records by the [ST_Crosses](https://dev.mysql.com/doc/refman/8.0/en/spatial-relation-functions-object-shapes.html#function_st-crosses) function. + +| parameter name | type +| ------------------ | -------------------- +| `$column` | `string` +| `$geometryOrColumn` | `Geometry \| string` + +
Example + +```php +Place::create(['line_string' => LineString::fromJson('{"type":"LineString","coordinates":[[0,0],[2,0]]}')]); + +Place::query() + ->whereCrosses('line_string', Polygon::fromJson('{"type":"Polygon","coordinates":[[[-1,-1],[1,-1],[1,1],[-1,1],[-1,-1]]]}')) + ->exists(); // true +``` +
+ +### whereDisjoint + +Filters records by the [ST_Disjoint](https://dev.mysql.com/doc/refman/8.0/en/spatial-relation-functions-object-shapes.html#function_st-disjoint) function. + +| parameter name | type +| ------------------ | -------------------- +| `$column` | `string` +| `$geometryOrColumn` | `Geometry \| string` + +
Example + +```php +Place::create(['location' => new Point(0, 0)]); + +Place::query() + ->whereDisjoint('location', Polygon::fromJson('{"type":"Polygon","coordinates":[[[-1,-1],[-0.5,-1],[-0.5,-0.5],[-1,-0.5],[-1,-1]]]}')) + ->exists(); // true +``` +
+ +### whereEquals + +Filters records by the [ST_Equal](https://dev.mysql.com/doc/refman/8.0/en/spatial-relation-functions-object-shapes.html#function_st-equals) function. + +| parameter name | type +| ------------------ | -------------------- +| `$column` | `string` +| `$geometryOrColumn` | `Geometry \| string` + +
Example + +```php +Place::create(['location' => new Point(0, 0)]); + +Place::query() + ->whereEquals('location', new Point(0, 0)) + ->exists(); // true +``` +
+ diff --git a/README.md b/README.md index e69de29..f35b538 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,155 @@ +# Laravel Eloquent Spatial + +[![Latest Version on Packagist](https://img.shields.io/packagist/v/matanyadaev/laravel-eloquent-spatial.svg?style=flat-square)](https://packagist.org/packages/matanyadaev/laravel-eloquent-spatial) +![GitHub Workflow Status](https://img.shields.io/github/workflow/status/matanyadaev/laravel-eloquent-spatial/Tests?label=tests) +![Lint](https://github.com/matanyadaev/laravel-eloquent-spatial/workflows/Lint/badge.svg) +[![Total Downloads](https://img.shields.io/packagist/dt/matanyadaev/laravel-eloquent-spatial.svg?style=flat-square)](https://packagist.org/packages/matanyadaev/laravel-eloquent-spatial) + +Laravel package to work with spatial data types and functions. + +This package supports MySQL 5.7 & 8.0. The package works on PHP 8 & Laravel 8 only. + +## Installation + +You can install the package via composer: + +```bash +composer require matanyadaev/laravel-eloquent-spatial +``` + +## Quickstart +Generate a new model with a migration file: +```bash +php artisan make:model --migration +``` + +Add some spatial columns to the migration file: + +```php +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; + +class CreatePlacesTable extends Migration +{ + public function up(): void + { + Schema::create('places', static function (Blueprint $table) { + $table->id(); + $table->string('name')->unique(); + $table->point('location')->nullable(); + $table->polygon('area')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('places'); + } +} +``` + +Run the migration: + +```bash +php artisan migrate +``` + +Fill the `$fillable` and `$casts` arrays and add custom eloquent builder to your new model: + +```php +namespace App\Models; + +use Illuminate\Database\Eloquent\Model; +use MatanYadaev\EloquentSpatial\SpatialBuilder; +use MatanYadaev\EloquentSpatial\Objects\Point; +use MatanYadaev\EloquentSpatial\Objects\Polygon; + +/** + * @property Point $location + * @property Polygon $area + * @method static SpatialBuilder query() + */ +class Place extends Model +{ + protected $fillable = [ + 'name' + 'location', + 'area', + ]; + + protected $spatialFields = [ + 'location' => Point::class, + 'area' => Polygon::class, + ]; + + public function newEloquentBuilder($query): SpatialBuilder + { + return new SpatialBuilder($query); + } +} +``` + +Access spatial data: + +```php +use App\Models\Place; +use MatanYadaev\EloquentSpatial\Objects\Polygon; +use MatanYadaev\EloquentSpatial\Objects\LineString; +use MatanYadaev\EloquentSpatial\Objects\Point; + +$londonEye = Place::create([ + 'name' => 'London Eye', + 'location' => new Point(51.5032973, -0.1195537) +]); + +$vaticanCity = Place::create([ + 'name' => 'Vatican City', + 'area' => new Polygon([ + new LineString([ + new Point(12.455363273620605, 41.90746728266806), + new Point(12.450309991836548, 41.906636872349075), + new Point(12.445632219314575, 41.90197359839437), + new Point(12.447413206100464, 41.90027269624499), + new Point(12.457906007766724, 41.90000118654431), + new Point(12.458517551422117, 41.90281205461268), + new Point(12.457584142684937, 41.903107507989986), + new Point(12.457734346389769, 41.905918239316286), + new Point(12.45572805404663, 41.90637337450963), + new Point(12.455363273620605, 41.90746728266806), + ]) + ]) +]) +``` + +Retrieve a record with spatial data: + +```php +echo $londonEye->location->latitude; // 51.5032973 +echo $londonEye->location->longitude; // -0.1195537 + +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]]]} +``` + +## API + +Please see [API](API.md) for more informative API documentation. + +## Tip for better IDE support + + +## Tests + +``` bash +composer phpunit +# or with coverage +composer phpunit-coverage +``` + +## Changelog + +Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. + +## License + +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. diff --git a/composer.json b/composer.json index 6447b95..349ee2c 100644 --- a/composer.json +++ b/composer.json @@ -1,18 +1,12 @@ { "name": "matanyadaev/laravel-eloquent-spatial", - "description": "Laravel Eloquent spatial extension", - "keywords": [ - "matanyadaev", - "laravel-eloquent-spatial" - ], + "description": "Spatial library for Laravel", "homepage": "https://github.com/matanyadaev/laravel-eloquent-spatial", "license": "MIT", "authors": [ { "name": "Matan Yadaev", - "email": "matan.yed@gmail.com", - "homepage": "https://spatie.be", - "role": "Developer" + "email": "matan.yed@gmail.com" } ], "require": { @@ -25,7 +19,7 @@ "friendsofphp/php-cs-fixer": "^2.17", "matt-allan/laravel-code-style": "^0.6.0", "nunomaduro/larastan": "^0.7.0", - "nunomaduro/phpinsights": "dev-feature/php8-and-github-action", + "nunomaduro/phpinsights": "dev-master", "orchestra/testbench": "^6.0", "phpunit/phpunit": "^9.3" }, @@ -62,15 +56,5 @@ } }, "minimum-stability": "dev", - "prefer-stable": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/spatie" - }, - { - "type": "other", - "url": "https://spatie.be/open-source/support-us" - } - ] + "prefer-stable": true } diff --git a/phpinsights.php b/phpinsights.php index 3d66d88..2b1cc6c 100644 --- a/phpinsights.php +++ b/phpinsights.php @@ -96,6 +96,7 @@ InlineDocCommentDeclarationSniff::class => [ 'exclude' => [ 'src/Factory.php', + 'src/Objects/Geometry.php', ], ], UnusedParameterSniff::class => [ @@ -108,6 +109,7 @@ 'exclude' => [ 'src/SpatialBuilder.php', 'src/Objects/Geometry.php', + 'src/Objects/GeometryCollection.php', ], ], ClassTraitAndInterfaceLengthSniff::class => [ diff --git a/phpstan.neon b/phpstan.neon index 7d3c23e..9dd2958 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,7 +6,6 @@ parameters: - tests level: max ignoreErrors: - - '#Method MatanYadaev\\EloquentSpatial\\Objects\\.+::getGeometries\(\) should return array<.+> but returns array\.#' - '#Method MatanYadaev\\EloquentSpatial\\Objects\\(Geometry|GeometryCollection)::(toJson|toFeatureCollectionJson)\(\) should return string but returns string\|false\.#' excludePaths: - ./src/Factory.php diff --git a/src/Factory.php b/src/Factory.php index 55ee68d..c99f1ed 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -25,11 +25,9 @@ class Factory { - public static function parse(string $value): Geometry + public static function parse(string $value, bool $isWkb): Geometry { - $isJson = (bool) is_object(json_decode($value)); - - if (! $isJson) { + if ($isWkb) { // MySQL adds 4 NULL bytes at the start of the WKB $value = substr($value, 4); } diff --git a/src/Objects/Geometry.php b/src/Objects/Geometry.php index 093863c..280b4cd 100644 --- a/src/Objects/Geometry.php +++ b/src/Objects/Geometry.php @@ -32,7 +32,7 @@ public function toJson($options = 0): string */ public static function fromWkb(string $wkb): static { - $geometry = Factory::parse($wkb); + $geometry = Factory::parse($wkb, true); if (! ($geometry instanceof static)) { throw new InvalidArgumentException( @@ -52,7 +52,7 @@ public static function fromWkb(string $wkb): static */ public static function fromJson(string $geoJson): static { - $geometry = Factory::parse($geoJson); + $geometry = Factory::parse($geoJson, false); if (! ($geometry instanceof static)) { throw new InvalidArgumentException( @@ -82,18 +82,27 @@ public function toArray(): array ]; } - /** - * @return array{ - * type: string, properties: array, geometry: array{type: string, coordinates: array} - * } - */ - public function toFeature(): array + public function toFeatureCollectionJson(): string { - return [ - 'type' => 'Feature', - 'properties' => [], - 'geometry' => $this->toArray(), - ]; + if (static::class === GeometryCollection::class) { + /** @var GeometryCollection $this */ + $geometries = $this->geometries; + } else { + $geometries = collect([$this]); + } + + $features = $geometries->map(static function (self $geometry): array { + return [ + 'type' => 'Feature', + 'properties' => [], + 'geometry' => $geometry->toArray(), + ]; + }); + + return json_encode([ + 'type' => 'FeatureCollection', + 'features' => $features, + ]); } /** diff --git a/src/Objects/GeometryCollection.php b/src/Objects/GeometryCollection.php index d9452c0..ace4238 100644 --- a/src/Objects/GeometryCollection.php +++ b/src/Objects/GeometryCollection.php @@ -4,13 +4,14 @@ namespace MatanYadaev\EloquentSpatial\Objects; +use ArrayAccess; use Illuminate\Database\Query\Expression; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use InvalidArgumentException; -class GeometryCollection extends Geometry +class GeometryCollection extends Geometry implements ArrayAccess { /** @var Collection */ protected Collection $geometries; @@ -71,28 +72,50 @@ public function toArray(): array } /** - * @return array + * @return Collection */ - public function getGeometries(): array + public function getGeometries(): Collection { - return $this->geometries->all(); + return $this->geometries->collect(); } - public function toFeatureCollectionJson(): string + /** + * @param mixed $offset + * + * @return bool + */ + public function offsetExists($offset): bool { - /** @var Collection $geometries */ - $geometries = static::class === self::class - ? $this->geometries - : collect([$this]); + return isset($this->geometries[$offset]); + } - $features = $geometries->map(static function (Geometry $geometry): array { - return $geometry->toFeature(); - }); + /** + * @param mixed $offset + * + * @return Geometry + */ + public function offsetGet($offset): Geometry + { + return $this->geometries[$offset]; + } - return json_encode([ - 'type' => 'FeatureCollection', - 'features' => $features, - ]); + /** + * @param mixed $offset + * @param Geometry $geometry + */ + public function offsetSet($offset, $geometry): void + { + $this->geometries[$offset] = $geometry; + $this->validateGeometriesType(); + } + + /** + * @param mixed $offset + */ + public function offsetUnset($offset): void + { + $this->geometries->splice($offset, 1); + $this->validateGeometriesCount(); } /** diff --git a/src/Objects/LineString.php b/src/Objects/LineString.php index fc53b4a..a28c7e7 100644 --- a/src/Objects/LineString.php +++ b/src/Objects/LineString.php @@ -15,12 +15,4 @@ public function toWkt(): Expression { return DB::raw("LINESTRING({$this->toCollectionWkt()})"); } - - /** - * @return array - */ - public function getGeometries(): array - { - return parent::getGeometries(); - } } diff --git a/src/Objects/MultiLineString.php b/src/Objects/MultiLineString.php index cde7984..408e1fc 100644 --- a/src/Objects/MultiLineString.php +++ b/src/Objects/MultiLineString.php @@ -8,6 +8,10 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; +/** + * @method array getGeometries() + * @method LineString offsetGet(mixed $offset) + */ class MultiLineString extends GeometryCollection { /** @var Collection */ @@ -29,12 +33,4 @@ public function toWkt(): Expression { return DB::raw("MULTILINESTRING({$this->toCollectionWkt()})"); } - - /** - * @return array - */ - public function getGeometries(): array - { - return parent::getGeometries(); - } } diff --git a/src/Objects/MultiPolygon.php b/src/Objects/MultiPolygon.php index 87dba38..3ee1a3d 100644 --- a/src/Objects/MultiPolygon.php +++ b/src/Objects/MultiPolygon.php @@ -9,6 +9,10 @@ use Illuminate\Support\Facades\DB; use InvalidArgumentException; +/** + * @method array getGeometries() + * @method Polygon offsetGet(mixed $offset) + */ class MultiPolygon extends GeometryCollection { /** @var Collection */ @@ -32,12 +36,4 @@ public function toWkt(): Expression { return DB::raw("MULTIPOLYGON({$this->toCollectionWkt()})"); } - - /** - * @return array - */ - public function getGeometries(): array - { - return parent::getGeometries(); - } } diff --git a/src/Objects/PointCollection.php b/src/Objects/PointCollection.php index bcc888a..17dc419 100644 --- a/src/Objects/PointCollection.php +++ b/src/Objects/PointCollection.php @@ -7,6 +7,10 @@ use Illuminate\Support\Collection; use InvalidArgumentException; +/** + * @method array getGeometries() + * @method Point offsetGet(mixed $offset) + */ abstract class PointCollection extends GeometryCollection { /** @var Collection */ @@ -23,12 +27,4 @@ public function __construct(Collection | array $geometries) { parent::__construct($geometries); } - - /** - * @return array - */ - public function getGeometries(): array - { - return parent::getGeometries(); - } } diff --git a/src/SpatialBuilder.php b/src/SpatialBuilder.php index b717519..7d96aea 100644 --- a/src/SpatialBuilder.php +++ b/src/SpatialBuilder.php @@ -35,7 +35,7 @@ public function whereDistance( string $column, Geometry | string $geometryOrColumn, string $operator, - int | float $distance + int | float $value ): self { $geometryOrColumn = $this->toExpression($geometryOrColumn); @@ -45,7 +45,7 @@ public function whereDistance( "`{$column}`", $geometryOrColumn, $operator, - $distance, + $value, ) ); } @@ -92,8 +92,8 @@ public function whereDistanceSphere( string $column, Geometry | string $geometryOrColumn, string $operator, - int | float $distance): self - { + int | float $value + ): self { $geometryOrColumn = $this->toExpression($geometryOrColumn); return $this->whereRaw( @@ -102,7 +102,7 @@ public function whereDistanceSphere( "`{$column}`", $geometryOrColumn, $operator, - $distance + $value ) ); } diff --git a/tests/Objects/GeometryCollectionTest.php b/tests/Objects/GeometryCollectionTest.php index 8682f83..99a8f6d 100644 --- a/tests/Objects/GeometryCollectionTest.php +++ b/tests/Objects/GeometryCollectionTest.php @@ -36,25 +36,23 @@ public function it_stores_geometry_collection(): void $this->assertTrue($testPlace->geometry_collection instanceof GeometryCollection); - $geometries = $testPlace->geometry_collection->getGeometries(); /** @var Polygon $polygon */ - $polygon = $geometries[0]; - $lineStrings = $polygon->getGeometries(); - $points = $lineStrings[0]->getGeometries(); - - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); - $this->assertEquals(179, $points[1]->latitude); - $this->assertEquals(1, $points[1]->longitude); - $this->assertEquals(178, $points[2]->latitude); - $this->assertEquals(2, $points[2]->longitude); - $this->assertEquals(177, $points[3]->latitude); - $this->assertEquals(3, $points[3]->longitude); - $this->assertEquals(180, $points[4]->latitude); - $this->assertEquals(0, $points[4]->longitude); + $polygon = $testPlace->geometry_collection[0]; + $lineString = $polygon[0]; + + $this->assertEquals(180, $lineString[0]->latitude); + $this->assertEquals(0, $lineString[0]->longitude); + $this->assertEquals(179, $lineString[1]->latitude); + $this->assertEquals(1, $lineString[1]->longitude); + $this->assertEquals(178, $lineString[2]->latitude); + $this->assertEquals(2, $lineString[2]->longitude); + $this->assertEquals(177, $lineString[3]->latitude); + $this->assertEquals(3, $lineString[3]->longitude); + $this->assertEquals(180, $lineString[4]->latitude); + $this->assertEquals(0, $lineString[4]->longitude); /** @var Point $point */ - $point = $geometries[1]; + $point = $testPlace->geometry_collection[1]; $this->assertEquals(180, $point->latitude); $this->assertEquals(0, $point->longitude); @@ -72,25 +70,23 @@ public function it_stores_geometry_collection_from_geo_json(): void $this->assertTrue($testPlace->geometry_collection instanceof GeometryCollection); - $geometries = $testPlace->geometry_collection->getGeometries(); /** @var Polygon $polygon */ - $polygon = $geometries[0]; - $lineStrings = $polygon->getGeometries(); - $points = $lineStrings[0]->getGeometries(); - - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); - $this->assertEquals(179, $points[1]->latitude); - $this->assertEquals(1, $points[1]->longitude); - $this->assertEquals(178, $points[2]->latitude); - $this->assertEquals(2, $points[2]->longitude); - $this->assertEquals(177, $points[3]->latitude); - $this->assertEquals(3, $points[3]->longitude); - $this->assertEquals(180, $points[4]->latitude); - $this->assertEquals(0, $points[4]->longitude); + $polygon = $testPlace->geometry_collection[0]; + $lineString = $polygon[0]; + + $this->assertEquals(180, $lineString[0]->latitude); + $this->assertEquals(0, $lineString[0]->longitude); + $this->assertEquals(179, $lineString[1]->latitude); + $this->assertEquals(1, $lineString[1]->longitude); + $this->assertEquals(178, $lineString[2]->latitude); + $this->assertEquals(2, $lineString[2]->longitude); + $this->assertEquals(177, $lineString[3]->latitude); + $this->assertEquals(3, $lineString[3]->longitude); + $this->assertEquals(180, $lineString[4]->latitude); + $this->assertEquals(0, $lineString[4]->longitude); /** @var Point $point */ - $point = $geometries[1]; + $point = $testPlace->geometry_collection[1]; $this->assertEquals(180, $point->latitude); $this->assertEquals(0, $point->longitude); @@ -108,25 +104,23 @@ public function it_stores_geometry_collection_from_feature_collection_geo_json() $this->assertTrue($testPlace->geometry_collection instanceof GeometryCollection); - $geometries = $testPlace->geometry_collection->getGeometries(); /** @var Polygon $polygon */ - $polygon = $geometries[0]; - $lineStrings = $polygon->getGeometries(); - $points = $lineStrings[0]->getGeometries(); - - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); - $this->assertEquals(179, $points[1]->latitude); - $this->assertEquals(1, $points[1]->longitude); - $this->assertEquals(178, $points[2]->latitude); - $this->assertEquals(2, $points[2]->longitude); - $this->assertEquals(177, $points[3]->latitude); - $this->assertEquals(3, $points[3]->longitude); - $this->assertEquals(180, $points[4]->latitude); - $this->assertEquals(0, $points[4]->longitude); + $polygon = $testPlace->geometry_collection[0]; + $lineString = $polygon[0]; + + $this->assertEquals(180, $lineString[0]->latitude); + $this->assertEquals(0, $lineString[0]->longitude); + $this->assertEquals(179, $lineString[1]->latitude); + $this->assertEquals(1, $lineString[1]->longitude); + $this->assertEquals(178, $lineString[2]->latitude); + $this->assertEquals(2, $lineString[2]->longitude); + $this->assertEquals(177, $lineString[3]->latitude); + $this->assertEquals(3, $lineString[3]->longitude); + $this->assertEquals(180, $lineString[4]->latitude); + $this->assertEquals(0, $lineString[4]->longitude); /** @var Point $point */ - $point = $geometries[1]; + $point = $testPlace->geometry_collection[1]; $this->assertEquals(180, $point->latitude); $this->assertEquals(0, $point->longitude); @@ -196,4 +190,114 @@ public function it_throws_exception_when_geometry_collection_has_composed_by_inv 'invalid-value', ]); } + + /** @test */ + public function it_unsets_geometry_collection_item(): 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), + ]); + + unset($geometryCollection[0]); + + $this->assertInstanceOf(Point::class, $geometryCollection[0]); + $this->assertCount(1, $geometryCollection->getGeometries()); + } + + /** @test */ + public function it_unsets_geometry_collection_item_below_minimum(): void + { + $this->expectException(InvalidArgumentException::class); + + $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), + ]), + ]); + + unset($polygon[0]); + } + + /** @test */ + public function it_checks_if_geometry_collection_item_is_exists(): 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), + ]); + + $this->assertTrue(isset($geometryCollection[0])); + $this->assertTrue(isset($geometryCollection[1])); + $this->assertFalse(isset($geometryCollection[2])); + } + + /** @test */ + public function it_sets_valid_item_to_geometry_collection(): void + { + /** @var TestPlace $testPlace */ + $testPlace = TestPlace::factory()->create([ + '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), + ]), + ]), + ]); + + $testPlace->polygon[1] = new LineString([ + new Point(180, 0), + new Point(179, 1), + new Point(178, 2), + new Point(177, 3), + new Point(180, 0), + ]); + + $testPlace->save(); + + $testPlace->refresh(); + + $this->assertInstanceOf(LineString::class, $testPlace->polygon[1]); + } + + /** @test */ + public function it_sets_invalid_item_to_geometry_collection(): void + { + $this->expectException(InvalidArgumentException::class); + + $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), + ]), + ]); + + $polygon[1] = new Point(180, 0); + } } diff --git a/tests/Objects/LineStringTest.php b/tests/Objects/LineStringTest.php index 63fe0f9..64b8a13 100644 --- a/tests/Objects/LineStringTest.php +++ b/tests/Objects/LineStringTest.php @@ -27,12 +27,10 @@ public function it_stores_line_string(): void $this->assertTrue($testPlace->line_string instanceof LineString); - $points = $testPlace->line_string->getGeometries(); - - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); - $this->assertEquals(179, $points[1]->latitude); - $this->assertEquals(1, $points[1]->longitude); + $this->assertEquals(180, $testPlace->line_string[0]->latitude); + $this->assertEquals(0, $testPlace->line_string[0]->longitude); + $this->assertEquals(179, $testPlace->line_string[1]->latitude); + $this->assertEquals(1, $testPlace->line_string[1]->longitude); $this->assertDatabaseCount($testPlace->getTable(), 1); } @@ -47,12 +45,10 @@ public function it_stores_line_string_from_json(): void $this->assertTrue($testPlace->line_string instanceof LineString); - $points = $testPlace->line_string->getGeometries(); - - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); - $this->assertEquals(179, $points[1]->latitude); - $this->assertEquals(1, $points[1]->longitude); + $this->assertEquals(180, $testPlace->line_string[0]->latitude); + $this->assertEquals(0, $testPlace->line_string[0]->longitude); + $this->assertEquals(179, $testPlace->line_string[1]->latitude); + $this->assertEquals(1, $testPlace->line_string[1]->longitude); $this->assertDatabaseCount($testPlace->getTable(), 1); } diff --git a/tests/Objects/MultiLineStringTest.php b/tests/Objects/MultiLineStringTest.php index d2fbede..6300269 100644 --- a/tests/Objects/MultiLineStringTest.php +++ b/tests/Objects/MultiLineStringTest.php @@ -29,13 +29,12 @@ public function it_stores_multi_line_string(): void $this->assertTrue($testPlace->multi_line_string instanceof MultiLineString); - $lineStrings = $testPlace->multi_line_string->getGeometries(); - $points = $lineStrings[0]->getGeometries(); + $lineString = $testPlace->multi_line_string[0]; - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); - $this->assertEquals(179, $points[1]->latitude); - $this->assertEquals(1, $points[1]->longitude); + $this->assertEquals(180, $lineString[0]->latitude); + $this->assertEquals(0, $lineString[0]->longitude); + $this->assertEquals(179, $lineString[1]->latitude); + $this->assertEquals(1, $lineString[1]->longitude); $this->assertDatabaseCount($testPlace->getTable(), 1); } @@ -50,13 +49,12 @@ public function it_stores_multi_line_string_from_geo_json(): void $this->assertTrue($testPlace->multi_line_string instanceof MultiLineString); - $lineStrings = $testPlace->multi_line_string->getGeometries(); - $points = $lineStrings[0]->getGeometries(); + $lineString = $testPlace->multi_line_string[0]; - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); - $this->assertEquals(179, $points[1]->latitude); - $this->assertEquals(1, $points[1]->longitude); + $this->assertEquals(180, $lineString[0]->latitude); + $this->assertEquals(0, $lineString[0]->longitude); + $this->assertEquals(179, $lineString[1]->latitude); + $this->assertEquals(1, $lineString[1]->longitude); $this->assertDatabaseCount($testPlace->getTable(), 1); } diff --git a/tests/Objects/MultiPointTest.php b/tests/Objects/MultiPointTest.php index 3e9e34e..d5375bf 100644 --- a/tests/Objects/MultiPointTest.php +++ b/tests/Objects/MultiPointTest.php @@ -27,10 +27,8 @@ public function it_stores_multi_point(): void $this->assertTrue($testPlace->multi_point instanceof MultiPoint); - $points = $testPlace->multi_point->getGeometries(); - - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); + $this->assertEquals(180, $testPlace->multi_point[0]->latitude); + $this->assertEquals(0, $testPlace->multi_point[0]->longitude); $this->assertDatabaseCount($testPlace->getTable(), 1); } @@ -45,10 +43,8 @@ public function it_stores_multi_point_from_json(): void $this->assertTrue($testPlace->multi_point instanceof MultiPoint); - $points = $testPlace->multi_point->getGeometries(); - - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); + $this->assertEquals(180, $testPlace->multi_point[0]->latitude); + $this->assertEquals(0, $testPlace->multi_point[0]->longitude); $this->assertDatabaseCount($testPlace->getTable(), 1); } diff --git a/tests/Objects/MultiPolygonTest.php b/tests/Objects/MultiPolygonTest.php index ca871c1..67dd1cc 100644 --- a/tests/Objects/MultiPolygonTest.php +++ b/tests/Objects/MultiPolygonTest.php @@ -35,20 +35,18 @@ public function it_stores_multi_polygon(): void $this->assertTrue($testPlace->multi_polygon instanceof MultiPolygon); - $polygons = $testPlace->multi_polygon->getGeometries(); - $lineStrings = $polygons[0]->getGeometries(); - $points = $lineStrings[0]->getGeometries(); - - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); - $this->assertEquals(179, $points[1]->latitude); - $this->assertEquals(1, $points[1]->longitude); - $this->assertEquals(178, $points[2]->latitude); - $this->assertEquals(2, $points[2]->longitude); - $this->assertEquals(177, $points[3]->latitude); - $this->assertEquals(3, $points[3]->longitude); - $this->assertEquals(180, $points[4]->latitude); - $this->assertEquals(0, $points[4]->longitude); + $lineString = $testPlace->multi_polygon[0][0]; + + $this->assertEquals(180, $lineString[0]->latitude); + $this->assertEquals(0, $lineString[0]->longitude); + $this->assertEquals(179, $lineString[1]->latitude); + $this->assertEquals(1, $lineString[1]->longitude); + $this->assertEquals(178, $lineString[2]->latitude); + $this->assertEquals(2, $lineString[2]->longitude); + $this->assertEquals(177, $lineString[3]->latitude); + $this->assertEquals(3, $lineString[3]->longitude); + $this->assertEquals(180, $lineString[4]->latitude); + $this->assertEquals(0, $lineString[4]->longitude); $this->assertDatabaseCount($testPlace->getTable(), 1); } @@ -63,20 +61,18 @@ public function it_stores_multi_polygon_from_geo_json(): void $this->assertTrue($testPlace->multi_polygon instanceof MultiPolygon); - $polygons = $testPlace->multi_polygon->getGeometries(); - $lineStrings = $polygons[0]->getGeometries(); - $points = $lineStrings[0]->getGeometries(); - - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); - $this->assertEquals(179, $points[1]->latitude); - $this->assertEquals(1, $points[1]->longitude); - $this->assertEquals(178, $points[2]->latitude); - $this->assertEquals(2, $points[2]->longitude); - $this->assertEquals(177, $points[3]->latitude); - $this->assertEquals(3, $points[3]->longitude); - $this->assertEquals(180, $points[4]->latitude); - $this->assertEquals(0, $points[4]->longitude); + $lineString = $testPlace->multi_polygon[0][0]; + + $this->assertEquals(180, $lineString[0]->latitude); + $this->assertEquals(0, $lineString[0]->longitude); + $this->assertEquals(179, $lineString[1]->latitude); + $this->assertEquals(1, $lineString[1]->longitude); + $this->assertEquals(178, $lineString[2]->latitude); + $this->assertEquals(2, $lineString[2]->longitude); + $this->assertEquals(177, $lineString[3]->latitude); + $this->assertEquals(3, $lineString[3]->longitude); + $this->assertEquals(180, $lineString[4]->latitude); + $this->assertEquals(0, $lineString[4]->longitude); $this->assertDatabaseCount($testPlace->getTable(), 1); } diff --git a/tests/Objects/PolygonTest.php b/tests/Objects/PolygonTest.php index 5698052..139ba27 100644 --- a/tests/Objects/PolygonTest.php +++ b/tests/Objects/PolygonTest.php @@ -32,19 +32,18 @@ public function it_stores_polygon(): void $this->assertTrue($testPlace->polygon instanceof Polygon); - $lineStrings = $testPlace->polygon->getGeometries(); - $points = $lineStrings[0]->getGeometries(); - - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); - $this->assertEquals(179, $points[1]->latitude); - $this->assertEquals(1, $points[1]->longitude); - $this->assertEquals(178, $points[2]->latitude); - $this->assertEquals(2, $points[2]->longitude); - $this->assertEquals(177, $points[3]->latitude); - $this->assertEquals(3, $points[3]->longitude); - $this->assertEquals(180, $points[4]->latitude); - $this->assertEquals(0, $points[4]->longitude); + $lineString = $testPlace->polygon[0]; + + $this->assertEquals(180, $lineString[0]->latitude); + $this->assertEquals(0, $lineString[0]->longitude); + $this->assertEquals(179, $lineString[1]->latitude); + $this->assertEquals(1, $lineString[1]->longitude); + $this->assertEquals(178, $lineString[2]->latitude); + $this->assertEquals(2, $lineString[2]->longitude); + $this->assertEquals(177, $lineString[3]->latitude); + $this->assertEquals(3, $lineString[3]->longitude); + $this->assertEquals(180, $lineString[4]->latitude); + $this->assertEquals(0, $lineString[4]->longitude); $this->assertDatabaseCount($testPlace->getTable(), 1); } @@ -59,19 +58,18 @@ public function it_stores_polygon_from_geo_json(): void $this->assertTrue($testPlace->polygon instanceof Polygon); - $lineStrings = $testPlace->polygon->getGeometries(); - $points = $lineStrings[0]->getGeometries(); - - $this->assertEquals(180, $points[0]->latitude); - $this->assertEquals(0, $points[0]->longitude); - $this->assertEquals(179, $points[1]->latitude); - $this->assertEquals(1, $points[1]->longitude); - $this->assertEquals(178, $points[2]->latitude); - $this->assertEquals(2, $points[2]->longitude); - $this->assertEquals(177, $points[3]->latitude); - $this->assertEquals(3, $points[3]->longitude); - $this->assertEquals(180, $points[4]->latitude); - $this->assertEquals(0, $points[4]->longitude); + $lineString = $testPlace->polygon[0]; + + $this->assertEquals(180, $lineString[0]->latitude); + $this->assertEquals(0, $lineString[0]->longitude); + $this->assertEquals(179, $lineString[1]->latitude); + $this->assertEquals(1, $lineString[1]->longitude); + $this->assertEquals(178, $lineString[2]->latitude); + $this->assertEquals(2, $lineString[2]->longitude); + $this->assertEquals(177, $lineString[3]->latitude); + $this->assertEquals(3, $lineString[3]->longitude); + $this->assertEquals(180, $lineString[4]->latitude); + $this->assertEquals(0, $lineString[4]->longitude); $this->assertDatabaseCount($testPlace->getTable(), 1); }