laravel-mysql-spatial

by grimzy

MySQL Spatial Data Extension integration with Laravel.

496 Stars 76 Forks Last release: 5 months ago (3.0.0) Other 187 Commits 24 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

Laravel MySQL Spatial extension

Build Status Code Climate Code Climate Packagist Packagist StyleCI license

Laravel package to easily work with MySQL Spatial Data Types and MySQL Spatial Functions.

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.

Versions

  • 1.x.x
    : MySQL 5.6 (also supports MySQL 5.5 but not all spatial analysis functions)
  • 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 for compatibility.

Installation

Add the package using composer:

$ composer require grimzy/laravel-mysql-spatial

For MySQL 5.7:

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

For MySQL 5.6 and 5.5:

$ 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
:
'providers' => [
  /*
   * Package Service Providers...
   */
  Grimzy\LaravelMysqlSpatial\SpatialServiceProvider::class,
],

Quickstart

Create a migration

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. For Laravel versions prior to 5.5, you can use the Blueprint provided by this package (Grimzy\LaravelMysqlSpatial\Schema\Blueprint):

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

// For Laravel < 5.5 // use Grimzy\LaravelMysqlSpatial\Schema\Blueprint;

class CreatePlacesTable extends Migration {

/**
 * Run the migrations.
 *
 * @return void
 */
public function up()
{
    Schema::create('places', function(Blueprint $table)
    {
        $table-&gt;increments('id');
        $table-&gt;string('name')-&gt;unique();
        // Add a Point spatial data field named location
        $table-&gt;point('location')-&gt;nullable();
        // Add a Polygon spatial data field named area
        $table-&gt;polygon('area')-&gt;nullable();
        $table-&gt;timestamps();
    });

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

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

/**
 * Reverse the migrations.
 *
 * @return void
 */
public function down()
{
    Schema::drop('places');
}

}

Run the migration:

php artisan migrate

Create a model

From the command line:

php artisan make:model Place

Then edit the model you just created. It must use the

SpatialTrait
and define an array called
$spatialFields
with the name of the MySQL Spatial Data field(s) created in the migration:
namespace App;

use Illuminate\Database\Eloquent\Model; use Grimzy\LaravelMysqlSpatial\Eloquent\SpatialTrait;

/**

  • @property \Grimzy\LaravelMysqlSpatial\Types\Point $location

  • @property \Grimzy\LaravelMysqlSpatial\Types\Polygon $area

  • / class Place extends Model { use SpatialTrait;

    protected $fillable = [

      'name'

    ];

    protected $spatialFields = [

      'location',
      'area'

    ]; }

Saving a model

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



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

// saving a point $place1->location = new Point(40.7484404, -73.9878441); // (lat, lng) $place1->save();

// saving a polygon $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) ])]); $place1->save();

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

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

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

// 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

$place2 = Place::first();
$lat = $place2->location->getLat(); // 40.7484404
$lng = $place2->location->getLng(); // -73.9878441

Geometry classes

Available Geometry classes

| Grimzy\LaravelMysqlSpatial\Types | OpenGIS Class | | ------------------------------------------------------------ | ------------------------------------------------------------ | |

Point($lat, $lng, $srid = 0)
| Point | |
MultiPoint(Point[], $srid = 0)
| MultiPoint | |
LineString(Point[], $srid = 0)
| LineString | |
MultiLineString(LineString[], $srid = 0)
| MultiLineString | |
Polygon(LineString[], $srid = 0)
(exterior and interior boundaries) | Polygon | |
MultiPolygon(Polygon[], $srid = 0)
| MultiPolygon | |
GeometryCollection(Geometry[], $srid = 0)
| GeometryCollection |

Check out the Class diagram.

Using Geometry classes

In order for your Eloquent Model to handle the Geometry classes, it must use the

Grimzy\LaravelMysqlSpatial\Eloquent\SpatialTrait
trait and define a
protected
property
$spatialFields
as an array of MySQL Spatial Data Type column names (example in Quickstart).

IteratorAggregate and ArrayAccess

The collection Geometries (

LineString
,
Polygon
,
MultiPoint
,
MultiLineString
, and
GeometryCollection
) implement
IteratorAggregate
and
ArrayAccess
; making it easy to perform Iterator and Array operations. For example:
$polygon = $multipolygon[10];   // ArrayAccess

// IteratorAggregate for($polygon as $i => $linestring) { echo (string) $linestring; }

Helpers

From/To Well Known Text (WKT)
// fromWKT($wkt, $srid = 0)
$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))

From/To String
// fromString($wkt, $srid = 0)
$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)

From/To JSON (GeoJSON)

The Geometry classes implement

JsonSerializable
and

Illuminate\Contracts\Support\Jsonable
to help serialize into GeoJSON:
$point = new Point(40.7484404, -73.9878441);

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

// { // "type": "Feature", // "properties": {}, // "geometry": { // "type": "Point", // "coordinates": [ // -73.9878441, // 40.7484404 // ] // } // }

To deserialize a GeoJSON string into a Geometry class, you can use

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

Scopes: Spatial analysis functions

Spatial analysis functions are implemented using Eloquent Local Scopes.

Available scopes:

  • distance($geometryColumn, $geometry, $distance)
  • distanceExcludingSelf($geometryColumn, $geometry, $distance)
  • distanceSphere($geometryColumn, $geometry, $distance)
  • distanceSphereExcludingSelf($geometryColumn, $geometry, $distance)
  • comparison($geometryColumn, $geometry, $relationship)
  • within($geometryColumn, $polygon)
  • crosses($geometryColumn, $geometry)
  • contains($geometryColumn, $geometry)
  • disjoint($geometryColumn, $geometry)
  • equals($geometryColumn, $geometry)
  • 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).

Migrations

For Laravel versions prior to 5.5, you can use the Blueprint provided with this package:

Grimzy\LaravelMysqlSpatial\Schema\Blueprint
.
use Illuminate\Database\Migrations\Migration;
use Grimzy\LaravelMysqlSpatial\Schema\Blueprint;

class CreatePlacesTable extends Migration { // ... }

Columns

Available MySQL Spatial Types migration blueprints:

  • $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

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:

For

MyISAM
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 regarding Index Lengths in the Laravel 5.6 documentation.

For example, as a follow up to the Quickstart; from the command line, generate a new migration:

php artisan make:migration update_places_table

Then edit the migration file that you just created:

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-&gt;point('location')-&gt;change();

        // Add a spatial index on the location field
        $table-&gt;spatialIndex('location');
    });
}

/**
 * Reverse the migrations.
 *
 * @return void
 */
public function down()
{
    Schema::table('places', function (Blueprint $table) {
        $table-&gt;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-&gt;point('location')-&gt;nullable()-&gt;change();
    });
}

}

Tests

$ 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:

$ make start_db     # starts MySQL 8.0
# or
$ make start_db V=5.7   # starts 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.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.