lootils/geo
Lootils Geo provides convenient geographic data helpers for Laravel and PHP applications, making it easy to work with countries, regions, and related location data. Lightweight, simple to integrate, and useful for building location-aware features.
lootils/geo is a small, framework-agnostic PHP value-object library for geodetic math — coordinates, distances, bearings, and related equations. It does not ship a Laravel service provider, config file, or database layer, so integration is entirely up to you. That is the point: it gives you correct math, you give it a home in your app.
Minimal setup:
composer require lootils/geo
First, open vendor/lootils/geo/src/ and the README before writing code. Confirm three things immediately:
(latitude, longitude) or (longitude, latitude)?Distance object? Are angles in degrees or radians?A first smoke test in php artisan tinker:
use Lootils\Geo\Coordinate; // verify FQCN in src/
$london = new Coordinate(51.5074, -0.1278);
$paris = new Coordinate(48.8566, 2.3522);
$distance = $london->distanceTo($paris);
dump($distance); // inspect type and units before using it anywhere
Your first real use case: store a venue's latitude/longitude, then compute the distance from a user's position to sort "nearest" results.
// app/Services/GeoService.php
public function distanceKm(float $lat1, float $lng1, float $lat2, float $lng2): float
{
return (new Coordinate($lat1, $lng1))
->distanceTo(new Coordinate($lat2, $lng2)); // adjust for units
}
Wrap it immediately. Do not sprinkle Coordinate construction through controllers.
1. Adapter/service layer (do this first).
Keep the package behind one class so the rest of the app speaks your vocabulary (LatLng, kilometers, floats) and you can swap implementations without a grep-and-replace across the codebase.
final class GeoService
{
public function distanceKm(LatLng $a, LatLng $b): float
{
$d = (new Coordinate($a->lat, $a->lng))
->distanceTo(new Coordinate($b->lat, $b->lng));
return $this->toKilometers($d); // encapsulate unit conversion here
}
}
2. Bounding-box prefilter in SQL, exact math in PHP.
The package computes distances in PHP; it will not build SQL for you. For "within N km" queries, compute a lat/lng bounding box, filter with indexed WHERE clauses, then run exact geodetic distance in PHP on the survivors.
$latDelta = $radiusKm / 111.0;
$lngDelta = $radiusKm / (111.0 * cos(deg2rad($lat)));
$candidates = Venue::query()
->whereBetween('latitude', [$lat - $latDelta, $lat + $latDelta])
->whereBetween('longitude', [$lng - $lngDelta, $lng + $lngDelta])
->get();
$nearby = $candidates
->map(fn ($v) => [$v, app(GeoService::class)->distanceKm($origin, $v->latLng())])
->filter(fn ($row) => $row[1] <= $radiusKm)
->sortBy(fn ($row) => $row[1]);
For high-volume or production geo search, prefer a spatial index (PostGIS/MySQL POINT with SRID 4326) and use lootils/geo for the math you still need in PHP — bearing, midpoint, destination points, validation of coordinates.
3. Eloquent cast / accessor for coordinates.
Expose a single LatLng value object rather than two loose floats.
protected function latLng(): Attribute
{
return Attribute::make(
get: fn () => new LatLng((float) $this->latitude, (float) $this->longitude),
);
}
4. Validation.
Validate ranges at the boundary — latitude -90..90, longitude -180..180 — in a FormRequest. The package is not a validator; malformed coordinates should never reach it.
5. Workflow integration. Good fits: delivery/ETA estimation, store locators, matching a driver to a pickup, sorting search results by proximity, geo-fencing checks, and route waypoint calculations. Poor fit: heavy GIS pipelines, polygon containment at scale, and tile/rendering work. For those, add PostGIS or a dedicated geo service alongside it.
6. Testing.
Pin known coordinate pairs (London↔Paris ≈ 343 km) as regression tests in your adapter. Assert with a tolerance (assertEqualsWithDelta), never exact float equality.
LatLng::fromLatLng()) in your wrapper.distanceTo() uses.== coordinates or distances. Use tolerances.POINT(lng, lat) versus your (lat, lng) convention is another swap trap.composer.json, keep the wrapper thin, and write your own tests around the behaviors you depend on.GeoService over subclassing package classes. If you need polygon operations, geohashing, or spatial indexes, add a second library rather than forking this one.How can I help you explore Laravel packages today?