shabushabu / laravel-postgis
PostGIS query expression collection for Laravel
Fund package maintenance!
boris-glumpler
Requires
- php: ^8.2
- brick/geo: ^0.11.1
- illuminate/contracts: ^11.0
- spatie/laravel-package-tools: ^1.16
- tpetry/laravel-query-expressions: ^1.4
Requires (Dev)
- larastan/larastan: ^2.9
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1
- orchestra/testbench: ^9.0
- pestphp/pest: ^2.34
- pestphp/pest-plugin-arch: ^2.7
- pestphp/pest-plugin-laravel: ^2.3
- pestphp/pest-plugin-type-coverage: ^2.8
- phpstan/extension-installer: ^1.3
- phpstan/phpstan-deprecation-rules: ^1.1
- phpstan/phpstan-phpunit: ^1.3
- tpetry/laravel-postgresql-enhanced: ^2.0
README
Laravel PostGIS
Select collection of Laravel query expressions for PostGIS.
Supported minimum versions
Installation
Caution
Please note that this is a new package and, even though it is well tested, it should be considered pre-release software
Before installing the package you should install and enable the PostGIS extension.
You can install the package via composer:
composer require shabushabu/laravel-postgis
Usage
Please see the expressions folder for a full list of supported PostGIS functions.
We do accept pull requests for any additional functions!
The expressions can be used in queries or in migrations.
PostGIS queries often have subtle differences between them, so the way we use this package at ShabuShabu is to extend the Eloquent builder for a given model and use the expressions in there.
Query examples
Getting the GeoJSON representation of a geometry column
use ShabuShabu\PostGIS\Expressions\As; use Tpetry\QueryExpressions\Language\Alias; use ShabuShabu\PostGIS\Expressions\Enums\Option; Track::query() ->select(new Alias(new As\GeoJSON('geom', 6, Option::bbox), 'json')) ->where('trail_id', 27) ->value('json');
Get a GeoJson Feature collection
use ShabuShabu\PostGIS\Expressions\As; use Tpetry\QueryExpressions\Language\Alias; use ShabuShabu\PostGIS\Expressions\Casts\AsJson; use ShabuShabu\PostGIS\Expressions\Helpers\JsonAgg; use ShabuShabu\PostGIS\Expressions\Helpers\JsonBuildObject; DB::query() ->select([ new Alias(new JsonBuildObject([ 'type' => 'FeatureCollection', 'features' => new JsonAgg(new AsJson(new As\GeoJson('t.*', null, null))) ]), 'features') ]) ->from( Track::query()->select(['geom', 'id', 'title']), 't' );
Get an elevation profile from a linestring zm
use Tpetry\QueryExpressions\Language\Alias; use ShabuShabu\PostGIS\Expressions\Collect; use ShabuShabu\PostGIS\Expressions\Simplify; use ShabuShabu\PostGIS\Expressions\DumpPoints; use ShabuShabu\PostGIS\Expressions\Position\Elevation; use ShabuShabu\PostGIS\Expressions\Position\Timestamp; DB::query()->select([ new Alias(new Elevation('geom'), 'x'), new Alias(new Timestamp('geom'), 'y'), ])->from( Track::query()->select( new Alias(new DumpPoints(new Simplify(new Collect('geom'), 0.15)), 'geom') )->where('trail_id', 27), 't' );
Get a bounding box for a collection of geometries
use Tpetry\QueryExpressions\Language\Alias; use ShabuShabu\PostGIS\Expressions\Collect; use ShabuShabu\PostGIS\Expressions\Envelope; use ShabuShabu\PostGIS\Expressions\Box\TwoD; use ShabuShabu\PostGIS\Expressions\Math\Round; use ShabuShabu\PostGIS\Expressions\Casts\AsJson; use ShabuShabu\PostGIS\Expressions\Casts\AsNumeric; use ShabuShabu\PostGIS\Expressions\Helpers\MakeArray; use ShabuShabu\PostGIS\Expressions\Helpers\ArrayToJson; use ShabuShabu\PostGIS\Expressions\Position\MinLatitude; use ShabuShabu\PostGIS\Expressions\Position\MaxLatitude; use ShabuShabu\PostGIS\Expressions\Position\MinLongitude; use ShabuShabu\PostGIS\Expressions\Position\MaxLongitude; DB::query() ->select([ new Alias(new AsJson(new ArrayToJson(new MakeArray([ new Round(new AsNumeric(new MinLongitude('bbox')), 9), new Round(new AsNumeric(new MinLatitude('bbox')), 9), new Round(new AsNumeric(new MaxLongitude('bbox')), 9), new Round(new AsNumeric(new MaxLatitude('bbox')), 9), ]))), 'bbox') ]) ->from( Track::query()->select([ new Alias(new TwoD(new Envelope(new Collect('geom'))), 'bbox') ]), 't' );
Migration examples
Adding a generated column from coordinate columns
use ShabuShabu\PostGIS\Expressions\SetSRID; use ShabuShabu\PostGIS\Expressions\Position\MakePoint; Schema::create('locations', static function (Blueprint $table) { // all the other table columns... $table->decimal('lat', 10, 6)->nullable(); $table->decimal('lng', 10, 6)->nullable(); $table ->geometry('geom', 'point', 4326) ->storedAs(new SetSRID(new MakePoint('lng', 'lat'), 4326)); });
Setting the center for a given multipolygon
use ShabuShabu\PostGIS\Expressions\Centroid; Schema::create('countries', static function (Blueprint $table) { // all the other table columns... $table->geometry('geom', 'multipolygon', 4326); $table ->geometry('center', 'point', 4326) ->storedAs(new Centroid('geom')); });
Setting the area in square kilometers
use ShabuShabu\PostGIS\Expressions\Area; use Tpetry\QueryExpressions\Value\Value; use ShabuShabu\PostGIS\Expressions\Math\Round; use ShabuShabu\PostGIS\Expressions\Casts\AsNumeric; use ShabuShabu\PostGIS\Expressions\Casts\AsGeography; use Tpetry\QueryExpressions\Operator\Arithmetic\Divide; Schema::create('provinces', static function (Blueprint $table) { // all the other table columns... $table->geometry('geom', 'multipolygon', 4326); $table ->integer('area_km2') ->storedAs(new Round( new AsNumeric( new Divide( new Area(new AsGeography('geom')), new Value(1e+6) ) ) )); });
Model Casts
You can cast your geometry columns to their respective \Brick\Geo\Geometry
counterparts. Here's an example:
use Brick\Geo\Point; use ShabuShabu\PostGIS\Casts\Geometry; use Illuminate\Database\Eloquent\Model; class Location extends Model { // ... protected function casts(): array { return [ 'point' => Geometry::using(Point::class), ]; } }
Brick/Geo Integration
All the necessary wiring to use brick/geo
with PostGIS is already done for you. You can just resolve ShabuShabu\PostGIS\Geometry
from the container:
use Brick\Geo\Polygon; use ShabuShabu\PostGIS\Geometry; echo app(Geometry::class)->area( Polygon::fromText('POLYGON ((0 0, 0 3, 3 3, 0 0))') );
Mapbox Vector Tile Server
Caution
Please note that atm you will need to install the develop
branch to use this feature! This feature is not currently covered by any tests, so use at your own risk.
Add a gate
You will need to add a gate to the boot
method of your AppServiceProvider
to ensure that only certain users can use the tile server:
Gate::define( 'access-tile-server', static fn (User $user) => $user->is_admin, );
Create sources
The next step is to create sources for all your MVTs. All sources should extend the ShabuShabu\PostGIS\Servers\Tiles\Source
class.
<?php declare(strict_types=1); namespace App\Services\MVT; use BackedEnum; use App\Models\Country; use ShabuShabu\PostGIS\Servers\Tiles\Source; use Tpetry\QueryExpressions\Language\Alias; use ShabuShabu\PostGIS\Expressions\Simplify; use Illuminate\Contracts\Database\Query\Builder; class Countries extends Source { public function name(): string | BackedEnum { return 'countries'; } public function query(): Builder { return Country::query()->select([ 'id', new Alias(new Simplify('geom', 0.1), 'geom'), new Alias('name', 'title'), 'code', ]); } public function columns(): array { return ['title', 'code']; } }
The name
method must return a unique name identifying the source. This value will also be used as the name for the layer within the MVT.
The query
method must return an id
and a geom
column, plus any optional columns you need for display.
Finally, the columns
method should return all the optional column names.
Additionally, you also have access to the current request via $this->request
.
Add sources to the config file
The last required step is to add your sources to the config file:
return [ 'tiles' => [ // other values... 'sources' => [ \App\Services\MVT\Countries::class, ], // other values... ], ];
Additional Steps
Optionally, you can also adjust the route settings in the config file, like setting middleware, changing the route prefix or rolling your own setup!
Integrating the tile server on the frontend
Here's an example how you can use the tile server with Mapbox. The server can be used with any map provider that supports MVTs, tho.
map.addSource('countries', { type: 'vector', tiles: ['https://your-site.com/services/tiles/countries/{z}/{x}/{y}.pbf'], minzoom: 0, maxzoom: 16, })
To request multiple layers, just comma-separate them:
https://your-site.com/services/tiles/countries,provinces/{z}/{x}/{y}.pbf
GeoJson Feature Server
Caution
Please note that atm you will need to install the develop
branch to use this feature! This feature is not currently covered by any tests, so use at your own risk.
Add a gate
You will need to add a gate to the boot
method of your AppServiceProvider
to ensure that only certain users can use the feature server:
Gate::define( 'access-feature-server', static fn (User $user) => $user->is_admin, );
Add UIDs to any models
The feature server makes use of globally unique IDs via our UIDs for Laravel package.
Please follow the installation instruction there to set up UIDs for your feature-enabled models!
Add the Geomable interface to any models
Additionally, any feature-enabled models need to implement the ShabuShabu\PostGIS\Servers\Features\Contracts\Geomable
interface.
The geoJsonColumns
method should return an array of column strings or query expressions. These will be added to the GeoJson response as properties:
return [ new Alias('continent_id', 'continent'), 'name', 'description', 'slug', ];
Additional Steps
Optionally, you can also adjust the route settings in the config file, like setting middleware, changing the route prefix or rolling your own setup!
Integrating the feature server on the frontend
Here's an example how you can use the feature server with Mapbox. The server can be used with any map provider that supports GeoJson, tho.
map.addSource('geom', { type: 'geojson', data: `https://your-site.com/services/features/${uid}.geojson`, maxzoom: 16 })
GeoJson Collection Server
Caution
Please note that atm you will need to install the develop
branch to use this feature! This feature is not currently covered by any tests, so use at your own risk.
Add a gate
You will need to add a gate to the boot
method of your AppServiceProvider
to ensure that only certain users can use the collection server:
Gate::define( 'access-collection-server', static fn (User $user) => $user->is_admin, );
Create collections
The next step is to create collections. All collections should extend the ShabuShabu\PostGIS\Servers\Features\Collection
class.
<?php declare(strict_types=1); namespace App\Services\Features; use BackedEnum; use App\Models\Country; use Tpetry\QueryExpressions\Language\Alias; use ShabuShabu\PostGIS\Expressions\Simplify; use Illuminate\Contracts\Database\Query\Builder; use ShabuShabu\PostGIS\Servers\Features\Collection; class Countries extends Collection { public function name(): string | BackedEnum { return 'countries'; } public function query(): Builder { return Country::query()->select([ new Alias(new Simplify('geom', 0.1), 'geom'), 'name', 'code', ]); } }
The name
method must return a unique name identifying the collection.
The query
method must return a geom
column, plus any optional columns you want to pass along as properties.
Additionally, you also have access to the current request via $this->request
.
Add collections to the config file
The last required step is to add your collections to the config file:
return [ 'features' => [ // other values... 'sources' => [ \App\Services\Features\Countries::class, ], // other values... ], ];
Additional Steps
Optionally, you can also adjust the route settings in the config file, like setting middleware, changing the route prefix or rolling your own setup!
Integrating the collection server on the frontend
Here's an example how you can use the collection server with Mapbox. The server can be used with any map provider that supports GeoJson, tho.
map.addSource('geom', { type: 'geojson', data: `https://your-site.com/services/features/collections/${collectionName}.geojson`, maxzoom: 16 })
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
Disclaimer
This is a 3rd party package and ShabuShabu is not affiliated with either Laravel or PostGIS.
License
The MIT License (MIT). Please see License File for more information.