juanparati / larageos
A Laravel library that understands and speaks GeoSpatial data types.
Requires
- php: ^8.2|^8.3|^8.4|^8.5
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0
This package is auto-updated.
Last update: 2026-08-26 07:23:45 UTC
README
A Laravel GeoSpatial library for your ORM. Store points and polygons in native spatial columns, query them with distance scopes, and exchange them as GeoJSON.
Based on Laravel Spatial by Tarfin Labs.
Requirements
- PHP 8.2+
- Laravel 12+
- One of:
- MySQL 8.0+ (the library relies on the
axis-orderWKT option introduced in 8.0) - MariaDB 11.4+ — you must use Laravel's
mariadbdriver, notmysql. Writes on themysqldriver generate MySQL-specific SQL (the three-argumentST_GeomFromTextwithaxis-order) that MariaDB rejects. - PostgreSQL with PostGIS
- MySQL 8.0+ (the library relies on the
Installation
composer require juanparati/larageos
Optionally publish the config file:
php artisan vendor:publish --tag=larageos
// config/larageos.php return [ // SRID applied when a geometry is stored without an explicit SRID. 'default_srid' => 4326, ];
Migrations
Use Laravel's native geography() (or geometry()) column types:
Schema::create('addresses', function (Blueprint $table) { $table->id(); $table->geography('location', subtype: 'point'); // SRID 4326 by default $table->geography('area', subtype: 'polygon')->nullable(); $table->timestamps(); });
What that creates per database:
| Driver | Column type | Notes |
|---|---|---|
| MySQL | point SRID 4326 |
SRID-constrained geometry |
| MariaDB | point ref_system_id=4326 |
Cartesian semantics |
| PostGIS | geography(point,4326) |
True geography type |
Casts
Cast columns to rich Point / Polygon value objects:
use Juanparati\LaraGeos\Casts\LocationCast; use Juanparati\LaraGeos\Casts\RegionCast; use Juanparati\LaraGeos\Traits\HasGeoSpatial; class Address extends Model { use HasGeoSpatial; // Add HasGeoSpatial trait to the model protected $casts = [ 'location' => LocationCast::class, // Point 'area' => RegionCast::class, // Polygon ]; }
Points
use Juanparati\LaraGeos\Types\Point; $address = new Address(); $address->location = new Point(lat: 27.1234, lng: 39.1234); // SRID 4326 by default $address->save(); $address->refresh(); $address->location->getLat(); $address->location->getLng(); $address->location->getSrid(); $address->location->toWkt(); // POINT(39.1234 27.1234)
Latitude must be within [-90, 90] and longitude within [-180, 180]; out-of-range
values throw an InvalidArgumentException.
Polygons
Polygons support interior rings (holes) and round-trip them faithfully:
use Juanparati\LaraGeos\Types\Polygon; // A flat list of points is the exterior ring (auto-closed): $area = new Polygon([ new Point(lat: 0, lng: 0), new Point(lat: 0, lng: 4), new Point(lat: 4, lng: 4), new Point(lat: 4, lng: 0), ]); // Or pass rings: [exterior, ...holes] $area = new Polygon([ [$p1, $p2, $p3, $p4], // exterior ring [$h1, $h2, $h3], // hole ]); $area->getExteriorRing(); // Point[] $area->getInteriorRings(); // Point[][] $area->getRings(); // Point[][] (exterior first) $area->toWkt(); // POLYGON((...),(...))
Every ring needs at least 3 unique points and is closed automatically.
Distance scopes
Models using HasGeoSpatial get three query scopes:
$center = new Point(lat: 27.1234, lng: 39.1234); // Add a `distance` column to the select: Address::query()->selectDistanceTo('location', $center)->get(); // Only rows within the given distance (unit: see the table below!): Address::query()->withinDistanceTo('location', $center, 10_000)->get(); // Order by proximity: Address::query()->orderByDistanceTo('location', $center)->get(); // nearest first Address::query()->orderByDistanceTo('location', $center, 'desc')->get(); // farthest first
Distance units
Distances — both the distance value returned by selectDistanceTo and the
threshold passed to withinDistanceTo — are in meters on every driver:
| Driver | Function | Model |
|---|---|---|
| MySQL 8 | ST_Distance on geographic SRIDs |
geodesic (ellipsoid) |
| MariaDB | ST_Distance_Sphere |
spherical (within ~0.5% of ellipsoid results) |
| PostGIS | ST_Distance on geography columns |
geodesic (ellipsoid) |
Caveats:
- MariaDB:
ST_Distance_Sphereonly accepts POINT geometries, so the distance scopes work on point columns only there (polygon columns throw a database error). - PostGIS
geometrycolumns:ST_Distancereturns SRS units (degrees for 4326) instead of meters. Usegeographycolumns for meters.
Unsupported drivers (e.g. SQLite) throw
Juanparati\LaraGeos\Exceptions\UnsupportedDriverException.
Spatial predicate scopes
Point-in-polygon and other topological filters:
$point = new Point(lat: 2, lng: 2); $area = Polygon::fromGeoJson([ 'type' => 'Polygon', 'coordinates' => [[[0, 0], [4, 0], [4, 4], [0, 4], [0, 0]]], ]); // Rows whose polygon column contains the given point (or whole polygon): Region::query()->whereContains('area', $point)->get(); // Rows whose column lies inside the given polygon: Address::query()->whereWithin('location', $area)->get(); // Rows whose column intersects (shares any point with) the given geometry: Region::query()->whereIntersects('area', $area)->get();
Functions used per driver:
| Scope | MySQL / MariaDB | PostGIS |
|---|---|---|
whereContains |
ST_Contains |
ST_Covers |
whereWithin |
ST_Within |
ST_CoveredBy |
whereIntersects |
ST_Intersects |
ST_Intersects |
Caveats:
- Boundary points: PostGIS
geographycolumns do not supportST_Contains/ST_Within, so the scopes useST_Covers/ST_CoveredBythere. The practical difference is only at the edges: a point exactly on a polygon's boundary counts as contained on PostGIS, but not on MySQL/MariaDB. - Edge semantics: MySQL (geographic SRIDs) and PostGIS
geographytreat polygon edges as geodesics; MariaDB treats them as straight lines in coordinate space. Results near the edges of large polygons can differ between drivers.
GeoJSON
Both types convert to and from RFC 7946 GeoJSON geometries (coordinates are
[lng, lat]; GeoJSON is always WGS 84, so no SRID is emitted):
$point = Point::fromGeoJson('{"type":"Point","coordinates":[39.1234,27.1234]}'); $point->toGeoJson(); // ['type' => 'Point', 'coordinates' => [39.1234, 27.1234]] json_encode($point); // {"type":"Point","coordinates":[39.1234,27.1234]} $polygon = Polygon::fromGeoJson([ 'type' => 'Polygon', 'coordinates' => [[[0, 0], [4, 0], [4, 4], [0, 0]]], ]); json_encode($polygon); // {"type":"Polygon","coordinates":[[[0,0],[4,0],[4,4],[0,0]]]}
WKT factories are also available: Point::fromWkt() / Polygon::fromWkt().
Model serialization
toArray() / toJson() on a model serialize spatial attributes as:
// Point ['lat' => 27.1234, 'lng' => 39.1234, 'srid' => 4326] // Polygon ['rings' => [[['lat' => ..., 'lng' => ...], ...], ...], 'srid' => 4326]
Testing
composer test
The CI matrix runs the suite on PHP 8.2–8.5 against MySQL 8.4, MariaDB 11.4, and PostGIS 16.
License
MIT. See LICENSE.