Geolocalização
Módulo de geolocalização 100% self-hosted: coleta de latitude/longitude pela API do navegador, cálculo de distância/trajetória e plot de mapas — sem nenhuma API paga ou externa. O mapa padrão é um plot SVG tile-free (sem imagens de tiles, sem requests). Espelha o módulo geo do tempest-fastapi-sdk, então os tipos e a matemática batem entre cliente e servidor.
Por que tile-free?
Qualquer mapa com imagem (OpenStreetMap, Mapbox, Google) baixa tiles de um servidor externo. Isso colide com o requisito de "nada externo". Por isso o TrajectoryMap desenha a trajetória projetada em SVG (Web Mercator) sem fundo — zero request. Se você hospeda seu próprio servidor de tiles, dá pra ligar a camada Leaflet opcional passando tileUrl.
Quando usar
- Coletar a posição do usuário (uma vez ou contínua) via
navigator.geolocation. - Gravar e exibir uma trajetória (corrida, entrega, trilha).
- Calcular distância entre pontos ou estimar tempo de viagem offline.
- Plotar tudo sem depender de mapa pago/externo.
Tipos — Coordinate, TrackPoint, TravelEstimate
Os tipos espelham os schemas do FastAPI SDK (snake_case preservado no TravelEstimate pra desserializar direto de uma resposta):
import type { Coordinate, TrackPoint, TravelEstimate, TravelMode } from "tempest-react-sdk";
const sp: Coordinate = { latitude: -23.5505, longitude: -46.6333 };
// TrackPoint = Coordinate + quando foi capturado (+ accuracy opcional)
const ponto: TrackPoint = { ...sp, timestamp: Date.now(), accuracy: 12 };
// TravelMode = "car" | "motorcycle" | "bus"
const modo: TravelMode = "car";
Validadores utilitários: isValidLatitude, isValidLongitude, isCoordinate (type guard), clampLatitude, normalizeLongitude (wrap no antimeridiano).
Distância e trajetória — haversineKm, pathLengthKm, bearingDeg
Matemática pura, sem rede, idêntica ao servidor:
import { haversineKm, pathLengthKm, bearingDeg } from "tempest-react-sdk";
const sp = { latitude: -23.5505, longitude: -46.6333 };
const rio = { latitude: -22.9068, longitude: -43.1729 };
haversineKm(sp, rio); // ≈ 360.9 (km, grande-círculo)
// Comprimento total de uma trajetória (soma dos trechos):
pathLengthKm([sp, { latitude: -23.2, longitude: -45 }, rio]);
// Azimute inicial (graus, 0 = norte, sentido horário):
bearingDeg(sp, rio); // ≈ 65 (nordeste)
Enquadrar um conjunto de pontos — boundingBox, boundsCenter, expandBounds
Para centralizar um mapa numa trajetória, ou decidir o zoom que a cobre:
import { boundingBox, boundsCenter, expandBounds } from "tempest-react-sdk";
const bounds = boundingBox([sp, rio]); // { north, south, east, west } | null
if (bounds) {
boundsCenter(bounds); // o ponto médio, para centralizar
expandBounds(bounds, 0.1); // 10% de folga, para a rota não colar na borda
}
O boundingBox devolve null para lista vazia — não existe moldura em volta
de ponto nenhum, e devolver uma caixa zerada jogaria o mapa no Atlântico.
expandBounds existe porque enquadrar exatamente nos extremos encosta o primeiro
e o último ponto na moldura — a margem é o que faz o traçado caber visualmente.
A matemática crua
haversineKm é a fórmula pronta, mas as duas peças dela saem avulsas para quem
monta um cálculo próprio: EARTH_RADIUS_KM (6371, o raio médio que a fórmula
assume) e toRadians(graus). E unprojectMercator é o par de
projectMercator — converte de volta de coordenada de tela para lat/long, que é
o que um clique no mapa precisa.
import { EARTH_RADIUS_KM, projectMercator, toRadians, unprojectMercator } from "tempest-react-sdk";
const xy = projectMercator({ latitude: -23.55, longitude: -46.63 });
unprojectMercator(xy); // de volta ao par lat/long
EARTH_RADIUS_KM * toRadians(1); // ≈ 111.2 km por grau de latitude
Estimativa de viagem offline — estimateTravel
Sem rede: distância grande-círculo × fator de sinuosidade, duração por velocidade média ajustada ao modo. Espelha estimate_travel do FastAPI SDK.
import { estimateTravel } from "tempest-react-sdk";
const est = estimateTravel(sp, rio, "car");
// { mode: "car", distance_km: ~469, duration_minutes: ~562, source: "heuristic" }
// Ônibus é ~1.6× mais lento que carro no mesmo trecho:
estimateTravel(sp, rio, "bus").duration_minutes;
// Ajuste os fatores:
estimateTravel(sp, rio, "car", { circuityFactor: 1.4, carSpeedKmh: 60 });
Fatores padrão
circuityFactor = 1.3 (a estrada é ~30% maior que a reta), carSpeedKmh = 50, multiplicadores de duração car: 1.0 · motorcycle: 0.95 · bus: 1.6.
Eles também são exportados — DEFAULT_CIRCUITY_FACTOR, DEFAULT_CAR_SPEED_KMH
e DEFAULT_MODE_DURATION_FACTORS — para uma tela de ajustes mostrar o valor
que está prestes a sobrescrever, em vez de repeti-lo e sair de sincronia.
Rota real (opt-in) — createOSRMBackend
Quando você hospeda um OSRM e quer rota por ruas de verdade, construa um backend injetando a URL do seu servidor. É o único ponto que faz request — e só quando você o cria.
import { createOSRMBackend } from "tempest-react-sdk";
const backend = createOSRMBackend({ baseUrl: "https://osrm.interno" });
const est = await backend.route(sp, rio, { mode: "car" });
// { …, source: "osrm" }
Isto faz request de rede
O SDK não embute nenhum endpoint. Aponte baseUrl pra um motor de roteamento seu. Pra estimativa zero-rede, use estimateTravel.
Qualquer motor (Valhalla, GraphHopper) serve: implemente a interface RoutingBackend (route(origin, destination, { mode }) => Promise<TravelEstimate>).
Rastreamento ao vivo — usePositionTracker
Grava uma trajetória a partir de watchPosition, filtrando jitter e acumulando distância. Ciclo de vida amarrado ao componente (o watch é encerrado no unmount).
import { usePositionTracker, TrajectoryMap } from "tempest-react-sdk";
export function CorridaAoVivo() {
const { points, lastPoint, distanceKm, isTracking, start, stop, clear } =
usePositionTracker({ minDistanceKm: 0.01, positionOptions: { enableHighAccuracy: true } });
return (
<div>
<button onClick={isTracking ? stop : start}>
{isTracking ? "Parar" : "Rastrear"}
</button>
<button onClick={clear}>Limpar</button>
<span>{distanceKm.toFixed(2)} km · {points.length} pontos</span>
<TrajectoryMap points={points} current={lastPoint} height={360} />
</div>
);
}
minDistanceKm(default0.005= 5 m) descarta amostras muito próximas — filtra o tremido do GPS parado.maxPointslimita o array retido (a distância total continua contando).autoStart: truejá começa a rastrear na montagem.
Versão imperativa — createPositionTracker
Fora do React (services), o controller expõe start/stop/clear + getters points/distanceKm/status:
import { createPositionTracker } from "tempest-react-sdk";
const tracker = createPositionTracker({
minDistanceKm: 0.01,
onUpdate: (pts) => console.log(pts.length),
});
tracker.start();
// …depois
tracker.stop();
console.log(tracker.distanceKm);
Plot do mapa — TrajectoryMap
Por padrão, plot SVG tile-free: projeta os pontos (Web Mercator), auto-ajusta aos bounds, desenha a polyline + marcadores (início verde, atual pulsante) + grid + barra de escala em km. Zero dependência, zero rede.
import { TrajectoryMap } from "tempest-react-sdk";
<TrajectoryMap
points={points}
current={lastPoint}
height={360}
showGrid
showScale
/>;
Tiles reais (opt-in) com Leaflet
Se você hospeda tiles próprios, passe tileUrl — o componente carrega o Leaflet sob demanda (import dinâmico) e sobe uma camada de tiles de verdade. Requer o leaflet instalado no app e a folha de estilo importada uma vez.
// npm install leaflet
import "leaflet/dist/leaflet.css";
import { TrajectoryMap } from "tempest-react-sdk";
<TrajectoryMap
points={points}
tileUrl="https://tiles.SEU-SERVIDOR/{z}/{x}/{y}.png"
tileAttribution="© Seus dados de mapa"
height={420}
/>;
Leaflet é peer opcional
Sem tileUrl, o Leaflet nunca entra no bundle (o import é lazy). Se tileUrl for passado e o leaflet não estiver instalado, o componente mostra uma mensagem em vez de quebrar.
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
points |
readonly Coordinate[] |
— | Trajetória a desenhar. |
current |
Coordinate \| null |
— | Marcador de posição atual (pulsante). |
height |
number |
320 |
Altura do viewport em px. |
padding |
number |
24 |
Margem interna em px. |
showGrid / showScale |
boolean |
true |
Grid e barra de escala (modo SVG). |
strokeColor |
string |
token primário | Cor da linha. |
tileUrl |
string |
— | Template {z}/{x}/{y} do seu tile server → liga o Leaflet. |
tileAttribution |
string |
— | Atribuição sobre os tiles. |
Recap
- Coleta:
usePositionTracker/createPositionTrackergravam trajetória vianavigator.geolocation, filtrando jitter — tudo no navegador. - Matemática:
haversineKm,pathLengthKm,bearingDeg,estimateTravel— offline, espelhando otempest-fastapi-sdk. - Mapa:
TrajectoryMapplota em SVG sem tiles por padrão;tileUrlliga o Leaflet só com servidor seu. - Rota real:
createOSRMBackendé opt-in e aponta pro seu OSRM — o único ponto que toca a rede.
Veja também
- Hooks utilitários —
useGeolocation(fix único / watch de baixo nível) - HTTP — enviar a trajetória pro backend
- Offline — persistir a trajetória localmente