jackpopp/geodistance
Laravel/PHP package to calculate geographic distances between coordinates. Supports common formulas and helpers to get miles/kilometers between points, useful for proximity search, radius filtering, and location-based features in apps.
Installation
composer require jackpopp/geodistance
Publish the config file (if needed):
php artisan vendor:publish --provider="Jackpopp\GeoDistance\GeoDistanceServiceProvider"
Model Integration
Add HasGeoDistance trait to your Eloquent model:
use Jackpopp\GeoDistance\HasGeoDistance;
class Restaurant extends Model
{
use HasGeoDistance;
protected $coordinates = 'latitude,longitude'; // Define your coordinate columns
}
First Query Find locations within 10km of a point (e.g., 40.7128° N, -74.0060° W):
$nearby = Restaurant::near(40.7128, -74.0060, 10)->get();
config/geodistance.php (for distance unit defaults, precision, etc.)src/HasGeoDistance.php (trait methods and query builder extensions)src/GeoDistanceServiceProvider.php (service registration)// Basic radius search (default: kilometers)
$results = Model::near($lat, $lng, $radius)->get();
// With unit specification
$results = Model::near($lat, $lng, $radius, 'miles')->get();
$sorted = Model::near($lat, $lng, 50)
->orderByDistance()
->get();
$results = Model::near($lat, $lng, 20)
->where('category', 'pizza')
->whereOpen()
->get();
class Store extends Model
{
use HasGeoDistance;
protected $coordinates = 'lat,long'; // Custom column names
}
$distance = Model::distanceTo($lat, $lng); // Returns distance in configured unit
Schema::table('restaurants', function (Blueprint $table) {
$table->decimal('latitude', 10, 8)->index();
$table->decimal('longitude', 11, 8)->index();
});
$cacheKey = "nearby_{$lat}_{$lng}_{$radius}";
$results = Cache::remember($cacheKey, now()->addHours(1), function () use ($lat, $lng, $radius) {
return Model::near($lat, $lng, $radius)->get();
});
// Add a geohash column and index it
$table->string('geohash')->index();
GeoDistance calculations in tests:
$model = new Model();
$this->partialMock(GeoDistance::class, ['haversine'])
->shouldReceive('haversine')
->with($lat, $lng, $model->latitude, $model->longitude)
->andReturn(5.5);
protected $precision = 6; in your model or config to limit decimal places.miles or meters in queries.Model::near($lat, $lng, 5, 'miles')->get();
(latitude, longitude) convention or document your model’s order.Enable Laravel’s query logging to inspect generated SQL:
DB::enableQueryLog();
Model::near($lat, $lng, 10)->get();
dd(DB::getQueryLog());
Ensure coordinates are within valid ranges:
if (!($lat >= -90 && $lat <= 90) || !($lng >= -180 && $lng <= 180)) {
throw new \InvalidArgumentException("Invalid coordinates");
}
NULL coordinates will break distance calculations. Add guards:
if (is_null($model->latitude) || is_null($model->longitude)) {
return Model::query()->whereNull('latitude');
}
Override the default Haversine formula in your model:
use Jackpopp\GeoDistance\Contracts\DistanceCalculator;
class CustomModel extends Model implements DistanceCalculator
{
use HasGeoDistance;
public function distance($lat1, $lon1, $lat2, $lon2)
{
// Implement your custom logic (e.g., Vincenty formula)
}
}
Extend the model’s toArray() or toJson():
public function toArray()
{
return array_merge(parent::toArray(), [
'distance' => $this->distanceTo($lat, $lng),
]);
}
Add reusable scopes to your model:
public function scopeWithinCity($query, $lat, $lng)
{
return $query->near($lat, $lng, 50); // 50km radius
}
For full-text + geo search, combine with Scout:
use Jackpopp\GeoDistance\ScoutExtensions\HasGeoScout;
class Product extends Model
{
use HasGeoScout;
public function toSearchableArray()
{
return [
'name' => $this->name,
'coordinates' => [$this->latitude, $this->longitude],
];
}
}
How can I help you explore Laravel packages today?