Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Mysql Spatial Laravel Package

grimzy/laravel-mysql-spatial

Laravel package for working with MySQL spatial data types and functions. Adds Eloquent support for geometry columns like POINT, LINESTRING and POLYGON, with spatial queries and helpers for storing, retrieving and querying geographic data in your models.

View on GitHub
Deep Wiki
Context7

Getting Started

This package adds MySQL spatial column types, typed geometry objects, and Eloquent query helpers to Laravel. It is a good fit for store locators, delivery zones, geofencing, and any feature where you would otherwise hand-write ST_* SQL.

Requirements and install

  • Laravel 8.x is the supported framework ceiling as of 5.0.0 — do not assume Laravel 9/10/11 works.
  • MySQL 5.7+ or MySQL 8.x (MariaDB has partial/different spatial support).
  • No SQLite or PostgreSQL support; your test database must be MySQL.
  • Install with Composer; the service provider is auto-discovered and there is no config file to publish:
composer require grimzy/laravel-mysql-spatial

Pin the version explicitly if you are upgrading an existing app, since 5.0.0 is a major release:

composer require grimzy/laravel-mysql-spatial:^5.0

Where to look first

  • README.md for the supported type and query examples.
  • CHANGELOG.md / release notes for 5.0.0 — it is a major version bump, so check for renamed helpers or changed signatures before upgrading.
  • src/Types/ for Point, LineString, Polygon, MultiPoint, etc.
  • src/Eloquent/SpatialTrait.php for how attributes are cast and persisted.
  • The package tests for exact query-builder method names in your installed version.

Minimal first use case: a radius search

Migration:

Schema::create('places', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->point('location', 4326); // SRID 4326 for GPS coordinates
    $table->spatialIndex('location');
    $table->timestamps();
});

Model:

use Grimzy\LaravelMysqlSpatial\Eloquent\SpatialTrait;

class Place extends Model
{
    use SpatialTrait;

    protected $fillable = ['name', 'location'];

    protected $spatialFields = ['location'];
}

Write and read:

$place = Place::create([
    'name' => 'HQ',
    'location' => new Point(37.7749, -122.4194, 4326),
]);

$place->location->getLat();
$place->location->getLng();

Query nearby places:

$center = new Point(37.7749, -122.4194, 4326);

$nearby = Place::whereDistanceSphere('location', $center, '<=', 5000)
    ->orderByDistanceSphere('location', $center)
    ->get();

If the query macro names differ in your installed version, check the README/tests and use whereRaw('ST_Distance_Sphere(location, POINT(?, ?)) <= ?', [...]) as a fallback.

Implementation Patterns

Wrap spatial filters in model scopes

Keep the query language out of controllers and make the units explicit:

public function scopeNearby(Builder $query, Point $center, int $meters): Builder
{
    return $query
        ->whereDistanceSphere('location', $center, '<=', $meters)
        ->orderByDistanceSphere('location', $center);
}

Then compose normally:

Place::where('active', true)
    ->nearby($center, 5000)
    ->paginate(20);

Geofencing with polygons

Use Polygon for delivery zones or service areas. For point-in-polygon checks, the stored point should be within the polygon:

$zone = Polygon::fromWKT(
    'POLYGON((...))',
    4326
);

$inside = Place::whereWithin('location', $zone)->get();

whereIntersects is useful for overlapping regions; whereContains is the inverse direction and argument order matters.

Ingesting GeoJSON and WKT from APIs

The geometry types can be constructed from common formats:

$point = Point::fromWKT('POINT(37.7749 -122.4194)', 4326);
$geometry = Geometry::fromJson($request->input('geojson'), 4326);

Normalize incoming data at the boundary, then store typed geometry objects on the model.

Return GeoJSON from APIs

Geometry objects can serialize to GeoJSON. A common pattern is to build a Feature manually so you control property names:

return [
    'type' => 'Feature',
    'geometry' => json_decode($place->location->toJson(), true),
    'properties' => [
        'name' => $place->name,
    ],
];

If you expect toArray() to emit GeoJSON automatically, verify the package version; otherwise add an accessor or $appends entry.

Use raw SQL as the escape hatch

The package covers the common predicates, but PostGIS-style operations such as buffers, envelopes, and GeoJSON output often need raw expressions:

Place::whereRaw(
    'ST_Contains(ST_GeomFromText(?, 4326), location)',
    [$wkt]
)->get();

Place::selectRaw('*, ST_AsGeoJSON(location) as location_geojson')->get();

Do not mix raw selected GeoJSON aliases with the model's spatial cast unless you alias them away from the original column name.

Bulk operations

The trait only applies to Eloquent model assignments. Bulk inserts through DB::table() bypass casting, so use raw geometry constructors:

DB::table('places')->insert([
    'name' => 'Warehouse',
    'location' => DB::raw("ST_GeomFromText('POINT(37.7749 -122.4194)', 4326)"),
]);

For a small number of rows, creating models is safer because validation and casting run.

Testing

Use a MySQL connection in phpunit.xml; SQLite will fail on point, spatialIndex, and ST_* functions. Factories should assign geometry objects:

'location' => new Point(37.7749, -122.4194, 4326),

Gotchas and Tips

Maintenance and framework compatibility

5.0.0 is the current line. Its stated target is Laravel 8 with MySQL 5.7, which means:

  • Laravel 9, 10, and 11 are not declared as supported. Check the installed package's composer.json constraints before adopting it on a newer app; you will likely hit a dependency conflict at install time rather than a runtime error, which is at least easy to detect.
  • MySQL 5.7 is explicitly supported again in this release, but 5.7 lacks some MySQL 8 spatial refinements (notably SRID-aware functions and the stricter axis-order handling). If you need SRID-aware ST_Distance/ST_Contains semantics, test on 8.x.
  • Because this is a major version bump, treat it as breaking until proven otherwise: run your migration, a write/read round-trip, and every spatial predicate you use in a real test against a throwaway database. Diff the README and tests against your pinned expectation of method names.
  • If you are on Laravel 9+ or need active maintenance, evaluate maintained alternatives such as matanyadaev/laravel-eloquent-spatial or clickbar/laravel-magellan, or vendor a fork.

Spatial indexes require NOT NULL

In MySQL, columns in a SPATIAL INDEX generally must be NOT NULL. A common failure is:

$table->point('location', 4326)->nullable();
$table->spatialIndex('location'); // may fail

Either make the column non-nullable with a sensible default, or omit the spatial index and accept slower predicate queries.

SRID and axis order

Point uses latitude/longitude order, while GeoJSON coordinates are longitude/latitude. MySQL 8 with SRID 4326 also follows the SRID's axis order. Mixing SRID 0 and SRID 4326 values can produce errors such as "binary geometry function given two geometries of different SRIDs." Be explicit:

new Point($lat, $lng, 4326);

and pass the same SRID when constructing query geometries.

Distance units are not interchangeable

  • distance / planar ST_Distance returns units of the coordinate system; with SRID 0 this is effectively degrees, not meters.
  • distanceSphere / ST_Distance_Sphere returns meters and should be used for radius searches with GPS coordinates.

Do not compare a meter value against a planar distance query.

ST_Distance does not use the spatial index

Radius queries that order by distance will scan. A practical pattern is a bounding-box prefilter with whereWithin or whereIntersects using an envelope polygon, followed by orderByDistanceSphere for exact ordering. For high-traffic endpoints, consider a generated column or a dedicated geohash/grid column.

Casting is driven by $spatialFields

If you forget the trait or misspell a column in $spatialFields, Eloquent returns raw WKB/binary rather than a Point. If you use selectRaw, column aliases, or partial selects, the cast can break; include the original spatial column or handle the alias separately.

Assign geometry objects, not strings

Assignment expects a geometry instance:

$place->location = new Point(37.7749, -122.4194, 4326);

If you have WKT from an external source, parse it first with Point::fromWKT() or Geometry::fromWKT(). Do not assume a string will be cast automatically.

Polygon validity

Polygon rings must be closed (first point equals last point) and contain at least four points. LineString needs at least two. fromWKT will usually validate this; manually built arrays may produce MySQL errors.

Do not rely on exact coordinate equality

Coordinates are floating-point values. Use ST_Equals, a small distance tolerance, or compare rounded values rather than = on geometry columns.

Changing spatial columns is risky

$table->point(...)->change() is not reliably supported for spatial types. Prefer dropping and re-adding the column in a migration, and test on a copy of production data.

Version-specific query macro names

The package has exposed spatial query helpers under names such as whereDistanceSphere, orderByDistanceSphere, whereWithin, and related predicates, but naming has varied across releases — and a major version bump is exactly when this is most likely to bite. Confirm against your installed version's README or tests. When in doubt, whereRaw with bound WKT and SRID is predictable and easy to debug with toSql() and getBindings().

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect