/** Contrôleur qui gère la carte (charge son contenu et contrôle l’interactivité) Voir la macro Twig `map` qui produit le HTML géré par ce contrôleur. Cf **/ import { Controller } from '@hotwired/stimulus'; import 'leaflet'; export default class extends Controller { static values = { geojson: String, overpassResult: String, icon: String, popupUrl: String, } connect() { // Constitue une collection d’icones aux couleurs Bootstrap const iconHtml = ` `; const icons = { 'danger': L.divIcon({ html: iconHtml, className: 'svg-icon text-danger', iconSize: [16, 16], iconAnchor: [8, 16], }), 'warning': L.divIcon({ html: iconHtml, className: 'svg-icon text-warning', iconSize: [16, 16], iconAnchor: [8, 16], }), 'success': L.divIcon({ html: iconHtml, className: 'svg-icon text-success', iconSize: [16, 16], iconAnchor: [8, 16], }), 'info': L.divIcon({ html: iconHtml, className: 'svg-icon text-info', iconSize: [16, 16], iconAnchor: [8, 16], }), }; var geojsons, _this = this, map = L.map(this.element.querySelector('#map')); this.mapInstance = map; // Commence par déclarer le fond de carte classique OSM par défaut L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map); // Crée un ensemble de couches pour mieux les manipuler // individuellement var layer = L.featureGroup(); // Crée la couche dédiée à Overpass var overpassLayer = L.featureGroup(); if (this.overpassResultValue !== '') { geojsons = JSON.parse(this.overpassResultValue); if (geojsons.elements.length > 0) { // Ajoute chaque forme geojsons.elements.forEach(function (element) { // Cas d’un nœud if (element.type === 'node') { L.marker([ element.lat, element.lon ], { icon: icons['info'], }).addTo(overpassLayer).bindPopup(L.popup({ overpassElement: element, // on transmet les données du geojson à la popup par là }).setContent('…')); // le contenu définitif de la popup sera chargé plus tard en ajax } // Cas d’autre chose qu’un nœud, sans distinction if (element.members) { element.members.forEach(function (member) { // TODO On est parti du principe que les features du // geojson sont toutes des polylines ce qui est un peu // réducteur, à terme il faudrait distinguer les géométries const polygon = L.polyline(member.geometry.map(function (coord) { return L.latLng(coord.lat, coord.lon)}), { color: '#0dcaf0', // bleu info bootstrap weight: 6, // l’idée c’est que ce soit plus gros (par défaut c’est 3) que le tracé des données des tâches parce que ce sera en dessous opacity: 0.8, }).addTo(overpassLayer).bindPopup(L.popup({ overpassElement: element, // on transmet les données du geojson à la popup par là }).setContent('…')); // le contenu définitif de la popup sera chargé plus tard en ajax }); } }); // Intervient lors de l’ouverture de la popup associée à la forme overpassLayer.on('popupopen', function (event) { // Récupère le geojson de la forme var element = event.popup.options.overpassElement; // Enlève ce qui nous est inutile (les points, etc) delete element.members; // Ajoute ce qui peut-être utile (concernant la carte0 element['map'] = { 'center': map.getCenter(), 'zoom': map.getZoom(), }; // Effectue l’appel ajax pour récupérer le contenu de la popup fetch(_this.popupUrlValue + '?' + (new URLSearchParams({ 'element': JSON.stringify(element), }))) .then(function (response) { return response.text(); }) .then(function (text) { event.popup.setContent(text); }); }); overpassLayer.addTo(layer); } } // Créé la couche dédiée aux tâches var taskLayer = L.featureGroup(); geojsons = JSON.parse(this.geojsonValue); if (geojsons.length > 0) { geojsons.forEach(function (geojson) { geojson.features.forEach(function (feature) { // Dessine la forme de la tâche avec la proprieté `name` qui // s’ffiche au survol et cliquable vers l’adresse web dans la // propriété `url` if (feature.geometry.type === 'Point') { L.marker([ feature.geometry.coordinates[1], feature.geometry.coordinates[0], ], { icon: icons[feature.properties.color], title: feature.properties.name, clickUrl: feature.properties.url, }).addTo(taskLayer).on('click', function (event) { window.location.href = event.target.options.clickUrl; }); } else { const polygon = L.geoJSON(feature, { style: function (feature) { // Par défaut c’est un bleu par défaut de leaflet mais // sinon on utilise les couleurs de Bootstrap var color = 'blue'; switch (feature.properties.color) { case 'danger' : color = '#dc3545'; break case 'warning' : color = '#ffc107'; break case 'success' : color = '#198754'; break } if (feature.geometry.type === 'Polygon') { return { color: color, weight: 1, fillOpacity: 0.5, }; } else { return {color: color}; } } }).bindTooltip(feature.properties.name).addTo(taskLayer).on('click', function (event) { window.location.href = event.layer.feature.properties.url; }); } }); }); taskLayer.addTo(layer); } layer.addTo(map); // Si la couche Overpass n’est pas vide, ajoute le sélecteur de couches // sur la carte if (this.overpassResultValue !== '') { L.control.layers({}, { 'Overpass': overpassLayer, 'Tâches': taskLayer, }).addTo(map); } // Zoome la carte pour que les données des tâches soient toutes // visibles map.fitBounds(taskLayer.getBounds()); } openInOsm() { const url = "https://www.openstreetmap.org/#map="+this.mapInstance.getZoom()+"/"+this.mapInstance.getCenter().lat+"/"+this.mapInstance.getCenter().lng; window.open(url, '_blank'); } openInPanoramax() { const url = "https://api.panoramax.xyz/#focus=map&map="+this.mapInstance.getZoom()+"/"+this.mapInstance.getCenter().lat+"/"+this.mapInstance.getCenter().lng; window.open(url, '_blank'); } openInGeohack() { const url = "https://geohack.toolforge.org/geohack.php?params="+this.mapInstance.getCenter().lat+"_N_"+this.mapInstance.getCenter().lng+"_E"; window.open(url, '_blank'); } openInGeoportail() { const url = "https://www.geoportail.gouv.fr/carte?c="+this.mapInstance.getCenter().lng+","+this.mapInstance.getCenter().lat+"&z="+this.mapInstance.getZoom()+"&permalink=yes"; window.open(url, '_blank'); } openInMapillary() { const url = "https://www.mapillary.com/app/?lat="+this.mapInstance.getCenter().lat+"&lng="+this.mapInstance.getCenter().lng+"&z="+this.mapInstance.getZoom(); window.open(url, '_blank'); } openInGoogleMaps() { const url = "https://www.google.com/maps/@"+this.mapInstance.getCenter().lat+","+this.mapInstance.getCenter().lng+","+this.mapInstance.getZoom()+"z"; window.open(url, '_blank'); } openInBing() { const url = "https://www.bing.com/maps/?cp="+this.mapInstance.getCenter().lat+"%7E"+this.mapInstance.getCenter().lng+"&lvl="+this.mapInstance.getZoom(); window.open(url, '_blank'); } }