clickbar/laravel-magellan

此包为在 Laravel 中使用 postgis 扩展提供功能。

1.6.1 2024-08-08 20:44 UTC

README







一个现代化的 Laravel PostGIS 工具箱


Latest Version on Packagist Total Downloads GitHub Tests Action Status GitHub Code Style Action Status

简介

每一位水手都需要一艘美丽的船来航行七大洋 ⛵️

此包将帮助您在 Laravel 中访问与 PostGIS 相关的功能。它受到了 mstaack/laravel-postgis 的强烈启发,但自从那时起已经发展得更加完善。除了一些轻微的变化外,您应该很快就能熟悉 Magellan。

Magellan 内置了桨,并提供 GeoJson、WKB & WKT 的解析器/生成器。您可以通过使用我们的构建器函数,轻松在迁移中使用所有 PostGIS 数据类型,并避免使用原始 SQL 访问 PostGIS 函数。

此外,laravel-magellan 为 Schema、Query Builder 和 Postgres Grammar 提供了扩展,以便轻松访问像 ST_EXTENT 这样的 PostGIS 数据库函数。它所有这些操作都不会破坏与其他包的兼容性,例如需要扩展语法和连接的 tpetry/laravel-postgresql-enhanced

要求

Magellan 支持 Laravel 项目,这些项目满足以下要求

  • Laravel ^9.28^10.0
  • PHP ^8.1

安装

您可以通过 composer 安装此包

composer require clickbar/laravel-magellan

您可以使用以下命令发布和运行迁移

php artisan vendor:publish --tag="magellan-migrations"
php artisan migrate

您可以使用以下命令发布配置文件

php artisan vendor:publish --tag="magellan-config"

您可以在以下位置找到已发布的配置文件的内容: config/magellan.php

包含内容

  • 迁移 Schema 蓝图
  • 几何数据类
  • WKT 生成器 & 解析器
  • WKB 生成器 & 解析器
  • GeoJson 生成器 & 解析器
  • Eloquent 模型特性
  • 自动将 PostGIS 特性添加到模型中的命令
  • 使用不同投影自动转换插入
  • GeoJson 请求验证规则
  • 为表单请求转换几何形状
  • 几乎将所有 PostGIS 函数作为类型化函数暴露,可用于 select、where、orderBy、groupBy、having、from
  • 几何和 BBox 转换类
  • 使用返回几何或 bbox 的函数时自动转换
  • 空几何形状支持
  • 自定义更新 Builder 方法以提高转换安全性
  • 自动 PostGIS 函数文档生成器
  • BBox 支持在 $postgisColumns & 特性中(目前仅支持转换)
  • 自定义几何工厂 & 模型
  • 更多测试
  • ...

开始之前

我们强烈建议使用 barryvdh 的 laravel-ide-helper,以便能够在 IDEs 的自动完成中查看所有包含的内容。

使用 PostGIS 列创建表

Laravel-magellan 扩展了默认的 Schema Blueprint 以包含所有 PostGIS 函数。由于 Laravel 已经引入了基本的几何支持,所有方法都以前缀 magellan 开头。例如:

$table->magellanPoint('location', 4326);

List of all schema methods

准备模型

为了正确地将所有内容集成到模型中,您需要执行以下 2 个步骤

  1. HasPostgisColumns 特性添加到您的模型中
  2. $postgisColumns 数组添加到模型中
protected array $postgisColumns = [
        'location' => [
            'type' => 'geometry',
            'srid' => 4326,
        ],
    ];

以下命令可以自动化这两个步骤:

php artisan magellan:update-postgis-columns

该命令会自动扫描数据库并添加特性和数组。

使用几何数据类

我们包括以下常见几何形状的数据类:

  • 线字符串
  • 多边形
  • 多点
  • 多线字符串
  • 多多边形
  • 几何集合

要手动创建几何对象,请使用相应的 <GeometryClass>::make 方法。例如:

$point = Point::make(51.087, 8.76);

您会注意到,对于点类,有3种不同的带有不同参数的 make 方法

  1. make(...)
  2. makeGeodetic(...)
  3. makeEmpty(...)

让我们更详细地看看前两种

这是默认的工厂方法,可用于填充所有可能的值。该方法被认为是“普通”方式。当您使用非 lng/lat 投影(例如,不是 WGS84:srid=4326)时,应考虑使用此方法。

function make(float $x, float $y, ?float $z = null, ?float $m = null, ?int $srid = null): self

大多数常见的网络使用案例都使用 WGS84 投影。因此,大多数情况下,使用的术语将是纬度、经度和高度,而不是 x、y 和 z。为了提供更多便利,我们包括了一个接受这些术语并自动将 srid 设置为默认大地测量 srid 的工厂方法,该 srid 可以在配置文件中设置。

function makeGeodetic(float $latitude, float $longitude, ?float $altitude = null, ?float $m = null): self

当使用使用大地测量投影的点类时,您可以通过具有适当名称的获取器和设置器访问纬度、经度和高度

  • function getLatitude(): float
  • function setLatitude(float $latitude): void
  • function getLongitude(): float
  • function setLongitude(float $longitude): void
  • function getAltitude(): ?float
  • function setAltitude(float $altitude): void

如果尝试在没有在 geodetic_srids 配置中列出 srid 的点上使用此函数,将抛出异常。请改用默认的 x、y、z、m 获取器和设置器。

生成器和解析器

我们目前提供以下格式的解析器和生成器:

  • EWKB
  • EWKT
  • GeoJson

这些也用于将我们的数据类格式化为字符串,将数据库返回的值(以 EWKB 格式)转换为字符串,并将数据输出到前端,例如 GeoJson。

注意 在以下内容中,我们将 EWKB & WBK 或 EWKT & WKT 互换使用,尽管我们总是使用每个的扩展版本。

配置文件允许您自定义您希望使用的表示方式,例如,当对数据类进行 JSON 序列化时,GeoJson 是默认的。

$point = Point::makeGeodetic(51.087, 8.76);

json_encode($point); // returns GeoJson
// "{"type":"Point","coordinates":[8.76,51.087]}"

您始终可以使用每个解析器/生成器的实例自行进行解析/生成。
虽然生成器必须在需要时创建,但解析器已在应用容器中作为单例实例化,您可以使用如下方式使用它们:

$parser = app(WKTParser::class);

$point = $parser->parse('SRID=4326;POINT (2, 2)');

$generator = new WKBGenerator();

$generator->generate($point);
// "0101000020E610000000000000000000400000000000000040"

在此示例中,我们获取 WKTParser 的实例并将字符串转换为我们的数据类之一。$point 是一个有效的 Point 实例,然后我们可以使用其他生成器,例如 WKBGenerator,将 $point 输出为十六进制 WKB 格式。

请求验证和转换

当一个表单请求包含 Geojson 格式的几何形状时,您可以使用 GeometryGeojsonRule 进行验证。您甚至可以通过传递包含类的数组来限制允许的几何形状类型。

为了正确地继续处理接收到的几何形状,您可以使用 TransformsGeojsonGeometry 特性来自动将 geojson 转换为正确的几何对象。因此,请返回 geometries(): array 函数中的键。

注意 目前我们只支持简单的字段转换。数组和通配符表示支持将随后到来。

class StorePortRequest extends FormRequest
{
    use TransformsGeojsonGeometry;

    public function rules(): array
    {
        return [
            'name' => ['required', 'string'],
            'country' => ['required', 'string'],
            'location' => ['required', new GeometryGeojsonRule([Point::class])],
        ];
    }

    public function geometries(): array
    {
        return ['location'];
    }
}

与数据库交互

示例设置

出于演示目的,我们考虑以下虚构场景:

我们是一名帆船爱好者,拥有一艘可爱的帆船和世界各地的几个港口的数据库。
对于每个端口,我们存储名称、国家和位置。

以下是用于创建端口表的迁移操作

Schema::create('ports', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('country');
    $table->magellanPoint('location');
    $table->timestamps();
});

以及模型实现

class Port extends Model
{
    use HasFactory;
    use HasPostgisColumns;

    protected $guarded = [];

    protected array $postgisColumns = [
        'location' => [
            'type' => 'geometry',
            'srid' => 4326,
        ],
    ];
}

插入/更新

只要在受影响模型的 $postgisColumns 中指定,Magellan 几何对象就可以直接插入。在我们的例子中,我们可以这样插入一个新的端口

Port::create([
    'name' => 'Magellan Home Port',
    'country' => 'Germany',
    'location' => Point::makeGeodetic(49.87108851299202, 8.625026485851762),
]);

当您想更新一个几何对象时,您可以将新位置分配给模型并调用 save(),或者在使用查询构建器的 update() 方法上使用

$port->location = Point::makeGeodetic(55, 11);
$port->save();

// -- or --

Port::where('name', 'Magellan Home Port')
    ->update(['location' => Point::makeGeodetic(55, 11)]);

具有不同 SRID 的插入/更新

从外部系统获取几何对象时,您可能收到与数据库中不同的投影。考虑我们想要插入或更新一个具有不同 SRID 的几何对象

Port::create([
    'name' => 'Magellan Home Port',
    'country' => 'Germany',
    'location' => Point::make(473054.9891044726, 5524365.310057224, srid: 25832),
]);

// -- or --

$port = Port::find(1);
$port->location = Point::make(473054.9891044726, 5524365.310057224, srid: 25832);
$port->save();

由于我们的端口表使用 SRID=4326 的点,Magellan 将引发错误

SRID 不匹配:数据库具有 SRID 4326,几何对象具有 SRID 25832。考虑启用 magellan.eloquent.transform_to_database_projection 以应用自动转换

我们包含了一个自动转换选项,它可以直接为您应用 ST_Transform(geometry, databaseSRID)

注意
此选项仅当直接在 eloquent 模型上插入/更新时应用。
此选项不应用于地理列。

选择

当从使用 HasPostgisColumns 特性的模型中选择数据时,所有属性都将直接解析到内部数据类中

$port = Port::first();
dd($port->location);
Clickbar\Magellan\Data\Geometries\Point {#1732
  #srid: 4326
  #dimension: Clickbar\Magellan\Data\Geometries\Dimension {#740
    +name: "DIMENSION_2D"
    +value: "2D"
  }
  #x: 8.6250264858452
  #y: 49.87108851299
  #z: null
  #m: null
}

可能存在您还想要使用 box2d 或 box3d 作为列类型的情况。目前,我们不支持在 $postgisColumns 中的盒子。请使用 BBoxCast

在查询中使用 PostGIS 函数

laravel-magallan 的一大特点是其广泛的查询构建功能。为了提供无缝且易于使用 PostGIS 函数,我们包括了一组广泛的应用通常以 ST-前缀的函数,可以直接与 Laravel 的查询构建器一起使用。

每次您想要在查询构建器上使用 PostGIS 函数时,您都必须使用我们的构建器方法之一。所有这些方法都以 st 为前缀。
我们目前提供以下功能

  • stSelect
  • stWhere
  • stOrWhere
  • stOrderBy
  • stGroupBy
  • stHaving
  • stFrom

注意
使用 MagellanExpression 并返回布尔值的 stWhere 总是需要在后面加上 true 或 false。

这是 Laravel 使用 ->where() 时的默认行为,但由于 PHP 支持诸如 if($boolean) 之类的功能,而无需显式 $boolean == true 条件,true/false 容易被遗忘,从而导致查询而不是布尔查询。

->stWhere(ST::contains('location', 'polygon'), true)

这些构建器方法都期望接收一个 MagellanExpression
MagellanExpression 是 PostGIS 中 ST-前缀函数的包装器。当与 Magellan 一起航行时,您永远不需要自己编写 ST_xxx 的原始 SQL。因此,我们包含了一些浆。

大多数 ST-前缀的函数可以通过 ST 类的静态函数访问。但不要多说,让我们开始航行(一些示例)

注意:必要的类可以按以下方式导入

use Clickbar\Magellan\Data\Geometries\Point;
use Clickbar\Magellan\Database\PostgisFunctions\ST;

假设我们有我们船只的当前位置,并想查询所有港口及其距离

$currentShipPosition = Point::makeGeodetic(50.107471773560114, 8.679861151457937);
$portsWithDistance = Port::select()
    ->stSelect(ST::distanceSphere($currentShipPosition, 'location'), 'distance_to_ship')
    ->get();

由于我们无法航行整个世界,让我们将距离限制在最大 50,000 米

$currentShipPosition = Point::makeGeodetic(50.107471773560114, 8.679861151457937);
$portsWithDistance = Port::select()
    ->stSelect(ST::distanceSphere($currentShipPosition, 'location'), 'distance_to_ship')
    ->stWhere(ST::distanceSphere($currentShipPosition, 'location'), '<=', 50000)
    ->get();

现在让我们根据距离对他们进行排序

$currentShipPosition = Point::makeGeodetic(50.107471773560114, 8.679861151457937);
$portsWithDistance = Port::select()
    ->stSelect(ST::distanceSphere($currentShipPosition, 'location'), as: 'distance_to_ship')
    ->stWhere(ST::distanceSphere($currentShipPosition, 'location'), '<=', 50000)
    ->stOrderBy(ST::distanceSphere($currentShipPosition, 'location'))
    ->get();

如您所见,使用 st-Builder 函数与使用默认 Laravel 函数一样简单。但更复杂的查询怎么办?关于按国家分组的所有港口的凸包,包括凸包的面积怎么办?没问题

$hullsWithArea = Port::select('country')
    ->stSelect(ST::convexHull(ST::collect('location')), 'hull')
    ->stSelect(ST::area(ST::convexHull(ST::collect('location'))))
    ->groupBy('country')
    ->get();

自动将 bbox 或几何对象转换为类型

在上一节中,我们使用了一些PostGIS函数。在第一个示例中,返回类型只包含标量值。但在更复杂的示例中,我们收到了一个几何形状作为返回值。

由于“hull”不在我们的$postgisColumns数组中,我们可能会故意将查询添加为类型转换

$hullWithArea = Port::select('country')
    ->stSelect(ST::convexHull(ST::collect('location')), 'hull')
    ->stSelect(ST::area(ST::convexHull(ST::collect('location'))))
    ->groupBy('country')
    ->withCasts(['hull' => GeometryWKBCast::class]) /* <======= */
    ->first();

但这是不必要的
Magellan会自动为所有返回几何形状、box2d或box3d的函数添加类型转换。

测试

composer test

变更日志

有关最近更改的详细信息,请参阅变更日志

贡献

有关详细信息,请参阅贡献指南

安全漏洞

有关详细信息,请参阅安全漏洞

谢谢

鸣谢

许可证

MIT许可证(MIT)。有关更多信息,请参阅许可证文件