Files
SIB/frontend/src/composables/useParadaCercana.ts

148 lines
5.7 KiB
TypeScript

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<BusStop | null>(null);
const distanciaMetros = ref<number>(0);
const duracionCaminata = ref<number>(0);
const { registrarPolyline } = useMapState();
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 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
};
}