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.

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