import { ref } from 'vue'; import type { BusStop } from '@/types'; import { useMapState } from './useMapState'; // Fórmula Haversine para distancia en línea recta (km) function getHaversineDistance(lat1: number, lon1: number, lat2: number, lon2: number): number { const R = 6371; // Radio de la Tierra en km const dLat = (lat2 - lat1) * (Math.PI / 180); const dLon = (lon2 - lon1) * (Math.PI / 180); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } export function useParadaCercana() { const paradaCercana = ref(null); const distanciaMetros = ref(0); const duracionCaminata = ref(0); const { registrarPolyline } = useMapState(); const caminandoPolyline = ref(null); const limpiarCaminata = () => { if (caminandoPolyline.value) { caminandoPolyline.value.setMap(null); caminandoPolyline.value = null; } paradaCercana.value = null; distanciaMetros.value = 0; duracionCaminata.value = 0; }; const encontrarParadaCercana = async ( ubicacionUsuario: { lat: number; lng: number }, paradas: BusStop[], map: google.maps.Map | undefined ) => { if (!paradas || paradas.length === 0 || !ubicacionUsuario) return; limpiarCaminata(); // 1. Pre-filtar (Haversine) - Top 5 más cercanas const paradasConDistLineal = paradas.map(p => ({ parada: p, distancia: getHaversineDistance(ubicacionUsuario.lat, ubicacionUsuario.lng, p.latitude, p.longitude) })); paradasConDistLineal.sort((a, b) => a.distancia - b.distancia); const top5 = paradasConDistLineal.slice(0, 5).map(item => item.parada); // 2. Usar Routes API para encontrar la más cercana por calles reales let mejorParada: BusStop | null = null; let minimaDistanciaCalles = Infinity; let mejorDuracion = 0; let mejorRutaPuntos: google.maps.LatLng[] = []; try { const { RoutesService } = await google.maps.importLibrary("routes") as any; const routeService = new RoutesService(); const routePromises = top5.map(async (stop) => { try { const response = await routeService.computeRoutes({ origin: { location: { latLng: { latitude: ubicacionUsuario.lat, longitude: ubicacionUsuario.lng } } }, destination: { location: { latLng: { latitude: stop.latitude, longitude: stop.longitude } } }, travelMode: 'DRIVE', routingPreference: 'TRAFFIC_UNAWARE', polylineQuality: 'HIGH_QUALITY', polylineEncoding: 'ENCODED_POLYLINE', }); if (response.routes && response.routes.length > 0) { const route = response.routes[0]; return { stop, distance: route.distanceMeters || Infinity, duration: parseInt(route.duration || "0"), points: route.polyline?.encodedPolyline ? google.maps.geometry.encoding.decodePath(route.polyline.encodedPolyline) : [] }; } } catch (e) { console.warn('Error calculando ruta a parada', stop.name, e); } return null; }); const results = await Promise.all(routePromises); for (const res of results) { if (res && res.distance < minimaDistanciaCalles) { minimaDistanciaCalles = res.distance; mejorDuracion = res.duration; mejorParada = res.stop; mejorRutaPuntos = res.points; } } } catch (e) { console.error('Error cargando Routes API en useParadaCercana', e); } // 3. Fallback a la más cercana lineal si falla API if (!mejorParada) { mejorParada = top5[0] || null; minimaDistanciaCalles = (paradasConDistLineal[0]?.distancia || 0) * 1000; mejorDuracion = (minimaDistanciaCalles / 1000) / 5 * 60 * 60; // asumiendo caminata a 5km/h } paradaCercana.value = mejorParada; distanciaMetros.value = minimaDistanciaCalles; duracionCaminata.value = mejorDuracion; // en segundos // 4. Dibujar polilínea de caminata punteada azul if (map && mejorRutaPuntos.length > 0) { caminandoPolyline.value = new google.maps.Polyline({ path: mejorRutaPuntos, strokeColor: '#F59E0B', strokeOpacity: 0, strokeWeight: 3, icons: [{ icon: { path: 'M 0,-1 0,1', strokeOpacity: 1, scale: 3, strokeColor: '#F59E0B' }, offset: '0', repeat: '12px' }], map: map }); registrarPolyline(caminandoPolyline.value); } }; return { encontrarParadaCercana, limpiarCaminata, paradaCercana, distanciaMetros, duracionCaminata }; }