F
Federal Pro · Sistema de Busca
Firebase OK GeoFire v1.0
Documentação Técnica · Sistema em Produção

Busca de motoristas com raio crescente e notificações inteligentes

Sistema completo para encontrar o motorista ideal em tempo real. Expande o raio de busca a cada minuto, notifica cada motorista no máximo duas vezes e integra Firebase Realtime Database, GeoFire, Google Maps Platform e Expo Notifications.

Raio Inicial
2 km
+2 km por rodada
Intervalo
60 s
entre rodadas
Máx. Notificações
2x
por motorista
Expiração
7 min
sem aceitação
02

Arquitetura

Toda a stack é integrada: Firebase Realtime Database guarda posições em tempo real via GeoFire, o backend PHP persiste corridas e tokens no MySQL, e o Expo Notifications entrega push para os dispositivos.

🔥 Firebase Realtime DB

Armazena posições dos motoristas online com geohash, corridas pendentes e controle de notificações.

🐘 MySQL + PHP

Persistência de corridas, motoristas, tokens de push e histórico financeiro. Endpoints REST para o app.

📍 GeoFire

Consultas geográficas eficientes via geohash. Encontra motoristas dentro de um raio específico.

🔔 Expo Push

Envio de notificações push para iOS e Android via Expo Push API com suporte a FCM v1.

03

Infraestrutura

Serviços base que sustentam o sistema. Todos já configurados no projeto.

Serviço Onde Estado
Firebase Realtime DB Projeto federal-driver Ativo
Regras com índice Realtime DB → Rules Precisa adicionar
Tabela corridas MySQL · cPanel Adicionar colunas
Tabela corridas_notificacoes MySQL · cPanel Criar
Backend PHP /backend/api/ Existe
Chave REST Google Maps app.config.ts → extra.google Configurada
FCM v1 (Service Account) EAS Credentials Configurada
04

Bibliotecas

Pacotes npm necessários no app React Native. Todos compatíveis com Expo SDK 57.

Biblioteca Propósito Estado
geofire-common Funções puras: geohashForLocation, distanceBetween, geohashQueryBounds Instalado
geofire Wrapper para queries prontas com new GeoFire(ref).query() — opcional Recomendado
firebase (v9+) SDK modular para Realtime DB, Auth, Functions Instalado
expo-location Obter GPS do dispositivo em foreground e background Instalado
expo-notifications Push notifications nativas com suporte a FCM v1 Instalado
react-native-maps Renderizar mapa nativo (alternativa ao Leaflet/WebView) Instalado
💡
Diferença entre geofire-common e geofire O geofire-common fornece apenas as funções de cálculo (geohash, distância, bounds). Para usar a API new GeoFire(ref).query(...), instale também o pacote geofire.
05

Google Maps Platform

As APIs REST do Google complementam o GeoFire. O GeoFire encontra motoristas por raio; as APIs do Google calculam rotas, endereços e distâncias.

API Função Free tier / mês
Geocoding API Converter endereço ↔ coordenadas (reverse geocode) 10.000 chamadas
Directions API Calcular rota A→B com polyline codificada 10.000 chamadas
Distance Matrix API Distância e tempo entre múltiplos pontos 10.000 chamadas
Places API Autocomplete de endereços, detalhes de locais 10.000 chamadas
Static Maps API Imagem do mapa com rota (usado no Confirmar Viagem) 100.000 chamadas
💰
Preços em 2025 O Google substituiu o crédito mensal de $200 por limites de chamadas gratuitas por API. Para o volume inicial, o custo será $0,00/mês. Configure alertas de orçamento em console.cloud.google.com/billing para evitar surpresas.
06

Tabelas SQL

Ajustes necessários na base MySQL para suportar o sistema de raio crescente.

MySQL · Alterações
-- Adicionar colunas de controle à tabela corridas
ALTER TABLE corridas
  ADD COLUMN raio_atual       INT      DEFAULT 2000,
  ADD COLUMN rodada_atual     INT      DEFAULT 0,
  ADD COLUMN ultima_expansao  DATETIME NULL,
  ADD COLUMN aceita_em        DATETIME NULL;

-- Tabela de controle de notificações (máx 2x por motorista)
CREATE TABLE corridas_notificacoes (
  id            INT AUTO_INCREMENT PRIMARY KEY,
  corrida_id    VARCHAR(50) NOT NULL,
  motorista_id  VARCHAR(50) NOT NULL,
  vezes         TINYINT DEFAULT 1,
  primeira_em   DATETIME NOT NULL,
  ultima_em     DATETIME NOT NULL,
  UNIQUE KEY uniq_corrida_motorista (corrida_id, motorista_id),
  INDEX idx_corrida (corrida_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Índices para performance
CREATE INDEX idx_motoristas_online
  ON motoristas (online, ativo, status);
07

Regras Firebase

Adicione o índice .indexOn no nó de motoristas online. Sem isto, o Firebase baixa todos os dados para o cliente e filtra localmente — lento e caro.

Firebase Realtime DB · Rules
{
  "rules": {
    "motoristas_online_geofire": {
      ".indexOn": "g"
    },
    "corridas": {
      ".indexOn": ["status", "criadoEm"]
    }
  }
}
📌
Estrutura dos dados do GeoFire Cada motorista é guardado com g (geohash) e l (array [lat, lng]). O índice .indexOn: "g" permite queries por prefixo de geohash.
08

Fluxo Completo

Do momento em que o passageiro pede uma corrida até o motorista aceitar.

1
Passageiro cria corrida App chama salvar_corridas.php → MySQL grava → Firebase recebe nó corridas/{id}
↓
2
Rodada 1 dispara (raio 2 km) Motoristas online dentro de 2 km recebem push pela 1ª vez
↓
3
Aos 60 s, expande para 4 km Novos motoristas recebem 1ª vez; os mais próximos recebem 2ª (se todos acima já foram notificados)
↓
4
Aos 120 s, expande para 6 km Continua enquanto ninguém aceita. Motoristas atingindo 2x são ignorados
↓
5
Alguém aceita ou expira em 7 min Firebase atualiza status: "aceita" → loop para automaticamente
09

Lógica PHP

Alternativa sem Cloud Functions. Executa dentro da requisição HTTP com set_time_limit(). Ideal para baixo volume de corridas simultâneas.

PHP · salvar_corridas.php (extrato)
// Após gravar a corrida e obter $corrida_id
function haversineMetros($lat1, $lon1, $lat2, $lon2) {
  $R = 6371000;
  $dLat = deg2rad($lat2 - $lat1);
  $dLon = deg2rad($lon2 - $lon1);
  $a = sin($dLat/2)**2 + cos(deg2rad($lat1))
     * cos(deg2rad($lat2)) * sin($dLon/2)**2;
  return 2 * $R * asin(sqrt($a));
}

$tempo_inicial  = time();
$tempo_maximo   = 420;   // 7 minutos
$raio_inicial   = 2000;  // 2 km
$incremento     = 2000;  // +2 km por minuto

while ((time() - $tempo_inicial) < $tempo_maximo) {
  // Verifica se a corrida já foi aceita
  $stmt = $pdo->prepare("SELECT status FROM corridas WHERE id = ?");
  $stmt->execute([$corrida_id]);
  if ($stmt->fetchColumn() !== 'buscando_motorista') break;

  // Calcula raio atual
  $decorrido   = time() - $tempo_inicial;
  $raio_atual  = $raio_inicial + floor($decorrido / 60) * $incremento;

  // Busca motoristas online e filtra por raio
  $motoristas = $pdo->query(
    "SELECT user_id, expo, fcm_token, latitude, longitude
     FROM motoristas WHERE online = 1 AND ativo = 1"
  )->fetchAll();

  foreach ($motoristas as $m) {
    $d = haversineMetros($origem_lat, $origem_lng, $m['latitude'], $m['longitude']);
    if ($d > $raio_atual) continue;

    // Verifica quantas vezes já foi notificado
    $st = $pdo->prepare("SELECT vezes FROM corridas_notificacoes
                       WHERE corrida_id = ? AND motorista_id = ?");
    $st->execute([$corrida_id, $m['user_id']]);
    $vezes = (int) $st->fetchColumn();

    if ($vezes >= 2) continue;

    // Envia push via Expo
    enviarPushMotorista(
      $m['expo'] ?: $m['fcm_token'],
      $corrida_id,
      $vezes === 0 ? 'Nova corrida' : 'Corrida ainda disponível',
      'A ' . round($d) . 'm de você'
    );

    // Registra no MySQL
    $pdo->prepare("INSERT INTO corridas_notificacoes
        (corrida_id, motorista_id, vezes, primeira_em, ultima_em)
        VALUES (?, ?, 1, NOW(), NOW())
        ON DUPLICATE KEY UPDATE vezes = vezes + 1, ultima_em = NOW()")
      ->execute([$corrida_id, $m['user_id']]);
  }

  sleep(60);
}
10

App React Native

Código do motorista para salvar a posição com geohash e do passageiro para consultar motoristas próximos.

React Native · salvar posição do motorista
import * as Location from 'expo-location';
import { ref, set } from 'firebase/database';
import * as geofire from 'geofire-common';
import { database } from './firebaseConnection';

export async function atualizarPosicao(userId) {
  const { coords } = await Location.getCurrentPositionAsync({});
  const { latitude, longitude, heading } = coords;

  // Gera o geohash para busca por raio
  const geohash = geofire.geohashForLocation([latitude, longitude]);

  await set(ref(database, `motoristas_online_geofire/${userId}`), {
    g: geohash,             // geohash (indexado)
    l: [latitude, longitude], // [lat, lng]
    heading,
    updatedAt: Date.now(),
  });
}
React Native · consultar motoristas próximos (app passageiro)
import { getDatabase, ref, get } from 'firebase/database';
import * as geofire from 'geofire-common';

async function buscarMotoristasProximos(origemLat, origemLng, raioKm) {
  const center = [origemLat, origemLng];
  const radiusInM = raioKm * 1000;

  // Gera bounds de geohash para o raio
  const bounds = geofire.geohashQueryBounds(center, radiusInM);
  const db = getDatabase();
  const promises = [];

  for (const b of bounds) {
    const q = query(
      ref(db, 'motoristas_online_geofire'),
      orderByChild('g'),
      startAt(b[0]),
      endAt(b[1])
    );
    promises.push(get(q));
  }

  const snapshots = await Promise.all(promises);
  const motoristas = [];

  for (const snap of snapshots) {
    snap.forEach((child) => {
      const data = child.val();
      const [lat, lng] = data.l;
      const distanceKm = geofire.distanceBetween(center, [lat, lng]);

      if (distanceKm <= raioKm) {
        motoristas.push({
          id: child.key,
          latitude: lat,
          longitude: lng,
          distanciaKm: distanceKm,
        });
      }
    });
  }

  // Ordena do mais próximo ao mais distante
  motoristas.sort((a, b) => a.distanciaKm - b.distanciaKm);
  return motoristas;
}
11

Checklist de Implementação

Ordem recomendada para implementar tudo do zero.

✓
Firebase Realtime Database ativa no projeto federal-driver
✓
Chave FCM v1 configurada no EAS Credentials
✓
Notificações push a chegar no dispositivo (testado em expo.dev/notifications)
✓
Coluna expo na tabela usuarios com o token ExpoPush
✓
Adicionar índice .indexOn: "g" nas regras do Firebase
✓
Criar tabela corridas_notificacoes no MySQL
✓
Adicionar colunas raio_atual, rodada_atual, ultima_expansao na tabela corridas
✓
Instalar geofire no app (opcional, se quiser usar a API pronta)
✓
Implementar atualizarPosicao() no app do motorista com geohash
✓
Implementar buscarMotoristasProximos() no app do passageiro
✓
Adicionar lógica de raio crescente no PHP ou Firebase Cloud Functions
✓
Testar com corrida real: 5 motoristas distribuídos, validar notificações 1x e 2x
{"success":false,"message":"user_id ausente na requisicao."}