2026-02-26 13:13:56 -05:00
|
|
|
import { ref } from 'vue';
|
|
|
|
|
import type { BusStop } from '@/types';
|
2026-02-27 10:57:42 -05:00
|
|
|
import { useMapState } from './useMapState';
|
2026-02-26 13:13:56 -05:00
|
|
|
|
|
|
|
|
// 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<BusStop | null>(null);
|
|
|
|
|
const distanciaMetros = ref<number>(0);
|
|
|
|
|
const duracionCaminata = ref<number>(0);
|
2026-02-27 10:57:42 -05:00
|
|
|
const { registrarPolyline } = useMapState();
|
2026-02-26 13:13:56 -05:00
|
|
|
const caminandoPolyline = ref<google.maps.Polyline | null>(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 Directions 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[] = [];
|
|
|
|
|
|
|
|
|
|
const directionsService = new google.maps.DirectionsService();
|
|
|
|
|
|
|
|
|
|
for (const stop of top5) {
|
|
|
|
|
try {
|
|
|
|
|
const response = await directionsService.route({
|
|
|
|
|
origin: new google.maps.LatLng(ubicacionUsuario.lat, ubicacionUsuario.lng),
|
|
|
|
|
destination: new google.maps.LatLng(stop.latitude, stop.longitude),
|
|
|
|
|
travelMode: google.maps.TravelMode.DRIVING // Calles reales
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (response.routes && response.routes.length > 0) {
|
|
|
|
|
const route = response.routes[0];
|
2026-02-26 20:58:10 -05:00
|
|
|
if (!route) continue;
|
2026-02-26 13:13:56 -05:00
|
|
|
let distTotal = 0;
|
|
|
|
|
let durTotal = 0;
|
|
|
|
|
|
|
|
|
|
if (route.legs) {
|
|
|
|
|
for (const leg of route.legs) {
|
|
|
|
|
distTotal += leg.distance?.value || 0;
|
|
|
|
|
durTotal += leg.duration?.value || 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (distTotal < minimaDistanciaCalles) {
|
|
|
|
|
minimaDistanciaCalles = distTotal;
|
|
|
|
|
mejorDuracion = durTotal;
|
|
|
|
|
mejorParada = stop;
|
|
|
|
|
mejorRutaPuntos = route.overview_path;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn('Error calculando ruta a parada', stop.name, e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Fallback a la más cercana lineal si falla API
|
|
|
|
|
if (!mejorParada) {
|
2026-02-26 20:58:10 -05:00
|
|
|
mejorParada = top5[0] || null;
|
|
|
|
|
minimaDistanciaCalles = (paradasConDistLineal[0]?.distancia || 0) * 1000;
|
2026-02-26 13:13:56 -05:00
|
|
|
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,
|
2026-02-27 10:57:42 -05:00
|
|
|
strokeColor: '#F59E0B',
|
2026-02-26 13:13:56 -05:00
|
|
|
strokeOpacity: 0,
|
2026-02-27 10:57:42 -05:00
|
|
|
strokeWeight: 3,
|
2026-02-26 13:13:56 -05:00
|
|
|
icons: [{
|
|
|
|
|
icon: {
|
|
|
|
|
path: 'M 0,-1 0,1',
|
|
|
|
|
strokeOpacity: 1,
|
2026-02-27 10:57:42 -05:00
|
|
|
scale: 3,
|
|
|
|
|
strokeColor: '#F59E0B'
|
2026-02-26 13:13:56 -05:00
|
|
|
},
|
|
|
|
|
offset: '0',
|
2026-02-27 10:57:42 -05:00
|
|
|
repeat: '12px'
|
2026-02-26 13:13:56 -05:00
|
|
|
}],
|
|
|
|
|
map: map
|
|
|
|
|
});
|
2026-02-27 10:57:42 -05:00
|
|
|
registrarPolyline(caminandoPolyline.value);
|
2026-02-26 13:13:56 -05:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
encontrarParadaCercana,
|
|
|
|
|
limpiarCaminata,
|
|
|
|
|
paradaCercana,
|
|
|
|
|
distanciaMetros,
|
|
|
|
|
duracionCaminata
|
|
|
|
|
};
|
|
|
|
|
}
|