sotlas-frontend/src/components/MiniMap.vue

257 wiersze
8.4 KiB
Vue

<template>
<MglMap v-if="(mapCenter || bounds) && mapStyle" :mapStyle="mapStyle" :bounds="bounds" :fitBoundsOptions="fitBoundsOptions" :center="mapCenter" :zoom="12.5" :dragPan="dragPanEnabled" :dragRotate="false" :attributionControl="false" @load="onMapLoaded" @click="onMapClicked" @contextmenu="onMapRightClicked" @idle="onMapIdle">
<MglGeolocateControl v-if="!$mq.mobile || isEnlarged" :positionOptions="{ enableHighAccuracy: true }" :fitBoundsOptions="{ maxZoom: 12.5 }" :trackUserLocation="true" position="top-right" />
<MglNavigationControl v-if="!$mq.mobile" position="top-right" :showCompass="false" />
<MglScaleControl v-if="!$mq.mobile || isEnlarged" position="bottom-left" />
<div v-if="canEnlarge" class="mapboxgl-ctrl-top-left">
<MapEnlargeControl :isEnlarged="isEnlarged" @enlarge="$emit('enlarge')" />
</div>
<MglAttributionControl :compact="true" position="bottom-right" />
<MapRoute v-for="route in routes" :key="route.id" :route="route" />
<MapPhoto v-for="photo in mapPhotos" :key="photo.filename" :summit="summit" :photo="photo" @photoClicked="photo => $emit('photoClicked', photo)" />
<MapInfoPopup v-if="infoCoordinates !== null" :coordinates="infoCoordinates" @close="infoCoordinates = null" />
<div v-if="zoomWarningVisible" class="zoom-warning">Zoom in to see all activations</div>
</MglMap>
</template>
<script>
import { MglMap, MglGeolocateControl, MglNavigationControl, MglScaleControl, MglAttributionControl } from 'vue-mapbox'
import MapRoute from './MapRoute.vue'
import MapPhoto from './MapPhoto.vue'
import MapInfoPopup from './MapInfoPopup.vue'
import MapEnlargeControl from './MapEnlargeControl.vue'
import mapstyle from '../mixins/mapstyle.js'
import utils from '../mixins/utils.js'
import longtouch from '../mixins/longtouch.js'
export default {
name: 'MiniMap',
props: {
summit: Object,
routes: {
type: Array,
default: () => { return [] }
},
filter: Array,
bounds: Array,
isEnlarged: Boolean,
zoomWarning: Boolean,
showInactiveSummits: Boolean,
canEnlarge: Boolean
},
components: {
MglMap, MglGeolocateControl, MglNavigationControl, MapEnlargeControl, MglScaleControl, MglAttributionControl, MapRoute, MapPhoto, MapInfoPopup
},
mixins: [utils, mapstyle, longtouch],
watch: {
summit: {
immediate: true,
handler () {
this.highlightCurrentSummit()
}
},
showInactiveSummits () {
this.showHideInactiveSummits()
},
dragPanEnabled () {
if (this.dragPanEnabled) {
this.map.dragPan.enable()
} else {
this.map.dragPan.disable()
}
}
},
computed: {
mapCenter () {
if (this.summit && this.summit.coordinates) {
return [this.summit.coordinates.longitude, this.summit.coordinates.latitude]
} else {
return undefined
}
},
mapPhotos () {
if (!this.summit || !this.summit.photos) {
return []
}
return this.summit.photos.filter(photo => {
if (photo.coordinates === undefined) {
return false
}
if (photo.positioningError && photo.positioningError > 100) {
return false
}
return true
})
},
dragPanEnabled () {
return (!this.$mq.mobile || this.isEnlarged)
},
fitBoundsOptions () {
return { padding: { left: 60, top: 40, right: 60, bottom: 40 }, maxZoom: 12 }
}
},
methods: {
showHideInactiveSummits () {
if (!this.map) {
return
}
if (this.showInactiveSummits) {
this.map.setLayoutProperty('summits_inactive_names', 'visibility', 'visible')
this.map.setLayoutProperty('summits_inactive_circles', 'visibility', 'visible')
} else {
this.map.setLayoutProperty('summits_inactive_names', 'visibility', 'none')
this.map.setLayoutProperty('summits_inactive_circles', 'visibility', 'none')
}
},
highlightCurrentSummit () {
if (!this.map || !this.summit || !this.summit.code) {
return
}
this.map.setFilter('summits_selected', ['==', 'code', this.summit.code])
},
onMapLoaded (event) {
this.map = event.map
this.map.touchZoomRotate.disableRotation()
this.$nextTick(() => {
this.map.resize()
});
['summits_circles', 'summits_inactive_circles'].forEach(layer => {
this.map.on('mouseenter', layer, () => {
this.map.getCanvas().style.cursor = 'pointer'
})
this.map.on('mouseleave', layer, () => {
this.map.getCanvas().style.cursor = ''
})
})
this.map.setFilter('summits_circles', this.filter)
this.map.setFilter('summits_names', this.filter)
this.map.setFilter('summits_activations', this.filter)
this.map.setFilter('summits_inactive_circles', this.filter)
this.map.setFilter('summits_inactive_names', this.filter)
this.map.setLayoutProperty('contour', 'visibility', 'visible')
this.map.setLayoutProperty('contour_index', 'visibility', 'visible')
this.map.setLayoutProperty('contour_label', 'visibility', 'visible')
this.map.setLayoutProperty('hillshading', 'visibility', 'visible')
this.updateDifficultyLayer()
this.installLongTouchHandler(this.map, (e) => {
this.infoCoordinates = {
latitude: e.lngLat.lat,
longitude: e.lngLat.lng
}
})
this.showHideInactiveSummits()
this.highlightCurrentSummit()
},
onMapClicked (event) {
if (event.mapboxEvent.originalEvent.hitMarker) {
return
}
// Search for summit circles with some padding/fuzz to make it easier to hit on mobile devices
let point = event.mapboxEvent.point
let bbox = [[point.x - 10, point.y - 10], [point.x + 10, point.y + 10]]
let features = this.map.queryRenderedFeatures(bbox, { layers: ['summits_circles', 'summits_inactive_circles'] })
if (features.length > 0) {
// Find the summit closest to where the user tapped
let minDistance = null
let chosenFeature = null
features.forEach(feature => {
let projected = this.map.project(feature.geometry.coordinates)
let distance = Math.pow(projected.x - point.x, 2) + Math.pow(projected.y - point.y, 2)
if (minDistance === null || distance < minDistance) {
minDistance = distance
chosenFeature = feature
}
})
if (chosenFeature && chosenFeature.properties.code) {
if (this.summit && chosenFeature.properties.code === this.summit.code) {
this.$router.push('/map/summits/' + chosenFeature.properties.code)
} else {
this.$router.push('/summits/' + chosenFeature.properties.code)
}
}
}
},
onMapRightClicked (event) {
this.infoCoordinates = {
latitude: event.mapboxEvent.lngLat.lat,
longitude: event.mapboxEvent.lngLat.lng
}
},
onMapIdle () {
if (this.map) {
this.zoomWarningVisible = (this.map.getZoom() < 3) && this.zoomWarning
}
},
updateDifficultyLayer () {
if (!this.map) {
return
}
// Glean main map difficulty visibility setting
let mapOptions
try {
mapOptions = JSON.parse(localStorage.getItem('mapOptions'))
if (mapOptions && mapOptions.difficulty === false) {
this.map.setLayoutProperty('road_path_pedestrian_sac', 'visibility', 'none')
this.map.setLayoutProperty('road_path_pedestrian_sac_label', 'visibility', 'none')
} else {
this.map.setLayoutProperty('road_path_pedestrian_sac', 'visibility', 'visible')
this.map.setLayoutProperty('road_path_pedestrian_sac_label', 'visibility', 'visible')
}
} catch (e) {}
},
resize () {
this.$nextTick(() => {
if (this.map) {
this.map.resize()
}
})
},
easeTo (coordinates, zoom) {
if (this.map) {
this.map.easeTo({
center: coordinates,
zoom
})
}
}
},
data () {
return {
infoCoordinates: null,
zoomWarningVisible: false
}
}
}
</script>
<style scoped>
>>> .mapboxgl-canvas-container.mapboxgl-interactive {
cursor: auto;
}
.map >>> .mapboxgl-popup {
max-width: 400px !important;
}
.zoom-warning {
position: absolute;
left: 50%;
top: 1.5rem;
transform: translate(-50%, 0);
background-color: #feffd2;
padding: 0.2em 0.5em;
border-radius: 0.5em;
text-align: center;
opacity: 0.9;
}
</style>