Skip to content

Commit f124c23

Browse files
authored
Merge pull request #79 from grimzy/issue-64-scope-order-by-distance
Adds scopes orderByDistance() and orderByDistanceSphere()
2 parents b1db057 + 4523cf4 commit f124c23

File tree

7 files changed

+367
-112
lines changed

7 files changed

+367
-112
lines changed

README.md

+145-103
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
[![Build Status](https://img.shields.io/travis/grimzy/laravel-mysql-spatial.svg?style=flat-square)](https://travis-ci.org/grimzy/laravel-mysql-spatial)
44
[![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)
5-
[![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)
6-
[![Packagist](https://img.shields.io/packagist/dt/grimzy/laravel-mysql-spatial.svg?style=flat-square)](https://packagist.org/packages/grimzy/laravel-mysql-spatial)
5+
[![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)
6+
[![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)
77
[![license](https://img.shields.io/github/license/mashape/apistatus.svg?style=flat-square)](LICENSE)
88

9-
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).
9+
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).
1010

1111
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.
1212

@@ -52,11 +52,14 @@ From the command line:
5252
php artisan make:migration create_places_table
5353
```
5454

55-
Then edit the migration you just created by adding at least one spatial data field:
55+
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):
5656

5757
```php
5858
use Illuminate\Database\Migrations\Migration;
59-
use Grimzy\LaravelMysqlSpatial\Schema\Blueprint;
59+
use Illuminate\Database\Schema\Blueprint;
60+
61+
// For Laravel < 5.5
62+
// use Grimzy\LaravelMysqlSpatial\Schema\Blueprint;
6063

6164
class CreatePlacesTable extends Migration {
6265

@@ -114,7 +117,8 @@ use Illuminate\Database\Eloquent\Model;
114117
use Grimzy\LaravelMysqlSpatial\Eloquent\SpatialTrait;
115118

116119
/**
117-
* @property \Grimzy\LaravelMysqlSpatial\Types\Point $location
120+
* @property \Grimzy\LaravelMysqlSpatial\Types\Point $location
121+
* @property \Grimzy\LaravelMysqlSpatial\Types\Polygon $area
118122
*/
119123
class Place extends Model
120124
{
@@ -166,103 +170,19 @@ $lat = $place2->location->getLat(); // 40.7484404
166170
$lng = $place2->location->getLng(); // -73.9878441
167171
```
168172

169-
## Migrations
170-
171-
### Columns
172-
173-
Available [MySQL Spatial Types](https://dev.mysql.com/doc/refman/5.7/en/spatial-datatypes.html) migration blueprints:
174-
175-
-
176-
`$table->geometry('column_name');`
177-
178-
- `$table->point('column_name');`
179-
- `$table->lineString('column_name');`
180-
- `$table->polygon('column_name');`
181-
- `$table->multiPoint('column_name');`
182-
- `$table->multiLineString('column_name');`
183-
- `$table->multiPolygon('column_name');`
184-
- `$table->geometryCollection('column_name');`
185-
186-
### Spatial indexes
187-
188-
You can add or drop spatial indexes in your migrations with the `spatialIndex` and `dropSpatialIndex` blueprints.
189-
190-
- `$table->spatialIndex('column_name');`
191-
- `$table->dropSpatialIndex(['column_name']);` or `$table->dropSpatialIndex('index_name')`
192-
193-
Note about spatial indexes from the [MySQL documentation](https://dev.mysql.com/doc/refman/5.7/en/creating-spatial-indexes.html):
194-
195-
> 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`.
196-
197-
Also please read this [**important note**](https://laravel.com/docs/5.5/migrations#indexes) regarding Index Lengths in the Laravel 5.6 documentation.
198-
199-
For example, as a follow up to the [Quickstart](#user-content-create-a-migration); from the command line, generate a new migration:
200-
201-
```shell
202-
php artisan make:migration update_places_table
203-
```
204-
205-
Then edit the migration file that you just created:
206-
207-
```php
208-
use Illuminate\Database\Migrations\Migration;
209-
use Illuminate\Database\Schema\Blueprint;
210-
use Illuminate\Support\Facades\Schema;
211-
212-
class UpdatePlacesTable extends Migration
213-
{
214-
/**
215-
* Run the migrations.
216-
*
217-
* @return void
218-
*/
219-
public function up()
220-
{
221-
// MySQL < 5.7.5: table has to be MyISAM
222-
// \DB::statement('ALTER TABLE places ENGINE = MyISAM');
223-
224-
Schema::table('places', function (Blueprint $table) {
225-
// Make sure point is not nullable
226-
$table->point('location')->change();
227-
228-
// Add a spatial index on the location field
229-
$table->spatialIndex('location');
230-
});
231-
}
232-
233-
/**
234-
* Reverse the migrations.
235-
*
236-
* @return void
237-
*/
238-
public function down()
239-
{
240-
Schema::table('places', function (Blueprint $table) {
241-
$table->dropSpatialIndex(['location']); // either an array of column names or the index name
242-
});
243-
244-
// \DB::statement('ALTER TABLE places ENGINE = InnoDB');
245-
246-
Schema::table('places', function (Blueprint $table) {
247-
$table->point('location')->nullable()->change();
248-
});
249-
}
250-
}
251-
```
252-
253173
## Geometry classes
254174

255175
### Available Geometry classes
256176

257-
| Grimzy\LaravelMysqlSpatial\Types | OpenGIS Class |
258-
| ---------------------------------------- | ---------------------------------------- |
259-
| `Point($lat, $lng)` | [Point](https://dev.mysql.com/doc/refman/5.7/en/gis-class-point.html) |
260-
| `MultiPoint(Point[])` | [MultiPoint](https://dev.mysql.com/doc/refman/5.7/en/gis-class-multipoint.html) |
261-
| `LineString(Point[])` | [LineString](https://dev.mysql.com/doc/refman/5.7/en/gis-class-linestring.html) |
262-
| `MultiLineString(LineString[])` | [MultiLineString](https://dev.mysql.com/doc/refman/5.7/en/gis-class-multilinestring.html) |
263-
| `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) |
264-
| `MultiPolygon(Polygon[])` | [MultiPolygon](https://dev.mysql.com/doc/refman/5.7/en/gis-class-multipolygon.html) |
265-
| `GeometryCollection(Geometry[])` | [GeometryCollection](https://dev.mysql.com/doc/refman/5.7/en/gis-class-geometrycollection.html) |
177+
| Grimzy\LaravelMysqlSpatial\Types | OpenGIS Class |
178+
| ------------------------------------------------------------ | ------------------------------------------------------------ |
179+
| `Point($lat, $lng)` | [Point](https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html) |
180+
| `MultiPoint(Point[])` | [MultiPoint](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipoint.html) |
181+
| `LineString(Point[])` | [LineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-linestring.html) |
182+
| `MultiLineString(LineString[])` | [MultiLineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multilinestring.html) |
183+
| `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) |
184+
| `MultiPolygon(Polygon[])` | [MultiPolygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipolygon.html) |
185+
| `GeometryCollection(Geometry[])` | [GeometryCollection](https://dev.mysql.com/doc/refman/8.0/en/gis-class-geometrycollection.html) |
266186

267187
Check out the [Class diagram](https://user-images.githubusercontent.com/1837678/30788608-a5afd894-a16c-11e7-9a51-0a08b331d4c4.png).
268188

@@ -290,17 +210,21 @@ for($polygon as $i => $linestring) {
290210

291211
```php
292212
// fromWKT($wkt)
293-
$polygon = Polygon::fromWKT('POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))');
213+
$point = Point::fromWKT('POINT(2 1)');
214+
$point->toWKT(); // POINT(2 1)
294215

216+
$polygon = Polygon::fromWKT('POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))');
295217
$polygon->toWKT(); // POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))
296218
```
297219

298220
##### From/To String
299221

300222
```php
301223
// fromString($wkt)
302-
$polygon = Polygon::fromString('(0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)');
224+
$point = new Point(1, 2); // lat, lng
225+
(string)$point // lng, lat: 2 1
303226

227+
$polygon = Polygon::fromString('(0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)');
304228
(string)$polygon; // (0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)
305229
```
306230

@@ -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)')
309233
The Geometry classes implement [`JsonSerializable`](http://php.net/manual/en/class.jsonserializable.php) and `Illuminate\Contracts\Support\Jsonable` to help serialize into GeoJSON:
310234

311235
```php
312-
$point = new Point(10, 20);
236+
$point = new Point(40.7484404, -73.9878441);
313237

314238
json_encode($point); // or $point->toJson();
315239

@@ -329,7 +253,7 @@ json_encode($point); // or $point->toJson();
329253
To deserialize a GeoJSON string into a Geometry class, you can use `Geometry::fromJson($json_string)` :
330254

331255
```php
332-
$locaction = Geometry::fromJson('{"type":"Point","coordinates":[3.4,1.2]}');
256+
$location = Geometry::fromJson('{"type":"Point","coordinates":[3.4,1.2]}');
333257
$location instanceof Point::class; // true
334258
$location->getLat(); // 1.2
335259
$location->getLng()); // 3.4
@@ -354,9 +278,127 @@ Available scopes:
354278
- `intersects($geometryColumn, $geometry)`
355279
- `overlaps($geometryColumn, $geometry)`
356280
- `doesTouch($geometryColumn, $geometry)`
281+
- `orderBySpatial($geometryColumn, $geometry, $orderFunction, $direction = 'asc')`
282+
- `orderByDistance($geometryColumn, ​$geometry, ​$direction = 'asc')`
283+
- `orderByDistanceSphere($geometryColumn, ​$geometry, ​$direction = 'asc')`
357284

358285
*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)).*
359286

287+
## Migrations
288+
289+
For Laravel versions prior to 5.5, you can use the Blueprint provided with this package: `Grimzy\LaravelMysqlSpatial\Schema\Blueprint`.
290+
291+
```php
292+
use Illuminate\Database\Migrations\Migration;
293+
use Grimzy\LaravelMysqlSpatial\Schema\Blueprint;
294+
295+
class CreatePlacesTable extends Migration {
296+
// ...
297+
}
298+
```
299+
300+
### Columns
301+
302+
Available [MySQL Spatial Types](https://dev.mysql.com/doc/refman/5.7/en/spatial-datatypes.html) migration blueprints:
303+
304+
- `$table->geometry('column_name')`
305+
- `$table->point('column_name')`
306+
- `$table->lineString('column_name')`
307+
- `$table->polygon('column_name')`
308+
- `$table->multiPoint('column_name')`
309+
- `$table->multiLineString('column_name')`
310+
- `$table->multiPolygon('column_name')`
311+
- `$table->geometryCollection('column_name')`
312+
313+
### Spatial indexes
314+
315+
You can add or drop spatial indexes in your migrations with the `spatialIndex` and `dropSpatialIndex` blueprints.
316+
317+
- `$table->spatialIndex('column_name')`
318+
- `$table->dropSpatialIndex(['column_name'])` or `$table->dropSpatialIndex('index_name')`
319+
320+
Note about spatial indexes from the [MySQL documentation](https://dev.mysql.com/doc/refman/5.7/en/creating-spatial-indexes.html):
321+
322+
> 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`.
323+
324+
Also please read this [**important note**](https://laravel.com/docs/5.5/migrations#indexes) regarding Index Lengths in the Laravel 5.6 documentation.
325+
326+
For example, as a follow up to the [Quickstart](#user-content-create-a-migration); from the command line, generate a new migration:
327+
328+
```shell
329+
php artisan make:migration update_places_table
330+
```
331+
332+
Then edit the migration file that you just created:
333+
334+
```php
335+
use Illuminate\Database\Migrations\Migration;
336+
use Illuminate\Database\Schema\Blueprint;
337+
use Illuminate\Support\Facades\Schema;
338+
339+
class UpdatePlacesTable extends Migration
340+
{
341+
/**
342+
* Run the migrations.
343+
*
344+
* @return void
345+
*/
346+
public function up()
347+
{
348+
// MySQL < 5.7.5: table has to be MyISAM
349+
// \DB::statement('ALTER TABLE places ENGINE = MyISAM');
350+
351+
Schema::table('places', function (Blueprint $table) {
352+
// Make sure point is not nullable
353+
$table->point('location')->change();
354+
355+
// Add a spatial index on the location field
356+
$table->spatialIndex('location');
357+
});
358+
}
359+
360+
/**
361+
* Reverse the migrations.
362+
*
363+
* @return void
364+
*/
365+
public function down()
366+
{
367+
Schema::table('places', function (Blueprint $table) {
368+
$table->dropSpatialIndex(['location']); // either an array of column names or the index name
369+
});
370+
371+
// \DB::statement('ALTER TABLE places ENGINE = InnoDB');
372+
373+
Schema::table('places', function (Blueprint $table) {
374+
$table->point('location')->nullable()->change();
375+
});
376+
}
377+
}
378+
```
379+
380+
## Tests
381+
382+
```shell
383+
composer test
384+
# or
385+
composer test:unit
386+
composer test:integration
387+
```
388+
389+
Integration tests require a running MySQL database. If you have Docker installed, you can start easily start one:
390+
391+
```shell
392+
make start_db # starts MySQL 8.0
393+
# or
394+
make start_db V=5.7 # starts a MySQL 5.7
395+
```
396+
397+
## Contributing
398+
399+
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.
400+
360401
## Credits
361402

362403
Originally inspired from [njbarrett's Laravel postgis package](https://github.com/njbarrett/laravel-postgis).
404+

composer.json

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
{
22
"name": "grimzy/laravel-mysql-spatial",
33
"description": "MySQL spatial data types extension for Laravel.",
4+
"scripts": {
5+
"test": "phpunit -c phpunit.xml.dist",
6+
"test:unit": "phpunit -c phpunit.xml.dist --testsuite unit",
7+
"test:integration": "phpunit -c phpunit.xml.dist --testsuite integration"
8+
},
49
"type": "library",
510
"license": "MIT",
611
"authors": [
@@ -11,6 +16,8 @@
1116
],
1217
"require": {
1318
"php": ">=5.5",
19+
"ext-pdo": "*",
20+
"ext-json": "*",
1421
"illuminate/database": "^5.2",
1522
"geo-io/wkb-parser": "^1.0",
1623
"jmikola/geojson": "^1.0"

phpunit.xml.dist

+7-2
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
processIsolation="false"
1010
stopOnFailure="false">
1111
<testsuites>
12-
<testsuite name="Unit Tests">
12+
<testsuite name="unit">
1313
<directory suffix="Test.php">./tests/Unit</directory>
1414
</testsuite>
15-
<testsuite name="Integration Tests">
15+
<testsuite name="integration">
1616
<directory suffix="Test.php">./tests/Integration</directory>
1717
</testsuite>
1818
</testsuites>
@@ -36,5 +36,10 @@
3636
<env name="CACHE_DRIVER" value="array"/>
3737
<env name="SESSION_DRIVER" value="array"/>
3838
<env name="QUEUE_DRIVER" value="sync"/>
39+
<env name="DB_DATABASE" value="spatial_test"/>
40+
<env name="DB_HOST" value="127.0.0.1"/>
41+
<env name="DB_PORT" value="3306"/>
42+
<env name="DB_USERNAME" value="root"/>
43+
<env name="DB_PASSWORD" value=""/>
3944
</php>
4045
</phpunit>

0 commit comments

Comments
 (0)