/** * rechercher les bornes de recharge, * afficher des cercles colorés selon la puissance max de la station * lister les bornes trouvées dans la page * @type {boolean} */ import config from './config.js' import utils from './utils.js' import colorUtils from './color-utils.js' import { sendToJOSM, createJOSMEditLink } from './editor.js' let geojsondata; let lastLatLng; // serveurs de tuiles: https://wiki.openstreetmap.org/wiki/Tile_servers // https://stamen-tiles.a.ssl.fastly.net/toner/{z}/{x}/{y}.png // https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png // https://tile.openstreetmap.org/{z}/{x}/{y}.png // 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png' // Créer la carte centrée sur Rouen // Liste des 20 villes les plus peuplées de France avec leurs coordonnées géographiques // Initialisation de la carte avec la vue centrée sur la ville choisie let map = L.map('map') L.control.scale().addTo(map) /** * filtres à toggle par des boutons dans la page * à appliquer à chaque rafraîchissement des points geojson * TODO: make buttons and filter in refresh circles */ let filterStatesAvailable = ['hide', 'show', 'showOnly'] let display_type2_sockets = 'show'; let display_type2_combo_sockets = 'show'; let display_unknown_max_power_station = 'show'; let display_known_max_power_station = 'show'; let display_type2_combo_sockets_with_cable = 'show'; let display_lower_than_50kw = 'show'; let display_higer_than_50kw = 'show'; let display_lower_than_200kw = 'show'; let display_higer_than_200kw = 'show'; let display_chelou = 'show'; // les stations avec une valeur suspecte, plus de 400kW function setRandomView() { let randomCity = utils.cities[Math.floor(Math.random() * utils.cities.length)]; map = map.setView(randomCity.coords, config.initialZoom); } function setCoordinatesOfLeafletMapFromQueryParameters() { const urlParams = new URLSearchParams(window.location.href); const lat = urlParams.get('lat'); const lng = urlParams.get('lng'); const zoom = urlParams.get('zoom'); if (lat && lng && zoom) { map = map.setView([lat, lng], zoom); } else { console.error('Les paramètres de coordonnées et de zoom doivent être présents dans l\'URL.'); setRandomView(); } } function updateURLWithMapCoordinatesAndZoom() { // Récupère les coordonnées et le niveau de zoom de la carte const center = map.getCenter() const zoom = map.getZoom() // Construit l'URL avec les paramètres de coordonnées et de zoom const url = `#coords=1&lat=${center.lat}&lng=${center.lng}&zoom=${zoom}` // Met à jour l'URL de la page 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' }) var cycle = L.tileLayer(config.tileServers.cycle, { attribution: config.osmMention + '© OpenCycleMap contributors' }) var transport = L.tileLayer(config.tileServers.transport, { attribution: config.osmMention }) let tileGrey = L.tileLayer(config.tileServers.cartodb, { attribution: config.osmMention }) let stamen = L.tileLayer(config.tileServers.stamen, { attribution: config.osmMention }) var baseLayers = { 'Grey': tileGrey, 'Stamen': stamen, 'OpenStreetMap': osm, // 'OpenCycleMap': cycle, 'Transport': transport } let overlays = { stations_bof: all_stations_markers } 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'; const kilometersMarginForLoading = 2; const marginInDegrees = kilometersMarginForLoading / 111; 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) { query = `?data=[out:json][timeout:15];( nwr[amenity=charging_station](${bounds}); );out body geom;`; } else { let nodeQuery = 'node[' + overpassQuery + '](' + bounds + ');'; let wayQuery = 'way[' + overpassQuery + '](' + bounds + ');'; let relationQuery = 'relation[' + overpassQuery + '](' + bounds + ');'; query = '?data=[out:json][timeout:15];(' + nodeQuery + wayQuery + relationQuery + ');out body geom;'; } resultUrl = baseUrl + query; return resultUrl; } function supprimerMarqueurs() { all_stations_markers.clearLayers(); map.eachLayer((layer) => { if (layer instanceof L.Marker) { layer.remove(); } }); } let coef_reduction_bars = 0.8 function calculerPourcentage(partie, total, reduc) { if (total === 0) { return 'Division par zéro impossible' } let coef_reduction = 1 if (reduc) { coef_reduction = coef_reduction_bars } return ((partie / total) * 100 * coef_reduction).toFixed(1) } function displayStatsFromGeoJson(resultAsGeojson) { let count = resultAsGeojson.features.length; let count_station_output = 0; let count_ref_eu = 0; let output_more_than_300 = 0; let output_more_than_200 = 0; let output_more_than_100 = 0; let output_more_than_50 = 0; let count_station_outputoutput_between_1_and_50 = 0; let count_output_unknown = 0; let count_estimated_type2combo = 0; let count_found_type2combo = 0; let count_found_type2 = 0; resultAsGeojson.features.map(feature => { let found_type2_combo = false; let found_type2 = false; let keys_of_object = Object.keys(feature.properties.tags); keys_of_object.map(tagKey => { if (tagKey.indexOf('type2_combo') !== -1) { found_type2_combo = true; } if (tagKey.indexOf('type2') !== -1) { found_type2 = true; } }); let outputPower = utils.guessOutputPowerFromFeature(feature); if (found_type2_combo) { count_found_type2combo++; } if (found_type2) { count_found_type2++; } if (outputPower == 0) { count_output_unknown++; } if (outputPower >= 200 && !found_type2_combo) { count_estimated_type2combo++; } if (outputPower > 0 && outputPower < 50) { count_station_outputoutput_between_1_and_50++; } if (outputPower >= 50 && outputPower < 100) { output_more_than_50++; } else if (outputPower >= 100 && outputPower < 200) { output_more_than_100++; } else if (outputPower >= 200 && outputPower < 300) { output_more_than_200++; } else if (outputPower >= 300) { feature.properties.puissance_haute = true; output_more_than_300++; } if (feature.properties.tags['charging_station:output']) { count_station_output++; } if (feature.properties.tags['ref:EU:EVSE']) { count_ref_eu++; } }); let bar_powers = `
${count_output_unknown}
${count_station_outputoutput_between_1_and_50 ? count_station_outputoutput_between_1_and_50 : ''}
${output_more_than_50 ? output_more_than_50 : ''}
${output_more_than_100 ? output_more_than_100 : ''}
${output_more_than_200 ? output_more_than_200 : '' | ''}
${output_more_than_300 ? output_more_than_300 : ''}
`; let stats_content = `
Type Nombre Pourcentage
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)}%
`; $('#found_charging_stations').html(stats_content); $('#bars_power').html(bar_powers); } function bindEventsOnJosmRemote() { let josm_remote_buttons = $(`#sendToJOSM`) $(josm_remote_buttons[0]).on('click', () => { let josm_link = $(josm_remote_buttons[0]).attr('data-href') $.get(josm_link, (res) => { console.log('res', res) }) }) } function displayPointsFromApi(points) { if (points) { geojsondata = osmtogeojson(points); } displayStatsFromGeoJson(geojsondata); let resultLayer = L.geoJson(geojsondata, { style: function (feature) { return {color: '#f00'}; }, filter: function (feature, layer) { let isPolygon = (feature.geometry) && (feature.geometry.type !== undefined) && (feature.geometry.type === 'Polygon'); if (isPolygon) { feature.geometry.type = 'Point'; let polygonCenter = L.latLngBounds(feature.geometry.coordinates[0]).getCenter(); feature.geometry.coordinates = [polygonCenter.lat, polygonCenter.lng]; } return true; }, onmoveend: function (event) { // console.log('déplacement terminé'); }, onzoomend: function (event) { supprimerMarqueurs(); displayPointsFromApi(); }, onEachFeature: eachFeature, }); } function makePopupOfFeature(feature) { let popupContent = '' popupContent += '
' let type2 = feature.properties.tags['socket:type2'] let type2_combo = feature.properties.tags['socket:type2_combo'] if (type2) { popupContent += ' prise de type 2' if (type2 !== 'yes') { popupContent += 'x ' + type2 + '' } } if (feature.properties.tags['socket:type2_combo']) { popupContent += ' prise de type 2 combo CCS' if (type2_combo !== 'yes') { popupContent += 'x ' + type2_combo + '' } } popupContent += '
' popupContent += '
' // ne montrer que certains champs dans la popup 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) { value = '' + value + '' } popupContent = popupContent + '
' + key + ' :' + value + '' } } }) popupContent += '
' return popupContent; } function eachFeature(feature, layer) { let link_josm = createJOSMEditLink(feature); let popupContent = makePopupOfFeature(feature); layer.bindPopup(popupContent); let outPowerGuessed = utils.guessOutputPowerFromFeature(feature); let color = colorUtils.getColor(feature); let displayOutPowerGuessed = '? kW'; if (outPowerGuessed) { displayOutPowerGuessed = outPowerGuessed + ' kW max'; } if (!popupContent) { popupContent = ` Aucune information renseignée, ajoutez la dans OpenStreetMap!`; } let html = ` 🚗🚴‍♀️👠 ✏️JOSM ${displayOutPowerGuessed}${popupContent}`; let zoom = map.getZoom(); let radius = 20; let opacity = 0.5; let ratio_circle = 10; if (zoom < 13) { ratio_circle = 5; } else if (zoom < 15) { ratio_circle = 1; opacity = 0.25; } else if (zoom <= 16) { ratio_circle = 0.5; } else if (zoom <= 18) { ratio_circle = 0.25; } if (!layer._latlng) { if (lastLatLng) { layer._latlng = lastLatLng; } } else { lastLatLng = layer._latlng; } if (!outPowerGuessed) { radius = radius * ratio_circle; } else { radius = outPowerGuessed * ratio_circle; } let circle = L.circle(layer._latlng, { color: color, fillColor: color, fillOpacity: opacity, colorOpacity: opacity, radius: radius }).addTo(all_stations_markers); if (zoom > 15) { let circle_center = L.circle(layer._latlng, { color: 'black', fillColor: color, fillOpacity: 1, radius: 0.1 }).addTo(all_stations_markers); } circle.bindPopup(html); circle.on({ mouseover: function () { this.openPopup(); bindEventsOnJosmRemote(); }, mouseout: function () { // setTimeout(() => this.closePopup(), 15000); }, click: function () { this.openPopup(); bindEventsOnJosmRemote(); }, }); } function makeCssClassFromTags(tags) { let tagKeys = Object.keys(tags) if (!tags) { return '' } let listOfClasses = [] tagKeys.forEach((element) => { listOfClasses.push('tag-' + element + '_' + tags[element].replace(':', '--').replace(' ', '-')) }) return listOfClasses.join(' ') } function getIconFromTags(tags) { let iconFileName = '' // let iconFileName = 'icon_restaurant.png'; if (tags['man_made']) { iconFileName = 'fountain.png' } return iconFileName } function toggleMinPower(showHighPower) { showHighPower = !showHighPower; addFilteredMarkers(showHighPower); this.textContent = showHighPower ? 'Montrer puissance haute' : 'Montrer puissance normale'; } function addFilteredMarkers(showHighPower) { allMarkers.clearLayers(); let counter = 0; geojsondata.features.forEach(function (feature) { if (feature.properties.puissance_haute === showHighPower) { counter++; let marker = L.marker(feature.geometry.coordinates).bindPopup(feature.properties.puissance_haute ? 'Puissance haute' : 'Puissance normale'); allMarkers.addLayer(marker); } }); } let isLoading = false function loadOverpassQuery() { if (!isLoading) { isLoading = true; $('#spinning_icon').fadeIn(); let queryTextfieldValue = $('#query-textfield').val(); let overpassApiUrl = buildOverpassApiUrl(map, queryTextfieldValue); $.get(overpassApiUrl, function (geoDataPointsFromApi) { geojsondata = geoDataPointsFromApi; refreshDisplay(); $('#spinning_icon').fadeOut(); $('#message-loading').fadeOut(); isLoading = false; }); } } function refreshDisplay() { supprimerMarqueurs(); displayPointsFromApi(geojsondata); } function onMapMoveEnd() { let center = map.getCenter() let zoom = map.getZoom() let infos = `Lat: ${center.lat}, Lon: ${center.lng}, Zoom : ${zoom}` if (zoom < 10) { $('#zoomMessage').show() } else { $('#zoomMessage').hide() loadOverpassQuery() } if (map.getZoom() > 14) { searchFoodPlaces(map); } else { food_places_markers.clearLayers(); } $('#infos_carte').html(infos) // 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(); } } } setCoordinatesOfLeafletMapFromQueryParameters() $(document).ready(function () { bindEventsOnJosmRemote(); onMapMoveEnd(); map.on('moveend', onMapMoveEnd); $('#spinning_icon').hide(); $('#removeMarkers').on('click', function () { supprimerMarqueurs(); }); $('#load').on('click', function () { loadOverpassQuery(); }); $('#toggleSidePanel').on('click', function () { $('body').toggleClass('side-panel-open'); }); $('#chercherButton').on('click', function () { supprimerMarqueurs(); loadOverpassQuery(); }); $('#setRandomView').on('click', function () { setRandomView(); loadOverpassQuery(); }); $('#filterUnkown').on('click', function () { display_unknown_max_power_station = cycleVariableState(display_unknown_max_power_station, '#filterUnkown'); showActiveFilter(display_unknown_max_power_station, '#filterUnkown'); refreshDisplay(); }); showActiveFilter(display_unknown_max_power_station, '#filterUnkown'); }); function showActiveFilter(filterVariableName, selectorId) { $(selectorId).attr('class', 'filter-state-' + filterVariableName) } function cycleVariableState(filterVariableName, selectorId) { console.log('filterVariableName', filterVariableName, filterStatesAvailable) if (filterVariableName) { if (filterVariableName == filterStatesAvailable[0]) { filterVariableName = filterStatesAvailable[1] } else if (filterVariableName == filterStatesAvailable[1]) { filterVariableName = filterStatesAvailable[2] } else if (filterVariableName == filterStatesAvailable[2]) { filterVariableName = filterStatesAvailable[0] } } else { filterVariableName = filterStatesAvailable[0] } showActiveFilter(filterVariableName, selectorId) console.log('filterVariableName after', filterVariableName) return filterVariableName } // toggle des stats $('#toggle-stats').on('click', function() { $('#found_charging_stations').slideToggle(); // Change le symbole de la flèche let text = $(this).text(); if(text.includes('🔽')) { $(this).text(text.replace('🔽', '🔼')); } else { $(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); $('#sendToJOSM').on('click', () => { sendToJOSM(map, geojsondata) .then(() => { console.log('Données envoyées à JOSM avec succès !'); }) .catch(() => { alert('Erreur : JOSM doit être ouvert avec l\'option "Contrôle à distance" activée'); }); }); $('#searchButton').on('click', searchLocation); $('#shareUrl').on('click', copyCurrentUrl); document.getElementById('searchButton').addEventListener('click', searchLocation); } function copyCurrentUrl() { const url = window.location.href; var dummy = document.createElement('input'), text = window.location.href; document.body.appendChild(dummy); dummy.value = text; dummy.select(); document.execCommand('copy'); document.body.removeChild(dummy); } function searchLocation() { const location = document.getElementById('searchLocation').value; if (!location) { alert('Veuillez entrer un lieu à rechercher.'); return; } const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(location)}`; fetch(url) .then(response => response.json()) .then(data => { if (data.length > 0) { const place = data[0]; const lat = parseFloat(place.lat); const lon = parseFloat(place.lon); map.setView([lat, lon], 13); // Ajustez le niveau de zoom selon vos besoins } else { alert('Lieu non trouvé. Veuillez essayer un autre terme de recherche.'); } }) .catch(error => { console.error('Erreur lors de la recherche du lieu :', error); alert('Erreur lors de la recherche du lieu.'); }); } init()