/** * 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" console.log('config', config) let geojsondata; // 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' const tileServer = 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png' const tileServer_stamen = 'https://a.tile.openstreetmap.fr/osmfr/{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'); function setRandomView(){ console.log('set random view') // Choix au hasard d'une ville parmi la liste let randomCity = utils.cities[Math.floor(Math.random() * utils.cities.length)] console.log('randomCity', randomCity) map = map.setView(randomCity.coords, config.initialZoom) } function setCoordinatesOfLeafletMapFromQueryParameters() { // Récupère les paramètres de l'URL // console.log('window.location', window.location.href, window) const urlParams = new URLSearchParams(window.location.href); // console.log('urlParams', urlParams) // Récupère les coordonnées et le zoom à partir des paramètres de l'URL const lat = urlParams.get('lat'); const lng = urlParams.get('lng'); const zoom = urlParams.get('zoom'); // console.log('lat,lng,zoom', lat, lng, zoom) // Vérifie si les paramètres sont présents et valides if (lat && lng && zoom) { // Initialise la carte avec les coordonnées et le zoom récupérés map = map.setView([lat, lng], zoom); } else { // Affiche une erreur si les paramètres sont absents ou invalides 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}`; // console.log('updateURLWithMapCoordinatesAndZoom url', url) // Met à jour l'URL de la page history.replaceState(null, null, url); } var osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: config.osmMention+'© OpenStreetMap contributors' }) var cycle = L.tileLayer('https://{s}.tile.opencyclemap.org/{z}/{x}/{y}.png', { attribution: config.osmMention+'© OpenCycleMap contributors' }) var transport = L.tileLayer('https://{s}.tile.thunderforest.com/transport/{z}/{x}/{y}.png', { attribution: config.osmMention }) let tileGrey = L.tileLayer(tileServer, { attribution: config.osmMention }) let stamen = L.tileLayer(tileServer_stamen, { attribution: config.osmMention }) var baseLayers = { 'Grey': tileGrey, 'Stamen': stamen, 'OpenStreetMap': osm, // 'OpenCycleMap': cycle, 'Transport': transport } let overlays = {} // Si vous avez des calques superposables, ajoutez-les ici const layerControl = L.control.layers(baseLayers, overlays, { collapsed: true }).addTo(map) tileGrey.addTo(map) let filteredMarkers = L.layerGroup().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() let resultUrl, query = '' if (config.overrideQuery) { query = `?data=[out:json][timeout:15];( node[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 } const tags_to_display_in_popup = [ 'name', 'capacity', '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) { var coordinates = feature.geometry.coordinates var nodeId = feature.properties.id var left = coordinates[0] - margin_josm_bbox var right = coordinates[0] + margin_josm_bbox var bottom = coordinates[1] - margin_josm_bbox var top = coordinates[1] + margin_josm_bbox var josmUrl = `http://127.0.0.1:8111/load_and_zoom?changeset_hashtags=IRVE&layer_name=irve-depuis-OSM&left=${left}&top=${top}&right=${right}&bottom=${bottom}&select=${nodeId}` return josmUrl } function supprimerMarqueurs (map) { 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 // trouver si les tags présentent un type combo let found_type2 = false // trouver si les tags présentent un type 2 let keys_of_object = Object.keys(feature.properties.tags) keys_of_object.map(tagKey => { // console.log('tagKey', tagKey) if (tagKey.indexOf('type2_combo') !== -1) { found_type2_combo = true // console.log('tagkey trouvé combo', tagKey) } 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) { /** * si on trouve une puissance supérieure à 200kW on peut partir du principe que la station dispose d'une prise type_2_combo à minima */ 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}
${output_more_than_50}
${output_more_than_100}
${output_more_than_200}
${output_more_than_300}
` 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.
${count_ref_eu} (${calculerPourcentage(count_ref_eu, count)}%) ont une référence européenne ref:EU:EVSE.
${count_output_unknown} (${calculerPourcentage(count_output_unknown, count)}%) ont une puissance max inconnue *output*.
${output_more_than_300} (${calculerPourcentage(output_more_than_300, count)}%) ont une puissance max supérieure à 300 kW *output*.
${output_more_than_200} (${calculerPourcentage(output_more_than_200, count)}%) ont une puissance max supérieure à 200 kW *output*.
${output_more_than_100} (${calculerPourcentage(output_more_than_100, count)}%) ont une puissance max supérieure à 100 kW *output*.
${output_more_than_50} (${calculerPourcentage(output_more_than_50, count)}%) ont une puissance max supérieure à 50 kW *output*.
${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`) // console.log('josm_remote_buttons', josm_remote_buttons[0]) $(josm_remote_buttons[0]).on('click', () => { // console.log('link', josm_remote_buttons[0]) let josm_link = $(josm_remote_buttons[0]).attr('data-href') // console.log('lancer la télécommande josm', josm_link) $.get(josm_link, (res) => { console.log('res', res) }) }) } const ratio_circle = 0.9 function displayPointsFromApi (points) { geojsondata = osmtogeojson(points) // console.log('resultAsGeojson', geojsondata) displayStatsFromGeoJson(geojsondata) let resultLayer = L.geoJson(geojsondata, { style: function (feature) { return { color: '#f00' } }, /** * enlever les polygones, ne garder que les points * @param feature * @param layer * @returns {boolean} */ 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) { // console.log('event', event) }, onEachFeature: function (feature, layer) { 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 tags_to_display_in_popup.forEach(function (key) { if (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 += '
' 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 link_josm = createJOSMEditLink(feature) // console.log('link_josm', link_josm) // boutons d'itinéraire let html = ` 🚗🚴‍♀️👠 ✏️JOSM ${displayOutPowerGuessed}${popupContent}` let radius = 20 if (outPowerGuessed > 300) { radius = 70 * ratio_circle } else if (outPowerGuessed > 200) { radius = 60 * ratio_circle } else if (outPowerGuessed > 100) { radius = 50 * ratio_circle } else if (outPowerGuessed > 50) { radius = 40 * ratio_circle } else if (outPowerGuessed > 20) { radius = 30 * ratio_circle } else if (outPowerGuessed > 7) { radius = 20 * ratio_circle } let circle = L.circle(layer._latlng, { color: color, fillColor: color, fillOpacity: 0.5, radius: radius }).addTo(map) circle.bindPopup(html) circle.on({ mouseover: function () { this.openPopup() bindEventsOnJosmRemote() }, mouseout: function () { // setTimeout(() => this.closePopup(), 15000) }, click: function () { this.openPopup() }, }) }, }) } function makeCssClassFromTags (tags) { // console.log('tags', tags) let tagKeys = Object.keys(tags) // console.log('tagKeys', tagKeys) 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 } // $('#toggleMinPower_50').on('click', toggleMinPower(50)) // $('#toggleMinPower_100').on('click', toggleMinPower(100)) // document.getElementById('toggleMinPower_300').addEventListener('click', toggleMinPower(showHighPower)) function toggleMinPower (showHighPower) { console.log('toggle', showHighPower) showHighPower = !showHighPower addFilteredMarkers(showHighPower) this.textContent = showHighPower ? 'Montrer puissance haute' : 'Montrer puissance normale' } function addFilteredMarkers (showHighPower) { filteredMarkers.clearLayers() // Supprimer les marqueurs existants console.log('addFilteredMarkers: clear des marqueurs fait') 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') filteredMarkers.addLayer(marker) } }) console.log('addFilteredMarkers: ', counter) } let isLoading = false function loadOverpassQuery () { // ne pas charger si on recherche déjà if (!isLoading) { isLoading = true $('#spinning_icon').fadeIn() let queryTextfieldValue = $('#query-textfield').val() let overpassApiUrl = buildOverpassApiUrl(map, queryTextfieldValue) $.get(overpassApiUrl, function (geoDataPointsFromApi) { displayPointsFromApi(geoDataPointsFromApi) $('#spinning_icon').fadeOut() $('#message-loading').fadeOut() isLoading = false }) // end of the getting from overpass API } } $('#spinning_icon').hide() $('#message-loading').hide() function onMapMoveEnd () { let center = map.getCenter() let zoom = map.getZoom() let infos = `Lat: ${center.lat}, Lon: ${center.lng}, Zoom : ${zoom}` if (zoom > 10) { loadOverpassQuery() } else { infos += '(zoomez au niveau 11 ou plus pour charger les stations en vous déplaçant)' } $('#infos_carte').html(infos) updateURLWithMapCoordinatesAndZoom() } setCoordinatesOfLeafletMapFromQueryParameters() map.on('moveend', onMapMoveEnd) $(document).ready(function () { bindEventsOnJosmRemote() onMapMoveEnd() }) /* boutons de toggle */ // test $('#test').on('click', function () { console.log('filteredMarkers', filteredMarkers) supprimerMarqueurs(map) // loadOverpassQuery() })