Eloquent models
The HasLocation trait casts a coordinates attribute over latitude and longitude columns and adds a withinRadius() query scope:
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Geolocation\Concerns\HasLocation;
use RoundlyConsulting\Geolocation\DataTransferObjects\Coordinates;
final class Store extends Model
{
use HasLocation; // casts a `coordinates` attribute over `latitude`/`longitude` columns
}
$store = new Store;
$store->coordinates = new Coordinates(48.1486, 17.1077);
$store->save();
Store::query()->withinRadius(new Coordinates(48.15, 17.11), radiusKm: 5)->get();The package ships no migration — add the two columns to your own table:
Schema::create('stores', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->float('latitude')->nullable();
$table->float('longitude')->nullable();
});- Reading coordinates returns null when either column is null.
- Assigning null clears both columns.
- Assigning anything other than a Coordinates instance throws InvalidCoordinatesException.
Exact radius queries
withinRadius() is a bounding-box filter, so rows in the box’s corners can sit slightly outside the true circle. Near the antimeridian it matches longitudes on both sides of ±180°, and near a pole it matches every longitude. Combine it with an exact Haversine check for precise results:
$center = new Coordinates(48.15, 17.11);
$nearby = Store::query()
->withinRadius($center, radiusKm: 5) // cheap bounding-box pre-filter in SQL
->get()
->filter(fn (Store $store): bool => $store->coordinates?->near($center, radiusKm: 5) ?? false);Using the cast directly
Apply CoordinatesCast without the trait, or map it onto a different column pair:
use RoundlyConsulting\Geolocation\Casts\CoordinatesCast;
// Without the trait — latitude / longitude columns:
protected $casts = ['coordinates' => CoordinatesCast::class];
// A custom column pair (cast arguments map to latitudeColumn, longitudeColumn):
protected $casts = ['position' => CoordinatesCast::class.':lat,lng'];The withinRadius() scope always filters on latitude and longitude, so custom column names work with the cast but not with the scope.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.