/**
|
|
|
|
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 <https://leafletjs.com/reference.html>
|
|
|
|
**/
|
|
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 = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="bi bi-geo-alt-fill" viewBox="0 0 16 16">
|
|
<path fill="currentColor" d="M8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10m0-7a3 3 0 1 1 0-6 3 3 0 0 1 0 6"/>
|
|
</svg>
|
|
`;
|
|
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);
|
|
|
|
// 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: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
|
}).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) {
|
|
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 nœuds,
|
|
// des chemins, des polygones, etc.
|
|
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) {
|
|
// TODO Ici aussi on ne distingue pas les géométries des
|
|
// features mais ça vaudrait peut-être le coup de s’adapter un
|
|
// peu à la situation en cas de nœud, chemin ou zone…
|
|
|
|
// Par facilité on conserve les propriétés de la première feature pour plus tard
|
|
const feature0 = geojson.features[0].properties;
|
|
|
|
// 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`
|
|
const polygon = L.geoJSON(geojson, {
|
|
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
|
|
}
|
|
return {color: color};
|
|
}
|
|
}).bindTooltip(feature0.name).addTo(taskLayer).on('click', function (event) {
|
|
window.location.href = event.layer.feature.properties.url;
|
|
});
|
|
|
|
// Ajoute un marqueur au centroïde de la forme (car les
|
|
// marqueurs gardent la même taille quelque-soit le zoom
|
|
L.marker(polygon.getBounds().getCenter(), {
|
|
icon: icons[feature0.color],
|
|
title: feature0.name,
|
|
clickUrl: feature0.url,
|
|
}).addTo(taskLayer).on('click', function (event) {
|
|
window.location.href = event.target.options.clickUrl;
|
|
});
|
|
|
|
});
|
|
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());
|
|
}
|
|
}
|