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.

488 lines
13 KiB

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