Structures.vue 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <template>
  2. <LayoutContainer>
  3. <v-responsive :aspect-ratio="1" width="100%">
  4. <!-- /!\ We do not use the nuxt-leaflet library here,
  5. because in its current version it's way too slow to deal with hundreds of markers -->
  6. <div id="map" />
  7. </v-responsive>
  8. <div class="advice">
  9. {{ $t("click_on_land_to_go_there") }}
  10. </div>
  11. <v-row class="map-shortcuts">
  12. <v-col v-for="shortcut in shortcuts" :key="shortcut.src" cols="2">
  13. <div @click="setMapBounds(shortcut.bounds)">
  14. <nuxt-img
  15. :src="shortcut.src"
  16. :alt="shortcut.alt"
  17. />
  18. </div>
  19. </v-col>
  20. </v-row>
  21. </LayoutContainer>
  22. </template>
  23. <script>
  24. import 'leaflet/dist/leaflet.css'
  25. let L
  26. if (typeof window !== 'undefined') {
  27. // only require leaflet on client-side
  28. L = require('leaflet')
  29. }
  30. export default {
  31. props: {
  32. structures: {
  33. type: Array,
  34. required: false,
  35. default: () => []
  36. }
  37. },
  38. data () {
  39. return {
  40. map: null,
  41. defaultBounds: [[51.03, -5.78], [41.2, 9.70]],
  42. shortcuts: [
  43. { src: '/images/metropole.png', alt: 'Metropole', bounds: [[51.03, -5.78], [41.2, 9.70]] },
  44. { src: '/images/guadeloupe.png', alt: 'Guadeloupe', bounds: [[16.62, -62.03], [15.74, -60.97]] },
  45. { src: '/images/martinique.png', alt: 'Martinique', bounds: [[14.95, -61.43], [14.28, -60.60]] },
  46. { src: '/images/mayotte.png', alt: 'Mayotte', bounds: [[-12.51, 44.86], [-13.19, 45.45]] },
  47. { src: '/images/la_reunion.png', alt: 'La Réunion', bounds: [[-20.65, 54.92], [-21.65, 56.15]] },
  48. { src: '/images/guyane.png', alt: 'Guyane', bounds: [[6.24, -54.62], [1.87, -50.59]] }
  49. ],
  50. zoomRequired: false
  51. }
  52. },
  53. watch: {
  54. structures (oldval, newval) {
  55. if (oldval !== newval) {
  56. this.populateMarkers()
  57. }
  58. if (this.zoomRequired) {
  59. this._fitBoundsToMarkers()
  60. this.zoomRequired = false
  61. }
  62. }
  63. },
  64. mounted () {
  65. const options = { scrollWheelZoom: false, zoomSnap: 0.25 }
  66. const defaultCenter = [46.71, 1.94]
  67. const defaultZoom = 5.75
  68. const layerSource = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png'
  69. const attribution = '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
  70. this.map = L.map('map', options)
  71. this.map.setView(defaultCenter, defaultZoom)
  72. L.tileLayer(layerSource, { attribution }).addTo(this.map)
  73. this.populateMarkers()
  74. this.map.on('zoomend moveend', () => {
  75. this.$emit('boundsUpdated', this.map.getBounds())
  76. })
  77. },
  78. beforeDestroy () {
  79. if (this.map) {
  80. this.map.remove()
  81. }
  82. },
  83. methods: {
  84. populateMarkers () {
  85. // remove any existant marker layers
  86. this.map.eachLayer((layer) => {
  87. if (layer instanceof L.MarkerClusterGroup) {
  88. this.map.removeLayer(layer)
  89. }
  90. })
  91. // populate new layer
  92. const clusters = L.markerClusterGroup()
  93. for (const s of this.structures) {
  94. if (!s.latitude || !s.longitude) { continue }
  95. const marker = L.marker([s.latitude, s.longitude])
  96. marker.bindPopup(`<b>${s.name}</b><br/>${s.postalCode} ${s.addressCity}<br/><a href="${s.website}" target="_blank">${s.website}</a>`)
  97. clusters.addLayer(marker)
  98. }
  99. this.map.addLayer(clusters)
  100. },
  101. setMapBounds (bounds) {
  102. this.map.fitBounds(bounds) // << without this, the new bounds may not be properly set when the current view overlaps the new bounds.
  103. },
  104. resetBounds () {
  105. this.setMapBounds(this.defaultBounds)
  106. },
  107. zoomOnResults () {
  108. this.zoomRequired = true
  109. },
  110. _fitBoundsToMarkers () {
  111. const coords = this.structures.filter(
  112. (s) => { return s.latitude && s.longitude }
  113. ).map(
  114. (s) => { return [s.latitude, s.longitude] })
  115. if (!coords.length > 0) {
  116. return
  117. }
  118. this.setMapBounds(coords)
  119. }
  120. }
  121. }
  122. </script>
  123. <style scoped>
  124. #map {
  125. height: 100%;
  126. width: 100%;
  127. }
  128. .advice {
  129. margin: 1rem 0;
  130. color: #262626;
  131. font-weight: 750;
  132. text-align: center;
  133. font-size: 0.9rem;
  134. width: 100%;
  135. }
  136. .map-shortcuts .col {
  137. padding: 12px 6px;
  138. }
  139. .map-shortcuts img {
  140. border: solid 1px #000;
  141. width: 75px;
  142. height: 75px;
  143. }
  144. </style>