jeorgy/laravel-postgis

A production-grade Laravel package for PostGIS spatial types and geospatial queries through Eloquent.

Maintainers

Package info

github.com/jeorgy/laravel-postgis

pkg:composer/jeorgy/laravel-postgis

Statistics

Installs: 40

Dependents: 0

Suggesters: 0

Stars: 0

v2.0.1 2026-05-28 16:43 UTC

This package is auto-updated.

Last update: 2026-05-28 16:48:49 UTC


README

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 -90 e 90
  • Longitude deve estar entre -180 e 180
  • 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