22 KiB
22 KiB
🌍 Geocoding Service - Guia Completo de Uso (Google Maps)
📋 Índice
- Visão Geral
- Configuração da API Key
- Instalação e Configuração
- Uso Básico
- Exemplos Práticos
- Integração com Formulários
- Tratamento de Erros
- Performance e Otimização
- 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
- Acesse Google Cloud Console
- Crie um projeto ou selecione existente
- Ative a Geocoding API
- Crie credenciais (API Key)
- 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
- Configure sua API Key do Google
- Implemente cache para otimizar custos
- Monitore o uso da API
- Considere fallbacks para casos de erro
- Teste em produção com dados reais
🎉 Pronto para usar! O GeocodingService está configurado e otimizado para produção.