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.

444 lines
11 KiB

  1. <template>
  2. <div id="map-wrap">
  3. <!-- <pre style="z-index: 500; position: fixed; right: 0;">
  4. airportsOrig: {{ airportsOrig.length }}
  5. airportsDest: {{ airportsDest.length }}
  6. selectedOrig: {{ selectedOrig.key }}
  7. selectedDest: {{ selectedDest.key }}
  8. </pre> -->
  9. <client-only>
  10. <l-map ref="flMap" v-bind="map">
  11. <v-marker-cluster v-if="!selectedOrigNorm.key">
  12. <l-marker
  13. v-for="airport in airportsOrig"
  14. :key="airport.iata"
  15. :lat-lng="[airport.lat, airport.long]"
  16. >
  17. <l-icon
  18. :icon-anchor="[16, 37]"
  19. class-name="airport-icon orig-icon unselected"
  20. >
  21. <div class="map__icon-muni">
  22. {{ airport.municipality }}
  23. </div>
  24. </l-icon>
  25. <l-popup class="map__popup">
  26. <div class="map__popup-iata">
  27. {{ airport.iata }}
  28. </div>
  29. <div class="map__popup-muni">
  30. {{ airport.municipality }}
  31. </div>
  32. <div class="map__popup-buttons">
  33. <button class="btn btn--primary btn--map" @click="$emit('make-origin', airport)">
  34. start here
  35. </button>
  36. </div>
  37. </l-popup>
  38. </l-marker>
  39. </v-marker-cluster>
  40. <v-marker-cluster v-if="selectedOrigNorm.key && !selectedDestNorm.key">
  41. <l-marker
  42. v-for="airport in airportsDest"
  43. :key="airport.iata"
  44. :lat-lng="[airport.lat, airport.long]"
  45. >
  46. <l-icon
  47. :icon-anchor="[16, 37]"
  48. class-name="airport-icon dest-icon unselected"
  49. >
  50. <div class="map__icon-muni">
  51. {{ airport.municipality }}
  52. </div>
  53. </l-icon>
  54. <l-popup class="map__popup">
  55. <div class="map__popup-iata">
  56. {{ airport.iata }}
  57. </div>
  58. <div class="map__popup-muni">
  59. {{ airport.municipality }}
  60. </div>
  61. <div class="map__popup-buttons">
  62. <button
  63. class="btn btn--primary btn--map"
  64. @click="$emit('make-destination', airport)"
  65. >
  66. land here
  67. </button>
  68. <button class="btn btn--secondary btn--map" @click="$emit('make-origin', airport)">
  69. start here
  70. </button>
  71. </div>
  72. </l-popup>
  73. </l-marker>
  74. </v-marker-cluster>
  75. <l-marker v-if="selectedOrigNorm.key" v-bind="selectedOrigNorm">
  76. <l-icon
  77. :icon-anchor="[16, 37]"
  78. class-name="airport-icon orig-icon selected"
  79. >
  80. <div class="map__icon-muni">
  81. {{ selectedOrig.municipality }}
  82. </div>
  83. </l-icon>
  84. <l-popup class="map__popup">
  85. <div class="map__popup-iata">
  86. {{ selectedOrig.iata }}
  87. </div>
  88. <div class="map__popup-muni">
  89. {{ selectedOrig.municipality }}
  90. </div>
  91. <div class="map__popup-buttons">
  92. <button class="btn btn--cancel btn--map" @click="$emit('clear-origin')">
  93. remove
  94. </button>
  95. </div>
  96. </l-popup>
  97. </l-marker>
  98. <l-marker v-if="selectedDestNorm.key" v-bind="selectedDestNorm">
  99. <l-icon
  100. :icon-anchor="[16, 37]"
  101. class-name="airport-icon dest-icon selected"
  102. >
  103. <div class="map__icon-muni">
  104. {{ selectedDest.municipality }}
  105. </div>
  106. </l-icon>
  107. <l-popup class="map__popup">
  108. <div class="map__popup-iata">
  109. {{ selectedDest.iata }}
  110. </div>
  111. <div class="map__popup-muni">
  112. {{ selectedDest.municipality }}
  113. </div>
  114. <div class="map__popup-buttons">
  115. <button class="btn btn--cancel btn--map" @click="$emit('clear-destination')">
  116. remove
  117. </button>
  118. </div>
  119. </l-popup>
  120. </l-marker>
  121. <!-- <l-polyline :lat-lngs="polyline.latlngs" :color="polyline.color" /> -->
  122. <!-- <l-tile-layer v-bind="tileLayer" :url="mapBoxUrl" /> -->
  123. <l-tile-layer :url="osmUrl" :attribution="osmAttribution" />
  124. </l-map>
  125. </client-only>
  126. </div>
  127. </template>
  128. <script>
  129. export default {
  130. props: {
  131. airportsOrig: {
  132. type: [Array, Object],
  133. default () {
  134. return []
  135. }
  136. },
  137. airportsDest: {
  138. type: [Array, Object],
  139. default () {
  140. return []
  141. }
  142. },
  143. selectedOrig: {
  144. type: [Object],
  145. default () {
  146. return {}
  147. }
  148. },
  149. selectedDest: {
  150. type: [Object],
  151. default () {
  152. return {}
  153. }
  154. }
  155. },
  156. data () {
  157. return {
  158. map: {
  159. zoom: 3,
  160. minZoom: 3,
  161. maxZoom: 13,
  162. center: {
  163. lat: 63.8333,
  164. lng: -152
  165. },
  166. bounds: [
  167. [
  168. 71.0394374664382,
  169. -178.4706617597655
  170. ],
  171. [
  172. 50.44556513009691,
  173. -127.25240004101528
  174. ]
  175. ]
  176. },
  177. mapBoxAccessToken: 'pk.eyJ1IjoiZmx5bG9jYWwiLCJhIjoiY2t0OHUxZXB6MTVueTJ4cGVwOHRuc2s2NyJ9.YF9frLvISHfOuT7nqs3TNg',
  178. mapBoxAttribution: 'FlyLocal | Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
  179. osmUrl: 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
  180. osmAttribution:
  181. '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
  182. mapBoxUrl: 'https://api.mapbox.com/styles/v1/flylocal/ckt9x8aho0igb18mmm9ks2fsv/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiZmx5bG9jYWwiLCJhIjoiY2t0OHUxZXB6MTVueTJ4cGVwOHRuc2s2NyJ9.YF9frLvISHfOuT7nqs3TNg'
  183. }
  184. },
  185. computed: {
  186. selectedOrigNorm () {
  187. return {
  188. key: this.selectedOrig.iata,
  189. iata: this.selectedOrig.iata,
  190. 'lat-lng': [this.selectedOrig.lat, this.selectedOrig.long],
  191. municipality: this.selectedOrig.municipality
  192. }
  193. },
  194. selectedDestNorm () {
  195. return {
  196. key: this.selectedDest.iata,
  197. iata: this.selectedDest.iata,
  198. 'lat-lng': [this.selectedDest.lat, this.selectedDest.long],
  199. municipality: this.selectedDest.municipality
  200. }
  201. },
  202. mapBoxApiUrl () {
  203. return `https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=${this.mapBoxAccessToken}`
  204. },
  205. tileLayer () {
  206. return {
  207. // url: this.mapBoxApiUrl,
  208. maxZoom: 18,
  209. // id: 'flylocal/ckt9x8aho0igb18mmm9ks2fsv',
  210. tileSize: 512,
  211. zoomOffset: -1,
  212. accessToken: 'your.mapbox.access.token',
  213. attribution: this.mapBoxAttribution
  214. }
  215. }
  216. },
  217. updated () {
  218. /* selected none */
  219. if (!this.selectedOrigNorm.key && !this.selectedDestNorm.key) {
  220. const o = this.airportsOrig.map((m) => { return [m.lat, m.long] })
  221. this.$refs.flMap.mapObject.fitBounds([...o])
  222. }
  223. /* selected org */
  224. if (this.selectedOrigNorm.key && !this.selectedDestNorm.key) {
  225. const so = [this.selectedOrig.lat, this.selectedOrig.long]
  226. const d = this.airportsDest.map((m) => { return [m.lat, m.long] })
  227. this.$refs.flMap.mapObject.fitBounds([so, ...d])
  228. }
  229. /* selected both */
  230. if (this.selectedOrigNorm.key && this.selectedDestNorm.key) {
  231. const so = [this.selectedOrig.lat, this.selectedOrig.long]
  232. const sd = [this.selectedDest.lat, this.selectedDest.long]
  233. this.$refs.flMap.mapObject.fitBounds([so, sd])
  234. }
  235. // const o = this.airportsOrig.map((m) => { return [m.lat, m.long] })
  236. // const d = this.airportsDest.map((m) => { return [m.lat, m.long] })
  237. // const so = [this.selectedOrig.lat, this.selectedOrig.long]
  238. // const sd = [this.selectedDest.lat, this.selectedDest.long]
  239. // this.$refs.flMap.mapObject.fitBounds([...o, ...d, ...so, ...sd])
  240. // this.$refs.flMap.mapObject.fitBounds([[40, -73], [5, -55]])
  241. },
  242. methods: {
  243. onReady () {
  244. // const o = this.airportsOrig.map((m) => { return [m.lat, m.long] })
  245. // const d = this.airportsDest.map((m) => { return [m.lat, m.long] })
  246. // const so = [this.selectedOrig.lat, this.selectedOrig.long]
  247. // const sd = [this.selectedDest.lat, this.selectedDest.long]
  248. // this.$refs.flMap.mapObject.fitBounds(...o, ...d, ...so, ...sd)
  249. this.$refs.flMap.mapObject.fitBounds([[40, -73], [5, -55]])
  250. }
  251. }
  252. }
  253. </script>
  254. <style lang="css" >
  255. :root {
  256. --map-shadow: 5px 3px 10px rgb(0 0 0 / 20%);
  257. }
  258. #map-wrap {
  259. height: 0;
  260. flex: 1 0 auto;
  261. }
  262. .map__icon-muni {
  263. white-space: nowrap;
  264. }
  265. .map__popup {
  266. font-size: 0.9rem;
  267. }
  268. .map__popup-iata {
  269. white-space: nowrap;
  270. font-size: 3em;
  271. line-height: 1;
  272. }
  273. .map__popup-muni {
  274. white-space: nowrap;
  275. margin-bottom: 0.5rem;
  276. }
  277. .map__popup-buttons {
  278. display: flex;
  279. flex-direction: row;
  280. gap: 0.5em;
  281. }
  282. .leaflet-popup {
  283. top: 0 !important;
  284. left: 0 !important;
  285. }
  286. .leaflet-popup-content-wrapper {
  287. border-radius: 0.2rem 1.5rem 1.5rem 1.5rem;
  288. box-shadow: var(--map-shadow);
  289. }
  290. .leaflet-popup-tip-container,
  291. .leaflet-popup-close-button {
  292. display: none;
  293. }
  294. .leaflet-popup-content {
  295. margin: 0.75rem;
  296. }
  297. /* TODO: change these? */
  298. .leaflet-top {
  299. top: auto;
  300. bottom: 0;
  301. }
  302. .leaflet-top .leaflet-control {
  303. margin-top: auto;
  304. margin-bottom: 10px;
  305. }
  306. .airport-icon {
  307. padding: 0.55rem;
  308. border-radius: 0.2rem 1.5rem 1.5rem 1.5rem;
  309. box-shadow: var(--map-shadow);
  310. text-align: center;
  311. width: auto !important;
  312. height: auto !important;
  313. margin: 0 !important;
  314. border: 0.2rem solid transparent;
  315. transition: all 0.2s;
  316. }
  317. .airport-icon.orig-icon {
  318. background-color: white;
  319. color: black;
  320. }
  321. .airport-icon.orig-icon:hover {
  322. border-color: #007fff;
  323. }
  324. .airport-icon.dest-icon {
  325. background-color: #ddd;
  326. color: black;
  327. }
  328. .airport-icon.orig-icon.selected {
  329. border-color: #007fff;
  330. }
  331. .airport-icon.dest-icon:hover {
  332. background-color: #007fff;
  333. color: white;
  334. }
  335. .airport-icon.dest-icon.selected {
  336. background-color: #007fff;
  337. color: white;
  338. }
  339. #map-wrap .marker-cluster-small {
  340. background-color: rgba(47, 151, 255, 0.15);
  341. }
  342. #map-wrap .marker-cluster-small div {
  343. background-color: rgba(0, 127, 255, 0.15);
  344. }
  345. #map-wrap .marker-cluster-medium {
  346. background-color: rgba(47, 151, 255, 0.5);
  347. }
  348. #map-wrap .marker-cluster-medium div {
  349. background-color: rgba(0, 127, 255, 0.5);
  350. }
  351. #map-wrap .marker-cluster-large {
  352. background-color: rgba(47, 151, 255, 0.9);
  353. }
  354. #map-wrap .marker-cluster-large div {
  355. background-color: rgba(0, 127, 255, 0.9);
  356. }
  357. /* .marker-cluster {
  358. background-clip: padding-box;
  359. border-radius: 20px;
  360. }
  361. .marker-cluster div {
  362. width: 30px;
  363. height: 30px;
  364. margin-left: 5px;
  365. margin-top: 5px;
  366. text-align: center;
  367. border-radius: 15px;
  368. font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
  369. }
  370. .marker-cluster span {
  371. line-height: 30px;
  372. } */
  373. .btn {
  374. border-radius: 999px;
  375. padding: 0.4em 0.8em;
  376. font-size: 1rem;
  377. white-space: nowrap;
  378. transition: 0.2s all;
  379. }
  380. .btn.btn--primary {
  381. background: #007fff;
  382. color: #fff;
  383. }
  384. .btn.btn--primary:hover {
  385. background: #72b9ff;
  386. color: #fff;
  387. }
  388. .btn.btn--cancel {
  389. background: red;
  390. color: #fff;
  391. }
  392. .btn.btn--cancel:hover {
  393. background: rgb(255, 83, 83);
  394. color: #fff;
  395. }
  396. .btn.btn--secondary {
  397. background: #ccc;
  398. color: #000;
  399. }
  400. .btn.btn--secondary:hover {
  401. background: rgb(151, 151, 151);
  402. color: #000;
  403. }
  404. </style>