Skip to content

Commit

Permalink
Merge pull request #119 from grimzy/srid
Browse files Browse the repository at this point in the history
MySQL 8 SRID support
  • Loading branch information
grimzy authored Apr 14, 2020
2 parents 7f62564 + e3bcd37 commit 49b0daf
Show file tree
Hide file tree
Showing 34 changed files with 895 additions and 504 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ php:
- '7.0'
- '7.1'
- '7.2'
- '7.3'

env:
- MYSQL_VERSION=5.7
- MYSQL_VERSION=8.0

dist: trusty
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
V=5.7
V=8.0
DB_DIR=$(shell pwd)/_db-$(V)
mV=10.3
mDB_DIR=$(shell pwd)/_db-$(mV)
Expand Down
122 changes: 84 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,29 @@ Please check the documentation for your MySQL version. MySQL's Extension for Spa
**Versions**

- `1.x.x`: MySQL 5.6 (also supports MySQL 5.5 but not all spatial analysis functions)
- `2.x.x`: MySQL 5.7 and 8.0
- `2.x.x`: MySQL 5.7
- **`3.x.x`: MySQL 8.0 with SRID support (Current branch)**

This package also works with MariaDB. Please refer to the [MySQL/MariaDB Spatial Support Matrix](https://mariadb.com/kb/en/library/mysqlmariadb-spatial-support-matrix/) for compatibility.

## Installation

Add the package using composer:

```sh
$ composer require grimzy/laravel-mysql-spatial
```

For MySQL 5.7:

```shell
composer require grimzy/laravel-mysql-spatial
$ composer require grimzy/laravel-mysql-spatial:^2.0
```

For MySQL 5.6 and 5.5:

```shell
composer require grimzy/laravel-mysql-spatial:^1.0
$ composer require grimzy/laravel-mysql-spatial:^1.0
```

For Laravel versions before 5.5 or if not using auto-discovery, register the service provider in `config/app.php`:
Expand Down Expand Up @@ -80,6 +87,19 @@ class CreatePlacesTable extends Migration {
$table->polygon('area')->nullable();
$table->timestamps();
});

// Or create the spatial fields with an SRID (e.g. 4326 WGS84 spheroid)

// Schema::create('places', function(Blueprint $table)
// {
// $table->increments('id');
// $table->string('name')->unique();
// // Add a Point spatial data field named location with SRID 4326
// $table->point('location', 4326)->nullable();
// // Add a Polygon spatial data field named area with SRID 4326
// $table->polygon('area', 4326)->nullable();
// $table->timestamps();
// });
}

/**
Expand Down Expand Up @@ -158,11 +178,37 @@ $place1->area = new Polygon([new LineString([
new Point(40.74894149554006, -73.98615270853043)
])]);
$place1->save();
```

Or if your database fields were created with a specific SRID:

```php
use Grimzy\LaravelMysqlSpatial\Types\Point;
use Grimzy\LaravelMysqlSpatial\Types\Polygon;
use Grimzy\LaravelMysqlSpatial\Types\LineString;

$place1 = new Place();
$place1->name = 'Empire State Building';

$place1->area = new Polygon();
// saving a point with SRID 4326 (WGS84 spheroid)
$place1->location = new Point(40.7484404, -73.9878441, 4326); // (lat, lng, srid)
$place1->save();

// saving a polygon with SRID 4326 (WGS84 spheroid)
$place1->area = new Polygon([new LineString([
new Point(40.74894149554006, -73.98615270853043),
new Point(40.74848633046773, -73.98648262023926),
new Point(40.747925497790725, -73.9851602911949),
new Point(40.74837050671544, -73.98482501506805),
new Point(40.74894149554006, -73.98615270853043)
])], 4326);
$place1->save();
```

> **Note**: When saving collection Geometries (`LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, and `GeometryCollection`), only the top-most geometry should have an SRID set in the constructor.
>
> In the example above, when creating a `new Polygon()`, we only set the SRID on the `Polygon` and use the default for the `LineString` and the `Point` objects.
### Retrieving a model

```php
Expand All @@ -177,13 +223,13 @@ $lng = $place2->location->getLng(); // -73.9878441

| 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) |
| `Point($lat, $lng, $srid = 0)` | [Point](https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html) |
| `MultiPoint(Point[], $srid = 0)` | [MultiPoint](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipoint.html) |
| `LineString(Point[], $srid = 0)` | [LineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-linestring.html) |
| `MultiLineString(LineString[], $srid = 0)` | [MultiLineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multilinestring.html) |
| `Polygon(LineString[], $srid = 0)` *([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[], $srid = 0)` | [MultiPolygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipolygon.html) |
| `GeometryCollection(Geometry[], $srid = 0)` | [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).

Expand All @@ -193,7 +239,7 @@ In order for your Eloquent Model to handle the Geometry classes, it must use the

#### IteratorAggregate and ArrayAccess

The "composite" Geometries (`LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, and `GeometryCollection`) implement [`IteratorAggregate`](http://php.net/manual/en/class.iteratoraggregate.php) and [`ArrayAccess`](http://php.net/manual/en/class.arrayaccess.php); making it easy to perform Iterator and Array operations. For example:
The collection Geometries (`LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, and `GeometryCollection`) implement [`IteratorAggregate`](http://php.net/manual/en/class.iteratoraggregate.php) and [`ArrayAccess`](http://php.net/manual/en/class.arrayaccess.php); making it easy to perform Iterator and Array operations. For example:

```php
$polygon = $multipolygon[10]; // ArrayAccess
Expand All @@ -207,10 +253,10 @@ for($polygon as $i => $linestring) {

#### Helpers

##### From/To Well Known Text ([WKT](https://dev.mysql.com/doc/refman/5.7/en/gis-data-formats.html#gis-wkt-format))
##### From/To Well Known Text ([WKT](https://dev.mysql.com/doc/refman/8.0/en/gis-data-formats.html#gis-wkt-format))

```php
// fromWKT($wkt)
// fromWKT($wkt, $srid = 0)
$point = Point::fromWKT('POINT(2 1)');
$point->toWKT(); // POINT(2 1)

Expand All @@ -221,9 +267,9 @@ $polygon->toWKT(); // POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))
##### From/To String

```php
// fromString($wkt)
// fromString($wkt, $srid = 0)
$point = new Point(1, 2); // lat, lng
(string)$point // lng, lat: 2 1
(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)
Expand Down Expand Up @@ -255,9 +301,9 @@ To deserialize a GeoJSON string into a Geometry class, you can use `Geometry::fr

```php
$location = Geometry::fromJson('{"type":"Point","coordinates":[3.4,1.2]}');
$location instanceof Point::class; // true
$location->getLat(); // 1.2
$location->getLng()); // 3.4
$location instanceof Point::class; // true
$location->getLat(); // 1.2
$location->getLng()); // 3.4
```

## Scopes: Spatial analysis functions
Expand All @@ -280,10 +326,10 @@ Available scopes:
- `overlaps($geometryColumn, $geometry)`
- `doesTouch($geometryColumn, $geometry)`
- `orderBySpatial($geometryColumn, $geometry, $orderFunction, $direction = 'asc')`
- `orderByDistance($geometryColumn, $geometry, $direction = 'asc')`
- `orderByDistanceSphere($geometryColumn, $geometry, $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)).*
*Note that behavior and availability of MySQL spatial analysis functions differs in each MySQL version (cf. [documentation](https://dev.mysql.com/doc/refman/8.0/en/spatial-function-reference.html)).*

## Migrations

Expand All @@ -300,16 +346,16 @@ class CreatePlacesTable extends Migration {

### Columns

Available [MySQL Spatial Types](https://dev.mysql.com/doc/refman/5.7/en/spatial-datatypes.html) migration blueprints:
Available [MySQL Spatial Types](https://dev.mysql.com/doc/refman/8.0/en/spatial-type-overview.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')`
- `$table->geometry(string $column_name, int $srid = 0)`
- `$table->point(string $column_name, int $srid = 0)`
- `$table->lineString(string $column_name, int $srid = 0)`
- `$table->polygon(string $column_name, int $srid = 0)`
- `$table->multiPoint(string $column_name, int $srid = 0)`
- `$table->multiLineString(string $column_name, int $srid = 0)`
- `$table->multiPolygon(string $column_name, int $srid = 0)`
- `$table->geometryCollection(string $column_name, int $srid = 0)`

### Spatial indexes

Expand All @@ -318,9 +364,9 @@ You can add or drop spatial indexes in your migrations with the `spatialIndex` a
- `$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):
Note about spatial indexes from the [MySQL documentation](https://dev.mysql.com/doc/refman/8.0/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`.
> For [`MyISAM`](https://dev.mysql.com/doc/refman/8.0/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.

Expand Down Expand Up @@ -381,18 +427,18 @@ class UpdatePlacesTable extends Migration
## Tests

```shell
composer test
$ composer test
# or
composer test:unit
composer test:integration
$ 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
$ make start_db # starts MySQL 8.0
# or
make start_db V=5.7 # starts a MySQL 5.7
$ make start_db V=5.7 # starts MySQL 5.7
```

## Contributing
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
"dev-master": "3.0.x-dev"
},
"laravel": {
"providers": [
Expand Down
14 changes: 10 additions & 4 deletions src/Eloquent/BaseBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ class BaseBuilder extends QueryBuilder
{
protected function cleanBindings(array $bindings)
{
$bindings = array_map(function ($binding) {
return $binding instanceof SpatialExpression ? $binding->getSpatialValue() : $binding;
}, $bindings);
$spatialBindings = [];
foreach ($bindings as &$binding) {
if ($binding instanceof SpatialExpression) {
$spatialBindings[] = $binding->getSpatialValue();
$spatialBindings[] = $binding->getSrid();
} else {
$spatialBindings[] = $binding;
}
}

return parent::cleanBindings($bindings);
return parent::cleanBindings($spatialBindings);
}
}
7 changes: 6 additions & 1 deletion src/Eloquent/SpatialExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ class SpatialExpression extends Expression
{
public function getValue()
{
return 'ST_GeomFromText(?)';
return "ST_GeomFromText(?, ?, 'axis-order=long-lat')";
}

public function getSpatialValue()
{
return $this->value->toWkt();
}

public function getSrid()
{
return $this->value->getSrid();
}
}
24 changes: 16 additions & 8 deletions src/Eloquent/SpatialTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,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,
]);

Expand All @@ -148,8 +149,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;
Expand All @@ -165,17 +167,19 @@ 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(),
]);
}

public function scopeDistanceSphere($query, $geometryColumn, $geometry, $distance)
{
$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,
]);

Expand All @@ -188,8 +192,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;
Expand All @@ -204,8 +209,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(),
]);
}

Expand All @@ -217,8 +223,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;
Expand Down Expand Up @@ -272,8 +279,9 @@ public function scopeOrderBySpatial($query, $geometryColumn, $geometry, $orderFu
throw new UnknownSpatialFunctionException($orderFunction);
}

$query->orderByRaw("st_{$orderFunction}(`$geometryColumn`, ST_GeomFromText(?)) {$direction}", [
$query->orderByRaw("st_{$orderFunction}(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) {$direction}", [
$geometry->toWkt(),
$geometry->getSrid(),
]);

return $query;
Expand Down
Loading

0 comments on commit 49b0daf

Please sign in to comment.