libre-charge-map/js/lcm_routing.js
2025-05-05 10:15:20 +02:00

260 lines
No EOL
12 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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: '🟢<br>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: '🔴<br>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 = `<div class="battery-info">
<h3>Niveau de batterie : ${level}%</h3>`;
if (level <= minBatteryLevel) {
popupContent += `<p class="warning">⚠️ Recharge recommandée !</p>`;
}
popupContent += `<p>Distance parcourue : ${(accumulatedDistance + segmentDistance * ratio).toFixed(1)} km</p>
<p>Consommation : ${((accumulatedDistance + segmentDistance * ratio) * consumptionPerKm / 1000).toFixed(1)} kWh</p>
</div>`;
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(`
<section class="routing">
<h2>Itinéraire</h2>
<div>Distance: <strong>${totalDistanceKm.toFixed(1)} km</strong></div>
<div class="battery-info">
<h3>Niveaux de batterie :</h3>
<div>Départ : <strong>${batteryStartLevel}%</strong></div>
<div>Arrivée : <strong>${batteryEndLevel.toFixed(1)}%</strong></div>
</div>
<div class="duration-info">
<h3>Durées estimées :</h3>
<div>Sans pause : <strong>${durationNoPause}</strong></div>
<div>Avec pauses (10 min) : <strong>${durationWithPauses}</strong></div>
<div>Avec recharges : <strong>${durationWithRecharge}</strong></div>
</div>
<div>Nombre de pauses recommandées: <strong>${numberOfPauses}</strong> (10 min toutes les 2h)</div>
<div>Consommation totale estimée: <strong>${totalConsumptionKwh.toFixed(1)} kWh</strong></div>
<div>
<button onclick="window.routing.calculerEtAfficherItineraire(window.routing.lastRouteDestination, 'car')">🚗 Voiture</button>
<button onclick="window.routing.calculerEtAfficherItineraire(window.routing.lastRouteDestination, 'bike')">🚴 Vélo</button>
<button onclick="window.routing.calculerEtAfficherItineraire(window.routing.lastRouteDestination, 'foot')">🚶 Piéton</button>
</div>
</section>
`);
this.lastRouteDestination = destination;
} catch (e) {
$('#routing_infos').html('Erreur lors du calcul de l\'itinéraire.');
}
}
}
export default Routing;