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.

452 lines
12 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 v-if="selectedOrigNorm.key && selectedDestNorm.key" :lat-lngs="[selectedOrigNorm['lat-lng'],selectedDestNorm['lat-lng']]" color="#007fff" />
  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. zoomSnap: 0.1,
  163. center: {
  164. lat: 63.8333,
  165. lng: -152
  166. },
  167. bounds: [
  168. [
  169. 71.0394374664382,
  170. -178.4706617597655
  171. ],
  172. [
  173. 50.44556513009691,
  174. -127.25240004101528
  175. ]
  176. ]
  177. },
  178. mapBoxAccessToken: 'pk.eyJ1IjoiZmx5bG9jYWwiLCJhIjoiY2t0OHUxZXB6MTVueTJ4cGVwOHRuc2s2NyJ9.YF9frLvISHfOuT7nqs3TNg',
  179. mapBoxAttribution: 'FlyLocal | Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
  180. osmUrl: 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
  181. osmAttribution:
  182. '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
  183. mapBoxUrl: 'https://api.mapbox.com/styles/v1/flylocal/ckt9x8aho0igb18mmm9ks2fsv/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiZmx5bG9jYWwiLCJhIjoiY2t0OHUxZXB6MTVueTJ4cGVwOHRuc2s2NyJ9.YF9frLvISHfOuT7nqs3TNg'
  184. }
  185. },
  186. computed: {
  187. selectedOrigNorm () {
  188. return {
  189. key: this.selectedOrig.iata,
  190. iata: this.selectedOrig.iata,
  191. 'lat-lng': [this.selectedOrig.lat, this.selectedOrig.long],
  192. municipality: this.selectedOrig.municipality
  193. }
  194. },
  195. selectedDestNorm () {
  196. return {
  197. key: this.selectedDest.iata,
  198. iata: this.selectedDest.iata,
  199. 'lat-lng': [this.selectedDest.lat, this.selectedDest.long],
  200. municipality: this.selectedDest.municipality
  201. }
  202. },
  203. mapBoxApiUrl () {
  204. return `https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=${this.mapBoxAccessToken}`
  205. },
  206. tileLayer () {
  207. return {
  208. // url: this.mapBoxApiUrl,
  209. maxZoom: 18,
  210. // id: 'flylocal/ckt9x8aho0igb18mmm9ks2fsv',
  211. tileSize: 512,
  212. zoomOffset: -1,
  213. accessToken: 'your.mapbox.access.token',
  214. attribution: this.mapBoxAttribution
  215. }
  216. }
  217. },
  218. updated () {
  219. if (this.$refs.flMap && this.$refs.flMap.mapObject) {
  220. /* selected none */
  221. if (!this.selectedOrigNorm.key && !this.selectedDestNorm.key && this.airportsOrig.length > 0) {
  222. const o = this.airportsOrig.map((m) => { return [m.lat, m.long] })
  223. // console.log('origins:')
  224. // console.log(o)
  225. if (o) {
  226. this.$refs.flMap.mapObject.fitBounds([...o], { padding: [50, 70] })
  227. }
  228. }
  229. /* selected org */
  230. if (this.selectedOrigNorm.key && !this.selectedDestNorm.key) {
  231. const so = [this.selectedOrig.lat, this.selectedOrig.long]
  232. const d = this.airportsDest.map((m) => { return [m.lat, m.long] })
  233. // console.log('so:')
  234. // console.log(so)
  235. // console.log('dests:')
  236. // console.log(d)
  237. if (so && d) {
  238. this.$refs.flMap.mapObject.fitBounds([so, ...d], { padding: [50, 70] })
  239. }
  240. }
  241. /* selected both */
  242. if (this.selectedOrigNorm.key && this.selectedDestNorm.key) {
  243. const so = [this.selectedOrig.lat, this.selectedOrig.long]
  244. const sd = [this.selectedDest.lat, this.selectedDest.long]
  245. // console.log('so:')
  246. // console.log(so)
  247. // console.log('sd:')
  248. // console.log(sd)
  249. if (so && sd) {
  250. this.$refs.flMap.mapObject.fitBounds([so, sd], { padding: [50, 70] })
  251. }
  252. }
  253. }
  254. // const o = this.airportsOrig.map((m) => { return [m.lat, m.long] })
  255. // const d = this.airportsDest.map((m) => { return [m.lat, m.long] })
  256. // const so = [this.selectedOrig.lat, this.selectedOrig.long]
  257. // const sd = [this.selectedDest.lat, this.selectedDest.long]
  258. // this.$refs.flMap.mapObject.fitBounds([...o, ...d, ...so, ...sd])
  259. // this.$refs.flMap.mapObject.fitBounds([[40, -73], [5, -55]])
  260. }
  261. }
  262. </script>
  263. <style lang="css" >
  264. :root {
  265. --map-shadow: 5px 3px 10px rgb(0 0 0 / 20%);
  266. }
  267. #map-wrap {
  268. height: 0;
  269. flex: 1 0 auto;
  270. }
  271. .map__icon-muni {
  272. white-space: nowrap;
  273. }
  274. .map__popup {
  275. font-size: 0.9rem;
  276. }
  277. .map__popup-iata {
  278. white-space: nowrap;
  279. font-size: 3em;
  280. line-height: 1;
  281. }
  282. .map__popup-muni {
  283. white-space: nowrap;
  284. margin-bottom: 0.5rem;
  285. }
  286. .map__popup-buttons {
  287. display: flex;
  288. flex-direction: row;
  289. gap: 0.5em;
  290. }
  291. .leaflet-popup {
  292. top: 0 !important;
  293. left: 0 !important;
  294. }
  295. .leaflet-popup-content-wrapper {
  296. border-radius: 0.2rem 1.5rem 1.5rem 1.5rem;
  297. box-shadow: var(--map-shadow);
  298. }
  299. .leaflet-popup-tip-container,
  300. .leaflet-popup-close-button {
  301. display: none;
  302. }
  303. .leaflet-popup-content {
  304. margin: 0.75rem;
  305. }
  306. /* TODO: change these? */
  307. .leaflet-top {
  308. top: auto;
  309. bottom: 0;
  310. }
  311. .leaflet-top .leaflet-control {
  312. margin-top: auto;
  313. margin-bottom: 10px;
  314. }
  315. .airport-icon {
  316. padding: 0.55rem;
  317. border-radius: 0.2rem 1.5rem 1.5rem 1.5rem;
  318. box-shadow: var(--map-shadow);
  319. text-align: center;
  320. width: auto !important;
  321. height: auto !important;
  322. margin: 0 !important;
  323. border: 0.2rem solid transparent;
  324. transition: all 0.2s;
  325. }
  326. .airport-icon.orig-icon {
  327. background-color: white;
  328. color: black;
  329. }
  330. .airport-icon.orig-icon:hover {
  331. border-color: #007fff;
  332. }
  333. .airport-icon.dest-icon {
  334. background-color: #ddd;
  335. color: black;
  336. }
  337. .airport-icon.orig-icon.selected {
  338. border-color: #007fff;
  339. }
  340. .airport-icon.dest-icon:hover {
  341. background-color: #007fff;
  342. color: white;
  343. }
  344. .airport-icon.dest-icon.selected {
  345. background-color: #007fff;
  346. color: white;
  347. }
  348. #map-wrap .marker-cluster-small {
  349. background-color: rgba(47, 151, 255, 0.15);
  350. }
  351. #map-wrap .marker-cluster-small div {
  352. background-color: rgba(0, 127, 255, 0.15);
  353. }
  354. #map-wrap .marker-cluster-medium {
  355. background-color: rgba(47, 151, 255, 0.5);
  356. }
  357. #map-wrap .marker-cluster-medium div {
  358. background-color: rgba(0, 127, 255, 0.5);
  359. }
  360. #map-wrap .marker-cluster-large {
  361. background-color: rgba(47, 151, 255, 0.9);
  362. }
  363. #map-wrap .marker-cluster-large div {
  364. background-color: rgba(0, 127, 255, 0.9);
  365. }
  366. /* .marker-cluster {
  367. background-clip: padding-box;
  368. border-radius: 20px;
  369. }
  370. .marker-cluster div {
  371. width: 30px;
  372. height: 30px;
  373. margin-left: 5px;
  374. margin-top: 5px;
  375. text-align: center;
  376. border-radius: 15px;
  377. font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
  378. }
  379. .marker-cluster span {
  380. line-height: 30px;
  381. } */
  382. .btn {
  383. border-radius: 999px;
  384. padding: 0.4em 0.8em;
  385. font-size: 1rem;
  386. white-space: nowrap;
  387. transition: 0.2s all;
  388. }
  389. .btn.btn--primary {
  390. background: #007fff;
  391. color: #fff;
  392. }
  393. .btn.btn--primary:hover {
  394. background: #72b9ff;
  395. color: #fff;
  396. }
  397. .btn.btn--cancel {
  398. background: red;
  399. color: #fff;
  400. }
  401. .btn.btn--cancel:hover {
  402. background: rgb(255, 83, 83);
  403. color: #fff;
  404. }
  405. .btn.btn--secondary {
  406. background: #ccc;
  407. color: #000;
  408. }
  409. .btn.btn--secondary:hover {
  410. background: rgb(151, 151, 151);
  411. color: #000;
  412. }
  413. </style>