2026-02-26 12:39:15 -05:00
|
|
|
import { ref } from 'vue';
|
|
|
|
|
|
2026-02-27 10:57:42 -05:00
|
|
|
import { useMapState } from './useMapState';
|
|
|
|
|
|
2026-02-26 12:39:15 -05:00
|
|
|
export interface Parada {
|
|
|
|
|
id: number;
|
|
|
|
|
nombre: string;
|
|
|
|
|
latitud: number;
|
|
|
|
|
longitud: number;
|
|
|
|
|
orden: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useDirectionsRoute() {
|
|
|
|
|
const estasCargando = ref<boolean>(false);
|
|
|
|
|
const errorRuta = ref<string | null>(null);
|
2026-02-27 10:57:42 -05:00
|
|
|
const { registrarRenderer, renderers } = useMapState();
|
2026-02-26 12:39:15 -05:00
|
|
|
|
|
|
|
|
// Limpia los tramos anteriores dibujados en el mapa
|
|
|
|
|
const limpiarRuta = () => {
|
2026-02-27 10:57:42 -05:00
|
|
|
if (renderers.value.length > 0) {
|
|
|
|
|
renderers.value.forEach((renderer) => {
|
2026-02-26 12:39:15 -05:00
|
|
|
renderer.setMap(null);
|
|
|
|
|
});
|
2026-02-27 10:57:42 -05:00
|
|
|
renderers.value = [];
|
2026-02-26 12:39:15 -05:00
|
|
|
}
|
|
|
|
|
errorRuta.value = null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Función utilitaria para pausar ejecución
|
|
|
|
|
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
|
|
2026-02-26 22:05:55 -05:00
|
|
|
const trazarRuta = async (paradas: Parada[], map: google.maps.Map, isPast: boolean = false) => {
|
2026-02-26 12:39:15 -05:00
|
|
|
if (!paradas || paradas.length < 2) {
|
|
|
|
|
errorRuta.value = 'Se requieren al menos 2 paradas para trazar una ruta.';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
limpiarRuta();
|
|
|
|
|
estasCargando.value = true;
|
|
|
|
|
errorRuta.value = null;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const directionsService = new google.maps.DirectionsService();
|
|
|
|
|
// Límite de la API de Google Maps: Origen, Destino, y hasta 23 waypoints (25 puntos total por request)
|
|
|
|
|
const maxPuntosPorChunk = 25;
|
|
|
|
|
const overlaps = 1;
|
|
|
|
|
|
|
|
|
|
// Iteramos sobre las paradas dividiéndolas en chunks con 1 punto en común ("overlap")
|
|
|
|
|
// para asegurar que las secciones se conecten correctamente.
|
|
|
|
|
for (let i = 0; i < paradas.length - 1; i += (maxPuntosPorChunk - overlaps)) {
|
|
|
|
|
const chunk = paradas.slice(i, i + maxPuntosPorChunk);
|
|
|
|
|
|
|
|
|
|
// Si el chunk es muy pequeño (último fragmento o vector final), detenemos
|
|
|
|
|
if (chunk.length < 2) break;
|
|
|
|
|
|
|
|
|
|
const origen = new google.maps.LatLng(chunk[0]!.latitud, chunk[0]!.longitud);
|
|
|
|
|
const destino = new google.maps.LatLng(chunk[chunk.length - 1]!.latitud, chunk[chunk.length - 1]!.longitud);
|
|
|
|
|
|
|
|
|
|
// Excluimos el primero y último para que sean los waypoints intermedios
|
|
|
|
|
const waypoints: google.maps.DirectionsWaypoint[] = chunk.slice(1, -1).map(p => ({
|
|
|
|
|
location: new google.maps.LatLng(p.latitud, p.longitud),
|
|
|
|
|
stopover: true
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const request: google.maps.DirectionsRequest = {
|
|
|
|
|
origin: origen,
|
|
|
|
|
destination: destino,
|
|
|
|
|
waypoints: waypoints,
|
|
|
|
|
travelMode: google.maps.TravelMode.DRIVING,
|
|
|
|
|
optimizeWaypoints: false
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await directionsService.route(request);
|
|
|
|
|
|
|
|
|
|
const renderer = new google.maps.DirectionsRenderer({
|
|
|
|
|
map: map,
|
|
|
|
|
suppressMarkers: true, // SIBU maneja los suyos propios
|
|
|
|
|
preserveViewport: true, // No auto centrar en cada tramo para evitar parpadeos visuales
|
2026-02-26 22:05:55 -05:00
|
|
|
polylineOptions: isPast ? {
|
2026-02-27 10:57:42 -05:00
|
|
|
strokeColor: '#FDE68A', // amarillo muy tenue
|
2026-02-26 22:05:55 -05:00
|
|
|
strokeWeight: 3,
|
|
|
|
|
strokeOpacity: 0.4
|
|
|
|
|
} : {
|
2026-02-27 10:57:42 -05:00
|
|
|
strokeColor: '#FBBF24', // amarillo principal
|
2026-02-26 12:39:15 -05:00
|
|
|
strokeWeight: 5,
|
2026-02-26 22:05:55 -05:00
|
|
|
strokeOpacity: 0.95
|
2026-02-26 12:39:15 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
renderer.setDirections(response);
|
2026-02-27 10:57:42 -05:00
|
|
|
registrarRenderer(renderer);
|
2026-02-26 12:39:15 -05:00
|
|
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
console.warn(`SIBU | Tramo ${i} falló: `, err);
|
|
|
|
|
// La ruta continúa renderizando los siguientes tramos disponibles, no paramos todo.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Retardo para evitar sobrecargar a la API y el error "OVER_QUERY_LIMIT"
|
|
|
|
|
await delay(300);
|
|
|
|
|
}
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
errorRuta.value = `Error crítico al trazar la ruta: ${err.message || String(err)}`;
|
|
|
|
|
console.error(errorRuta.value);
|
|
|
|
|
} finally {
|
|
|
|
|
estasCargando.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
trazarRuta,
|
|
|
|
|
limpiarRuta,
|
|
|
|
|
estasCargando,
|
|
|
|
|
errorRuta
|
|
|
|
|
};
|
|
|
|
|
}
|