laravel-magellan

repository·main·Indexed 19 days ago

https://github.com/clickbar/laravel-magellan

A modern PostGIS toolbox for Laravel that provides seamless integration for working with spatial data. It extends Laravel's Schema, Query Builder, and Eloquent to support PostGIS datatypes, functions, and formats including GeoJSON, WKT, and WKB. Features include specialized geometry data classes (Point, LineString, Polygon, etc.), a static ST class for PostGIS functions in queries, and tools for GeoJSON validation and transformation in Form Requests.

Tokens
8.6K
Snippets
33
Records
37
Agent score
65%

What's inside laravel-magellan

  1. Use PostGIS functions in Laravel queries via the ST class

    main

    Magellan provides a static ST class that returns MagellanExpression objects. These can be used seamlessly within Laravel's query builder methods like select(), addSelect(), where(), orderBy(), and groupBy().

    Key Usage Patterns

    • Aliasing: Use the as(string) method on an expression to name the resulting column.
    • Boolean Expressions: When using where() with an expression that returns a boolean, you must explicitly provide the boolean value (e.g., true or false) to avoid null-check queries.
    • Aggregates/Collections: Use ST::collect() to pass a collection of geometries to functions like ST::convexHull().
    • Casting: Use AsGeometry or AsGeography to explicitly cast parameters within a function call.
    use Clickbar//... imports
    
    // Select with distance and filter
    Port::select()
        ->addSelect(ST::distanceSphere($currentPos, 'location')->as('distance_to_ship'))
        ->where(ST::distanceSphere($currentPos, 'location'), '<=', 50000)
        ->orderBy(ST::distanceSphere($currentPos, 'location'))
        ->get();
    
    // Complex grouping and hull calculation
    Port::query()
        ->select([
            'country',
            ST::convexHull(ST::collect('location'))->as('hull'),
            ST::area(ST::convexHull(ST::collect('location')))->as('area')
        ])
        ->groupBy('country')
        ->get();
    
    // Explicit casting to Geography for meter-based calculations
    Port::query()
        ->select(ST::buffer(new AsGeography('location'), 50)->as('buffered_location'))
        ->get();
  2. Handle SRID transformations on insert/update in 2.x

    main

    If you previously relied on magellan.eloquent.transform_to_database_projection = true, note that the auto-transform feature was removed with the removal of HasPostgisColumns. You must now manually transform geometries using ST::transform during inserts or updates to ensure they match the database SRID.

    // Manual transformation during creation
    Port::create([
        'name' => 'Magellan Home Port',
        'location' => ST::transform(Point::make(473054.9891044726, 5524365.310057224, srid: 25832), 4326),
    ]);
    
    // Manual transformation during update
    $port->query()->update([
        'location' => ST::transform(Point::make(473054.9891044726, 5524365.310057224, srid: 25832), 4326),
    ]);
  3. Replace GeometryWKBCast with specific Geometry classes

    main

    The GeometryWKBCast class has been removed in 2.x. When retrieving complex geometries (like a convex hull) from a query result, use the appropriate geometry class (e.g., Polygon::class) in your withCasts() or casts definition instead.

    $hullWithArea = Port::query()
        ->select([
            'country',
            ST::convexHull(ST::collect('location'))->as('hull'),
            ST::area(ST::convexHull(ST::collect('location'))),
        ])
        ->groupBy('country')
        ->withCasts(['hull' => Polygon::class])
        ->first();
  4. Cast inputs to Geometry or Geography in 2.x

    main

    In 2.x, the GeometryType enum has been removed. To explicitly cast an input to either geometry or geography within an ST function, wrap the expression or column name in AsGeometry or AsGeography classes.

    // Using AsGeography to ensure a column is treated as geography in a buffer function
    $bufferedPorts = Port::query()
        ->select(ST::buffer(new AsGeography('location'), 50)->as('buffered_location'))
        ->withCasts(['buffered_location' => Polygon::class])
        ->get();
  5. Publish migrations and configuration

    main

    After installation, you must publish the package's migrations to set up the necessary PostGIS support in your database, and publish the configuration file to customize behavior (such as default geodetic SRIDs or JSON serialization formats).

    Run the following commands:

    1. Publish migrations:

    php artisan vendor:publish --tag="magellan-migrations" 2. Run migrations: php artisan migrate 3. Publish config: php artisan vendor:publish --tag="magellan-config"

    php artisan vendor:publish --tag="magellan-migrations"
    php artisan migrate
    php artisan vendor:publish --tag="magellan-config"
  6. Migrate Eloquent Models from 1.x to 2.x

    main

    In Magellan 2.x, the HasPostgisColumns trait and $postgisColumns array have been removed in favor of standard Laravel attribute casting. You should now use specific geometry class names (e.g., Point::class, LineString::class, Polygon::class) directly in the $casts array. For bounding boxes, replace BBoxCast with Box2D::class or Box3D::class.

    // Magellan 2.x Model Example
    use Clickbar\
    Magellan\Data\Geometries\Point;
    use Clickbar\Magellan\Data\Boxes\Box2D;
    
    class Port extends Model
    {
        protected array $casts = [
            'location' => Point::class,
            'bounding_box' => Box2D::class,
        ];
    }
  7. Create tables with PostGIS columns in migrations

    main

    Magellan extends the Laravel Schema Blueprint. While Laravel now has built-in geometry and geography methods, Magellan provides specific methods for complex types.

    Note: Most magellan* prefixed methods (like magellanPoint) are deprecated in favor of Laravel's native geometry('column', 'TYPE', SRID) method. Only the following specialized collection and box types are not deprecated:

    • magellanBox2D('column')
    • magellanBox3D('column')
    • magellanGeometryCollection('column')
    • magellanGeometryCollectionM('column')
    • magellanGeometryCollectionZ('column')
    • magellanGeometryCollectionZM('column')
    // Use native Laravel methods for standard types
    $table->geometry('location', 'POINT', 4326);
    
    // Use Magellan for specialized types
    $table->magellanBox2D('bounds2d');
    $table->magellanGeometryCollection('collection');
  8. Configure custom SRID for GeoJSON validation and transformation

    main

    By default, GeoJSON is parsed with SRID 4326. To use a different Spatial Reference System (SRID):

    1. Validation: Pass the srid argument to GeometryGeojsonRule.
    2. Transformation: Override the geometrySrids(): array method in your Form Request to map field names to their respective SRIDs.
    3. Manual Parsing: Use GeojsonParser::parseWithSrid() for parsing outside of Form Requests.

    Fields not explicitly listed in geometrySrids() will default to 4326.

    // Validation with custom SRID
    'location' => ['required', new GeometryGeojsonRule([Point::class], srid: 25832)],
    
    // Form Request implementation
    class StorePortRequest extends FormRequest
    {
        use TransformsGeojsonGeometry;
    
        public function rules(): array
        {
            return [
                'location' => ['required', new GeometryGeojsonRule([Point::class], srid: 25832)],
            ];
        }
    
        public function geometries(): array
        {
            return ['location'];
        }
    
        public function geometrySrids(): array
        {
            return ['location' => 25832];
        }
    }
    
    // Manual parsing
    $point = app(GeojsonParser::class)->parseWithSrid($geojson, srid: 25832);
  9. Validate and transform GeoJSON in Form Requests

    main

    When handling GeoJSON input in Laravel Form Requests, use GeometryGeojsonRule for validation and the TransformsGeojsonGeometry trait to automatically convert GeoJSON into proper geometry objects.

    To enable transformation, you must implement the geometries(): array method in your Form Request, returning an array of the field names that should be transformed.

    Note: Currently, only simple field transformation is supported; wildcard or array notation is not yet available.

    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'];
        }
    }
  10. Prepare Eloquent models with Geometry casts

    main

    To integrate PostGIS data types with your Eloquent models, add the appropriate geometry or box data class to the $casts array. This allows Laravel to automatically transform database values into usable objects.

    protected $casts = [
        /** ... */
        'location' => Point::class,
        'bounds' => Box2D::class,
    ];
  11. Migrate Query Builder methods from 1.x to 2.x

    main

    Magellan 2.x removes the st-prefixed query builder methods (like stSelect, stWhere, stOrderBy). You should now use standard Laravel methods (select, addSelect, where, orderBy) and pass ST expressions directly into them.

    To handle result casting for ST functions that return geometry or bounding boxes, use the withMagellanCasts() method on the query builder. To rename an expression, use the ->as('alias') utility on the ST expression object.

    $currentShipPosition = Point::makeGeodetic(50.107471773560114, 8.679861151457937);
    
    // Magellan 2.x Query Pattern
    $portsWithDistance = Port::select(['name', 'country'])
        ->addSelect(ST::distanceSphere($currentShipPosition, 'location')->as('distance_to_ship'))
        ->where(ST::distanceSphere($currentShipPosition, 'location'), '<=', 50000)
        ->orderBy(ST::distanceSphere($currentShipPosition, 'location'))
        ->withMagellanCasts()
        ->get();