testes/Modulos Angular/projects/idt_app/docs/components/GEOCODING_USAGE_EXAMPLES.md

22 KiB

🌍 Geocoding Service - Guia Completo de Uso (Google Maps)

📋 Índice


🎯 Visão Geral

O GeocodingService é um serviço Angular que utiliza a Google Geocoding API para conversão entre coordenadas e endereços. Oferece alta precisão e confiabilidade para aplicações profissionais que necessitam de geolocalização precisa.

Principais Recursos

  • 🔍 Geocodificação Reversa: Lat/Lng → Endereço
  • 🗺️ Geocodificação Direta: Endereço → Lat/Lng
  • 📍 Localização Atual: GPS do usuário
  • 📏 Cálculo de Distância: Entre dois pontos
  • 🇧🇷 Otimizado para Brasil: Resultados priorizados
  • 🎯 Alta Precisão: Dados do Google Maps

🔑 Configuração da API Key

1. Obter Chave da API Google

  1. Acesse Google Cloud Console
  2. Crie um projeto ou selecione existente
  3. Ative a Geocoding API
  4. Crie credenciais (API Key)
  5. Configure restrições de segurança

2. Configurar no Projeto

Arquivo: geocoding.config.ts

export const GEOCODING_CONFIG = {
  // 🔑 Substitua pela sua chave real
  apiKey: 'AIzaSyBvOkBwgGlbUiuS-oSH7-UYvtqHTWQPgOQ',
  
  // 🌐 URLs da API
  baseUrl: 'https://maps.googleapis.com/maps/api/geocode/json',
  
  // ⚙️ Configurações
  timeout: 10000, // 10 segundos
  retryAttempts: 2,
  language: 'pt-BR',
  region: 'BR'
};

3. Variáveis de Ambiente

// environment.development.ts
export const environment = {
  // ... outras configurações
  googleMapsApiKey: 'SUA_CHAVE_DE_DESENVOLVIMENTO'
};

// environment.production.ts
export const environment = {
  // ... outras configurações  
  googleMapsApiKey: 'SUA_CHAVE_DE_PRODUCAO'
};

🚀 Instalação e Configuração

1. Importar o Service

import { GeocodingService } from './shared/services/geocoding/geocoding.service';

@Component({
  // ...
  providers: [GeocodingService] // ou providedIn: 'root'
})
export class MeuComponent {
  constructor(private geocodingService: GeocodingService) {}
}

2. Interfaces Disponíveis

interface GeocodingResult {
  latitude: number;
  longitude: number;
  address: string;
  city?: string;
  state?: string;
  country?: string;
  postalCode?: string;
  formattedAddress?: string;
  placeId?: string; // Google Place ID
}

interface LocationCoordinates {
  latitude: number;
  longitude: number;
}

💻 Uso Básico

🔍 1. Geocodificação Reversa (Coordenadas → Endereço)

// Coordenadas da Av. Paulista, São Paulo
const latitude = -23.561414;
const longitude = -46.656219;

this.geocodingService.reverseGeocode(latitude, longitude)
  .subscribe({
    next: (result: GeocodingResult) => {
      console.log('📍 Endereço encontrado:', result.formattedAddress);
      console.log('🏙️ Cidade:', result.city);
      console.log('🏛️ Estado:', result.state);
      console.log('📮 CEP:', result.postalCode);
      console.log('🆔 Place ID:', result.placeId);
    },
    error: (error) => {
      console.error('❌ Erro:', error.message);
    }
  });

🗺️ 2. Geocodificação Direta (Endereço → Coordenadas)

const endereco = 'Av. Paulista, 1000, São Paulo, SP';

this.geocodingService.geocode(endereco)
  .subscribe({
    next: (results: GeocodingResult[]) => {
      if (results.length > 0) {
        const primeiro = results[0];
        console.log('📍 Coordenadas:', primeiro.latitude, primeiro.longitude);
        console.log('📬 Endereço completo:', primeiro.formattedAddress);
        
        // Mostrar todas as opções encontradas
        results.forEach((result, index) => {
          console.log(`${index + 1}. ${result.formattedAddress}`);
        });
      }
    },
    error: (error) => {
      console.error('❌ Erro na busca:', error.message);
    }
  });

📍 3. Localização Atual do Usuário

async obterLocalizacaoAtual() {
  try {
    // Pedir permissão e obter coordenadas
    const coords = await this.geocodingService.getCurrentLocation();
    console.log('📍 Localização atual:', coords);

    // Converter em endereço
    this.geocodingService.reverseGeocode(coords.latitude, coords.longitude)
      .subscribe({
        next: (endereco) => {
          console.log('🏠 Você está em:', endereco.formattedAddress);
        }
      });

  } catch (error) {
    console.error('❌ Erro ao obter localização:', error.message);
    // Tratar erros: permissão negada, GPS desligado, etc.
  }
}

🎯 Exemplos Práticos

📱 1. Componente de Seleção de Localização

@Component({
  selector: 'app-location-selector',
  template: `
    <div class="location-selector">
      <!-- Input de busca -->
      <div class="search-box">
        <input 
          [(ngModel)]="searchTerm" 
          (keyup.enter)="buscarEndereco()"
          placeholder="Digite um endereço..."
        >
        <button (click)="buscarEndereco()">
          <i class="fas fa-search"></i>
        </button>
      </div>

      <!-- Botão de localização atual -->
      <button 
        class="current-location-btn" 
        (click)="usarLocalizacaoAtual()"
        [disabled]="loading"
      >
        <i class="fas fa-location-arrow"></i>
        {{ loading ? 'Obtendo...' : 'Usar minha localização' }}
      </button>

      <!-- Resultados da busca -->
      <div class="results" *ngIf="resultados.length > 0">
        <h4>📍 Endereços encontrados:</h4>
        <div 
          class="result-item" 
          *ngFor="let resultado of resultados; let i = index"
          (click)="selecionarEndereco(resultado)"
        >
          <div class="address">{{ resultado.formattedAddress }}</div>
          <div class="coordinates">
            {{ resultado.latitude.toFixed(6) }}, {{ resultado.longitude.toFixed(6) }}
          </div>
        </div>
      </div>

      <!-- Endereço selecionado -->
      <div class="selected-location" *ngIf="enderecoSelecionado">
        <h4>✅ Localização selecionada:</h4>
        <div class="selected-address">{{ enderecoSelecionado.formattedAddress }}</div>
        <div class="selected-details">
          <span><strong>Cidade:</strong> {{ enderecoSelecionado.city }}</span>
          <span><strong>Estado:</strong> {{ enderecoSelecionado.state }}</span>
          <span><strong>CEP:</strong> {{ enderecoSelecionado.postalCode }}</span>
        </div>
      </div>
    </div>
  `
})
export class LocationSelectorComponent {
  searchTerm = '';
  loading = false;
  resultados: GeocodingResult[] = [];
  enderecoSelecionado: GeocodingResult | null = null;

  @Output() locationSelected = new EventEmitter<GeocodingResult>();

  constructor(private geocodingService: GeocodingService) {}

  async buscarEndereco() {
    if (!this.searchTerm.trim()) return;

    this.loading = true;
    this.geocodingService.geocode(this.searchTerm)
      .subscribe({
        next: (results) => {
          this.resultados = results;
          this.loading = false;
        },
        error: (error) => {
          console.error('❌ Erro na busca:', error);
          this.loading = false;
        }
      });
  }

  async usarLocalizacaoAtual() {
    this.loading = true;
    try {
      const coords = await this.geocodingService.getCurrentLocation();
      
      this.geocodingService.reverseGeocode(coords.latitude, coords.longitude)
        .subscribe({
          next: (resultado) => {
            this.selecionarEndereco(resultado);
            this.loading = false;
          },
          error: (error) => {
            console.error('❌ Erro ao buscar endereço:', error);
            this.loading = false;
          }
        });
    } catch (error) {
      console.error('❌ Erro ao obter localização:', error);
      this.loading = false;
    }
  }

  selecionarEndereco(endereco: GeocodingResult) {
    this.enderecoSelecionado = endereco;
    this.locationSelected.emit(endereco);
  }
}

🚚 2. Rastreamento de Veículos

@Component({
  selector: 'app-vehicle-tracker',
  template: `
    <div class="vehicle-tracker">
      <h3>🚚 Rastreamento de Veículos</h3>
      
      <div class="vehicle-list">
        <div 
          class="vehicle-item" 
          *ngFor="let veiculo of veiculos"
        >
          <div class="vehicle-info">
            <h4>{{ veiculo.placa }}</h4>
            <p>{{ veiculo.modelo }}</p>
          </div>
          
          <div class="location-info">
            <div class="current-address">
              📍 {{ veiculo.enderecoAtual || 'Carregando...' }}
            </div>
            <div class="coordinates">
              {{ veiculo.latitude?.toFixed(6) }}, {{ veiculo.longitude?.toFixed(6) }}
            </div>
            <div class="last-update">
              🕒 Última atualização: {{ veiculo.ultimaAtualizacao | date:'dd/MM/yyyy HH:mm' }}
            </div>
          </div>
          
          <div class="distance-info" *ngIf="veiculo.distanciaBase">
            <span class="distance">
              📏 {{ geocodingService.formatDistance(veiculo.distanciaBase) }} da base
            </span>
          </div>
        </div>
      </div>
    </div>
  `
})
export class VehicleTrackerComponent implements OnInit {
  veiculos: VeiculoComLocalizacao[] = [];
  coordenadasBase = { latitude: -23.550520, longitude: -46.633308 }; // São Paulo

  constructor(
    private geocodingService: GeocodingService,
    private veiculoService: VeiculoService
  ) {}

  ngOnInit() {
    this.carregarVeiculos();
    
    // Atualizar localização a cada 30 segundos
    setInterval(() => {
      this.atualizarLocalizacoes();
    }, 30000);
  }

  async carregarVeiculos() {
    this.veiculos = await this.veiculoService.obterVeiculos();
    this.atualizarLocalizacoes();
  }

  async atualizarLocalizacoes() {
    for (const veiculo of this.veiculos) {
      if (veiculo.latitude && veiculo.longitude) {
        try {
          // Obter endereço atual
          const endereco = await this.geocodingService
            .reverseGeocode(veiculo.latitude, veiculo.longitude)
            .toPromise();
          
          veiculo.enderecoAtual = endereco.formattedAddress;

          // Calcular distância da base
          veiculo.distanciaBase = this.geocodingService.calculateDistance(
            this.coordenadasBase.latitude,
            this.coordenadasBase.longitude,
            veiculo.latitude,
            veiculo.longitude
          );

        } catch (error) {
          console.error(`❌ Erro ao atualizar localização do veículo ${veiculo.placa}:`, error);
        }
      }
    }
  }
}

interface VeiculoComLocalizacao {
  id: number;
  placa: string;
  modelo: string;
  latitude?: number;
  longitude?: number;
  enderecoAtual?: string;
  distanciaBase?: number;
  ultimaAtualizacao: Date;
}

📋 3. Formulário de Cadastro com Endereço

@Component({
  selector: 'app-cadastro-endereco',
  template: `
    <form [formGroup]="formulario" (ngSubmit)="salvar()">
      <!-- Dados básicos -->
      <div class="form-group">
        <label>Nome:</label>
        <input formControlName="nome" type="text">
      </div>

      <!-- Busca de endereço -->
      <div class="form-group">
        <label>Endereço:</label>
        <div class="address-search">
          <input 
            formControlName="enderecoTexto"
            (keyup.enter)="buscarEndereco()"
            placeholder="Digite o endereço completo"
          >
          <button type="button" (click)="buscarEndereco()">
            <i class="fas fa-search"></i>
          </button>
          <button type="button" (click)="usarGPS()">
            <i class="fas fa-location-arrow"></i>
          </button>
        </div>
      </div>

      <!-- Resultados da busca -->
      <div class="address-results" *ngIf="enderecosEncontrados.length > 0">
        <h5>Selecione o endereço correto:</h5>
        <div 
          class="address-option"
          *ngFor="let endereco of enderecosEncontrados"
          (click)="selecionarEndereco(endereco)"
        >
          {{ endereco.formattedAddress }}
        </div>
      </div>

      <!-- Endereço selecionado -->
      <div class="selected-address" *ngIf="formulario.get('endereco')?.value">
        <h5>✅ Endereço confirmado:</h5>
        <div class="address-display">
          {{ formulario.get('endereco')?.value.formattedAddress }}
        </div>
        
        <!-- Campos detalhados (readonly) -->
        <div class="address-details">
          <div class="form-row">
            <div class="form-group">
              <label>Cidade:</label>
              <input formControlName="cidade" readonly>
            </div>
            <div class="form-group">
              <label>Estado:</label>
              <input formControlName="estado" readonly>
            </div>
          </div>
          <div class="form-row">
            <div class="form-group">
              <label>CEP:</label>
              <input formControlName="cep" readonly>
            </div>
            <div class="form-group">
              <label>Coordenadas:</label>
              <input 
                [value]="formulario.get('latitude')?.value + ', ' + formulario.get('longitude')?.value" 
                readonly
              >
            </div>
          </div>
        </div>
      </div>

      <button type="submit" [disabled]="formulario.invalid">
        Salvar Cadastro
      </button>
    </form>
  `
})
export class CadastroEnderecoComponent {
  formulario: FormGroup;
  enderecosEncontrados: GeocodingResult[] = [];

  constructor(
    private fb: FormBuilder,
    private geocodingService: GeocodingService
  ) {
    this.formulario = this.fb.group({
      nome: ['', Validators.required],
      enderecoTexto: [''],
      endereco: [null, Validators.required],
      cidade: [''],
      estado: [''],
      cep: [''],
      latitude: [null],
      longitude: [null]
    });
  }

  buscarEndereco() {
    const texto = this.formulario.get('enderecoTexto')?.value;
    if (!texto?.trim()) return;

    this.geocodingService.geocode(texto)
      .subscribe({
        next: (resultados) => {
          this.enderecosEncontrados = resultados;
        },
        error: (error) => {
          console.error('❌ Erro na busca:', error);
        }
      });
  }

  async usarGPS() {
    try {
      const coords = await this.geocodingService.getCurrentLocation();
      
      this.geocodingService.reverseGeocode(coords.latitude, coords.longitude)
        .subscribe({
          next: (endereco) => {
            this.selecionarEndereco(endereco);
          },
          error: (error) => {
            console.error('❌ Erro ao buscar endereço:', error);
          }
        });
    } catch (error) {
      console.error('❌ Erro ao obter localização:', error);
    }
  }

  selecionarEndereco(endereco: GeocodingResult) {
    this.formulario.patchValue({
      endereco: endereco,
      enderecoTexto: endereco.formattedAddress,
      cidade: endereco.city,
      estado: endereco.state,
      cep: endereco.postalCode,
      latitude: endereco.latitude,
      longitude: endereco.longitude
    });
    
    this.enderecosEncontrados = [];
  }

  salvar() {
    if (this.formulario.valid) {
      console.log('💾 Salvando cadastro:', this.formulario.value);
      // Implementar salvamento
    }
  }
}

🛡️ Tratamento de Erros

📝 Tipos de Erro Comuns

async exemploComTratamentoCompleto() {
  try {
    const coords = await this.geocodingService.getCurrentLocation();
    
    this.geocodingService.reverseGeocode(coords.latitude, coords.longitude)
      .subscribe({
        next: (resultado) => {
          console.log('✅ Sucesso:', resultado);
        },
        error: (error) => {
          this.tratarErroGeocodificacao(error);
        }
      });
      
  } catch (error) {
    this.tratarErroLocalizacao(error);
  }
}

private tratarErroLocalizacao(error: any) {
  switch (error.message) {
    case 'Permissão de localização negada pelo usuário':
      this.mostrarMensagem('Por favor, permita o acesso à localização para continuar.');
      break;
    case 'Informações de localização não disponíveis':
      this.mostrarMensagem('Não foi possível obter sua localização. Verifique se o GPS está ativado.');
      break;
    case 'Tempo limite para obter localização excedido':
      this.mostrarMensagem('Tempo limite excedido. Tente novamente.');
      break;
    default:
      this.mostrarMensagem('Erro ao obter localização. Tente novamente.');
  }
}

private tratarErroGeocodificacao(error: any) {
  if (error.message.includes('OVER_QUERY_LIMIT')) {
    this.mostrarMensagem('Limite de consultas excedido. Tente novamente mais tarde.');
  } else if (error.message.includes('REQUEST_DENIED')) {
    this.mostrarMensagem('Acesso negado à API. Verifique a configuração da chave.');
  } else if (error.message.includes('INVALID_REQUEST')) {
    this.mostrarMensagem('Dados inválidos fornecidos.');
  } else {
    this.mostrarMensagem('Erro ao buscar endereço. Tente novamente.');
  }
}

🚀 Performance e Otimização

💾 1. Cache de Resultados

@Injectable({
  providedIn: 'root'
})
export class GeocodingCacheService {
  private cache = new Map<string, { result: GeocodingResult, timestamp: number }>();
  private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 minutos

  constructor(private geocodingService: GeocodingService) {}

  reverseGeocodeWithCache(lat: number, lng: number): Observable<GeocodingResult> {
    const key = `${lat.toFixed(6)},${lng.toFixed(6)}`;
    const cached = this.cache.get(key);
    
    // Verificar se o cache é válido
    if (cached && (Date.now() - cached.timestamp) < this.CACHE_DURATION) {
      return of(cached.result);
    }

    // Buscar novo resultado
    return this.geocodingService.reverseGeocode(lat, lng)
      .pipe(
        tap(result => {
          this.cache.set(key, { result, timestamp: Date.now() });
        })
      );
  }

  clearCache() {
    this.cache.clear();
  }
}

⏱️ 2. Debounce para Busca

@Component({
  selector: 'app-search-with-debounce',
  template: `
    <input 
      #searchInput
      placeholder="Digite um endereço..."
      (input)="onSearchInput($event)"
    >
    
    <div class="results" *ngIf="searchResults.length > 0">
      <div *ngFor="let result of searchResults">
        {{ result.formattedAddress }}
      </div>
    </div>
  `
})
export class SearchWithDebounceComponent implements OnInit, OnDestroy {
  searchResults: GeocodingResult[] = [];
  private searchSubject = new Subject<string>();
  private destroy$ = new Subject<void>();

  constructor(private geocodingService: GeocodingService) {}

  ngOnInit() {
    // Configurar debounce de 500ms
    this.searchSubject
      .pipe(
        debounceTime(500),
        distinctUntilChanged(),
        filter(term => term.length >= 3),
        switchMap(term => this.geocodingService.geocode(term)),
        takeUntil(this.destroy$)
      )
      .subscribe({
        next: (results) => {
          this.searchResults = results;
        },
        error: (error) => {
          console.error('❌ Erro na busca:', error);
          this.searchResults = [];
        }
      });
  }

  onSearchInput(event: any) {
    const value = event.target.value;
    this.searchSubject.next(value);
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

💰 Custos e Limites

📊 Google Geocoding API - Preços (2024)

  • Geocoding: $5.00 por 1.000 solicitações
  • Reverse Geocoding: $5.00 por 1.000 solicitações
  • Crédito gratuito: $200/mês (40.000 solicitações)

🛡️ Estratégias de Economia

// 1. Cache agressivo para coordenadas próximas
private isSimilarLocation(lat1: number, lng1: number, lat2: number, lng2: number): boolean {
  const distance = this.geocodingService.calculateDistance(lat1, lng1, lat2, lng2);
  return distance < 0.1; // Menos de 100 metros
}

// 2. Batch de requisições
private batchGeocode(addresses: string[]): Observable<GeocodingResult[][]> {
  // Agrupar requisições para otimizar
  return from(addresses).pipe(
    bufferTime(1000), // Agrupar por 1 segundo
    mergeMap(batch => 
      forkJoin(batch.map(addr => this.geocodingService.geocode(addr)))
    )
  );
}

// 3. Fallback para cache local
private geocodeWithFallback(address: string): Observable<GeocodingResult[]> {
  return this.geocodingService.geocode(address)
    .pipe(
      catchError(error => {
        if (error.message.includes('OVER_QUERY_LIMIT')) {
          return this.getFromLocalCache(address);
        }
        return throwError(() => error);
      })
    );
}

📈 Monitoramento de Uso

@Injectable()
export class GeocodingUsageService {
  private requestCount = 0;
  private dailyLimit = 1000;

  trackRequest() {
    this.requestCount++;
    
    if (this.requestCount > this.dailyLimit * 0.8) {
      console.warn('⚠️ Próximo do limite diário de geocodificação');
    }
    
    if (this.requestCount >= this.dailyLimit) {
      throw new Error('Limite diário de geocodificação excedido');
    }
  }

  getRemainingRequests(): number {
    return Math.max(0, this.dailyLimit - this.requestCount);
  }
}

🎯 Conclusão

O GeocodingService com Google Maps API oferece:

Vantagens

  • 🎯 Alta Precisão: Dados do Google Maps
  • 🌍 Cobertura Global: Funciona mundialmente
  • 🔄 Confiabilidade: 99.9% de uptime
  • 📱 Integração Fácil: API bem documentada
  • 🇧🇷 Otimizado para Brasil: Resultados localizados

⚠️ Considerações

  • 💰 Custo: $5 por 1.000 requests após cota gratuita
  • 🔑 API Key: Necessária configuração
  • 📊 Monitoramento: Acompanhar uso para evitar surpresas
  • 🛡️ Segurança: Configurar restrições na chave

🚀 Próximos Passos

  1. Configure sua API Key do Google
  2. Implemente cache para otimizar custos
  3. Monitore o uso da API
  4. Considere fallbacks para casos de erro
  5. Teste em produção com dados reais

🎉 Pronto para usar! O GeocodingService está configurado e otimizado para produção.