| name | geo |
| description | Use quando a feature envolver dados geográficos — localização, área, rota, raio de busca, mapa interativo. Cobre PostGIS (tipos, funções, índices GIST), API GeoJSON, Leaflet (frontend), GeoServer (opcional para camadas WMS/WFS), padrão brasileiro SIRGAS 2000 (EPSG:4674) e fontes de dados IBGE/CONCAR. |
Geo: PostGIS + Leaflet + GeoServer (opcional)
Padrões universais do projeto (CLAUDE.md) se aplicam normalmente —
UUIDs, hexagonal, CQRS, etc. Esta skill cobre apenas o que é específico
de dados geográficos.
Quando usar o quê
Dado geográfico simples (ponto, área, rota) + mapa de visualização
→ PostGIS no banco + API retornando GeoJSON + Leaflet no frontend
→ Não precisa de GeoServer
Múltiplas camadas temáticas + estilos complexos + integração com QGIS
+ grandes volumes de dados raster + publicação de serviços OGC para terceiros
→ PostGIS + GeoServer (WMS/WFS) + Leaflet consumindo WMS
→ GeoServer é um serviço adicional com seu próprio container
Dúvida de quando usar GeoServer: se o mapa precisa mostrar dados de
camadas externas (limites do IBGE, malha viária do DNIT) ou se o volume
de features for grande demais para carregar como GeoJSON bruto (>10k
features), considerar GeoServer.
Padrão de coordenadas obrigatório no Brasil
| Item | Valor | Por quê |
|---|
| Datum horizontal | SIRGAS 2000 | Obrigatório por resolução do IBGE desde 2005 |
| SRID no PostGIS | EPSG:4674 | SIRGAS 2000 geográfico (graus) |
| SRID alternativo | EPSG:31982 a 31985 | SIRGAS 2000 projetado (metros) por fuso UTM |
| Formato de troca | GeoJSON (RFC 7946) | Padrão web — coordenadas sempre em WGS84/SIRGAS |
| Nunca usar | SAD 69 (EPSG:4618), Córrego Alegre | Datums antigos, descontinuados pelo IBGE |
SELECT srtext FROM spatial_ref_sys WHERE srid = 4674;
PostGIS — banco de dados
Instalação e extensão
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;
SELECT PostGIS_Version();
Tipos de geometria
localizacao GEOMETRY(POINT, 4674)
area GEOMETRY(POLYGON, 4674)
trecho GEOMETRY(LINESTRING, 4674)
geometria GEOMETRY(GEOMETRY, 4674)
area GEOMETRY(MULTIPOLYGON, 4674)
Migration com PostGIS
CREATE TABLE ponto_interesse (
id UUID PRIMARY KEY,
nome TEXT NOT NULL,
descricao TEXT,
localizacao GEOMETRY(POINT, 4674) NOT NULL,
criado_em TIMESTAMPTZ NOT NULL DEFAULT now(),
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT now(),
excluido_em TIMESTAMPTZ
);
CREATE INDEX idx_ponto_interesse_localizacao
ON ponto_interesse USING GIST (localizacao);
Funções PostGIS mais usadas
ST_SetSRID(ST_MakePoint(-43.9345, -19.9167), 4674)
ST_GeomFromText('POINT(-43.9345 -19.9167)', 4674)
ST_Distance(
localizacao::geography,
ST_SetSRID(ST_MakePoint(-43.9345, -19.9167), 4674)::geography
) AS distancia_metros
WHERE ST_DWithin(
localizacao::geography,
ST_SetSRID(ST_MakePoint(-43.9345, -19.9167), 4674)::geography,
5000
)
WHERE ST_Within(localizacao, area_de_cobertura)
ST_Centroid(area)
ST_AsGeoJSON(localizacao, 6)
ST_SetSRID(ST_GeomFromGeoJSON('{"type":"Point","coordinates":[-43.93,-19.91]}'), 4674)
ST_Area(area::geography)
ST_Intersects(area_a, area_b)
Allowlist para ORDER BY com colunas geo
var COLUNAS_ORDENAVEIS_GEO = map[string]string{
"distancia": "ST_Distance(localizacao::geography, $1::geography)",
"nome": "nome",
}
API — retornando GeoJSON
O padrão de resposta para dados geográficos é GeoJSON (RFC 7946):
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-43.9345, -19.9167]
},
"properties": {
"id": "uuid",
"nome": "Praça da Liberdade",
"descricao": "..."
}
}
]
}
Endpoints geo seguem o mesmo padrão REST — não criar endpoints especiais:
GET /api/v1/pontos-interesse?lat=-19.9167&lon=-43.9345&raio=5000
→ FeatureCollection com pontos no raio de 5km
GET /api/v1/pontos-interesse/:id
→ Feature individual
POST /api/v1/pontos-interesse
Body: { nome, descricao, localizacao: { type: "Point", coordinates: [lon, lat] } }
Exemplo Go/Gin:
type PontoInteresse struct {
ID uuid.UUID `json:"id"`
Nome string `json:"nome"`
Localizacao GeoJSONGeometry `json:"localizacao"`
}
type GeoJSONGeometry struct {
Type string `json:"type"`
Coordinates []float64 `json:"coordinates"`
}
func (r *SQLPontoInteresseRepository) BuscarNoRaio(
ctx context.Context, lon, lat, raioMetros float64,
) ([]PontoInteresse, error) {
rows, err := r.db.QueryContext(ctx, `
SELECT
id, nome,
ST_AsGeoJSON(localizacao, 6)::json AS localizacao,
ST_Distance(localizacao::geography, ST_SetSRID(
ST_MakePoint($1, $2), 4674)::geography) AS distancia_m
FROM ponto_interesse
WHERE excluido_em IS NULL
AND ST_DWithin(
localizacao::geography,
ST_SetSRID(ST_MakePoint($1, $2), 4674)::geography,
$3
)
ORDER BY distancia_m
`, lon, lat, raioMetros)
}
Editor de mapas — desenho e edição de geometrias
Quando a feature precisa que o usuário desenhe ou edite geometrias
(ponto, linha, polígono), usar leaflet-geoman — plugin mais maduro para
edição de geometrias no Leaflet, output direto em GeoJSON.
npm install @geoman-io/leaflet-geoman-free
npm install --save-dev @types/leaflet
npm install @geoman-io/leaflet-geoman-free
Tipos de geometria suportados
| Ferramenta | Tipo GeoJSON | PostGIS |
|---|
| Marcador | Point | GEOMETRY(POINT, 4674) |
| Linha | LineString | GEOMETRY(LINESTRING, 4674) |
| Polígono | Polygon | GEOMETRY(POLYGON, 4674) |
| Retângulo | Polygon | GEOMETRY(POLYGON, 4674) |
| Círculo* | Point + raio | Salvar centro + raio separados |
*Círculo não é um tipo GeoJSON nativo — salvar como Point com campo
raio_metros separado, ou converter para polígono aproximado com
ST_Buffer.
Implementação Next.js
'use client'
import { useEffect, useRef } from 'react'
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
import '@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css'
interface MapEditorProps {
value?: GeoJSON.Geometry | null
onChange: (geom: GeoJSON.Geometry | null) => void
geometryType?: 'Point' | 'LineString' | 'Polygon' | 'all'
height?: string
}
export function MapEditor({
value, onChange, geometryType = 'all', height = '400px'
}: MapEditorProps) {
const mapRef = useRef<L.Map | null>(null)
const divRef = useRef<HTMLDivElement>(null)
const layerRef = useRef<L.FeatureGroup>(L.featureGroup())
( {
(!divRef. || mapRef.)
map = L.(divRef.).([-, -], )
mapRef. = map
L.(, {
:
}).(map)
layerRef..(map)
(value) {
layer = L.(value)
layer.( layerRef..(l))
map.(layerRef..())
}
map..({
: ,
: geometryType === || geometryType === ,
: geometryType === || geometryType === ,
: geometryType === || geometryType === ,
: geometryType === || geometryType === ,
: ,
: ,
: ,
: ,
: ,
})
= () => {
geojson = layerRef..()
features = (geojson .).
(features. === ) { (); }
(features. ===
? features[].
: { : , : features.( f.) }
)
}
map.(, { layerRef..(layer); () })
map.(, ())
map.(, ())
{ map.(); mapRef. = }
}, [])
(
)
}
<Controller name="localizacao" control={control}
render={({ field }) =>
<MapEditor
value={field.value}
onChange={field.onChange}
geometryType="Point" // ou "Polygon", "LineString", "all"
/>
}
/>
Salvar no backend — GeoJSON → PostGIS
_, err := db.ExecContext(ctx, `
UPDATE ponto_interesse
SET localizacao = ST_SetSRID(ST_GeomFromGeoJSON($1), 4674)
WHERE id = $2
`, geometriaJSON, id)
cursor.execute("""
UPDATE ponto_interesse
SET localizacao = ST_SetSRID(ST_GeomFromGeoJSON(%s), 4674)
WHERE id = %s
""", (json.dumps(geom), id))
Renderizar geometria salva (modo leitura)
<MapView
geojson={{ type: 'Feature', geometry: localizacao, properties: {} }}
height="300px"
/>
Leaflet — mapa no frontend (visualização)
Instalação
npm install leaflet react-leaflet
npm install --save-dev @types/leaflet
npm install leaflet
npm install --save-dev @types/leaflet
Mapa base (Next.js — componente client-side obrigatório)
'use client'
import { useEffect, useRef } from 'react'
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
delete (L.Icon.Default.prototype as any)._getIconUrl
L.Icon.Default.mergeOptions({
iconRetinaUrl: '/leaflet/marker-icon-2x.png',
iconUrl: '/leaflet/marker-icon.png',
shadowUrl: '/leaflet/marker-shadow.png',
})
interface MapViewProps {
center?: [number, number]
zoom?: number
geojson?: GeoJSON.FeatureCollection
height?: string
}
export function MapView({
center = [-15.7942, -47.8822], // Brasília como centro padrão do Brasil
zoom = 10,
geojson,
height =
}: ) {
mapRef = useRef<L. | >()
divRef = useRef<>()
( {
(!divRef. || mapRef.)
mapRef. = L.(divRef.).(center, zoom)
L.(, {
: ,
: ,
}).(mapRef.)
{ mapRef.?.(); mapRef. = }
}, [])
( {
(!mapRef. || !geojson)
layer = L.(geojson, {
: {
(feature.?.) {
layer.(feature..)
}
}
}).(mapRef.)
mapRef..(layer.())
{ layer.() }
}, [geojson])
(
)
}
Atenção — ordem de coordenadas:
- GeoJSON:
[longitude, latitude] (lon, lat)
- Leaflet:
[latitude, longitude] (lat, lon)
- Sempre converter ao passar dados do GeoJSON para o Leaflet
Camada WMS do GeoServer (quando disponível)
L.tileLayer.wms('http://geoserver.empresa.com/geoserver/wms', {
layers: 'workspace:nome_da_camada',
format: 'image/png',
transparent: true,
attribution: 'Fonte: GeoServer',
crs: L.CRS.EPSG4326,
}).addTo(mapRef.current)
GeoServer (opcional — ver "Quando usar o quê")
GeoServer é um servidor de dados geoespaciais que publica camadas via
protocolos OGC (WMS, WFS, WCS). Adicionar ao projeto quando necessário.
Container no docker-compose
services:
geoserver:
image: kartoza/geoserver:2.24.0
environment:
GEOSERVER_DATA_DIR: /opt/geoserver/data_dir
GEOWEBCACHE_CACHE_DIR: /opt/geoserver/gwc
GEOSERVER_ADMIN_PASSWORD: ${GEOSERVER_ADMIN_PASSWORD}
POSTGRES_JNDI_ENABLED: "true"
HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASS: ${POSTGRES_PASSWORD}
volumes:
- geoserver_data:/opt/geoserver/data_dir
ports:
- "8080:8080"
depends_on:
- postgres
volumes:
geoserver_data:
Variáveis de ambiente
GEOSERVER_URL=http://geoserver:8080/geoserver
GEOSERVER_ADMIN_USER=admin
GEOSERVER_ADMIN_PASSWORD=
GEOSERVER_WORKSPACE=nome_do_projeto
Configuração via REST API do GeoServer
O GeoServer expõe REST API para criar workspaces, stores e camadas
programaticamente — útil para automatizar a publicação de camadas:
curl -u admin:senha -X POST \
http://geoserver/rest/workspaces \
-H "Content-Type: application/json" \
-d '{"workspace":{"name":"meu_projeto"}}'
curl -u admin:senha -X POST \
http://geoserver/rest/workspaces/meu_projeto/datastores \
-H "Content-Type: application/json" \
-d '{
"dataStore": {
"name": "postgis",
"connectionParameters": {
"host": "postgres", "port": 5432,
"database": "meu_banco",
"user": "usuario", "passwd": "senha",
"dbtype": "postgis"
}
}
}'
Fontes de dados geográficos brasileiros
Formato de download padrão: Shapefile (.shp) ou GeoPackage (.gpkg).
Para importar ao PostGIS:
ogr2ogr -f "PostgreSQL" \
PG:"host=localhost dbname=meu_banco user=usuario" \
municipios_brasil.shp \
-nln municipios \
-t_srs EPSG:4674 \
-lco GEOMETRY_NAME=geometria \
-lco FID=id
Checklist geo no dev-fullstack