diff --git a/README.md b/README.md index cfea6da..b4af152 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@ # Carte des IRVE filtrable ![libre-charge-map_overview.jpg](libre-charge-map_overview.jpg) - - fait avec le données OpenStreetMap (OSM) ainsi que des icones ![libre-charge-map_popup.jpg](libre-charge-map_popup.jpg) +Venez discuter sur le forum OpenStreetMap: https://forum.openstreetmap.org/viewtopic.php?id=69882 développé par tykayn - https://www.cipherbliss.com - à partir d'un squelette d'example pour Leaflet. Mastodon: https://mastodon.cipherbliss.com/@tykayn +# Fonctionnalités +Affichage des stations de recharge colorées selon leur puissance maximale délivrée sur un totem. +Changement de fond de carte. # comment ça marche ? Avec une lib qui affiche un fond de carte sur lequel on peut naviguer et des marqueurs, on demande poliment à un site web, Overpass Turbo, quels sont les points et polygones d'OpenStreetMap correspondant à plusieurs types de restaurants et lieux où l'on peut trouver à manger et à boire à consommer sur place ou à emporter. @@ -39,6 +41,14 @@ let req = 'https://overpass-api.de/api/interpreter?data=[out:json][timeout:25]; 'nwr[amenity=charging_station](area.searchArea);' + 'out body geom;' ``` + +# Travaux en cours +- ouvrir les charging_station zone dans JOSM +- filtres avancés sur le type de prise +- affichage optionnel des restaurants et autres lieux où l'on peut trouver à manger et à boire comme dans MeltingPot. https://www.cipherbliss.com/ou-manger + + + # sources Sources disponibles sur https://forge.chapril.org/tykayn/libre-charge-map.git Carte similaire, celle des cuisines de restaurant: https://forge.chapril.org/tykayn/melting-pot diff --git a/index.html b/index.html index 1a4d811..ba96a7a 100644 --- a/index.html +++ b/index.html @@ -27,15 +27,20 @@ +
+

+ prise Libre Charge Map +

+
+
+ +
Zoomez pour voir les stations de recharge
-
- -
-
- -
+
- - + 423,301 350,250 277,301 303,215 + 231,161 321,161'/> + +
+
+ +
+
+ +
- +
- +
-

- prise Libre Charge Map -

+
@@ -110,6 +119,9 @@ + @@ -117,8 +129,11 @@
+
- filtres:
+

+ 🔍 Filtres: +

qualité diff --git a/js/config.js b/js/config.js index de9ff23..632a579 100644 --- a/js/config.js +++ b/js/config.js @@ -9,6 +9,35 @@ const config = { cartodb : 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', stamen : 'https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', transport : 'https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png' - } -} + }, + tags_to_display_in_popup: [ + 'name', + 'capacity', + 'description', + 'date_start', + 'charging_station:output', + 'socket:type_2', + 'socket:type2:output', + 'socket:typee', + 'socket:typee:output', + 'socket:type2_combo', + 'socket:type2_combo:output', + 'socket:chademo', + 'operator', 'ref:EU:EVSE', + 'network', + 'opening_hours', + 'contact', + 'phone', + 'contact:phone', + 'website', + 'contact:website', + 'ref', + 'fee', + 'payment', + 'payment:contactless', + 'authentication:app', + 'authentication:debit_card', + ] +} + export default config diff --git a/js/editor.js b/js/editor.js new file mode 100644 index 0000000..f979fda --- /dev/null +++ b/js/editor.js @@ -0,0 +1,29 @@ +/** + * Fonctions liées à l'édition des données OSM et à l'interaction avec JOSM + */ + +export function sendToJOSM(map) { + const bounds = map.getBounds(); + const bbox = `${bounds.getWest()},${bounds.getSouth()},${bounds.getEast()},${bounds.getNorth()}`; + + const josmUrl = `http://127.0.0.1:8111/load_and_zoom?left=${bounds.getWest()}&right=${bounds.getEast()}&top=${bounds.getNorth()}&bottom=${bounds.getSouth()}&select=node[amenity=charging_station]&changeset_hashtags=IRVE&layer_name=irve-depuis-OSM`; + + return fetch(josmUrl) + .then(response => { + if (response.ok) { + console.log('Données envoyées à JOSM avec succès'); + return true; + } else { + console.error('Erreur : JOSM doit être ouvert avec l\'option "Contrôle à distance" activée'); + throw new Error('JOSM non accessible'); + } + }) + .catch(error => { + console.error('Erreur JOSM:', error); + throw error; + }); +} + +export default { + sendToJOSM +}; \ No newline at end of file diff --git a/js/main.js b/js/main.js index 6d304d8..f20a8a8 100644 --- a/js/main.js +++ b/js/main.js @@ -7,6 +7,7 @@ import config from './config.js' import utils from './utils.js' import colorUtils from './color-utils.js' +import editor from './editor.js' console.log('config', config) let geojsondata; @@ -86,6 +87,10 @@ function updateURLWithMapCoordinatesAndZoom() { history.replaceState(null, null, url) } + +let all_stations_markers = L.layerGroup().addTo(map) // layer group pour tous les marqueurs +// let stations_much_speed_wow = L.layerGroup().addTo(map) // layer group des stations rapides + var osm = L.tileLayer(config.tileServers.osm, { attribution: config.osmMention + '© OpenStreetMap contributors' }) @@ -113,22 +118,26 @@ var baseLayers = { // 'OpenCycleMap': cycle, 'Transport': transport } - -let all_stations_markers = L.layerGroup().addTo(map) // layer group pour tous les marqueurs -// let stations_much_speed_wow = L.layerGroup().addTo(map) // layer group des stations rapides - let overlays = { stations_bof: all_stations_markers - // , stations_much_speed_wow -} // Si vous avez des calques superposables, ajoutez-les ici +} const layerControl = L.control.layers(baseLayers, overlays, {collapsed: true}).addTo(map) tileGrey.addTo(map) + function buildOverpassApiUrl(map, overpassQuery) { let baseUrl = 'https://overpass-api.de/api/interpreter' - let bounds = map.getBounds().getSouth() + ',' + map.getBounds().getWest() + ',' + map.getBounds().getNorth() + ',' + map.getBounds().getEast() + // Ajouter une marge de 2 kilomètres autour des bounds + // Conversion approximative: 1 degré = 111km à l'équateur + const kilometersMarginForLoading = 2 + const marginInDegrees = kilometersMarginForLoading / 111 // 2 kilomètres convertis en degrés + const south = map.getBounds().getSouth() - marginInDegrees + const west = map.getBounds().getWest() - marginInDegrees + const north = map.getBounds().getNorth() + marginInDegrees + const east = map.getBounds().getEast() + marginInDegrees + let bounds = south + ',' + west + ',' + north + ',' + east let resultUrl, query = '' if (config.overrideQuery) { @@ -144,36 +153,8 @@ function buildOverpassApiUrl(map, overpassQuery) { } resultUrl = baseUrl + query return resultUrl -} +} -const tags_to_display_in_popup = [ - 'name', - 'capacity', - 'description', - 'date_start', - 'charging_station:output', - 'socket:type_2', - 'socket:type2:output', - 'socket:typee', - 'socket:typee:output', - 'socket:type2_combo', - 'socket:type2_combo:output', - 'socket:chademo', - 'operator', 'ref:EU:EVSE', - 'network', - 'opening_hours', - 'contact', - 'phone', - 'contact:phone', - 'website', - 'contact:website', - 'ref', - 'fee', - 'payment', - 'payment:contactless', - 'authentication:app', - 'authentication:debit_card', -] const margin_josm_bbox = 0.00001 function createJOSMEditLink(feature) { @@ -288,6 +269,48 @@ function displayStatsFromGeoJson(resultAsGeojson) {
` + let stats_content = `
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeNombrePourcentage
Puissance inconnue${count_output_unknown}${calculerPourcentage(count_output_unknown, count)}%
1-50 kW${count_station_outputoutput_between_1_and_50}${calculerPourcentage(count_station_outputoutput_between_1_and_50, count)}%
50-100 kW${output_more_than_50}${calculerPourcentage(output_more_than_50, count)}%
100-200 kW${output_more_than_100}${calculerPourcentage(output_more_than_100, count)}%
200-300 kW${output_more_than_200}${calculerPourcentage(output_more_than_200, count)}%
300+ kW${output_more_than_300}${calculerPourcentage(output_more_than_300, count)}%
+
` + + /** + let stats_content = `
Statistiques des ${count} stations trouvées:
${count_station_output} (${calculerPourcentage(count_station_output, count)}%) ont une info de puissance max délivrée charging_station:output.
@@ -300,13 +323,14 @@ ${output_more_than_50} (${calculerPourcentage(output_more_than_50, count)}%) ont ${count_found_type2combo} (${calculerPourcentage(count_found_type2combo, count)}%) ont un prise combo définie *type2_combo*.
${count_estimated_type2combo} (${calculerPourcentage(count_estimated_type2combo, count)}%) ont une prise combo présumée à partir de la puissance max trouvée mais non spécifiée *type2_combo*.
${count_found_type2} (${calculerPourcentage(count_found_type2, count)}%) ont un prise type2 définie *type2*.
` - + * + */ $('#found_charging_stations').html(stats_content) $('#bars_power').html(bar_powers) } function bindEventsOnJosmRemote() { - let josm_remote_buttons = $(`.josm`) + let josm_remote_buttons = $(`#sendToJOSM`) // console.log('josm_remote_buttons', josm_remote_buttons[0]) $(josm_remote_buttons[0]).on('click', () => { // console.log('link', josm_remote_buttons[0]) @@ -383,8 +407,8 @@ function makePopupOfFeature(feature) { popupContent += '
' popupContent += '
' // ne montrer que certains champs dans la popup - tags_to_display_in_popup.forEach(function (key) { - if (tags_to_display_in_popup.indexOf(key)) { + config.tags_to_display_in_popup.forEach(function (key) { + if (config.tags_to_display_in_popup.indexOf(key)) { let value = feature.properties.tags[key] if (value) { if (value.indexOf('http') !== -1) { @@ -599,7 +623,20 @@ function onMapMoveEnd() { loadOverpassQuery() } $('#infos_carte').html(infos) - updateURLWithMapCoordinatesAndZoom() + // Stocker les dernières coordonnées connues + if (!window.lastKnownPosition) { + window.lastKnownPosition = center; + updateURLWithMapCoordinatesAndZoom(); + } else { + // Calculer la distance en km entre l'ancienne et la nouvelle position + const distanceKm = map.distance(center, window.lastKnownPosition) / 1000; + + // Ne mettre à jour que si on s'est déplacé de plus de 2km + if (distanceKm > 2) { + window.lastKnownPosition = center; + updateURLWithMapCoordinatesAndZoom(); + } + } } @@ -618,6 +655,9 @@ $(document).ready(function () { $('#load').on('click', function () { loadOverpassQuery() }) + $('#toggleSidePanel').on('click', function () { + $('body').toggleClass('side-panel-open') + }) // filtres // boutons de toggle et de cycle de visibilité // @@ -643,6 +683,7 @@ function showActiveFilter(filterVariableName, selectorId) { $(selectorId).attr('class', 'filter-state-' + filterVariableName) } + function cycleVariableState(filterVariableName, selectorId) { console.log('filterVariableName', filterVariableName, filterStatesAvailable) if (filterVariableName) { @@ -674,3 +715,93 @@ $('#toggle-stats').on('click', function() { $(this).text(text.replace('🔼', '🔽')); } }); + +// Ajouter ces variables avec les autres déclarations globales +let food_places_markers = L.layerGroup(); +const foodIcon = L.divIcon({ + className: 'food-marker', + html: '🍽️', + iconSize: [20, 20], + iconAnchor: [10, 10] +}); + +// Ajouter cette fonction avec les autres fonctions de recherche +function searchFoodPlaces(map) { + const bounds = map.getBounds(); + const bbox = bounds.getSouth() + ',' + bounds.getWest() + ',' + bounds.getNorth() + ',' + bounds.getEast(); + + const query = ` + [out:json][timeout:25]; + ( + node["amenity"="restaurant"](${bbox}); + node["amenity"="cafe"](${bbox}); + ); + out body; + >; + out skel qt;`; + + const url = `https://overpass-api.de/api/interpreter?data=${encodeURIComponent(query)}`; + + food_places_markers.clearLayers(); + + fetch(url) + .then(response => response.json()) + .then(data => { + const geojson = osmtogeojson(data); + geojson.features.forEach(feature => { + const coords = feature.geometry.coordinates; + const properties = feature.properties; + const name = properties.tags.name || 'Sans nom'; + const type = properties.tags.amenity; + + const marker = L.marker([coords[1], coords[0]], { + icon: foodIcon + }); + + marker.bindPopup(` + ${name}
+ Type: ${type}
+ ${properties.tags.cuisine ? 'Cuisine: ' + properties.tags.cuisine : ''} + `); + + food_places_markers.addLayer(marker); + }); + }) + .catch(error => console.error('Erreur lors de la recherche des restaurants:', error)); +} + +// Modifier la fonction init pour ajouter le contrôle des couches +function init() { + // ... existing map initialization code ... + + // Ajouter le groupe de marqueurs à la carte + food_places_markers.addTo(map); + $('#found_charging_stations').hide(); + + // Ajouter le contrôle des couches + const overlayMaps = { + "Stations de recharge": all_stations_markers, + "Restaurants et cafés": food_places_markers + }; + + L.control.layers(null, overlayMaps).addTo(map); + + // Ajouter l'événement de recherche sur le déplacement de la carte + map.on('moveend', function() { + if (map.getZoom() > 13) { // Ajuster le niveau de zoom selon vos besoins + searchFoodPlaces(map); + } else { + food_places_markers.clearLayers(); + } + }); + + document.getElementById('sendToJOSM').addEventListener('click', () => { + editor.sendToJOSM(map) + .then(() => { + alert('Données envoyées à JOSM avec succès !'); + }) + .catch(() => { + alert('Erreur : JOSM doit être ouvert avec l\'option "Contrôle à distance" activée'); + }); + }); +} diff --git a/styles/style.css b/styles/style.css index 28f2d2c..1c2022e 100644 --- a/styles/style.css +++ b/styles/style.css @@ -1,7 +1,7 @@ html, body { height: 100%; width: 100%; - background: #ccc; + background: #222; } body { @@ -94,7 +94,7 @@ img.leaflet-marker-icon.tag-socket\:type2_yes { float: right; } -#chercherButton { +.side-panel button { min-width: 10em; } @@ -112,7 +112,7 @@ img.leaflet-marker-icon.tag-socket\:type2_yes { background: #96b1ea; } -#chercherButton:hover, +button:hover, .edit-button:hover { background: #0d377b; border: solid 1px #08285c; @@ -149,8 +149,8 @@ a { #spinning_icon { position: fixed; - bottom: 11rem; - left: 20.5rem; + top: 0; + left: 0; z-index: 10; background: white; font-size: 2rem; @@ -292,6 +292,7 @@ marqueurs button { cursor: pointer; padding: 0.5rem; + background: white; } #bars_power { @@ -302,7 +303,7 @@ button { .bar { height: 1em; text-align: right; - padding: 0.55rem; + padding: 0.35rem; padding-right: 0.25rem; float: left; } @@ -339,6 +340,11 @@ button { #infos_carte{ padding: 1rem 0; } + +button + button{ + margin-left: 1rem; + +} .filter-group button{ padding: 1rem 2rem; border-radius: 0.25rem; @@ -376,6 +382,9 @@ button { background-size:contain; } +#round_power_legend{ + font-size: 0.8rem; +} .side-panel { font-size: 1rem; position: fixed; @@ -386,8 +395,36 @@ button { background: white; box-shadow: -2px 0 5px rgba(0,0,0,0.2); overflow-y: auto; - padding: 20px; + padding: 1rem 2rem; + padding-bottom: 15rem; z-index: 1000; + visibility: hidden; + top: 5.7rem; + width: 26vw; +} + +#toggleSidePanel{ + position: fixed; + top: 1rem; + right: 2rem; + z-index: 10; + background: white; + padding: 1rem 2rem; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); +} +header{ + padding-left: 2rem; + color: #666; +} +.side-panel-open .side-panel{ + visibility: visible; +} +.side-panel-open #map{ + margin-left: 23.5rem; +} +#infos_carte{ + clear:both; } #zoomMessage{ position: fixed; @@ -404,6 +441,17 @@ button { animation: rainbow-border 4s linear infinite; } +header{ + background: #222; + position: fixed; +} +header h1{ + line-height: 3rem; +} +header img{ + float: left; + margin-right: 1rem; +} @keyframes rainbow-border { 0% { border-left-color: #ff0000; } 17% { border-left-color: #ff8000; } @@ -416,7 +464,10 @@ button { #map { z-index: 1; - margin-right: 300px; /* Pour laisser de la place au panneau */ + top: 5.55rem; +} +.side-panel #map{ + margin-left: 20vw; } /* Style pour mobile */