# 🌍 **Geocoding Service - Guia Completo de Uso (Google Maps)** ## 📋 **Índice** - [Visão Geral](#visão-geral) - [Configuração da API Key](#configuração-da-api-key) - [Instalação e Configuração](#instalação-e-configuração) - [Uso Básico](#uso-básico) - [Exemplos Práticos](#exemplos-práticos) - [Integração com Formulários](#integração-com-formulários) - [Tratamento de Erros](#tratamento-de-erros) - [Performance e Otimização](#performance-e-otimização) - [Custos e Limites](#custos-e-limites) --- ## 🎯 **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](https://console.cloud.google.com/) 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`** ```typescript 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** ```typescript // 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** ```typescript 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** ```typescript 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)** ```typescript // 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)** ```typescript 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** ```typescript 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** ```typescript @Component({ selector: 'app-location-selector', template: `

📍 Endereços encontrados:

{{ resultado.formattedAddress }}
{{ resultado.latitude.toFixed(6) }}, {{ resultado.longitude.toFixed(6) }}

✅ Localização selecionada:

{{ enderecoSelecionado.formattedAddress }}
Cidade: {{ enderecoSelecionado.city }} Estado: {{ enderecoSelecionado.state }} CEP: {{ enderecoSelecionado.postalCode }}
` }) export class LocationSelectorComponent { searchTerm = ''; loading = false; resultados: GeocodingResult[] = []; enderecoSelecionado: GeocodingResult | null = null; @Output() locationSelected = new EventEmitter(); 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** ```typescript @Component({ selector: 'app-vehicle-tracker', template: `

🚚 Rastreamento de Veículos

{{ veiculo.placa }}

{{ veiculo.modelo }}

📍 {{ veiculo.enderecoAtual || 'Carregando...' }}
{{ veiculo.latitude?.toFixed(6) }}, {{ veiculo.longitude?.toFixed(6) }}
🕒 Última atualização: {{ veiculo.ultimaAtualizacao | date:'dd/MM/yyyy HH:mm' }}
📏 {{ geocodingService.formatDistance(veiculo.distanciaBase) }} da base
` }) 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** ```typescript @Component({ selector: 'app-cadastro-endereco', template: `
Selecione o endereço correto:
{{ endereco.formattedAddress }}
✅ Endereço confirmado:
{{ formulario.get('endereco')?.value.formattedAddress }}
` }) 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** ```typescript 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** ```typescript @Injectable({ providedIn: 'root' }) export class GeocodingCacheService { private cache = new Map(); private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 minutos constructor(private geocodingService: GeocodingService) {} reverseGeocodeWithCache(lat: number, lng: number): Observable { 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** ```typescript @Component({ selector: 'app-search-with-debounce', template: `
{{ result.formattedAddress }}
` }) export class SearchWithDebounceComponent implements OnInit, OnDestroy { searchResults: GeocodingResult[] = []; private searchSubject = new Subject(); private destroy$ = new Subject(); 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** ```typescript // 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 { // 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 { 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** ```typescript @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.**