import { mapGetters } from 'vuex'
import { getVehicleIconHTML } from '@/services/vehicle-service.js'
import { getBearingFromTwoLatAndLng } from '@/utils/map.js'
import { queueOperationOnce } from '@/utils/promise.js'
import mapMixin from '@/mixins/map.js'
const L = window.L //Attention, Leaflet should not be a global variable
/***
* Used by Diagnostic module
*/
export default {
inject: {
replayController: {
default: () => ({}),
},
},
data() {
return {
replaySpeed: 1000,
updateZoomEveryPositions: 3,
isReplayPlaying: false,
}
},
computed: {
...mapGetters({
positions: 'diagnostics/positions',
vehicleClassName: 'diagnostics/vehicleClassName',
replayPositionEndIndex: 'diagnostics/replayPositionEndIndex',
chartSelectedItemTrigger: 'diagnostics/chartSelectedItemTrigger',
}),
/**
* Needs to be computed due to positions change
* @returns
*/
replayOptions() {
console.log('computed::replayOptions')
let self = this
return {
min: 0,
max: this.$store.state.diagnostics.positions.length - 1,
animationOptions: {
index: this.positions
.map((pos, index) => {
return {
replayIndex: index,
}
})
.filter((pos) => {
return (
pos.replayIndex >=
self.$store.state.diagnostics.replayPositionIndex &&
(self.$store.state.diagnostics.replayPositionEndIndex === -1 ||
pos.replayIndex <=
self.$store.state.diagnostics.replayPositionEndIndex)
)
})
.map((pos) => {
return {
value: pos.replayIndex,
duration: this.replaySpeed,
}
}),
},
get currentIndex() {
return self.$store.state.diagnostics.replayPositionIndex
},
set currentIndex(value) {
self.$store.state.diagnostics.replayPositionIndex = value
self.$store.state.diagnostics.replayPosition =
self.positions[self.$store.state.diagnostics.replayPositionIndex]
},
onToggle(val) {
self.isReplayPlaying = val
},
onDestroy() {
console.log('replay-mixin::options::onDestroy')
updateMapVehicleMarker(self.$map.getLeafletWrapperVM(), null, {
visible: false,
})
},
/**
* @todo Drawing operations could be split using requestAnimationFrame
* @param {*} currentIndex
* @returns
*/
onAnimate(currentIndex) {
//Hide position marker popup and remove (it available)
let positionMarkerLayerGroup =
self.$map.getLeafletWrapperVM().layerGroups?.positions
if (positionMarkerLayerGroup) {
let positionMarker = positionMarkerLayerGroup.getLayers()[0] || null
if (positionMarker) {
positionMarker.closePopup && positionMarker.closePopup()
}
positionMarkerLayerGroup.remove()
}
//This will open the position details on the left panel
self.$store.state.diagnostics.chartSelectedItemTrigger = 'replay'
self.$store.state.diagnostics.chartSelectedItem =
self.$store.state.diagnostics.replayPosition
if (!self.positions[currentIndex]) {
console.log(`onAnimate::Can't find position`, {
currentIndex: currentIndex,
})
return
}
let currentPosition = self.positions[currentIndex]
//avoid drawing the lat/lng
if (
self.lastPosition &&
self.lastPosition.lat == currentPosition.lat &&
self.lastPosition.lng == currentPosition.lng
) {
return
}
updateMapVehicleMarker(
self.$map.getLeafletWrapperVM(),
currentPosition,
{
lastPosition: self.lastPosition,
slideDuration: self.replaySpeed,
vehicleName: self.vehicleName,
vehicleClassName: self.vehicleClassName,
}
)
self.lastPosition = currentPosition
self.timesSinceZoom =
self.timesSinceZoom === undefined
? self.updateZoomEveryPositions
: self.timesSinceZoom
let zoomLevel = 16
switch (self.replaySpeed) {
case 1000: {
zoomLevel = 15
self.updateZoomEveryPositions = 3
break
}
case 500: {
zoomLevel = 14
self.updateZoomEveryPositions = 2
break
}
case 200: {
zoomLevel = 13
self.updateZoomEveryPositions = 1
break
}
case 125: {
zoomLevel = 12
self.updateZoomEveryPositions = 1
break
}
default: {
zoomLevel = 10
self.updateZoomEveryPositions = 0
break
}
}
if (self.timesSinceZoom >= self.updateZoomEveryPositions) {
self.timesSinceZoom = 0
self.$map
.getLeafletWrapperVM()
.map.flyTo([currentPosition.lat, currentPosition.lng], 16)
} else {
self.timesSinceZoom++
}
},
}
},
},
watch: {
replaySpeed() {
this.replayReset()
},
replayPositionEndIndex() {
console.log('watch::replayPositionEndIndex')
this.replayReset()
},
chartSelectedItemTrigger() {
if (this.chartSelectedItemTrigger === 'chart') {
console.log('watch::chartSelectedItemTrigger')
this.replayReset()
}
},
},
methods: {
/**
* If playing: Restart playback (index might have changed)
* If stopped: Stop playback / Remove marker
*/
replayReset() {
console.log('method::replayReset')
if (this.replayController.currentAnimationPlaying) {
this.replayController.destroyAnimation()
this.replayPlayToggle()
} else {
this.replayController.destroyAnimation()
}
},
replayPlayToggle() {
this.replayController.playToggle(this.replayOptions)
},
replayPrev() {
this.replayController.prev(this.replayOptions)
},
replayNext() {
this.replayController.next(this.replayOptions)
},
terminateReplay() {
this.replayController.playToggle({
...this.replayOptions,
value: false,
})
this.replayController.destroyAnimation()
updateMapVehicleMarker(this.$map.getLeafletWrapperVM(), null, {
visible: false,
})
},
},
destroyed() {
this.terminateReplay()
},
}
function updateMapVehicleMarker(leafletVM, position, options = {}) {
position = { ...position }
let lastPosition = options.lastPosition
let degValue = lastPosition
? getBearingFromTwoLatAndLng(
[lastPosition.lat, lastPosition.lng],
[position.lat, position.lng]
)
: 90
let vehicleClassName = options.vehicleClassName
let vehicleName = options.vehicleName || 'Vehicle'
let hasContactOn = position.hasContactOn
const icon = L.divIcon({
className: 'realtime__marker',
html: `<div class="marker__content">
<div>
<img class="marker_icon"
data-computed-degrees="${degValue}"
style="transform:translate(-50%, -50%) rotate(${
degValue // > 180 ? 270 : 90
}deg)"
src="./lib/realtimeMap/assets/picto_pins/Pin.svg">
${getVehicleIconHTML(vehicleClassName, {
flipHorizontally: degValue > 180,
})}
<img class="marker__contact_icon"
style="background-color:var(--color-contact-${
hasContactOn ? 'on' : 'off'
})"
src="./lib/realtimeMap/assets/picto_status/Contact.svg" />
</div>
<p class="marker__label">
${vehicleName}
</p >
</div>
`,
})
position.positionId = position.id
position.id = 'vehicle'
let visible = options.visible !== undefined ? options.visible : true
let popupOptions = {
style: 'min-width:267px;',
}
let popupComponentImport = () =>
/* webpackChunkName "shared_components" */
import('@/components/shared/SimplicitiMap/PositionMarkerPopup.vue')
let smoothUpdateOptions = visible
? {
update(marker, newPos) {
marker.setIcon(icon)
marker.closePopup && marker.closePopup()
//After one second: Update popup component
queueOperationOnce(
`diagnostics_replay_marker_popup_update`,
() => {
marker.properties = newPos
mapMixin.methods.bindLeafletPopupComponent(
marker,
popupComponentImport,
{
...popupOptions,
parent: leafletVM,
props: {
map: leafletVM.map,
data: newPos,
},
}
)
},
{
clearPreviousTimeout: true,
timeout: 700, //This should save resources by avoiding instantiating multiple components in a very short amount of time (i.g speed x16)
}
)
marker.slideTo([newPos.lat, newPos.lng], {
duration: options.slideDuration || 1000, //Should be the same as replaySpeed
keepAtCenter: false,
})
},
}
: {}
let data = position ? [position] : []
if (!visible) {
data = []
}
leafletVM.drawGeometries({
visible,
data,
layerOptions: {
zIndex: 999,
},
layer: 'diagnosticsReplayMarker',
popupOptions,
popup: popupComponentImport,
preserveExistingLayers: true,
...smoothUpdateOptions,
generate(pos) {
return L.marker([pos.lat, pos.lng], {
icon,
zIndexOffset: 3,
})
},
})
}
Source