angel-source-labs/laravel-spatial

Laravel的空间数据类型扩展。

v1.1 2023-04-29 04:05 UTC

This package is auto-updated.

Last update: 2024-09-06 07:13:03 UTC


README

license PHPUnit tests

Laravel包,用于轻松地在PostGIS、MySQL 5.7和MySQL 8中处理GIS数据类型。

支持的兼容性

Laravel

此包针对以下Laravel版本进行了测试

  • Laravel 6.x
  • Laravel 7.x
  • Laravel 8.x
  • Laravel 9.x
  • Laravel 10.x

数据库

此包针对以下数据库进行了测试

未来

这些数据库的支持可能在未来版本中提供。此包已设计为支持这些数据库,但工作尚未完成。

  • SQLServer
  • SQLite

历史和动机

我们非常喜欢grimzy/laravel-mysql-spatial Laravel Eloquent API,并且我们希望也能使用Postgis。(参见问题137)。此包的目标是提供一个与grimzy/laravel-mysql-spatial包兼容的API,同时也支持Postgis和额外的数据库驱动程序。

此包是grimzy/laravel-mysql-spatial的分支和大量重构

  • 重构以使用laravel-expressions提供Postgis、MySQL 8和MySQL 5.7之间的数据库兼容性
  • 重构以在PHPUnit测试中使用orchestra/testbench
  • PHPUnit测试已更新为使用PHPUnit 9.x而不是PHPUnit 6.x

历史上,grimzy/laravel-mysql-spatial本身是njbarrett/laravel-postgis的分支,现在是mstaack/laravel-postgis。这些laravel-postgis包提供了对Postgis的访问,但不提供grimzy/laravel-mysql-spatial添加的Laravel Eloquent空间分析函数。

安装

使用composer添加包

$ composer require angel-source-labs/laravel-spatial

快速入门

创建迁移

从命令行

php artisan make:migration create_places_table

然后编辑您刚刚创建的迁移,添加至少一个空间数据字段。

use Illuminate\Database\Migrations\Migration;
// use SpatialBlueprint for Spatial features and for proper code completion
use AngelSourceLabs\LaravelSpatial\Schema\SpatialBlueprint as Blueprint;


class CreatePlacesTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('places', function(Blueprint $table)
        {
            $table->increments('id');
            $table->string('name')->unique();
            // Add a Point spatial data field named location
            $table->point('location')->nullable();
            // Add a Polygon spatial data field named area
            $table->polygon('area')->nullable();
            $table->timestamps();
        });
  
        // Or create the spatial fields with an SRID (e.g. 4326 WGS84 spheroid)
  
         Schema::create('places_with_srid', 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();
         });
         
         // In Postgis, you can also create spatial fields that are Geography types instead of Geometry types
          Schema::create('places_with_geography', 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, Blueprint::GEOGRAPHY)->nullable();
             // Add a Polygon spatial data field named area with SRID 4326
             $table->polygon('area', 4326, Blueprint::GEOGRAPHY)->nullable();
             $table->timestamps();
         });        
    }

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

运行迁移

php artisan migrate

创建模型

从命令行

php artisan make:model Place

然后编辑您刚刚创建的模型。它必须使用SpatialTrait并定义一个名为$spatialFields的数组,其中包含在迁移中创建的空间数据字段名称

namespace App;

use AngelSourceLabs\LaravelSpatial\Eloquent\SpatialTrait;
use Illuminate\Database\Eloquent\Model;

/**
 * @property \AngelSourceLabs\LaravelSpatial\Types\Point   $location
 * @property \AngelSourceLabs\LaravelSpatial\Types\Polygon $area
 */
class Place extends Model
{
    use SpatialTrait;

    protected $fillable = [
        'name'
    ];

    protected $spatialFields = [
        'location',
        'area'
    ];
}

保存模型

use AngelSourceLabs\LaravelSpatial\Types\LineString;
use AngelSourceLabs\LaravelSpatial\Types\Point;
use AngelSourceLabs\LaravelSpatial\Types\Polygon;

$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();

或者如果您的数据库字段是用特定的SRID创建的

use AngelSourceLabs\LaravelSpatial\Types\LineString;
use AngelSourceLabs\LaravelSpatial\Types\Point;
use AngelSourceLabs\LaravelSpatial\Types\Polygon;

$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();

注意:当保存集合几何体(LineStringPolygonMultiPointMultiLineStringGeometryCollection)时,仅顶部几何体应在构造函数中设置SRID。

在上面的示例中,当创建一个new Polygon()时,我们只设置了Polygon上的SRID,并使用默认值用于LineStringPoint对象。

检索模型

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

几何类

可用的几何类

@startuml
interface GeometryInterface
interface Jsonable
Interface JsonSerializable
interface Arrayable
interface IteratorAggregate
Interface Countable
Interface ArrayAccess
abstract Class Geometry
Class GeometryCollection
Class Point
abstract Class PointCollection
Class MultiLineString
Class MultiPolygon
Class MultiPoint
Class LineString
Class Polygon

Jsonable <|.. Geometry
JsonSerializable <|.. Geometry
GeometryInterface <|.. Geometry 
  Geometry <|-- Point
  Geometry <|-- GeometryCollection
    Arrayable <|.. GeometryCollection
    IteratorAggregate <|.. GeometryCollection
    Countable  <|.. GeometryCollection
    ArrayAccess  <|.. GeometryCollection
      GeometryCollection <|-- PointCollection
        PointCollection <|-- MultiPoint
        PointCollection <|-- LineString
      GeometryCollection <|-- MultiLineString
        MultiLineString <|-- Polygon  
      GeometryCollection <|-- MultiPolygon



使用几何类

为了使您的Eloquent模型处理几何类,它必须使用AngelSourceLabs\LaravelSpatial\Eloquent\SpatialTrait特性,并定义一个protected属性$spatialFields,该属性为一个包含空间数据类型列名的数组(示例请见快速入门)。

IteratorAggregate和ArrayAccess

几何集合(LineStringPolygonMultiPointMultiLineStringGeometryCollection)实现了IteratorAggregateArrayAccess,这使得进行迭代器和数组操作变得容易。例如

$polygon = $multipolygon[10];	// ArrayAccess

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

辅助函数

从/到已知文本(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))
从/到字符串
// 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)
从/到JSON(GeoJSON

几何类实现了JsonSerializableIlluminate\Contracts\Support\Jsonable,以帮助将其序列化为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
//     ]
//   }
// }

要将GeoJSON字符串反序列化为几何类,可以使用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

作用域:空间分析函数

空间分析函数是通过使用Eloquent本地作用域实现的。

可用作用域

  • 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')

请注意,空间分析函数的行为和可用性在每个数据库和数据库版本中都有所不同。

空间函数参考

迁移

use AngelSourceLabs\LaravelSpatial\Schema\SpatialBlueprint;
use Illuminate\Database\Migrations\Migration;


class CreatePlacesTable extends Migration {
    // ...
}

可用的空间类型迁移蓝图

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

空间类型参考

空间索引

您可以在迁移中使用spatialIndexdropSpatialIndex蓝图来添加或删除空间索引。

  • $table->spatialIndex('column_name')
  • $table->dropSpatialIndex(['column_name'])$table->dropSpatialIndex('index_name')

关于空间索引的说明,请见MySQL文档

对于MyISAM和(从MySQL 5.7.5开始)InnoDB表,MySQL可以使用类似于创建常规索引的语法创建空间索引,但使用SPATIAL关键字。空间索引中的列必须声明为NOT NULL

请务必阅读这篇有关 Laravel 文档中索引长度的重要说明(点击查看)

例如,作为快速入门的后续步骤;在命令行中,生成一个新的迁移文件

php artisan make:migration update_places_table

然后编辑您刚才创建的迁移文件

use Illuminate\Database\Migrations\Migration;
use AngelSourceLabs\LaravelSpatial\Schema\SpatialBlueprint as 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();
        });
    }
}

测试

启动用于测试的 MySQL 5.7、MySQL8 和 Postgis Docker 容器。

$ composer docker

运行测试。单元测试可以在没有数据库服务器 Docker 容器的情况下运行。

$ composer test
# or 
$ composer test:unit
$ composer test:integration

贡献

欢迎提出建议和拉取请求!带有测试的拉取请求是最好的!还有许多空间函数需要实现或以创新的方式使用空间函数。

鸣谢

本项目的灵感来源于 grimzy/laravel-mysql-spatialnjbarrett 的 Laravel postgis 包

许可证

为 Laravel 的空间函数是开源软件,使用 MIT 许可证授权。