From db76768bd991749156e3a75271e4b6c8c60ee33b Mon Sep 17 00:00:00 2001 From: Tykayn Date: Mon, 5 May 2025 10:15:20 +0200 Subject: [PATCH] refacto routing part --- index.html | 2 +- js/lcm_main.js | 301 ++++------------------------------------------ js/lcm_routing.js | 256 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 277 insertions(+), 282 deletions(-) diff --git a/index.html b/index.html index bf9517d..d3fad0a 100644 --- a/index.html +++ b/index.html @@ -76,7 +76,7 @@
-
+

diff --git a/js/lcm_main.js b/js/lcm_main.js index ff26d0f..5d6849d 100644 --- a/js/lcm_main.js +++ b/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_color_utils from './lcm_color_utils.js' import { sendToJOSM, createJOSMEditLink } from './lcm_editor.js' -import routing from './lcm_routing.js' +import Routing from './lcm_routing.js' let geojsondata; let lastLatLng; @@ -48,6 +48,11 @@ let endItinerary = [0, 0]; let map = L.map('map') L.control.scale().addTo(map) + + // Initialiser le routing + window.routing = new Routing(map); + + setCoordinatesOfLeafletMapFromQueryParameters() /** @@ -216,7 +221,7 @@ function searchLocation() { .text('Itinéraire '+index) .on('click', function(e) { 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) @@ -286,7 +291,7 @@ function setCoordinatesOfLeafletMapFromQueryParameters() { setRandomView(); } - calculerEtAfficherItineraire(endItinerary) + window.routing.calculerEtAfficherItineraire(endItinerary) } // mettre à jour les infos queryparam dans l'url pour passer le lien avec l'état inclus function updateURLWithMapCoordinatesAndZoom() { @@ -615,7 +620,7 @@ function displayPointsFromApi(points, convert_to_osm_json) { }); updateFilteredStationsCount(); - calculerEtAfficherItineraire(endItinerary); + window.routing.calculerEtAfficherItineraire(endItinerary); } @@ -1438,7 +1443,7 @@ function init() { // ... dans la gestion du clic sur un résultat Addok ... $('.route-to-place').on('click', function() { 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')); } }); + + + // Modifier les gestionnaires d'événements pour utiliser window.routing + map.on('contextmenu', function(e) { + let popup = L.popup() + .setLatLng(e.latlng) + .setContent(` +
+ + `) + .openOn(map); + }); } @@ -1628,278 +1645,4 @@ function updateFilteredStationsCount() { } else { $('#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(` -
- - `) - .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 = `
-

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); - 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(` -
-

Itinéraire

-
Distance : ${distanceKm} 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
-
- - - -
-
- `); - - // 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: '🟢
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: '🔴
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(); - }); } \ No newline at end of file diff --git a/js/lcm_routing.js b/js/lcm_routing.js index 5562a43..d379f4c 100644 --- a/js/lcm_routing.js +++ b/js/lcm_routing.js @@ -1,8 +1,260 @@ /** * 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: '🟢
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 \ No newline at end of file +export default Routing; \ No newline at end of file