mirror of
https://forge.chapril.org/tykayn/libre-charge-map
synced 2025-06-20 01:34:43 +02:00
refacto routing part
This commit is contained in:
parent
dd6fbeb0a1
commit
db76768bd9
3 changed files with 277 additions and 282 deletions
|
@ -76,7 +76,7 @@
|
||||||
<!-- </fieldset>-->
|
<!-- </fieldset>-->
|
||||||
</form>
|
</form>
|
||||||
<div id="current_station_infos"></div>
|
<div id="current_station_infos"></div>
|
||||||
<div class="routing_infos"></div>
|
<div id="routing_infos"></div>
|
||||||
<div id="round_power_legend">
|
<div id="round_power_legend">
|
||||||
<br>
|
<br>
|
||||||
<span class="marker-demo">
|
<span class="marker-demo">
|
||||||
|
|
301
js/lcm_main.js
301
js/lcm_main.js
|
@ -15,7 +15,7 @@ import lcm_config from './lcm_config.js'
|
||||||
import lcm_utils, { valid_qa_message } from './lcm_utils.js'
|
import lcm_utils, { valid_qa_message } from './lcm_utils.js'
|
||||||
import lcm_color_utils from './lcm_color_utils.js'
|
import lcm_color_utils from './lcm_color_utils.js'
|
||||||
import { sendToJOSM, createJOSMEditLink } from './lcm_editor.js'
|
import { sendToJOSM, createJOSMEditLink } from './lcm_editor.js'
|
||||||
import routing from './lcm_routing.js'
|
import Routing from './lcm_routing.js'
|
||||||
|
|
||||||
let geojsondata;
|
let geojsondata;
|
||||||
let lastLatLng;
|
let lastLatLng;
|
||||||
|
@ -48,6 +48,11 @@ let endItinerary = [0, 0];
|
||||||
let map = L.map('map')
|
let map = L.map('map')
|
||||||
L.control.scale().addTo(map)
|
L.control.scale().addTo(map)
|
||||||
|
|
||||||
|
|
||||||
|
// Initialiser le routing
|
||||||
|
window.routing = new Routing(map);
|
||||||
|
|
||||||
|
|
||||||
setCoordinatesOfLeafletMapFromQueryParameters()
|
setCoordinatesOfLeafletMapFromQueryParameters()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -216,7 +221,7 @@ function searchLocation() {
|
||||||
.text('Itinéraire '+index)
|
.text('Itinéraire '+index)
|
||||||
.on('click', function(e) {
|
.on('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
calculerEtAfficherItineraire(place, 'car');
|
window.routing.calculerEtAfficherItineraire(place, 'car');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ajoute le bouton à côté de l'option (ou dans une div, selon ton HTML)
|
// Ajoute le bouton à côté de l'option (ou dans une div, selon ton HTML)
|
||||||
|
@ -286,7 +291,7 @@ function setCoordinatesOfLeafletMapFromQueryParameters() {
|
||||||
setRandomView();
|
setRandomView();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculerEtAfficherItineraire(endItinerary)
|
window.routing.calculerEtAfficherItineraire(endItinerary)
|
||||||
}
|
}
|
||||||
// mettre à jour les infos queryparam dans l'url pour passer le lien avec l'état inclus
|
// mettre à jour les infos queryparam dans l'url pour passer le lien avec l'état inclus
|
||||||
function updateURLWithMapCoordinatesAndZoom() {
|
function updateURLWithMapCoordinatesAndZoom() {
|
||||||
|
@ -615,7 +620,7 @@ function displayPointsFromApi(points, convert_to_osm_json) {
|
||||||
});
|
});
|
||||||
|
|
||||||
updateFilteredStationsCount();
|
updateFilteredStationsCount();
|
||||||
calculerEtAfficherItineraire(endItinerary);
|
window.routing.calculerEtAfficherItineraire(endItinerary);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1438,7 +1443,7 @@ function init() {
|
||||||
// ... dans la gestion du clic sur un résultat Addok ...
|
// ... dans la gestion du clic sur un résultat Addok ...
|
||||||
$('.route-to-place').on('click', function() {
|
$('.route-to-place').on('click', function() {
|
||||||
const place = $(this).data('place');
|
const place = $(this).data('place');
|
||||||
calculerEtAfficherItineraire(place, 'car');
|
window.routing.calculerEtAfficherItineraire(place, 'car');
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1593,6 +1598,18 @@ function init() {
|
||||||
fillDetailsWithFeature($('#current_station_infos').data('currentFeature'));
|
fillDetailsWithFeature($('#current_station_infos').data('currentFeature'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Modifier les gestionnaires d'événements pour utiliser window.routing
|
||||||
|
map.on('contextmenu', function(e) {
|
||||||
|
let popup = L.popup()
|
||||||
|
.setLatLng(e.latlng)
|
||||||
|
.setContent(`
|
||||||
|
<button onclick="window.routing.setStartMarkerFromPopup([${e.latlng.lat},${e.latlng.lng}])">Choisir comme départ</button><br>
|
||||||
|
<button onclick="window.routing.setEndMarkerFromPopup([${e.latlng.lat},${e.latlng.lng}])">Choisir comme arrivée</button>
|
||||||
|
`)
|
||||||
|
.openOn(map);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1628,278 +1645,4 @@ function updateFilteredStationsCount() {
|
||||||
} else {
|
} else {
|
||||||
$('#filter_max_output_display').after(filterStats);
|
$('#filter_max_output_display').after(filterStats);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// au clic droit, proposer les points d'itinéraire
|
|
||||||
map.on('contextmenu', function(e) {
|
|
||||||
// Créer un menu contextuel simple
|
|
||||||
let popup = L.popup()
|
|
||||||
.setLatLng(e.latlng)
|
|
||||||
.setContent(`
|
|
||||||
<button onclick="window.setStartMarkerFromPopup([${e.latlng.lat},${e.latlng.lng}])">Choisir comme départ</button><br>
|
|
||||||
<button onclick="window.setEndMarkerFromPopup([${e.latlng.lat},${e.latlng.lng}])">Choisir comme arrivée</button>
|
|
||||||
`)
|
|
||||||
.openOn(map);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Fonctions globales pour le menu contextuel
|
|
||||||
window.setStartMarkerFromPopup = function(latlng) {
|
|
||||||
startItinerary = [latlng[0], latlng[1]]
|
|
||||||
|
|
||||||
setStartMarker({lat: latlng[0], lng: latlng[1]});
|
|
||||||
map.closePopup();
|
|
||||||
if (endItinerary) calculerEtAfficherItineraire();
|
|
||||||
};
|
|
||||||
window.setEndMarkerFromPopup = function(latlng) {
|
|
||||||
setEndMarker({lat: latlng[0], lng: latlng[1]});
|
|
||||||
endItinerary = [latlng[0], latlng[1]]
|
|
||||||
map.closePopup();
|
|
||||||
if (startItinerary) calculerEtAfficherItineraire();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Déclarer une variable globale pour le tracé
|
|
||||||
window.routePolyline = null;
|
|
||||||
window.lastRouteDestination = null;
|
|
||||||
|
|
||||||
// Ajouter cette fonction avant calculerEtAfficherItineraire
|
|
||||||
async function 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 [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function calculerEtAfficherItineraire(destination, mode = 'car') {
|
|
||||||
// Récupérer le point de départ (centre de la carte)
|
|
||||||
const startLat = startItinerary[0];
|
|
||||||
const startLon = startItinerary[1];
|
|
||||||
const endLat = endItinerary[0];
|
|
||||||
const endLon = 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;
|
|
||||||
|
|
||||||
// Construire l'URL OSRM
|
|
||||||
const url = `https://router.project-osrm.org/route/v1/${mode}/${startLon},${startLat};${endLon},${endLat}?overview=full&geometries=geojson&alternatives=false&steps=false`;
|
|
||||||
|
|
||||||
$('#current_station_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) {
|
|
||||||
$('#current_station_infos').html('Aucun itinéraire trouvé.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const route = data.routes[0];
|
|
||||||
const coords = route.geometry.coordinates.map(c => [c[1], c[0]]); // [lat, lon]
|
|
||||||
|
|
||||||
// Supprimer l'ancien tracé et les marqueurs
|
|
||||||
if (window.routePolyline) {
|
|
||||||
map.removeLayer(window.routePolyline);
|
|
||||||
}
|
|
||||||
if (window.pauseMarkers) {
|
|
||||||
window.pauseMarkers.forEach(marker => map.removeLayer(marker));
|
|
||||||
}
|
|
||||||
if (window.batteryMarkers) {
|
|
||||||
window.batteryMarkers.forEach(marker => map.removeLayer(marker));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Afficher le nouveau tracé sur la carte
|
|
||||||
window.routePolyline = L.polyline(coords, {color: '#0059ce', weight: 5}).addTo(map);
|
|
||||||
// Supprimer le fitBounds qui déplace la vue
|
|
||||||
// map.fitBounds(window.routePolyline.getBounds());
|
|
||||||
|
|
||||||
// Calculer la consommation totale
|
|
||||||
const totalDistanceKm = route.distance / 1000;
|
|
||||||
const totalConsumptionKwh = (totalDistanceKm * consumptionPerKm) / 1000;
|
|
||||||
const batteryStartKwh = (batteryCapacity * batteryStartLevel) / 100;
|
|
||||||
const batteryEndKwh = batteryStartKwh - totalConsumptionKwh;
|
|
||||||
const batteryEndLevel = (batteryEndKwh / batteryCapacity) * 100;
|
|
||||||
|
|
||||||
// Ajouter les marqueurs de niveau de batterie
|
|
||||||
window.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; // en km
|
|
||||||
const segmentConsumption = (segmentDistance * consumptionPerKm) / 1000; // en kWh
|
|
||||||
const nextBatteryLevel = ((batteryStartKwh - (accumulatedDistance * consumptionPerKm / 1000)) / batteryCapacity) * 100;
|
|
||||||
|
|
||||||
// Vérifier si on atteint un niveau de batterie à marquer
|
|
||||||
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, {
|
|
||||||
icon: L.divIcon({
|
|
||||||
className: 'battery-marker',
|
|
||||||
html: `🔋 ${level}%`,
|
|
||||||
iconSize: [30, 30]
|
|
||||||
})
|
|
||||||
}).addTo(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);
|
|
||||||
window.batteryMarkers.push(batteryMarker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
accumulatedDistance += segmentDistance;
|
|
||||||
currentBatteryLevel = nextBatteryLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculer le nombre de pauses nécessaires (1 pause toutes les 2 heures)
|
|
||||||
const durationHours = route.duration / 3600;
|
|
||||||
const numberOfPauses = Math.floor(durationHours / 2);
|
|
||||||
const pauseDurationMinutes = 10;
|
|
||||||
const totalPauseTimeMinutes = numberOfPauses * pauseDurationMinutes;
|
|
||||||
|
|
||||||
// Calculer les temps de recharge nécessaires
|
|
||||||
let totalRechargeTimeMinutes = 0;
|
|
||||||
if (numberOfPauses > 0) {
|
|
||||||
const pauseInterval = totalDistance / (numberOfPauses + 1);
|
|
||||||
|
|
||||||
for (let i = 1; i <= numberOfPauses; i++) {
|
|
||||||
const pauseDistance = pauseInterval * i;
|
|
||||||
const nextPauseDistance = (i < numberOfPauses) ? pauseInterval : (totalDistance - pauseDistance);
|
|
||||||
|
|
||||||
// Calculer la consommation jusqu'à la prochaine pause
|
|
||||||
const consumptionToNextPause = (nextPauseDistance / 1000) * consumptionPerKm / 1000; // en kWh
|
|
||||||
const batteryLevelNeeded = (consumptionToNextPause / batteryCapacity) * 100 + minBatteryLevel;
|
|
||||||
|
|
||||||
// Calculer le niveau de batterie actuel
|
|
||||||
const currentBatteryLevel = ((batteryStartKwh - (pauseDistance / 1000 * consumptionPerKm / 1000)) / batteryCapacity) * 100;
|
|
||||||
|
|
||||||
// Calculer la quantité d'énergie à recharger
|
|
||||||
const energyToRecharge = (batteryLevelNeeded - currentBatteryLevel) * batteryCapacity / 100;
|
|
||||||
|
|
||||||
// Calculer le temps de recharge nécessaire
|
|
||||||
const rechargeTimeHours = energyToRecharge / maxChargePower;
|
|
||||||
const rechargeTimeMinutes = Math.ceil(rechargeTimeHours * 60);
|
|
||||||
|
|
||||||
// Ajouter le temps de recharge au total (minimum 10 minutes)
|
|
||||||
totalRechargeTimeMinutes += Math.max(rechargeTimeMinutes, pauseDurationMinutes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convertir les différentes durées en format lisible
|
|
||||||
const 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`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const durationNoPause = formatDuration(durationMin);
|
|
||||||
const durationWithPauses = formatDuration(durationMin + totalPauseTimeMinutes);
|
|
||||||
const durationWithRecharge = formatDuration(durationMin + totalRechargeTimeMinutes);
|
|
||||||
|
|
||||||
$('#routing_infos').html(`
|
|
||||||
<section class="routing">
|
|
||||||
<h2>Itinéraire</h2>
|
|
||||||
<div>Distance : <strong>${distanceKm} 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="calculerEtAfficherItineraire(window.lastRouteDestination, 'car')">🚗 Voiture</button>
|
|
||||||
<button onclick="calculerEtAfficherItineraire(window.lastRouteDestination, 'bike')">🚴 Vélo</button>
|
|
||||||
<button onclick="calculerEtAfficherItineraire(window.lastRouteDestination, 'foot')">🚶 Piéton</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Stocker la destination pour recalculer facilement
|
|
||||||
window.lastRouteDestination = destination;
|
|
||||||
} catch (e) {
|
|
||||||
$('#current_station_infos').html('Erreur lors du calcul de l\'itinéraire.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setStartMarker(latlng) {
|
|
||||||
if (startMarker) map.removeLayer(startMarker);
|
|
||||||
startMarker = L.marker(latlng, {
|
|
||||||
draggable: true,
|
|
||||||
icon: L.divIcon({
|
|
||||||
className: 'start-marker',
|
|
||||||
html: '🟢<br>départ',
|
|
||||||
iconSize: [30, 30]
|
|
||||||
})
|
|
||||||
}).addTo(map);
|
|
||||||
startCoords = [latlng.lat, latlng.lng];
|
|
||||||
startMarker.on('dragend', function(e) {
|
|
||||||
startCoords = [e.target.getLatLng().lat, e.target.getLatLng().lng];
|
|
||||||
if (endCoords) calculerEtAfficherItineraire();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setEndMarker(latlng) {
|
|
||||||
if (endMarker) map.removeLayer(endMarker);
|
|
||||||
endMarker = L.marker(latlng, {
|
|
||||||
draggable: true,
|
|
||||||
icon: L.divIcon({
|
|
||||||
className: 'end-marker',
|
|
||||||
html: '🔴<br>arrivée',
|
|
||||||
iconSize: [30, 30]
|
|
||||||
})
|
|
||||||
}).addTo(map);
|
|
||||||
endCoords = [latlng.lat, latlng.lng];
|
|
||||||
endMarker.on('dragend', function(e) {
|
|
||||||
endCoords = [e.target.getLatLng().lat, e.target.getLatLng().lng];
|
|
||||||
if (startCoords) calculerEtAfficherItineraire();
|
|
||||||
});
|
|
||||||
}
|
}
|
|
@ -1,8 +1,260 @@
|
||||||
/**
|
/**
|
||||||
* gestion des itinéraires pour libre charge map
|
* gestion des itinéraires pour libre charge map
|
||||||
*/
|
*/
|
||||||
class routing{
|
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
|
export default Routing;
|
Loading…
Add table
Add a link
Reference in a new issue