spinen/laravel-geometry
A Laravel package for working with geometric data in Eloquent models. It provides casts and helpers for spatial types like points, lines, and polygons, making it easier to store, retrieve, and query geometry values in your database.
Install and confirm it's still installable. The last tagged release was June 2020, so before committing to it run:
composer require spinen/laravel-geometry
If Composer resolves against an old geoPHP constraint, check composer why-not before pulling it into a Laravel 10/11 app. Package discovery registers Spinen\Geometry\GeometryServiceProvider and a Geometry facade automatically — there is no publishable config file in most versions of this wrapper, so "configuration" means reading the source.
Read the source first. The wrapper is thin: vendor/spinen/laravel-geometry/src contains the service provider, the facade, and one or two classes that delegate to geoPHP::load(). Spend five minutes there — it tells you the exact method signatures the facade exposes and what the underlying object is (a geoPHP Geometry instance with getCentroid(), getArea(), getBoundingBox(), asText(), asJson(), contains(), etc.).
Smoke-test in Tinker. A first use case that exercises the whole path:
$point = Geometry::load('POINT(-81.3792 28.5383)', 'wkt');
$point->getX(); // -81.3792
$point->getY(); // 28.5383
$point->asJson(); // {"type":"Point","coordinates":[-81.3792,28.5383]}
If that works, your autoloading, service provider, and facade alias are all correct. Then try a Polygon and a GeometryCollection before writing any real code.
Parse on write, compute on read. The most common day-to-day pattern is accepting GeoJSON or WKT from an API/import, storing it as text, and doing geometry math on read. Use explicit type arguments rather than relying on autodetection — Geometry::load($payload, 'json') is deterministic, while Geometry::load($payload) guesses and can misclassify.
Normalising formats at the boundary. Because geoPHP handles WKT, WKB, GeoJSON, KML, and GPX, this package shines as a converter. A clean pattern is a small value object or Eloquent cast that stores WKT in the database and exposes GeoJSON to the frontend:
class GeometryCast implements CastsAttributes
{
public function get($model, $key, $value, $attributes)
{
return $value ? Geometry::load($value, 'wkt')->asJson() : null;
}
public function set($model, $key, $value, $attributes)
{
return ['geometry' => Geometry::load($value, 'json')->asText()];
}
}
Store as TEXT/LONGTEXT unless you specifically need a native spatial column — see the gotchas.
Geofencing and simple containment. For "is this address inside our delivery zone?", load both geometries and use contains():
$zone = Geometry::load($polygonWkt, 'wkt');
$zone->contains(Geometry::load($pointWkt, 'wkt'));
This is a PHP-side check. It's fine for a handful of zones per request; it is not a substitute for a database spatial index.
Centroids and map pins. A very common workflow: given a multipolygon boundary, compute getCentroid() to place a marker, or getBoundingBox() to set the initial map viewport. Both are cheap and cacheable — compute once, persist the lat/lng columns, and read plain floats at render time.
Batch processing in jobs. Parsing and computing over thousands of rows should live in queued jobs, not controllers. Instantiate per row inside the job rather than holding references to many geometry objects at once; geoPHP objects can be memory-hungry on large multipolygons.
Precompute and denormalise. Keep a latitude/longitude/area set of scalar columns alongside the text geometry. It makes ordering (ORDER BY area DESC) and bounding-box filters possible in plain SQL, and keeps the geometry library off the hot path.
getArea() and getLength() are in coordinate units, not metres. With SRID 4326 that means square degrees and degrees. For real distances, either project to a metric SRID first or do the distance math yourself with a haversine on the centroid. Don't ship a "distance in km" feature straight off geoPHP without checking this — it's the single most common source of subtle wrongness.
Native spatial columns and WKB are fiddly. If you use a GEOMETRY/GEOGRAPHY column, you must feed MySQL/PostGIS the right binary form and SRID, and geoPHP's asBinary() is not the same as a hex-encoded ST_GeomFromText payload. The safest integration is to keep a TEXT WKT column and use DB functions (ST_Contains, ST_Distance_Sphere) via DB::raw for the queries that need an index — the library computes, the database indexes.
Always pass the input type. Autodetection between WKT and GeoJSON is mostly reliable but fails on ambiguous strings. Also strip a SRID=4326; EWKT prefix before parsing; geoPHP does not always handle it.
Watch PHP version drift. The 2020 release predates PHP 8 strictness. Deprecation notices and null-returning methods on empty/EMPTY geometries are the usual failures. Wrap getCentroid() and friends in null checks — a disjoint GeometryCollection can return null where you expect an object.
Don't compare floats for equality. contains() on a point exactly on a boundary edge is implementation-defined. If your business rules require "on the line counts as inside", add your own buffer or tolerance rather than trusting the library's boundary semantics.
Unmaintained but not unfixable. Since releases stopped in 2020, treat this as a thin convenience wrapper you may need to patch. The extension point is the container binding: if you need custom behaviour (caching parsed geometries, swapping in a maintained fork of geoPHP), rebind the facade's underlying class in AppServiceProvider rather than forking the package. Keep the facade behind a small interface in your own code so replacing it later is a one-line change.
Debug with round-trips. When output looks wrong, serialise back out — ->asText() then re-parse — to confirm the geometry survived parsing intact. Coordinates with swapped lat/lng are far more common than library bugs, and a WKT round-trip exposes it immediately.
How can I help you explore Laravel packages today?