# 🌍 **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: `
0">
📍 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: `
`
})
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: `
0">
{{ 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.**