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.

248 lines
12 KiB

8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
2 months ago
8 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 targets = [ 'openLink' ];
  10. static values = {
  11. geojson: String,
  12. overpassResult: String,
  13. icon: String,
  14. popupUrl: String,
  15. }
  16. connect() {
  17. const self = this;
  18. // Constitue une collection d’icones aux couleurs Bootstrap
  19. const iconHtml = `
  20. <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="bi bi-geo-alt-fill" viewBox="0 0 16 16">
  21. <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"/>
  22. </svg>
  23. `;
  24. const icons = {
  25. 'danger': L.divIcon({ html: iconHtml, className: 'svg-icon text-danger', iconSize: [16, 16], iconAnchor: [8, 16], }),
  26. 'warning': L.divIcon({ html: iconHtml, className: 'svg-icon text-warning', iconSize: [16, 16], iconAnchor: [8, 16], }),
  27. 'success': L.divIcon({ html: iconHtml, className: 'svg-icon text-success', iconSize: [16, 16], iconAnchor: [8, 16], }),
  28. 'info': L.divIcon({ html: iconHtml, className: 'svg-icon text-info', iconSize: [16, 16], iconAnchor: [8, 16], }),
  29. };
  30. var geojsons, _this = this, map = L.map(this.element.querySelector('#map'));
  31. this.mapInstance = map;
  32. // Commence par déclarer le fond de carte classique OSM par défaut
  33. L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
  34. maxZoom: 19,
  35. attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
  36. }).addTo(map);
  37. // Suit les mouvements de la carte
  38. map.on('moveend', function() {
  39. var center = map.getCenter();
  40. var lat = center.lat;
  41. var lng = center.lng;
  42. self.openLinkTarget.setAttribute('href', `geo:${lat},${lng}`);
  43. });
  44. // Crée un ensemble de couches pour mieux les manipuler
  45. // individuellement
  46. var layer = L.featureGroup();
  47. // Crée la couche dédiée à Overpass
  48. var overpassLayer = L.featureGroup();
  49. if (this.overpassResultValue !== '') {
  50. geojsons = JSON.parse(this.overpassResultValue);
  51. if (geojsons.elements.length > 0) {
  52. // Ajoute chaque forme
  53. geojsons.elements.forEach(function (element) {
  54. // Cas d’un nœud
  55. if (element.type === 'node') {
  56. L.marker([ element.lat, element.lon ], {
  57. icon: icons['info'],
  58. }).addTo(overpassLayer).bindPopup(L.popup({
  59. overpassElement: element, // on transmet les données du geojson à la popup par là
  60. }).setContent('…')); // le contenu définitif de la popup sera chargé plus tard en ajax
  61. }
  62. // Cas d’autre chose qu’un nœud, sans distinction
  63. if (element.members) {
  64. element.members.forEach(function (member) {
  65. // TODO On est parti du principe que les features du
  66. // geojson sont toutes des polylines ce qui est un peu
  67. // réducteur, à terme il faudrait distinguer les géométries
  68. const polygon = L.polyline(member.geometry.map(function (coord) { return L.latLng(coord.lat, coord.lon)}), {
  69. color: '#0dcaf0', // bleu info bootstrap
  70. 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
  71. opacity: 0.8,
  72. }).addTo(overpassLayer).bindPopup(L.popup({
  73. overpassElement: element, // on transmet les données du geojson à la popup par là
  74. }).setContent('…')); // le contenu définitif de la popup sera chargé plus tard en ajax
  75. });
  76. }
  77. });
  78. // Intervient lors de l’ouverture de la popup associée à la forme
  79. overpassLayer.on('popupopen', function (event) {
  80. // Récupère le geojson de la forme
  81. var element = event.popup.options.overpassElement;
  82. // Enlève ce qui nous est inutile (les points, etc)
  83. delete element.members;
  84. // Ajoute ce qui peut-être utile (concernant la carte0
  85. element['map'] = {
  86. 'center': map.getCenter(),
  87. 'zoom': map.getZoom(),
  88. };
  89. // Effectue l’appel ajax pour récupérer le contenu de la popup
  90. fetch(_this.popupUrlValue + '?' + (new URLSearchParams({
  91. 'element': JSON.stringify(element),
  92. })))
  93. .then(function (response) {
  94. return response.text();
  95. })
  96. .then(function (text) {
  97. event.popup.setContent(text);
  98. });
  99. });
  100. overpassLayer.addTo(layer);
  101. }
  102. }
  103. // Créé la couche dédiée aux tâches
  104. var taskLayer = L.featureGroup();
  105. geojsons = JSON.parse(this.geojsonValue);
  106. if (geojsons.length > 0) {
  107. geojsons.forEach(function (geojson) {
  108. geojson.features.forEach(function (feature) {
  109. // Dessine la forme de la tâche avec la proprieté `name` qui
  110. // s’ffiche au survol et cliquable vers l’adresse web dans la
  111. // propriété `url`
  112. if (feature.geometry.type === 'Point') {
  113. L.marker([
  114. feature.geometry.coordinates[1],
  115. feature.geometry.coordinates[0],
  116. ], {
  117. icon: icons[feature.properties.color],
  118. title: feature.properties.name,
  119. clickUrl: feature.properties.url,
  120. }).addTo(taskLayer).on('click', function (event) {
  121. window.location.href = event.target.options.clickUrl;
  122. });
  123. } else {
  124. const polygon = L.geoJSON(feature, {
  125. style: function (feature) {
  126. // Par défaut c’est un bleu par défaut de leaflet mais
  127. // sinon on utilise les couleurs de Bootstrap
  128. var color = 'blue';
  129. switch (feature.properties.color) {
  130. case 'danger' : color = '#dc3545'; break
  131. case 'warning' : color = '#ffc107'; break
  132. case 'success' : color = '#198754'; break
  133. }
  134. if (feature.geometry.type === 'Polygon') {
  135. return {
  136. color: color,
  137. weight: 1,
  138. fillOpacity: 0.5,
  139. };
  140. } else {
  141. return {color: color};
  142. }
  143. }
  144. }).bindTooltip(feature.properties.name).addTo(taskLayer).on('click', function (event) {
  145. window.location.href = event.layer.feature.properties.url;
  146. });
  147. }
  148. });
  149. });
  150. taskLayer.addTo(layer);
  151. }
  152. layer.addTo(map);
  153. // Si la couche Overpass n’est pas vide, ajoute le sélecteur de couches
  154. // sur la carte
  155. if (this.overpassResultValue !== '') {
  156. L.control.layers({}, {
  157. 'Overpass': overpassLayer,
  158. 'Tâches': taskLayer,
  159. }).addTo(map);
  160. }
  161. // Zoome la carte pour que les données des tâches soient toutes
  162. // visibles
  163. map.fitBounds(taskLayer.getBounds());
  164. }
  165. openInOsm() {
  166. const url = "https://www.openstreetmap.org/#map="+this.mapInstance.getZoom()+"/"+this.mapInstance.getCenter().lat+"/"+this.mapInstance.getCenter().lng;
  167. window.open(url, '_blank');
  168. }
  169. openInPanoramax() {
  170. const url = "https://api.panoramax.xyz/#focus=map&map="+this.mapInstance.getZoom()+"/"+this.mapInstance.getCenter().lat+"/"+this.mapInstance.getCenter().lng;
  171. window.open(url, '_blank');
  172. }
  173. openInPifomap() {
  174. const self = this, url1 = "https://geo.api.gouv.fr/communes?lat="+this.mapInstance.getCenter().lat+"&lon="+this.mapInstance.getCenter().lng+"&fields=code,nom";
  175. fetch(url1).then(function (response) { return response.json(); }).then(function (json) {
  176. const hasInsee = (Array.isArray(json) && (json.length > 0) && json[0].hasOwnProperty('code'));
  177. if (hasInsee) {
  178. const url2 = "https://bano.openstreetmap.fr/pifometre/pifomap.html?insee="+json[0].code;
  179. window.open(url2, '_blank');
  180. } else {
  181. window.alert('Impossible de trouver le code INSEE de la commune…');
  182. }
  183. });
  184. }
  185. openInPifometre() {
  186. const self = this, url1 = "https://geo.api.gouv.fr/communes?lat="+this.mapInstance.getCenter().lat+"&lon="+this.mapInstance.getCenter().lng+"&fields=code,nom";
  187. fetch(url1).then(function (response) { return response.json(); }).then(function (json) {
  188. const hasInsee = (Array.isArray(json) && (json.length > 0) && json[0].hasOwnProperty('code'));
  189. if (hasInsee) {
  190. const url2 = "https://bano.openstreetmap.fr/pifometre/?insee="+json[0].code;
  191. window.open(url2, '_blank');
  192. } else {
  193. window.alert('Impossible de trouver le code INSEE de la commune…');
  194. }
  195. });
  196. }
  197. openInGeohack() {
  198. const url = "https://geohack.toolforge.org/geohack.php?params="+this.mapInstance.getCenter().lat+"_N_"+this.mapInstance.getCenter().lng+"_E";
  199. window.open(url, '_blank');
  200. }
  201. openInGeoportail() {
  202. const url = "https://www.geoportail.gouv.fr/carte?c="+this.mapInstance.getCenter().lng+","+this.mapInstance.getCenter().lat+"&z="+this.mapInstance.getZoom()+"&permalink=yes";
  203. window.open(url, '_blank');
  204. }
  205. openInMapillary() {
  206. const url = "https://www.mapillary.com/app/?lat="+this.mapInstance.getCenter().lat+"&lng="+this.mapInstance.getCenter().lng+"&z="+this.mapInstance.getZoom();
  207. window.open(url, '_blank');
  208. }
  209. openInGoogleMaps() {
  210. const url = "https://www.google.com/maps/@"+this.mapInstance.getCenter().lat+","+this.mapInstance.getCenter().lng+","+this.mapInstance.getZoom()+"z";
  211. window.open(url, '_blank');
  212. }
  213. openInBing() {
  214. const url = "https://www.bing.com/maps/?cp="+this.mapInstance.getCenter().lat+"%7E"+this.mapInstance.getCenter().lng+"&lvl="+this.mapInstance.getZoom();
  215. window.open(url, '_blank');
  216. }
  217. openInF4Map() {
  218. const url = "https://demo.f4map.com/#lat="+this.mapInstance.getCenter().lat+"&lon="+this.mapInstance.getCenter().lng+"&zoom="+this.mapInstance.getZoom();
  219. window.open(url, '_blank');
  220. }
  221. }