Skip to content

MySQL 8 SRID support #119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Apr 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4db0a4a
Updated Blueprint to accept srid for all geometry types
grimzy Feb 7, 2019
ccce934
Added tests for migrations. Abstracted createApplication process in i…
grimzy Feb 7, 2019
cb05acf
Added SRID to GeometryInterface and Geometry models. Updated query bu…
grimzy Feb 7, 2019
bf7888f
Fixed unit tests
grimzy Feb 8, 2019
eae31fa
Merge branch 'master' into srid
sikhlana Dec 7, 2019
0f94263
WKB parser now parses the SRID of the given WKB value.
sikhlana Dec 7, 2019
7d0578b
Fixes tests.
sikhlana Dec 7, 2019
2fbd40d
Updates dependency constraints.
sikhlana Dec 7, 2019
aa2a28a
Removes PHP 7.4 from .travis.yml
sikhlana Dec 7, 2019
81b8305
Merge branch 'srid-fix'
sikhlana Dec 7, 2019
11a6904
That was a weird condition.
sikhlana Dec 8, 2019
de5bd4d
Merge branch 'srid'
sikhlana Dec 8, 2019
c6462ff
Whops!
sikhlana Dec 8, 2019
01caece
Merge branch 'srid'
sikhlana Dec 8, 2019
e056dc3
Adds a new option to st_geomfromtext to make sure MySQL understands t…
sikhlana Dec 8, 2019
fe7d6bf
Merge branch 'srid'
sikhlana Dec 8, 2019
48c3554
Merge pull request #111 from sikhlana/srid
grimzy Feb 29, 2020
e4879ed
Apply fixes from StyleCI
grimzy Feb 29, 2020
3578a93
Fix versions after merging PR
grimzy Feb 29, 2020
aad71bf
Fixes for Laravel 5.2+/MySQL 8:
grimzy Feb 29, 2020
8fc0019
Apply fixes from StyleCI
grimzy Feb 29, 2020
b636f3c
Fixed MySqlGrammar::modifySrid()
grimzy Feb 29, 2020
db42f20
Fixed MySqlGrammar::__construct()
grimzy Feb 29, 2020
1314606
Merge remote-tracking branch 'origin/master' into srid
grimzy Mar 9, 2020
b9e6f4a
Apply fixes from StyleCI
grimzy Mar 9, 2020
eb01c85
Updated unit tests
grimzy Mar 9, 2020
9bbadd7
Updated doc :book:
grimzy Apr 14, 2020
e3bcd37
Updated links in doc :book:
grimzy Apr 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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