laravel-postgis maintained by jeorgy
Laravel PostGIS
Biblioteca para consultas geoespaciais em Eloquent com PostgreSQL/PostGIS: cálculo de distâncias, filtros por polígonos, conversão GeoJSON/WKT e objetos de valor imutáveis — com API moderna, tipada e nativa ao Laravel.
Requisitos
- PHP >= 8.4
- Laravel ^12.0
- PostgreSQL com extensão PostGIS habilitada
CREATE EXTENSION IF NOT EXISTS postgis;
Instalação
Via Packagist (versão estável)
composer require jeorgy/laravel-postgis
Via repositório local (desenvolvimento)
Se você está testando a versão de desenvolvimento antes da publicação, adicione um repositório local no composer.json da sua aplicação Laravel:
{
"repositories": [
{
"type": "path",
"url": "/caminho/absoluto/para/laravel-postgis"
}
],
"require": {
"jeorgy/laravel-postgis": "*"
}
}
Em seguida:
composer require jeorgy/laravel-postgis:@dev
Via repositório Git
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/jeorgy/laravel-postgis"
}
],
"require": {
"jeorgy/laravel-postgis": "dev-main"
}
}
O Service Provider é registrado automaticamente via Laravel Package Auto-Discovery.
Configuração do Modelo
Adicione a trait Postgis (ou HasSpatialColumns) ao seu modelo Eloquent que possui uma coluna geoespacial.
use Illuminate\Database\Eloquent\Model;
use Jeorgy\LaravelPostgis\Postgis;
class Place extends Model
{
use Postgis;
}
Coluna personalizada
Por padrão a trait usa a coluna location. Para alterar, defina a propriedade $location:
class Place extends Model
{
use Postgis;
protected $location = 'coordinates';
}
Unidade de distância
Use a propriedade $spatialUnit com o enum DistanceUnit para configurar a unidade retornada pelo escopo withDistance:
use Jeorgy\LaravelPostgis\Enums\DistanceUnit;
class Place extends Model
{
use Postgis;
protected DistanceUnit $spatialUnit = DistanceUnit::KILOMETER;
}
| Enum | Unidade | Fator de divisão |
|---|---|---|
DistanceUnit::METER |
metros | 1 (padrão) |
DistanceUnit::KILOMETER |
quilômetros | 1000 |
DistanceUnit::MILE |
milhas | 1609.344 |
Migration
Garanta que a coluna geoespacial seja do tipo adequado ao PostGIS:
Schema::create('places', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->specificType('location', 'geography(Point, 4326)');
$table->timestamps();
});
Objetos de Valor
Point
Objeto imutável (final readonly class) que representa um ponto geográfico.
use Jeorgy\LaravelPostgis\Geometries\Point;
$point = new Point(latitude: -23.5505, longitude: -46.6333);
$point = new Point(latitude: -23.5505, longitude: -46.6333, srid: 4326);
$point->latitude; // -23.5505
$point->longitude; // -46.6333
$point->srid; // 4326
$point->toWkt(); // "POINT(-46.6333 -23.5505)"
$point->toGeoJson();
// ['type' => 'Point', 'coordinates' => [-46.6333, -23.5505]]
Validações aplicadas automaticamente na construção:
- Latitude deve estar entre
-90e90 - Longitude deve estar entre
-180e180 - SRID deve ser um inteiro positivo
Polygon
Objeto que representa um polígono a partir de anéis de coordenadas ou GeoJSON.
use Jeorgy\LaravelPostgis\Geometries\Polygon;
// A partir de anéis de coordenadas [lng, lat]
$polygon = new Polygon([
[[30, 10], [10, 20], [20, 40], [40, 40]],
]);
// A partir de um objeto GeoJSON
$geoJson = json_decode('{"geometry":{"coordinates":[[[30,10],[10,20],[20,40],[40,40]]]}}');
$polygon = Polygon::fromGeoJson($geoJson);
$polygon->toWkt(); // "POLYGON((30 10, 10 20, 20 40, 40 40, 30 10))"
$polygon->toGeoJson(); // ['type' => 'Polygon', 'coordinates' => [...]]
O anel é fechado automaticamente no WKT se o primeiro e último ponto diferirem.
Escopos de Consulta
withDistance
Adiciona uma coluna distance ao resultado usando ST_DistanceSphere. O valor retornado respeita a $spatialUnit do modelo.
$origin = new Point(latitude: -23.5505, longitude: -46.6333);
$places = Place::query()
->withDistance($origin)
->orderBy('distance')
->get();
// $place->distance => distância em metros (ou km/milhas conforme $spatialUnit)
Passando null, a coluna distance retorna 0:
Place::query()->withDistance(null)->get();
Também aceita array [lat, lng] ou array associativo ['lat' => ..., 'lng' => ...]:
Place::query()->withDistance([-23.5505, -46.6333])->get();
Place::query()->withDistance(['lat' => -23.5505, 'lng' => -46.6333])->get();
whereDistance / orWhereDistance
Filtra registros por distância com um operador de comparação. O valor de $units é sempre em metros (independente da $spatialUnit).
use Jeorgy\LaravelPostgis\Geometries\Point;
$origin = new Point(latitude: -23.5505, longitude: -46.6333);
// Registros a menos de 5 km (5000 metros)
Place::query()
->whereDistance($origin, '<', 5000)
->get();
// Combinando com orWhereDistance
Place::query()
->whereDistance($origin, '<', 1000)
->orWhereDistance($origin, '>', 10000)
->get();
Operadores válidos: <, <=, >, >=, =
Operadores inválidos lançam SpatialQueryException.
whereCovers / orWhereCovers
Filtra registros cujo ponto geoespacial está dentro de um polígono, usando ST_Covers.
use Jeorgy\LaravelPostgis\Geometries\Polygon;
// A partir de um objeto Polygon
$polygon = new Polygon([
[[-46.7, -23.6], [-46.5, -23.6], [-46.5, -23.4], [-46.7, -23.4]],
]);
Place::query()->whereCovers($polygon)->get();
// A partir de um objeto GeoJSON
$geoJson = json_decode('{"geometry":{"coordinates":[[[30,10],[10,20],[20,40],[40,40]]]}}');
Place::query()
->where('active', true)
->whereCovers($geoJson)
->orWhereCovers($outroGeoJson)
->get();
Cast de Ponto
Para que o Eloquent hidrate e persista automaticamente a coluna como um objeto Point:
use Jeorgy\LaravelPostgis\Casts\PointCast;
class Place extends Model
{
use Postgis;
protected $casts = [
'location' => PointCast::class,
];
}
// Hidratação automática
$place = Place::find(1);
$place->location; // instância de Point
$place->location->latitude; // float
Exemplo completo
use Jeorgy\LaravelPostgis\Enums\DistanceUnit;
use Jeorgy\LaravelPostgis\Geometries\Point;
use Jeorgy\LaravelPostgis\Geometries\Polygon;
use Jeorgy\LaravelPostgis\Postgis;
class Place extends Model
{
use Postgis;
protected DistanceUnit $spatialUnit = DistanceUnit::KILOMETER;
protected $casts = [
'location' => \Jeorgy\LaravelPostgis\Casts\PointCast::class,
];
}
$origin = new Point(latitude: -23.5505, longitude: -46.6333);
$nearby = Place::query()
->withDistance($origin)
->whereDistance($origin, '<', 5000)
->orderBy('distance')
->limit(10)
->get();
$area = new Polygon([
[[-46.7, -23.6], [-46.5, -23.6], [-46.5, -23.4], [-46.7, -23.4]],
]);
$inside = Place::query()
->whereCovers($area)
->get();
Exceções
Todas as exceções do pacote estendem SpatialException:
| Exceção | Quando é lançada |
|---|---|
InvalidCoordinatesException |
Latitude/longitude fora do intervalo válido |
InvalidSridException |
SRID não positivo |
InvalidGeometryException |
Polígono malformado, anel com menos de 3 pontos, etc. |
UnsupportedGeometryException |
Tipo de geometria não suportado no GeoJSON |
SpatialQueryException |
Operador inválido ou representação de ponto inválida |
use Jeorgy\LaravelPostgis\Exceptions\SpatialException;
try {
$results = Place::query()->whereDistance($point, '!=', 1000)->get();
} catch (SpatialException $e) {
// Trate erros espaciais de forma unificada
logger()->error('Spatial error', ['message' => $e->getMessage()]);
}
Testes
vendor/bin/phpunit