diff --git a/.travis.yml b/.travis.yml index be0bd54a..e9ef9f38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,11 @@ language: php php: - - '5.5' - - '5.6' - - '7.0' - '7.1' - '7.2' + - '7.3' env: - - MYSQL_VERSION=5.7 - MYSQL_VERSION=8.0 dist: trusty @@ -19,6 +16,7 @@ services: - docker before_install: + - echo "memory_limit=2G" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - sudo /etc/init.d/mysql stop - make start_db V=$MYSQL_VERSION diff --git a/README.md b/README.md index d1318946..58a58de2 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ [![Build Status](https://img.shields.io/travis/grimzy/laravel-mysql-spatial.svg?style=flat-square)](https://travis-ci.org/grimzy/laravel-mysql-spatial) [![Code Climate](https://img.shields.io/codeclimate/maintainability/grimzy/laravel-mysql-spatial.svg?style=flat-square)](https://codeclimate.com/github/grimzy/laravel-mysql-spatial/maintainability) -[![Code Climate](https://img.shields.io/codeclimate/c/grimzy/laravel-mysql-spatial.svg?style=flat-square&colorB=4BCA2A)](https://codeclimate.com/github/grimzy/laravel-mysql-spatial/test_coverage)[![Packagist](https://img.shields.io/packagist/v/grimzy/laravel-mysql-spatial.svg?style=flat-square)](https://packagist.org/packages/grimzy/laravel-mysql-spatial) -[![Packagist](https://img.shields.io/packagist/dt/grimzy/laravel-mysql-spatial.svg?style=flat-square)](https://packagist.org/packages/grimzy/laravel-mysql-spatial) +[![Code Climate](https://img.shields.io/codeclimate/c/grimzy/laravel-mysql-spatial.svg?style=flat-square&colorB=4BCA2A)](https://codeclimate.com/github/grimzy/laravel-mysql-spatial/test_coverage) [![Packagist](https://img.shields.io/packagist/v/grimzy/laravel-mysql-spatial.svg?style=flat-square)](https://packagist.org/packages/grimzy/laravel-mysql-spatial) +[![Packagist](https://img.shields.io/packagist/dt/grimzy/laravel-mysql-spatial.svg?style=flat-square)](https://packagist.org/packages/grimzy/laravel-mysql-spatial) [![StyleCI](https://github.styleci.io/repos/83766141/shield?branch=master)](https://github.styleci.io/repos/83766141) [![license](https://img.shields.io/github/license/mashape/apistatus.svg?style=flat-square)](LICENSE) -Laravel package to easily work with [MySQL Spatial Data Types](https://dev.mysql.com/doc/refman/5.7/en/spatial-datatypes.html) and [MySQL Spatial Functions](https://dev.mysql.com/doc/refman/5.7/en/spatial-function-reference.html). +Laravel package to easily work with [MySQL Spatial Data Types](https://dev.mysql.com/doc/refman/8.0/en/spatial-type-overview.html) and [MySQL Spatial Functions](https://dev.mysql.com/doc/refman/8.0/en/spatial-function-reference.html). Please check the documentation for your MySQL version. MySQL's Extension for Spatial Data was added in MySQL 5.5 but many Spatial Functions were changed in 5.6 and 5.7. @@ -52,11 +52,14 @@ From the command line: php artisan make:migration create_places_table ``` -Then edit the migration you just created by adding at least one spatial data field: +Then edit the migration you just created by adding at least one spatial data field. For Laravel versions prior to 5.5, you can use the Blueprint provided by this package (Grimzy\LaravelMysqlSpatial\Schema\Blueprint): ```php use Illuminate\Database\Migrations\Migration; -use Grimzy\LaravelMysqlSpatial\Schema\Blueprint; +use Illuminate\Database\Schema\Blueprint; + +// For Laravel < 5.5 +// use Grimzy\LaravelMysqlSpatial\Schema\Blueprint; class CreatePlacesTable extends Migration { @@ -114,7 +117,8 @@ use Illuminate\Database\Eloquent\Model; use Grimzy\LaravelMysqlSpatial\Eloquent\SpatialTrait; /** - * @property \Grimzy\LaravelMysqlSpatial\Types\Point $location + * @property \Grimzy\LaravelMysqlSpatial\Types\Point $location + * @property \Grimzy\LaravelMysqlSpatial\Types\Polygon $area */ class Place extends Model { @@ -166,103 +170,19 @@ $lat = $place2->location->getLat(); // 40.7484404 $lng = $place2->location->getLng(); // -73.9878441 ``` -## Migrations - -### Columns - -Available [MySQL Spatial Types](https://dev.mysql.com/doc/refman/5.7/en/spatial-datatypes.html) migration blueprints: - -- - `$table->geometry('column_name');` - -- `$table->point('column_name');` -- `$table->lineString('column_name');` -- `$table->polygon('column_name');` -- `$table->multiPoint('column_name');` -- `$table->multiLineString('column_name');` -- `$table->multiPolygon('column_name');` -- `$table->geometryCollection('column_name');` - -### Spatial indexes - -You can add or drop spatial indexes in your migrations with the `spatialIndex` and `dropSpatialIndex` blueprints. - -- `$table->spatialIndex('column_name');` -- `$table->dropSpatialIndex(['column_name']);` or `$table->dropSpatialIndex('index_name')` - -Note about spatial indexes from the [MySQL documentation](https://dev.mysql.com/doc/refman/5.7/en/creating-spatial-indexes.html): - -> For [`MyISAM`](https://dev.mysql.com/doc/refman/5.7/en/myisam-storage-engine.html) and (as of MySQL 5.7.5) `InnoDB` tables, MySQL can create spatial indexes using syntax similar to that for creating regular indexes, but using the `SPATIAL` keyword. Columns in spatial indexes must be declared `NOT NULL`. - -Also please read this [**important note**](https://laravel.com/docs/5.5/migrations#indexes) regarding Index Lengths in the Laravel 5.6 documentation. - -For example, as a follow up to the [Quickstart](#user-content-create-a-migration); from the command line, generate a new migration: - -```shell -php artisan make:migration update_places_table -``` - -Then edit the migration file that you just created: - -```php -use Illuminate\Database\Migrations\Migration; -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\Schema; - -class UpdatePlacesTable extends Migration -{ - /** - * Run the migrations. - * - * @return void - */ - public function up() - { - // MySQL < 5.7.5: table has to be MyISAM - // \DB::statement('ALTER TABLE places ENGINE = MyISAM'); - - Schema::table('places', function (Blueprint $table) { - // Make sure point is not nullable - $table->point('location')->change(); - - // Add a spatial index on the location field - $table->spatialIndex('location'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('places', function (Blueprint $table) { - $table->dropSpatialIndex(['location']); // either an array of column names or the index name - }); - - // \DB::statement('ALTER TABLE places ENGINE = InnoDB'); - - Schema::table('places', function (Blueprint $table) { - $table->point('location')->nullable()->change(); - }); - } -} -``` - ## Geometry classes ### Available Geometry classes -| Grimzy\LaravelMysqlSpatial\Types | OpenGIS Class | -| ---------------------------------------- | ---------------------------------------- | -| `Point($lat, $lng)` | [Point](https://dev.mysql.com/doc/refman/5.7/en/gis-class-point.html) | -| `MultiPoint(Point[])` | [MultiPoint](https://dev.mysql.com/doc/refman/5.7/en/gis-class-multipoint.html) | -| `LineString(Point[])` | [LineString](https://dev.mysql.com/doc/refman/5.7/en/gis-class-linestring.html) | -| `MultiLineString(LineString[])` | [MultiLineString](https://dev.mysql.com/doc/refman/5.7/en/gis-class-multilinestring.html) | -| `Polygon(LineString[])` *([exterior and interior boundaries](https://dev.mysql.com/doc/refman/5.7/en/gis-class-polygon.html))* | [Polygon](https://dev.mysql.com/doc/refman/5.7/en/gis-class-polygon.html) | -| `MultiPolygon(Polygon[])` | [MultiPolygon](https://dev.mysql.com/doc/refman/5.7/en/gis-class-multipolygon.html) | -| `GeometryCollection(Geometry[])` | [GeometryCollection](https://dev.mysql.com/doc/refman/5.7/en/gis-class-geometrycollection.html) | +| Grimzy\LaravelMysqlSpatial\Types | OpenGIS Class | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| `Point($lat, $lng)` | [Point](https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html) | +| `MultiPoint(Point[])` | [MultiPoint](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipoint.html) | +| `LineString(Point[])` | [LineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-linestring.html) | +| `MultiLineString(LineString[])` | [MultiLineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multilinestring.html) | +| `Polygon(LineString[])` *([exterior and interior boundaries](https://dev.mysql.com/doc/refman/8.0/en/gis-class-polygon.html))* | [Polygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-polygon.html) | +| `MultiPolygon(Polygon[])` | [MultiPolygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipolygon.html) | +| `GeometryCollection(Geometry[])` | [GeometryCollection](https://dev.mysql.com/doc/refman/8.0/en/gis-class-geometrycollection.html) | Check out the [Class diagram](https://user-images.githubusercontent.com/1837678/30788608-a5afd894-a16c-11e7-9a51-0a08b331d4c4.png). @@ -290,8 +210,10 @@ for($polygon as $i => $linestring) { ```php // fromWKT($wkt) -$polygon = Polygon::fromWKT('POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))'); +$point = Point::fromWKT('POINT(2 1)'); +$point->toWKT(); // POINT(2 1) +$polygon = Polygon::fromWKT('POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))'); $polygon->toWKT(); // POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)) ``` @@ -299,8 +221,10 @@ $polygon->toWKT(); // POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)) ```php // fromString($wkt) -$polygon = Polygon::fromString('(0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)'); +$point = new Point(1, 2); // lat, lng +(string)$point // lng, lat: 2 1 +$polygon = Polygon::fromString('(0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)'); (string)$polygon; // (0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1) ``` @@ -309,7 +233,7 @@ $polygon = Polygon::fromString('(0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)') The Geometry classes implement [`JsonSerializable`](http://php.net/manual/en/class.jsonserializable.php) and `Illuminate\Contracts\Support\Jsonable` to help serialize into GeoJSON: ```php -$point = new Point(10, 20); +$point = new Point(40.7484404, -73.9878441); json_encode($point); // or $point->toJson(); @@ -329,7 +253,7 @@ json_encode($point); // or $point->toJson(); To deserialize a GeoJSON string into a Geometry class, you can use `Geometry::fromJson($json_string)` : ```php -$locaction = Geometry::fromJson('{"type":"Point","coordinates":[3.4,1.2]}'); +$location = Geometry::fromJson('{"type":"Point","coordinates":[3.4,1.2]}'); $location instanceof Point::class; // true $location->getLat(); // 1.2 $location->getLng()); // 3.4 @@ -354,9 +278,127 @@ Available scopes: - `intersects($geometryColumn, $geometry)` - `overlaps($geometryColumn, $geometry)` - `doesTouch($geometryColumn, $geometry)` +- `orderBySpatial($geometryColumn, $geometry, $orderFunction, $direction = 'asc')` +- `orderByDistance($geometryColumn, ​$geometry, ​$direction = 'asc')` +- `orderByDistanceSphere($geometryColumn, ​$geometry, ​$direction = 'asc')` *Note that behavior and availability of MySQL spatial analysis functions differs in each MySQL version (cf. [documentation](https://dev.mysql.com/doc/refman/5.7/en/spatial-function-reference.html)).* +## Migrations + +For Laravel versions prior to 5.5, you can use the Blueprint provided with this package: `Grimzy\LaravelMysqlSpatial\Schema\Blueprint`. + +```php +use Illuminate\Database\Migrations\Migration; +use Grimzy\LaravelMysqlSpatial\Schema\Blueprint; + +class CreatePlacesTable extends Migration { + // ... +} +``` + +### Columns + +Available [MySQL Spatial Types](https://dev.mysql.com/doc/refman/5.7/en/spatial-datatypes.html) migration blueprints: + +- `$table->geometry('column_name')` +- `$table->point('column_name')` +- `$table->lineString('column_name')` +- `$table->polygon('column_name')` +- `$table->multiPoint('column_name')` +- `$table->multiLineString('column_name')` +- `$table->multiPolygon('column_name')` +- `$table->geometryCollection('column_name')` + +### Spatial indexes + +You can add or drop spatial indexes in your migrations with the `spatialIndex` and `dropSpatialIndex` blueprints. + +- `$table->spatialIndex('column_name')` +- `$table->dropSpatialIndex(['column_name'])` or `$table->dropSpatialIndex('index_name')` + +Note about spatial indexes from the [MySQL documentation](https://dev.mysql.com/doc/refman/5.7/en/creating-spatial-indexes.html): + +> For [`MyISAM`](https://dev.mysql.com/doc/refman/5.7/en/myisam-storage-engine.html) and (as of MySQL 5.7.5) `InnoDB` tables, MySQL can create spatial indexes using syntax similar to that for creating regular indexes, but using the `SPATIAL` keyword. Columns in spatial indexes must be declared `NOT NULL`. + +Also please read this [**important note**](https://laravel.com/docs/5.5/migrations#indexes) regarding Index Lengths in the Laravel 5.6 documentation. + +For example, as a follow up to the [Quickstart](#user-content-create-a-migration); from the command line, generate a new migration: + +```shell +php artisan make:migration update_places_table +``` + +Then edit the migration file that you just created: + +```php +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +class UpdatePlacesTable extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + // MySQL < 5.7.5: table has to be MyISAM + // \DB::statement('ALTER TABLE places ENGINE = MyISAM'); + + Schema::table('places', function (Blueprint $table) { + // Make sure point is not nullable + $table->point('location')->change(); + + // Add a spatial index on the location field + $table->spatialIndex('location'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('places', function (Blueprint $table) { + $table->dropSpatialIndex(['location']); // either an array of column names or the index name + }); + + // \DB::statement('ALTER TABLE places ENGINE = InnoDB'); + + Schema::table('places', function (Blueprint $table) { + $table->point('location')->nullable()->change(); + }); + } +} +``` + +## Tests + +```shell +composer test +# or +composer test:unit +composer test:integration +``` + +Integration tests require a running MySQL database. If you have Docker installed, you can start easily start one: + +```shell +make start_db # starts MySQL 8.0 +# or +make start_db V=5.7 # starts a MySQL 5.7 +``` + +## Contributing + +Recommendations and pull request are most welcome! Pull requests with tests are the best! There are still a lot of MySQL spatial functions to implement or creative ways to use spatial functions. + ## Credits Originally inspired from [njbarrett's Laravel postgis package](https://github.com/njbarrett/laravel-postgis). + diff --git a/composer.json b/composer.json index 4b37e283..eea3a240 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,11 @@ { "name": "grimzy/laravel-mysql-spatial", "description": "MySQL spatial data types extension for Laravel.", + "scripts": { + "test": "phpunit -c phpunit.xml.dist", + "test:unit": "phpunit -c phpunit.xml.dist --testsuite unit", + "test:integration": "phpunit -c phpunit.xml.dist --testsuite integration" + }, "type": "library", "license": "MIT", "authors": [ @@ -10,15 +15,17 @@ } ], "require": { - "php": ">=5.5", - "illuminate/database": "^5.2", + "php": ">=7.1.3", + "ext-pdo": "*", + "ext-json": "*", + "illuminate/database": "^5.6|^6.0", "geo-io/wkb-parser": "^1.0", "jmikola/geojson": "^1.0" }, "require-dev": { "phpunit/phpunit": "~4.8||~5.7", "mockery/mockery": "^0.9.9", - "laravel/laravel": "^5.2", + "laravel/laravel": "^5.2|^6.0", "doctrine/dbal": "^2.5", "laravel/browser-kit-testing": "^2.0", "php-coveralls/php-coveralls": "^2.0" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3e54db39..1456a6ac 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,10 +9,10 @@ processIsolation="false" stopOnFailure="false"> - + ./tests/Unit - + ./tests/Integration @@ -36,5 +36,10 @@ + + + + + \ No newline at end of file diff --git a/src/Eloquent/SpatialExpression.php b/src/Eloquent/SpatialExpression.php index e4436885..9224af0f 100644 --- a/src/Eloquent/SpatialExpression.php +++ b/src/Eloquent/SpatialExpression.php @@ -8,7 +8,7 @@ class SpatialExpression extends Expression { public function getValue() { - return 'ST_GeomFromText(?, ?)'; + return "ST_GeomFromText(?, ?, 'axis-order=long-lat')"; } public function getSpatialValue() diff --git a/src/Eloquent/SpatialTrait.php b/src/Eloquent/SpatialTrait.php index 5132a622..e44788fc 100755 --- a/src/Eloquent/SpatialTrait.php +++ b/src/Eloquent/SpatialTrait.php @@ -3,6 +3,7 @@ namespace Grimzy\LaravelMysqlSpatial\Eloquent; use Grimzy\LaravelMysqlSpatial\Exceptions\SpatialFieldsNotDefinedException; +use Grimzy\LaravelMysqlSpatial\Exceptions\UnknownSpatialFunctionException; use Grimzy\LaravelMysqlSpatial\Exceptions\UnknownSpatialRelationFunction; use Grimzy\LaravelMysqlSpatial\Types\Geometry; use Grimzy\LaravelMysqlSpatial\Types\GeometryInterface; @@ -24,6 +25,9 @@ * @method static intersects($geometryColumn, $geometry) * @method static overlaps($geometryColumn, $geometry) * @method static doesTouch($geometryColumn, $geometry) + * @method static orderBySpatial($geometryColumn, $geometry, $orderFunction, $direction = 'asc') + * @method static orderByDistance($geometryColumn, $geometry, $direction = 'asc') + * @method static orderByDistanceSphere($geometryColumn, $geometry, $direction = 'asc') */ trait SpatialTrait { @@ -49,6 +53,11 @@ trait SpatialTrait 'touches', ]; + protected $stOrderFunctions = [ + 'distance', + 'distance_sphere', + ]; + /** * Create a new Eloquent query builder for the model. * @@ -123,8 +132,9 @@ public function scopeDistance($query, $geometryColumn, $geometry, $distance) { $this->isColumnAllowed($geometryColumn); - $query->whereRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?)) <= ?", [ + $query->whereRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) <= ?", [ $geometry->toWkt(), + $geometry->getSrid(), $distance, ]); @@ -137,8 +147,9 @@ public function scopeDistanceExcludingSelf($query, $geometryColumn, $geometry, $ $query = $this->scopeDistance($query, $geometryColumn, $geometry, $distance); - $query->whereRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?)) != 0", [ + $query->whereRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) != 0", [ $geometry->toWkt(), + $geometry->getSrid(), ]); return $query; @@ -154,8 +165,9 @@ public function scopeDistanceValue($query, $geometryColumn, $geometry) $query->select('*'); } - $query->selectRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?)) as distance", [ + $query->selectRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) as distance", [ $geometry->toWkt(), + $geometry->getSrid(), ]); } @@ -163,8 +175,9 @@ public function scopeDistanceSphere($query, $geometryColumn, $geometry, $distanc { $this->isColumnAllowed($geometryColumn); - $query->whereRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?)) <= ?", [ + $query->whereRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) <= ?", [ $geometry->toWkt(), + $geometry->getSrid(), $distance, ]); @@ -177,8 +190,9 @@ public function scopeDistanceSphereExcludingSelf($query, $geometryColumn, $geome $query = $this->scopeDistanceSphere($query, $geometryColumn, $geometry, $distance); - $query->whereRaw("st_distance_sphere($geometryColumn, ST_GeomFromText(?)) != 0", [ + $query->whereRaw("st_distance_sphere($geometryColumn, ST_GeomFromText(?, ?, 'axis-order=long-lat')) != 0", [ $geometry->toWkt(), + $geometry->getSrid(), ]); return $query; @@ -193,8 +207,9 @@ public function scopeDistanceSphereValue($query, $geometryColumn, $geometry) if (!$columns) { $query->select('*'); } - $query->selectRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?)) as distance", [ + $query->selectRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) as distance", [ $geometry->toWkt(), + $geometry->getSrid(), ]); } @@ -206,8 +221,9 @@ public function scopeComparison($query, $geometryColumn, $geometry, $relationshi throw new UnknownSpatialRelationFunction($relationship); } - $query->whereRaw("st_{$relationship}(`$geometryColumn`, ST_GeomFromText(?))", [ + $query->whereRaw("st_{$relationship}(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat'))", [ $geometry->toWkt(), + $geometry->getSrid(), ]); return $query; @@ -252,4 +268,30 @@ public function scopeDoesTouch($query, $geometryColumn, $geometry) { return $this->scopeComparison($query, $geometryColumn, $geometry, 'touches'); } + + public function scopeOrderBySpatial($query, $geometryColumn, $geometry, $orderFunction, $direction = 'asc') + { + $this->isColumnAllowed($geometryColumn); + + if (!in_array($orderFunction, $this->stOrderFunctions)) { + throw new UnknownSpatialFunctionException($orderFunction); + } + + $query->orderByRaw("st_{$orderFunction}(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) {$direction}", [ + $geometry->toWkt(), + $geometry->getSrid(), + ]); + + return $query; + } + + public function scopeOrderByDistance($query, $geometryColumn, $geometry, $direction = 'asc') + { + return $this->scopeOrderBySpatial($query, $geometryColumn, $geometry, 'distance', $direction); + } + + public function scopeOrderByDistanceSphere($query, $geometryColumn, $geometry, $direction = 'asc') + { + return $this->scopeOrderBySpatial($query, $geometryColumn, $geometry, 'distance_sphere', $direction); + } } diff --git a/src/Exceptions/UnknownSpatialFunctionException.php b/src/Exceptions/UnknownSpatialFunctionException.php new file mode 100644 index 00000000..19f5b6e9 --- /dev/null +++ b/src/Exceptions/UnknownSpatialFunctionException.php @@ -0,0 +1,7 @@ +parse($wkb); + /** @var Geometry $parsed */ + $parsed = $parser->parse($wkb); + + if ($srid > 0) { + $parsed->setSrid($srid); + } + + return $parsed; } - public static function fromWKT($wkt, $srid = 0) + public static function fromWKT($wkt, $srid = null) { $wktArgument = static::getWKTArgument($wkt); diff --git a/tests/Integration/IntegrationBaseTestCase.php b/tests/Integration/IntegrationBaseTestCase.php index daad3ab4..85c495e5 100644 --- a/tests/Integration/IntegrationBaseTestCase.php +++ b/tests/Integration/IntegrationBaseTestCase.php @@ -21,16 +21,13 @@ public function createApplication() $app->make('Illuminate\Contracts\Console\Kernel')->bootstrap(); - /** - * @param \Illuminate\Contracts\Config\Repository $config - */ - $config = $app->config; - $config->set('database.default', 'mysql'); - $config->set('database.connections.mysql.host', env('DB_HOST', '127.0.0.1')); - $config->set('database.connections.mysql.database', 'spatial_test'); - $config->set('database.connections.mysql.username', 'root'); - $config->set('database.connections.mysql.password', ''); - $config->set('database.connections.mysql.modes', [ + $app['config']->set('database.default', 'mysql'); + $app['config']->set('database.connections.mysql.host', env('DB_HOST')); + $app['config']->set('database.connections.mysql.port', env('DB_PORT')); + $app['config']->set('database.connections.mysql.database', env('DB_DATABASE')); + $app['config']->set('database.connections.mysql.username', env('DB_USERNAME')); + $app['config']->set('database.connections.mysql.password', env('DB_PASSWORD')); + $app['config']->set('database.connections.mysql.modes', [ 'ONLY_FULL_GROUP_BY', 'STRICT_TRANS_TABLES', 'NO_ZERO_IN_DATE', diff --git a/tests/Integration/SpatialTest.php b/tests/Integration/SpatialTest.php index 06cd9af0..3b09f02b 100644 --- a/tests/Integration/SpatialTest.php +++ b/tests/Integration/SpatialTest.php @@ -11,7 +11,7 @@ class SpatialTest extends IntegrationBaseTestCase { protected $migrations = [ CreateLocationTable::class, - UpdateLocationTable::class + UpdateLocationTable::class, ]; public function testSpatialFieldsNotDefinedException() @@ -221,6 +221,82 @@ public function testDistanceSphereValue() } } + public function testOrderBySpatialWithUnknownFunction() + { + $loc = new GeometryModel(); + $loc->location = new Point(1, 1); + + $this->assertException(\Grimzy\LaravelMysqlSpatial\Exceptions\UnknownSpatialFunctionException::class); + GeometryModel::orderBySpatial('location', $loc->location, 'does-not-exist')->get(); + } + + public function testOrderByDistance() + { + $loc2 = new GeometryModel(); + $loc2->location = new Point(2, 2); // Distance from loc1: 1.4142135623731 + $loc2->save(); + + $loc1 = new GeometryModel(); + $loc1->location = new Point(1, 1); + $loc1->save(); + + $loc3 = new GeometryModel(); + $loc3->location = new Point(3, 3); // Distance from loc1: 2.8284271247462 + $loc3->save(); + + $a = GeometryModel::orderByDistance('location', $loc1->location)->get(); + $this->assertCount(3, $a); + $this->assertEquals($loc1->location, $a[0]->location); + $this->assertEquals($loc2->location, $a[1]->location); + $this->assertEquals($loc3->location, $a[2]->location); + + // Excluding self + $b = GeometryModel::orderByDistance('location', $loc1->location, 'asc')->get(); + $this->assertCount(3, $b); + $this->assertEquals($loc1->location, $b[0]->location); + $this->assertEquals($loc2->location, $b[1]->location); + $this->assertEquals($loc3->location, $b[2]->location); + + $c = GeometryModel::orderByDistance('location', $loc1->location, 'desc')->get(); + $this->assertCount(3, $c); + $this->assertEquals($loc3->location, $c[0]->location); + $this->assertEquals($loc2->location, $c[1]->location); + $this->assertEquals($loc1->location, $c[2]->location); + } + + public function testOrderByDistanceSphere() + { + $loc2 = new GeometryModel(); + $loc2->location = new Point(40.767664, -73.971271); // Distance from loc1: 44.741406484588 + $loc2->save(); + + $loc1 = new GeometryModel(); + $loc1->location = new Point(40.767864, -73.971732); + $loc1->save(); + + $loc3 = new GeometryModel(); + $loc3->location = new Point(40.761434, -73.977619); // Distance from loc1: 870.06424066202 + $loc3->save(); + + $a = GeometryModel::orderByDistanceSphere('location', $loc1->location)->get(); + $this->assertCount(3, $a); + $this->assertEquals($loc1->location, $a[0]->location); + $this->assertEquals($loc2->location, $a[1]->location); + $this->assertEquals($loc3->location, $a[2]->location); + + $b = GeometryModel::orderByDistanceSphere('location', $loc1->location, 'asc')->get(); + $this->assertCount(3, $b); + $this->assertEquals($loc1->location, $b[0]->location); + $this->assertEquals($loc2->location, $b[1]->location); + $this->assertEquals($loc3->location, $b[2]->location); + + $c = GeometryModel::orderByDistanceSphere('location', $loc1->location, 'desc')->get(); + $this->assertCount(3, $c); + $this->assertEquals($loc3->location, $c[0]->location); + $this->assertEquals($loc2->location, $c[1]->location); + $this->assertEquals($loc1->location, $c[2]->location); + } + //public function testBounding() { // $point = new Point(0, 0); // diff --git a/tests/Integration/SridSpatialTest.php b/tests/Integration/SridSpatialTest.php index 05b85983..0089c44f 100644 --- a/tests/Integration/SridSpatialTest.php +++ b/tests/Integration/SridSpatialTest.php @@ -11,7 +11,7 @@ class SridSpatialTest extends IntegrationBaseTestCase { protected $migrations = [ CreateLocationTable::class, - UpdateLocationTable::class + UpdateLocationTable::class, ]; public function testInsertPointWithSrid() @@ -113,22 +113,22 @@ public function testInsertPointWithWrongSrid() 'of the geometry is 0, but the SRID of the column is 3857. ' . 'Consider changing the SRID of the geometry or the SRID property ' . 'of the column. (SQL: insert into `with_srid` (`location`) values ' . - '(ST_GeomFromText(POINT(2 1), 0)))' + '(ST_GeomFromText(POINT(2 1), 0, \'axis-order=long-lat\')))' ); $geo->save(); } -// public function testGeometryInsertedHasRightSrid () { -// $geo = new WithSridModel(); -// $geo->location = new Point(1, 2, 3857); -// $geo->save(); -// -// $srid = \DB::selectOne('select ST_SRID(location) as srid from with_srid'); -// $this->assertEquals(3857, $srid->srid); -// -// $result = WithSridModel::first(); -// -// $this->assertEquals($geo->location->getSrid(), $result->location->getSrid()); -// $a = 1; -// } + public function testGeometryInsertedHasRightSrid () { + $geo = new WithSridModel(); + $geo->location = new Point(1, 2, 3857); + $geo->save(); + + $srid = \DB::selectOne('select ST_SRID(location) as srid from with_srid'); + $this->assertEquals(3857, $srid->srid); + + $result = WithSridModel::first(); + + $this->assertEquals($geo->location->getSrid(), $result->location->getSrid()); + $a = 1; + } } diff --git a/tests/Unit/Eloquent/SpatialTraitTest.php b/tests/Unit/Eloquent/SpatialTraitTest.php index 1c3c0813..d6aa942b 100644 --- a/tests/Unit/Eloquent/SpatialTraitTest.php +++ b/tests/Unit/Eloquent/SpatialTraitTest.php @@ -37,7 +37,7 @@ public function testInsertUpdatePointHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`point`) values (ST_GeomFromText(?, ?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`point`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); @@ -45,7 +45,7 @@ public function testInsertUpdatePointHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `point` = ST_GeomFromText(?, ?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `point` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -60,7 +60,7 @@ public function testInsertUpdateLineStringHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`linestring`) values (ST_GeomFromText(?, ?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`linestring`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); @@ -68,7 +68,7 @@ public function testInsertUpdateLineStringHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `linestring` = ST_GeomFromText(?, ?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `linestring` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -87,14 +87,14 @@ public function testInsertUpdatePolygonHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`polygon`) values (ST_GeomFromText(?, ?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`polygon`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); $this->model->polygon = new \Grimzy\LaravelMysqlSpatial\Types\Polygon([$linestring1, $linestring2]); $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `polygon` = ST_GeomFromText(?, ?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `polygon` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -109,7 +109,7 @@ public function testInsertUpdateMultiPointHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`multipoint`) values (ST_GeomFromText(?, ?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`multipoint`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); @@ -117,7 +117,7 @@ public function testInsertUpdateMultiPointHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `multipoint` = ST_GeomFromText(?, ?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `multipoint` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -136,14 +136,14 @@ public function testInsertUpdateMultiLineStringHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`multilinestring`) values (ST_GeomFromText(?, ?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`multilinestring`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); $this->model->multilinestring = new \Grimzy\LaravelMysqlSpatial\Types\MultiLineString([$linestring1, $linestring2]); $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `multilinestring` = ST_GeomFromText(?, ?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `multilinestring` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -171,14 +171,14 @@ public function testInsertUpdateMultiPolygonHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`multipolygon`) values (ST_GeomFromText(?, ?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`multipolygon`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); $this->model->multipolygon = new \Grimzy\LaravelMysqlSpatial\Types\MultiPolygon([$polygon1, $polygon2]); $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `multipolygon` = ST_GeomFromText(?, ?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `multipolygon` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -195,14 +195,14 @@ public function testInsertUpdateGeometryCollectionHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`geometrycollection`) values (ST_GeomFromText(?, ?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`geometrycollection`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); $this->model->geometrycollection = new \Grimzy\LaravelMysqlSpatial\Types\GeometryCollection([$point1, $linestring1]); $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `geometrycollection` = ST_GeomFromText(?, ?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `geometrycollection` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -231,9 +231,9 @@ public function testScopeDistance() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertEquals('st_distance(`point`, ST_GeomFromText(?)) <= ?', $q->wheres[0]['sql']); + $this->assertEquals('st_distance(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) <= ?', $q->wheres[0]['sql']); $this->assertEquals('POINT(2 1)', $bindings[0]); - $this->assertEquals(10, $bindings[1]); + $this->assertEquals(10, $bindings[2]); } public function testScopeDistanceExcludingSelf() @@ -246,11 +246,11 @@ public function testScopeDistanceExcludingSelf() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertEquals('st_distance(`point`, ST_GeomFromText(?)) <= ?', $q->wheres[0]['sql']); - $this->assertEquals('st_distance(`point`, ST_GeomFromText(?)) != 0', $q->wheres[1]['sql']); + $this->assertEquals('st_distance(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) <= ?', $q->wheres[0]['sql']); + $this->assertEquals('st_distance(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) != 0', $q->wheres[1]['sql']); $this->assertEquals('POINT(2 1)', $bindings[0]); - $this->assertEquals(10, $bindings[1]); - $this->assertEquals('POINT(2 1)', $bindings[2]); + $this->assertEquals(10, $bindings[2]); + $this->assertEquals('POINT(2 1)', $bindings[3]); } public function testScopeDistanceSphere() @@ -263,9 +263,9 @@ public function testScopeDistanceSphere() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?)) <= ?', $q->wheres[0]['sql']); + $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) <= ?', $q->wheres[0]['sql']); $this->assertEquals('POINT(2 1)', $bindings[0]); - $this->assertEquals(10, $bindings[1]); + $this->assertEquals(10, $bindings[2]); } public function testScopeDistanceSphereExcludingSelf() @@ -278,11 +278,11 @@ public function testScopeDistanceSphereExcludingSelf() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?)) <= ?', $q->wheres[0]['sql']); - $this->assertEquals('st_distance_sphere(point, ST_GeomFromText(?)) != 0', $q->wheres[1]['sql']); + $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) <= ?', $q->wheres[0]['sql']); + $this->assertEquals('st_distance_sphere(point, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) != 0', $q->wheres[1]['sql']); $this->assertEquals('POINT(2 1)', $bindings[0]); - $this->assertEquals(10, $bindings[1]); - $this->assertEquals('POINT(2 1)', $bindings[2]); + $this->assertEquals(10, $bindings[2]); + $this->assertEquals('POINT(2 1)', $bindings[3]); } public function testScopeDistanceValue() @@ -297,7 +297,7 @@ public function testScopeDistanceValue() $this->assertNotEmpty($bindings); $this->assertEquals('*', $q->columns[0]); $this->assertInstanceOf(\Illuminate\Database\Query\Expression::class, $q->columns[1]); - $this->assertEquals('st_distance(`point`, ST_GeomFromText(?)) as distance', $q->columns[1]->getValue()); + $this->assertEquals('st_distance(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) as distance', $q->columns[1]->getValue()); $this->assertEquals('POINT(2 1)', $bindings[0]); } @@ -313,7 +313,7 @@ public function testScopeDistanceValueWithSelect() $this->assertNotEmpty($bindings); $this->assertEquals('some_column', $q->columns[0]); $this->assertInstanceOf(\Illuminate\Database\Query\Expression::class, $q->columns[1]); - $this->assertEquals('st_distance(`point`, ST_GeomFromText(?)) as distance', $q->columns[1]->getValue()); + $this->assertEquals('st_distance(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) as distance', $q->columns[1]->getValue()); $this->assertEquals('POINT(2 1)', $bindings[0]); } @@ -329,7 +329,7 @@ public function testScopeDistanceSphereValue() $this->assertNotEmpty($bindings); $this->assertEquals('*', $q->columns[0]); $this->assertInstanceOf(\Illuminate\Database\Query\Expression::class, $q->columns[1]); - $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?)) as distance', $q->columns[1]->getValue()); + $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) as distance', $q->columns[1]->getValue()); $this->assertEquals('POINT(2 1)', $bindings[0]); } @@ -345,7 +345,7 @@ public function testScopeDistanceSphereValueWithSelect() $this->assertNotEmpty($bindings); $this->assertEquals('some_column', $q->columns[0]); $this->assertInstanceOf(\Illuminate\Database\Query\Expression::class, $q->columns[1]); - $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?)) as distance', $q->columns[1]->getValue()); + $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) as distance', $q->columns[1]->getValue()); $this->assertEquals('POINT(2 1)', $bindings[0]); } @@ -373,7 +373,7 @@ public function testScopeComparison() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_within(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_within(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -386,7 +386,7 @@ public function testScopeWithin() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_within(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_within(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -399,7 +399,7 @@ public function testScopeCrosses() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_crosses(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_crosses(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -412,7 +412,7 @@ public function testScopeContains() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_contains(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_contains(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -425,7 +425,7 @@ public function testScopeDisjoint() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_disjoint(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_disjoint(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -438,7 +438,7 @@ public function testScopeEquals() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_equals(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_equals(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -451,7 +451,7 @@ public function testScopeIntersects() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_intersects(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_intersects(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -464,7 +464,7 @@ public function testScopeOverlaps() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_overlaps(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_overlaps(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -477,9 +477,44 @@ public function testScopeDoesTouch() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_touches(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_touches(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } + + public function testScopeOrderBySpatialThrowsExceptionWhenFunctionNotRegistered() + { + $point = new Point(1, 2); + $this->assertException(\Grimzy\LaravelMysqlSpatial\Exceptions\UnknownSpatialFunctionException::class); + TestModel::orderBySpatial('point', $point, 'does-not-exist'); + } + + public function testScopeOrderByDistance() + { + $point = new Point(1, 2); + $query = TestModel::orderByDistance('point', $point); + + $this->assertInstanceOf(\Grimzy\LaravelMysqlSpatial\Eloquent\Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->orders); + $bindings = $q->getRawBindings()['order']; + $this->assertNotEmpty($bindings); + $this->assertContains('st_distance(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) asc', $q->orders[0]['sql']); + $this->assertEquals('POINT(2 1)', $bindings[0]); + } + + public function testScopeOrderByDistanceSphere() + { + $point = new Point(1, 2); + $query = TestModel::orderByDistanceSphere('point', $point); + + $this->assertInstanceOf(\Grimzy\LaravelMysqlSpatial\Eloquent\Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->orders); + $bindings = $q->getRawBindings()['order']; + $this->assertNotEmpty($bindings); + $this->assertContains('st_distance_sphere(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) asc', $q->orders[0]['sql']); + $this->assertEquals('POINT(2 1)', $bindings[0]); + } } class TestModel extends Model