You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

154 lines
7.1 KiB

2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. /**
  2. Contrôleur qui gère la carte (charge son contenu et contrôle linteractivité)
  3. Voir la macro Twig `map` qui produit le HTML géré par ce contrôleur.
  4. Cf <https://leafletjs.com/reference.html>
  5. **/
  6. import { Controller } from '@hotwired/stimulus';
  7. import 'leaflet';
  8. export default class extends Controller {
  9. static values = {
  10. geojson: String,
  11. overpassResult: String,
  12. icon: String,
  13. popupUrl: String,
  14. }
  15. connect() {
  16. // Constitue une collection d’icones aux couleurs Bootstrap
  17. const iconHtml = `
  18. <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="bi bi-geo-alt-fill" viewBox="0 0 16 16">
  19. <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"/>
  20. </svg>
  21. `;
  22. const icons = {
  23. 'danger': L.divIcon({ html: iconHtml, className: 'svg-icon text-danger', iconSize: [16, 16], iconAnchor: [8, 16], }),
  24. 'warning': L.divIcon({ html: iconHtml, className: 'svg-icon text-warning', iconSize: [16, 16], iconAnchor: [8, 16], }),
  25. 'success': L.divIcon({ html: iconHtml, className: 'svg-icon text-success', iconSize: [16, 16], iconAnchor: [8, 16], }),
  26. 'info': L.divIcon({ html: iconHtml, className: 'svg-icon text-info', iconSize: [16, 16], iconAnchor: [8, 16], }),
  27. };
  28. var geojsons, _this = this, map = L.map(this.element);
  29. // Commence par déclarer le fond de carte classique OSM par défaut
  30. L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
  31. maxZoom: 19,
  32. attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
  33. }).addTo(map);
  34. // Crée un ensemble de couches pour mieux les manipuler
  35. // individuellement
  36. var layer = L.featureGroup();
  37. // Crée la couche dédiée à Overpass
  38. var overpassLayer = L.featureGroup();
  39. if (this.overpassResultValue !== '') {
  40. geojsons = JSON.parse(this.overpassResultValue);
  41. if (geojsons.elements.length > 0) {
  42. // Ajoute chaque forme
  43. geojsons.elements.forEach(function (element) {
  44. element.members.forEach(function (member) {
  45. // TODO On est parti du principe que les features du
  46. // geojson sont toutes des polylines ce qui est un peu
  47. // réducteur, à terme il faudrait distinguer les nœuds,
  48. // des chemins, des polygones, etc.
  49. const polygon = L.polyline(member.geometry.map(function (coord) { return L.latLng(coord.lat, coord.lon)}), {
  50. color: '#0dcaf0', // bleu info bootstrap
  51. 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
  52. opacity: 0.8,
  53. }).addTo(overpassLayer).bindPopup(L.popup({
  54. overpassElement: element, // on transmet les données du geojson à la popup par là
  55. }).setContent('…')); // le contenu définitif de la popup sera chargé plus tard en ajax
  56. });
  57. });
  58. // Intervient lors de l’ouverture de la popup associée à la forme
  59. overpassLayer.on('popupopen', function (event) {
  60. // Récupère le geojson de la forme
  61. var element = event.popup.options.overpassElement;
  62. // Enlève ce qui nous est inutile (les points, etc)
  63. delete element.members;
  64. // Ajoute ce qui peut-être utile (concernant la carte0
  65. element['map'] = {
  66. 'center': map.getCenter(),
  67. 'zoom': map.getZoom(),
  68. };
  69. // Effectue l’appel ajax pour récupérer le contenu de la popup
  70. fetch(_this.popupUrlValue + '?' + (new URLSearchParams({
  71. 'element': JSON.stringify(element),
  72. })))
  73. .then(function (response) {
  74. return response.text();
  75. })
  76. .then(function (text) {
  77. event.popup.setContent(text);
  78. });
  79. });
  80. overpassLayer.addTo(layer);
  81. }
  82. }
  83. // Créé la couche dédiée aux tâches
  84. var taskLayer = L.featureGroup();
  85. geojsons = JSON.parse(this.geojsonValue);
  86. if (geojsons.length > 0) {
  87. geojsons.forEach(function (geojson) {
  88. // TODO Ici aussi on ne distingue pas les géométries des
  89. // features mais ça vaudrait peut-être le coup de s’adapter un
  90. // peu à la situation en cas de nœud, chemin ou zone…
  91. // Par facilité on conserve les propriétés de la première feature pour plus tard
  92. const feature0 = geojson.features[0].properties;
  93. // Dessine la forme de la tâche avec la proprieté `name` qui
  94. // s’ffiche au survol et cliquable vers l’adresse web dans la
  95. // propriété `url`
  96. const polygon = L.geoJSON(geojson, {
  97. style: function (feature) {
  98. // Par défaut c’est un bleu par défaut de leaflet mais
  99. // sinon on utilise les couleurs de Bootstrap
  100. var color = 'blue';
  101. switch (feature.properties.color) {
  102. case 'danger' : color = '#dc3545'; break
  103. case 'warning' : color = '#ffc107'; break
  104. case 'success' : color = '#198754'; break
  105. }
  106. return {color: color};
  107. }
  108. }).bindTooltip(feature0.name).addTo(taskLayer).on('click', function (event) {
  109. window.location.href = event.layer.feature.properties.url;
  110. });
  111. // Ajoute un marqueur au centroïde de la forme (car les
  112. // marqueurs gardent la même taille quelque-soit le zoom
  113. L.marker(polygon.getBounds().getCenter(), {
  114. icon: icons[feature0.color],
  115. title: feature0.name,
  116. clickUrl: feature0.url,
  117. }).addTo(taskLayer).on('click', function (event) {
  118. window.location.href = event.target.options.clickUrl;
  119. });
  120. });
  121. taskLayer.addTo(layer);
  122. }
  123. layer.addTo(map);
  124. // Si la couche Overpass n’est pas vide, ajoute le sélecteur de couches
  125. // sur la carte
  126. if (this.overpassResultValue !== '') {
  127. L.control.layers({}, {
  128. 'Overpass': overpassLayer,
  129. 'Tâches': taskLayer,
  130. }).addTo(map);
  131. }
  132. // Zoome la carte pour que les données des tâches soient toutes
  133. // visibles
  134. map.fitBounds(taskLayer.getBounds());
  135. }
  136. }