mejoras en el mapa

This commit is contained in:
2026-03-10 09:33:39 -05:00
parent c0a476cdb8
commit bf9adf1d3a
4 changed files with 91 additions and 125 deletions

View File

@ -40,65 +40,46 @@ export function useDirectionsRoute() {
errorRuta.value = null; errorRuta.value = null;
try { try {
// Importar librerías necesarias de la nueva API const { DirectionsService } = await google.maps.importLibrary("routes") as any;
const { RoutesService } = await google.maps.importLibrary("routes") as any; const directionsService = new DirectionsService();
const routeService = new RoutesService();
// Límite de la API de Google Maps Routes: Origen, Destino, y hasta 25 intermediates const maxWaypoints = 23; // Google Directions limit is typically 25 including origin/dest
const maxPuntosPorChunk = 25;
const overlaps = 1;
for (let i = 0; i < paradas.length - 1; i += (maxPuntosPorChunk - overlaps)) { for (let i = 0; i < paradas.length - 1; i += maxWaypoints) {
const chunk = paradas.slice(i, i + maxPuntosPorChunk); const chunk = paradas.slice(i, i + maxWaypoints + 1);
if (chunk.length < 2) break; if (chunk.length < 2) break;
const origin = { const origin = { lat: chunk[0]!.latitud, lng: chunk[0]!.longitud };
location: { const destination = { lat: chunk[chunk.length - 1]!.latitud, lng: chunk[chunk.length - 1]!.longitud };
latLng: {
latitude: chunk[0]!.latitud,
longitude: chunk[0]!.longitud
}
}
};
const destination = { const waypoints = chunk.slice(1, -1).map(p => ({
location: { location: new google.maps.LatLng(p.latitud, p.longitud),
latLng: { stopover: true
latitude: chunk[chunk.length - 1]!.latitud,
longitude: chunk[chunk.length - 1]!.longitud
}
}
};
const intermediates = chunk.slice(1, -1).map(p => ({
location: {
latLng: {
latitude: p.latitud,
longitude: p.longitud
}
}
})); }));
try { try {
const response = await routeService.computeRoutes({ const result = await new Promise<google.maps.DirectionsResult>((resolve, reject) => {
directionsService.route({
origin, origin,
destination, destination,
intermediates, waypoints,
travelMode: 'DRIVE' as any, // 'DRIVE' es el nuevo estandar en computeRoutes travelMode: google.maps.TravelMode.DRIVING
routingPreference: 'TRAFFIC_UNAWARE' as any, }, (response: google.maps.DirectionsResult | null, status: google.maps.DirectionsStatus) => {
polylineQuality: 'HIGH_QUALITY' as any, if (status === google.maps.DirectionsStatus.OK && response) {
polylineEncoding: 'ENCODED_POLYLINE' as any, resolve(response);
} else {
reject(new Error(`Directions API failed: ${status}`));
}
});
}); });
if (response.routes && response.routes.length > 0) { if (result.routes && result.routes.length > 0) {
const route = response.routes[0]; const route = result.routes[0];
if (route.polyline && route.polyline.encodedPolyline) { if (route?.overview_path) {
const path = google.maps.geometry.encoding.decodePath(route.polyline.encodedPolyline);
const polyline = new google.maps.Polyline({ const polyline = new google.maps.Polyline({
path: path, path: route.overview_path,
map: map, map: map,
strokeColor: isPast ? '#9CA3AF' : '#FBBF24', // Gris para lo lejano, Amarillo para lo cercano strokeColor: isPast ? '#9CA3AF' : '#FBBF24',
strokeWeight: isPast ? 4 : 6, strokeWeight: isPast ? 4 : 6,
strokeOpacity: isPast ? 0.6 : 1.0, strokeOpacity: isPast ? 0.6 : 1.0,
icons: isPast ? [{ icons: isPast ? [{
@ -107,15 +88,16 @@ export function useDirectionsRoute() {
repeat: '10px' repeat: '10px'
}] : [] }] : []
}); });
registrarPolyline(polyline); registrarPolyline(polyline);
} }
} }
} catch (err: any) { } catch (err: any) {
console.warn(`SIBU | Tramo ${i} falló con Routes API: `, err); console.warn(`SIBU | Tramo ${i} falló con Directions API: `, err);
} }
await delay(200); if (i + maxWaypoints < paradas.length - 1) {
await delay(300);
}
} }
} catch (err: any) { } catch (err: any) {
errorRuta.value = `Error crítico al trazar la ruta: ${err.message || String(err)}`; errorRuta.value = `Error crítico al trazar la ruta: ${err.message || String(err)}`;

View File

@ -424,9 +424,8 @@ export function useGoogleMaps() {
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
try { try {
// Cargar ruta const { DirectionsService } = await google.maps.importLibrary("routes") as any;
const { RoutesService } = await google.maps.importLibrary("routes") as any; const directionsService = new DirectionsService();
const routeService = new RoutesService();
const tamañoChunk = 25; const tamañoChunk = 25;
@ -434,49 +433,34 @@ export function useGoogleMaps() {
const chunk = paradas.slice(i, i + tamañoChunk); const chunk = paradas.slice(i, i + tamañoChunk);
if (chunk.length < 2) break; if (chunk.length < 2) break;
const origin = { const origin = { lat: chunk[0]!.lat, lng: chunk[0]!.lng };
location: { const destination = { lat: chunk[chunk.length - 1]!.lat, lng: chunk[chunk.length - 1]!.lng };
latLng: { const waypoints = chunk.slice(1, -1).map(p => ({
latitude: chunk[0]!.lat, location: new google.maps.LatLng(p.lat, p.lng),
longitude: chunk[0]!.lng stopover: true
}
}
};
const destination = {
location: {
latLng: {
latitude: chunk[chunk.length - 1]!.lat,
longitude: chunk[chunk.length - 1]!.lng
}
}
};
const intermediates = chunk.slice(1, -1).map(p => ({
location: {
latLng: {
latitude: p.lat,
longitude: p.lng
}
}
})); }));
try { try {
const response = await routeService.computeRoutes({ const result = await new Promise<google.maps.DirectionsResult>((resolve, reject) => {
directionsService.route({
origin, origin,
destination, destination,
intermediates, waypoints,
travelMode: 'DRIVE', travelMode: google.maps.TravelMode.DRIVING
routingPreference: 'TRAFFIC_UNAWARE', }, (response: google.maps.DirectionsResult | null, status: google.maps.DirectionsStatus) => {
polylineQuality: 'HIGH_QUALITY', if (status === google.maps.DirectionsStatus.OK && response) {
polylineEncoding: 'ENCODED_POLYLINE', resolve(response);
} else {
reject(new Error(`Directions API failed: ${status}`));
}
});
}); });
if (response.routes && response.routes.length > 0) { if (result.routes && result.routes.length > 0) {
const route = response.routes[0]; const route = result.routes[0];
if (route.polyline && route.polyline.encodedPolyline) { if (route?.overview_path) {
const path = google.maps.geometry.encoding.decodePath(route.polyline.encodedPolyline);
const polyline = new google.maps.Polyline({ const polyline = new google.maps.Polyline({
path: path, path: route.overview_path,
map: map.value, map: map.value,
strokeColor: '#FBBF24', strokeColor: '#FBBF24',
strokeWeight: 5, strokeWeight: 5,
@ -487,11 +471,10 @@ export function useGoogleMaps() {
polylinesCreadas.push(polyline); polylinesCreadas.push(polyline);
registrarPolyline(polyline); registrarPolyline(polyline);
// Registrar en global overlays if (!globalOverlays.has(map.value!)) {
if (!globalOverlays.has(map.value)) { globalOverlays.set(map.value!, new Set());
globalOverlays.set(map.value, new Set());
} }
globalOverlays.get(map.value)!.add(polyline); globalOverlays.get(map.value!)!.add(polyline);
} }
} }
} catch (error) { } catch (error) {
@ -501,7 +484,7 @@ export function useGoogleMaps() {
await delay(200); await delay(200);
} }
} catch (e) { } catch (e) {
console.error('Error cargando Routes API:', e); console.error('Error cargando Directions API:', e);
} }
return polylinesCreadas; return polylinesCreadas;

View File

@ -56,31 +56,33 @@ export function useParadaCercana() {
let mejorRutaPuntos: google.maps.LatLng[] = []; let mejorRutaPuntos: google.maps.LatLng[] = [];
try { try {
const { RoutesService } = await google.maps.importLibrary("routes") as any; const { DirectionsService } = await google.maps.importLibrary("routes") as any;
const routeService = new RoutesService(); const directionsService = new DirectionsService();
const routePromises = top5.map(async (stop) => { const routePromises = top5.map(async (stop) => {
try { try {
const response = await routeService.computeRoutes({ const response = await new Promise<google.maps.DirectionsResult>((resolve, reject) => {
origin: { directionsService.route({
location: { latLng: { latitude: ubicacionUsuario.lat, longitude: ubicacionUsuario.lng } } origin: { lat: ubicacionUsuario.lat, lng: ubicacionUsuario.lng },
}, destination: { lat: stop.latitude, lng: stop.longitude },
destination: { travelMode: google.maps.TravelMode.WALKING
location: { latLng: { latitude: stop.latitude, longitude: stop.longitude } } }, (result: google.maps.DirectionsResult | null, status: google.maps.DirectionsStatus) => {
}, if (status === google.maps.DirectionsStatus.OK && result) {
travelMode: 'DRIVE', resolve(result);
routingPreference: 'TRAFFIC_UNAWARE', } else {
polylineQuality: 'HIGH_QUALITY', reject(new Error(`Directions failed: ${status}`));
polylineEncoding: 'ENCODED_POLYLINE', }
});
}); });
if (response.routes && response.routes.length > 0) { if (response.routes && response.routes.length > 0) {
const route = response.routes[0]; const route = response.routes[0];
const leg = route?.legs[0];
return { return {
stop, stop,
distance: route.distanceMeters || Infinity, distance: leg?.distance?.value || Infinity,
duration: parseInt(route.duration || "0"), duration: leg?.duration?.value || 0,
points: route.polyline?.encodedPolyline ? google.maps.geometry.encoding.decodePath(route.polyline.encodedPolyline) : [] points: route?.overview_path || []
}; };
} }
} catch (e) { } catch (e) {
@ -100,7 +102,7 @@ export function useParadaCercana() {
} }
} }
} catch (e) { } catch (e) {
console.error('Error cargando Routes API en useParadaCercana', e); console.error('Error cargando Directions API en useParadaCercana', e);
} }
// 3. Fallback a la más cercana lineal si falla API // 3. Fallback a la más cercana lineal si falla API

View File

@ -122,12 +122,12 @@ async function saveShuttle() {
<div class="form-group grid-row"> <div class="form-group grid-row">
<div class="input-box"> <div class="input-box">
<label>Nombre de la Empresa</label> <label>Nombre de la Empresa</label>
<input v-model="shuttleForm.company_name" type="text" placeholder="Ej: Chiriqui Transfers"> <input v-model="shuttleForm.company_name" type="text" placeholder="Ej: Chiriqui Transfers" />
</div> </div>
<div class="input-box"> <div class="input-box">
<label>Imagen del Transporte</label> <label>Imagen del Transporte</label>
<div class="file-upload-wrapper"> <div class="file-upload-wrapper">
<input type="file" @change="handleImageChange" accept="image/*" id="file-input"> <input type="file" @change="handleImageChange" accept="image/*" id="file-input" />
<label for="file-input" class="file-label"> <label for="file-input" class="file-label">
<span class="material-icons">cloud_upload</span> <span class="material-icons">cloud_upload</span>
{{ selectedFileName || 'SELECCIONAR IMAGEN' }} {{ selectedFileName || 'SELECCIONAR IMAGEN' }}
@ -140,18 +140,18 @@ async function saveShuttle() {
<div class="form-group grid-row"> <div class="form-group grid-row">
<div class="input-box"> <div class="input-box">
<label>Origen</label> <label>Origen</label>
<input v-model="shuttleForm.origin" type="text" placeholder="Boquete"> <input v-model="shuttleForm.origin" type="text" placeholder="Boquete" />
</div> </div>
<div class="input-box"> <div class="input-box">
<label>Destino</label> <label>Destino</label>
<input v-model="shuttleForm.destination" type="text" placeholder="Santa Catalina"> <input v-model="shuttleForm.destination" type="text" placeholder="Santa Catalina" />
</div> </div>
</div> </div>
<div class="form-group grid-row"> <div class="form-group grid-row">
<div class="input-box"> <div class="input-box">
<label>Tipo de Vehículo</label> <label>Tipo de Vehículo</label>
<input v-model="shuttleForm.vehicle_type" type="text" placeholder="Mini Van Compartida"> <input v-model="shuttleForm.vehicle_type" type="text" placeholder="Mini Van Compartida" />
</div> </div>
<div class="input-box"> <div class="input-box">
<label>Categoría</label> <label>Categoría</label>
@ -175,22 +175,22 @@ async function saveShuttle() {
<div class="form-group grid-row"> <div class="form-group grid-row">
<div class="input-box"> <div class="input-box">
<label>Duración Estimada</label> <label>Duración Estimada</label>
<input v-model="shuttleForm.estimated_duration" type="text" placeholder="4.5 horas"> <input v-model="shuttleForm.estimated_duration" type="text" placeholder="4.5 horas" />
</div> </div>
<div class="input-box"> <div class="input-box">
<label>Salidas</label> <label>Salidas</label>
<input v-model="shuttleForm.departure_times" type="text" placeholder="Todos los días 8:00 AM"> <input v-model="shuttleForm.departure_times" type="text" placeholder="Todos los días 8:00 AM" />
</div> </div>
</div> </div>
<div class="form-group grid-row"> <div class="form-group grid-row">
<div class="input-box"> <div class="input-box">
<label>Precio por Persona ($)</label> <label>Precio por Persona ($)</label>
<input v-model="shuttleForm.price_per_person" type="number"> <input v-model="shuttleForm.price_per_person" type="number" />
</div> </div>
<div class="input-box"> <div class="input-box">
<label>Precio Viaje Privado ($)</label> <label>Precio Viaje Privado ($)</label>
<input v-model="shuttleForm.price_private_trip" type="number"> <input v-model="shuttleForm.price_private_trip" type="number" />
</div> </div>
</div> </div>
@ -199,12 +199,12 @@ async function saveShuttle() {
<label>WhatsApp (Sin +)</label> <label>WhatsApp (Sin +)</label>
<div class="whatsapp-input"> <div class="whatsapp-input">
<span class="prefix">+</span> <span class="prefix">+</span>
<input v-model="shuttleForm.contact_whatsapp" type="text" placeholder="50760000000"> <input v-model="shuttleForm.contact_whatsapp" type="text" placeholder="50760000000" />
</div> </div>
</div> </div>
<div class="input-box"> <div class="input-box">
<label>Teléfono de Llamada</label> <label>Teléfono de Llamada</label>
<input v-model="shuttleForm.phone_number" type="text" placeholder="50760000000"> <input v-model="shuttleForm.phone_number" type="text" placeholder="50760000000" />
</div> </div>
</div> </div>
@ -215,7 +215,7 @@ async function saveShuttle() {
<span>¿Habla Inglés? (Bilingüe)</span> <span>¿Habla Inglés? (Bilingüe)</span>
</div> </div>
<div class="nexus-switch"> <div class="nexus-switch">
<input type="checkbox" v-model="shuttleForm.english_speaking"> <input type="checkbox" v-model="shuttleForm.english_speaking" />
<span class="slider"></span> <span class="slider"></span>
</div> </div>
</label> </label>
@ -870,4 +870,3 @@ async function saveShuttle() {
.preview-panel { order: -1; } .preview-panel { order: -1; }
} }
</style> </style>