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.

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