/** * gestion des itinéraires pour libre charge map */ class Routing { constructor(map) { this.map = map; this.routePolyline = null; this.pauseMarkers = []; this.batteryMarkers = []; this.startMarker = null; this.endMarker = null; this.startCoords = null; this.endCoords = null; this.startItinerary = [0, 0]; this.endItinerary = [0, 0]; this.lastRouteDestination = null; } setStartMarker(latlng) { if (this.startMarker) this.map.removeLayer(this.startMarker); this.startMarker = L.marker(latlng, { draggable: true, icon: L.divIcon({ className: 'start-marker', html: '🟢
départ', iconSize: [30, 30] }) }).addTo(this.map); this.startCoords = [latlng.lat, latlng.lng]; this.startMarker.on('dragend', (e) => { this.startCoords = [e.target.getLatLng().lat, e.target.getLatLng().lng]; if (this.endCoords) this.calculerEtAfficherItineraire(); }); } setEndMarker(latlng) { if (this.endMarker) this.map.removeLayer(this.endMarker); this.endMarker = L.marker(latlng, { draggable: true, icon: L.divIcon({ className: 'end-marker', html: '🔴
arrivée', iconSize: [30, 30] }) }).addTo(this.map); this.endCoords = [latlng.lat, latlng.lng]; this.endMarker.on('dragend', (e) => { this.endCoords = [e.target.getLatLng().lat, e.target.getLatLng().lng]; if (this.startCoords) this.calculerEtAfficherItineraire(); }); } setStartMarkerFromPopup(latlng) { this.startItinerary = [latlng[0], latlng[1]]; this.setStartMarker({lat: latlng[0], lng: latlng[1]}); this.map.closePopup(); if (this.endItinerary) this.calculerEtAfficherItineraire(); } setEndMarkerFromPopup(latlng) { this.setEndMarker({lat: latlng[0], lng: latlng[1]}); this.endItinerary = [latlng[0], latlng[1]]; this.map.closePopup(); if (this.startItinerary) this.calculerEtAfficherItineraire(); } async searchChargingStationsAroundPoint(lat, lon, radius = 1500) { const query = ` [out:json][timeout:25]; ( node["amenity"="charging_station"](around:${radius},${lat},${lon}); way["amenity"="charging_station"](around:${radius},${lat},${lon}); relation["amenity"="charging_station"](around:${radius},${lat},${lon}); ); out body; >; out skel qt;`; const url = `https://overpass-api.de/api/interpreter?data=${encodeURIComponent(query)}`; try { const response = await fetch(url); const data = await response.json(); return data.elements || []; } catch (error) { console.error('Erreur lors de la recherche des stations de recharge:', error); return []; } } formatDuration(minutes) { if (minutes >= 60) { const hours = Math.floor(minutes / 60); const mins = minutes % 60; return `${hours}h${mins > 0 ? ` ${mins}min` : ''}`; } return `${minutes} min`; } async calculerEtAfficherItineraire(destination, mode = 'car') { const startLat = this.startItinerary[0]; const startLon = this.startItinerary[1]; const endLat = this.endItinerary[0]; const endLon = this.endItinerary[1]; // Récupérer les paramètres de la batterie const batteryCapacity = parseFloat($('#battery_capacity').val()) || 40; const batteryStartLevel = parseFloat($('#battery_start_level').val()) || 90; const consumptionPerKm = parseFloat($('#consumption_per_km').val()) || 160; const minBatteryLevel = parseFloat($('#min_battery_level').val()) || 15; const chargeToLevel = parseFloat($('#charge_to_level').val()) || 80; const maxChargePower = parseFloat($('#max_charge_power').val()) || 50; const url = `https://router.project-osrm.org/route/v1/${mode}/${startLon},${startLat};${endLon},${endLat}?overview=full&geometries=geojson&alternatives=false&steps=false`; $('#routing_infos').html('Calcul de l\'itinéraire en cours…'); try { const response = await fetch(url); const data = await response.json(); if (!data.routes || !data.routes.length) { $('#routing_infos').html('Aucun itinéraire trouvé.'); return; } const route = data.routes[0]; const coords = route.geometry.coordinates.map(c => [c[1], c[0]]); // Nettoyer les anciens éléments if (this.routePolyline) { this.map.removeLayer(this.routePolyline); } this.pauseMarkers.forEach(marker => this.map.removeLayer(marker)); this.batteryMarkers.forEach(marker => this.map.removeLayer(marker)); // Afficher le nouveau tracé this.routePolyline = L.polyline(coords, {color: '#0059ce', weight: 5}).addTo(this.map); // Calculs de consommation const totalDistanceKm = route.distance / 1000; const totalConsumptionKwh = (totalDistanceKm * consumptionPerKm) / 1000; const batteryStartKwh = (batteryCapacity * batteryStartLevel) / 100; const batteryEndKwh = batteryStartKwh - totalConsumptionKwh; const batteryEndLevel = (batteryEndKwh / batteryCapacity) * 100; // Marqueurs de niveau de batterie this.batteryMarkers = []; const batteryLevels = [90, 80, 70, 60, 50, 40, 30, 20, 15]; let accumulatedDistance = 0; let currentBatteryLevel = batteryStartLevel; for (let i = 0; i < coords.length - 1; i++) { const segmentDistance = L.latLng(coords[i]).distanceTo(coords[i + 1]) / 1000; const segmentConsumption = (segmentDistance * consumptionPerKm) / 1000; const nextBatteryLevel = ((batteryStartKwh - (accumulatedDistance * consumptionPerKm / 1000)) / batteryCapacity) * 100; for (const level of batteryLevels) { if (currentBatteryLevel > level && nextBatteryLevel <= level) { const ratio = (level - currentBatteryLevel) / (nextBatteryLevel - currentBatteryLevel); const markerCoords = [ coords[i][0] + (coords[i + 1][0] - coords[i][0]) * ratio, coords[i][1] + (coords[i + 1][1] - coords[i][1]) * ratio ]; const batteryMarker = L.marker(markerCoords, { interactive: true, icon: L.divIcon({ className: 'battery-marker', html: `🔋 ${level}%`, iconSize: [30, 30] }) }).addTo(this.map); let popupContent = `

Niveau de batterie : ${level}%

`; if (level <= minBatteryLevel) { popupContent += `

⚠️ Recharge recommandée !

`; } popupContent += `

Distance parcourue : ${(accumulatedDistance + segmentDistance * ratio).toFixed(1)} km

Consommation : ${((accumulatedDistance + segmentDistance * ratio) * consumptionPerKm / 1000).toFixed(1)} kWh

`; batteryMarker.bindPopup(popupContent); this.batteryMarkers.push(batteryMarker); } } accumulatedDistance += segmentDistance; currentBatteryLevel = nextBatteryLevel; } // Calcul des pauses et temps de recharge const durationHours = route.duration / 3600; const numberOfPauses = Math.floor(durationHours / 2); const pauseDurationMinutes = 10; const totalPauseTimeMinutes = numberOfPauses * pauseDurationMinutes; let totalRechargeTimeMinutes = 0; if (numberOfPauses > 0) { const pauseInterval = totalDistanceKm / (numberOfPauses + 1); for (let i = 1; i <= numberOfPauses; i++) { const pauseDistance = pauseInterval * i; const nextPauseDistance = (i < numberOfPauses) ? pauseInterval : (totalDistanceKm - pauseDistance); const consumptionToNextPause = (nextPauseDistance / 1000) * consumptionPerKm / 1000; const batteryLevelNeeded = (consumptionToNextPause / batteryCapacity) * 100 + minBatteryLevel; const currentBatteryLevel = ((batteryStartKwh - (pauseDistance / 1000 * consumptionPerKm / 1000)) / batteryCapacity) * 100; const energyToRecharge = (batteryLevelNeeded - currentBatteryLevel) * batteryCapacity / 100; const rechargeTimeHours = energyToRecharge / maxChargePower; const rechargeTimeMinutes = Math.ceil(rechargeTimeHours * 60); totalRechargeTimeMinutes += Math.max(rechargeTimeMinutes, pauseDurationMinutes); } } const durationMin = Math.round(route.duration / 60); const durationNoPause = this.formatDuration(durationMin); const durationWithPauses = this.formatDuration(durationMin + totalPauseTimeMinutes); const durationWithRecharge = this.formatDuration(durationMin + totalRechargeTimeMinutes); $('#routing_infos').html(`

Itinéraire

Distance : ${totalDistanceKm.toFixed(1)} km

Niveaux de batterie :

Départ : ${batteryStartLevel}%
Arrivée : ${batteryEndLevel.toFixed(1)}%

Durées estimées :

Sans pause : ${durationNoPause}
Avec pauses (10 min) : ${durationWithPauses}
Avec recharges : ${durationWithRecharge}
Nombre de pauses recommandées : ${numberOfPauses} (10 min toutes les 2h)
Consommation totale estimée : ${totalConsumptionKwh.toFixed(1)} kWh
`); this.lastRouteDestination = destination; } catch (e) { $('#routing_infos').html('Erreur lors du calcul de l\'itinéraire.'); } } } export default Routing;