perf: optimization for maps & network
This commit is contained in:
110
frontend/src/composables/useDirectionsRoute.ts
Normal file
110
frontend/src/composables/useDirectionsRoute.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
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);
|
||||
const renderizadoresActivos = ref<google.maps.DirectionsRenderer[]>([]);
|
||||
|
||||
// Limpia los tramos anteriores dibujados en el mapa
|
||||
const limpiarRuta = () => {
|
||||
if (renderizadoresActivos.value.length > 0) {
|
||||
renderizadoresActivos.value.forEach((renderer) => {
|
||||
renderer.setMap(null);
|
||||
});
|
||||
renderizadoresActivos.value = [];
|
||||
}
|
||||
errorRuta.value = null;
|
||||
};
|
||||
|
||||
// Función utilitaria para pausar ejecución
|
||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const trazarRuta = async (paradas: Parada[], map: google.maps.Map) => {
|
||||
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
|
||||
polylineOptions: {
|
||||
strokeColor: '#1E40AF', // Azul (Tailwind blue-800)
|
||||
strokeWeight: 5,
|
||||
strokeOpacity: 0.8
|
||||
}
|
||||
});
|
||||
|
||||
renderer.setDirections(response);
|
||||
renderizadoresActivos.value.push(renderer);
|
||||
|
||||
} 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
|
||||
};
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/** Composable for Google Maps integration */
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, shallowRef, onMounted } from 'vue'
|
||||
import { setOptions, importLibrary } from '@googlemaps/js-api-loader'
|
||||
|
||||
const getApiKey = () => import.meta.env.VITE_GOOGLE_MAPS_API_KEY || ''
|
||||
@ -10,7 +10,7 @@ let mapsLoaded = false
|
||||
const globalOverlays = new Map<google.maps.Map, Set<google.maps.Marker | google.maps.Polyline>>()
|
||||
|
||||
export function useGoogleMaps() {
|
||||
const map = ref<google.maps.Map | null>(null)
|
||||
const map = shallowRef<google.maps.Map | null>(null)
|
||||
const isLoaded = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
@ -234,6 +234,73 @@ export function useGoogleMaps() {
|
||||
return polyline
|
||||
}
|
||||
|
||||
async function addRoutePolyline(paradas: Array<{ lat: number; lng: number }>) {
|
||||
if (!map.value) {
|
||||
console.error('Map not initialized')
|
||||
return []
|
||||
}
|
||||
if (!paradas || paradas.length < 2) {
|
||||
console.warn("Se necesitan al menos 2 paradas para trazar una ruta.");
|
||||
return []
|
||||
}
|
||||
|
||||
const directionsService = new google.maps.DirectionsService();
|
||||
const renderizadoresActivos: google.maps.DirectionsRenderer[] = [];
|
||||
const tamañoChunk = 25;
|
||||
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
for (let i = 0; i < paradas.length - 1; i += (tamañoChunk - 1)) {
|
||||
const chunk = paradas.slice(i, i + tamañoChunk);
|
||||
if (chunk.length < 2) break;
|
||||
|
||||
const origen = { lat: chunk[0]!.lat, lng: chunk[0]!.lng };
|
||||
const destino = { lat: chunk[chunk.length - 1]!.lat, lng: chunk[chunk.length - 1]!.lng };
|
||||
|
||||
const waypoints = chunk.slice(1, -1).map(p => ({
|
||||
location: { lat: p.lat, lng: p.lng },
|
||||
stopover: true
|
||||
}));
|
||||
|
||||
const request = {
|
||||
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.value,
|
||||
suppressMarkers: true,
|
||||
preserveViewport: true, // Siempre conservar la vista ya que trazamos fragmentos
|
||||
polylineOptions: {
|
||||
strokeColor: '#0057FF', // Azul
|
||||
strokeWeight: 4,
|
||||
strokeOpacity: 0.8
|
||||
}
|
||||
});
|
||||
|
||||
renderer.setDirections(response);
|
||||
renderizadoresActivos.push(renderer);
|
||||
|
||||
// Registrar en global overlays para limpiarlos después
|
||||
if (!globalOverlays.has(map.value)) {
|
||||
globalOverlays.set(map.value, new Set())
|
||||
}
|
||||
globalOverlays.get(map.value)!.add(renderer as any);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error trazando el tramo (Paradas ${i} a ${i + chunk.length}):`, error);
|
||||
}
|
||||
|
||||
await delay(200);
|
||||
}
|
||||
return renderizadoresActivos;
|
||||
}
|
||||
|
||||
function fitBounds(path: Array<{ lat: number; lng: number }>) {
|
||||
if (!map.value || path.length === 0) {
|
||||
return
|
||||
@ -378,6 +445,7 @@ export function useGoogleMaps() {
|
||||
addHtmlMarker,
|
||||
addNumberedMarker,
|
||||
addPolyline,
|
||||
addRoutePolyline,
|
||||
fitBounds,
|
||||
setCenter,
|
||||
setZoom,
|
||||
|
||||
Reference in New Issue
Block a user