<template>
<div>
<div class="title">{{ $t('nearby_vehicles.feature_title') }}</div>
<NearbyVehiclesForm v-model="radius" class="mt-2"> </NearbyVehiclesForm>
</div>
</template>
<script setup>
import NearbyVehiclesForm from './NearbyVehiclesForm.vue'
import { onMounted, onBeforeUnmount, inject } from 'vue'
import { fetchNearbyVehicles } from '@/services/nearby-vehicles-service.js'
const props = defineProps({
lat: {
type: Number,
default: 0,
},
lng: {
type: Number,
default: 0,
},
formattedAddress: {
type: String,
default: '',
},
})
const radius = ref(0)
const setMapToolboxContentMaxWidth = inject(
'setMapToolboxContentMaxWidth',
null
)
watchEffect(() => {
if (setMapToolboxContentMaxWidth) {
if (providerState.shouldRender && providerState.radius > 0) {
setMapToolboxContentMaxWidth('initial')
} else {
setMapToolboxContentMaxWidth('465px')
}
}
})
watchEffect(() => {
providerState.lat = props.lat
providerState.lng = props.lng
providerState.radius = radius.value
providerState.formattedAddress = props.formattedAddress
})
onMounted(() => {
resetProviderState()
})
onBeforeUnmount(() => {
resetProviderState()
})
</script>
<script>
import { ref, reactive, watchEffect, computed } from 'vue'
import L from 'leaflet'
import { createMarkerFromRealtimeItem } from '@/mixins/map.js'
import { drawLocateAddressMarker } from '@/components/shared/geocoding/geocoding-mixin.js'
let mapVmRef = ref(null)
/**
* Module context pattern
*/
const providerState = reactive(newProviderState())
const filteredMapVehiclesList = computed(() => {
return providerState.vehiclesList.filter(
(item) => !providerState.hiddenVehicleMarkers.includes(item.id)
)
})
const vehicleMarkersLayerName = 'nearbyVehiclesVehiclesMarkersLayer'
const radiusCircleLayerName = 'nearbyVehiclesRadiusCircleLayer'
const MapOperations = {
showLocateAddressResultMarker(show = true) {
if (show === false) {
return mapVmRef.value.removeLayerGroup(
'nearbyVehicleLocateAddressResultMarker'
)
}
drawLocateAddressMarker(
mapVmRef.value,
providerState.lat,
providerState.lng,
providerState.formattedAddress,
{
layer: 'nearbyVehicleLocateAddressResultMarker',
after: () =>
mapVmRef.value.map.flyTo([providerState.lat, providerState.lng], 14),
}
)
},
showRadiusCircle(show = true) {
if (show === false) {
return mapVmRef.value.removeLayerGroup(radiusCircleLayerName)
}
mapVmRef.value.drawGeometries({
layer: radiusCircleLayerName,
data: [
{
id: 'nearbyVehiclesRadiusCircle',
},
],
generate(item) {
return L.circle([providerState.lat, providerState.lng], {
color: 'rgba(0,0,0,0.5)',
fillColor: 'black',
fillOpacity: 0.02,
radius: providerState.radius * 1000,
})
},
})
},
showVehicleMarkers(show = true) {
if (show === false) {
return mapVmRef.value.removeLayerGroupBy((layerGroup) =>
layerGroup.name.includes('nearbyVehicles')
)
}
let data = filteredMapVehiclesList.value.map((item) => {
//We reuse createMarkerFromRealtimeItem which requires a realtime-like object
let realtimeIsoItem = {
...item,
lat: item.lat,
lng: item.lng,
cap: parseInt(item.vehicleOrientationDegrees),
//vehicleName: item.vehicleName,
//vehicleCategoryClassName: item.vehicleCategoryClassName,
//vehicleStatusColor: item.vehicleStatusColor,
driverChronoEnabled: false, //if true, add: driverChornoStatusOriginal
raw: {},
}
return createMarkerFromRealtimeItem(realtimeIsoItem)
})
mapVmRef.value.drawGeometries({
layer: vehicleMarkersLayerName,
data,
generate(item) {
return L.marker([item.normalizedItem.lat, item.normalizedItem.lng], {
icon: item.icon,
zIndexOffset: 3,
})
},
popupOptions: {
style: 'min-width:267px;',
},
popup: () =>
/* webpackChunkName "location_module" */
import(
'@c/location/LocationMap/LocationRealtimeMap/VehicleMarkerPopup.vue'
),
/**
* Fit bounds over vehicle markers
*/
after: ({ map }) => {
let latLngs = data
.reduce(
(a, v) =>
a.concat([
[
parseFloat(v.normalizedItem.lat),
parseFloat(v.normalizedItem.lng),
],
]),
[]
)
.filter((arr) => !!arr[0] && !!arr[1])
if (!!latLngs && latLngs.length > 0) {
map.flyToBounds(latLngs)
}
},
})
},
}
function newProviderState() {
return {
lat: 0, //<- Locate address lng
lng: 0, //<- Locate address lng
formattedAddress: '', //<- Locate address marker popup text
radius: 0, //<- radius input from user
vehiclesList: [], //<- Holds nearby vehicles items
shouldRender: false, //<- Indicates the map should be rendered
isProcessing: false, //<- Prevent us to call API twice
lastPayloadId: null, //<- Prevent us to call API twice with the same parameters (soft cache)
hiddenVehicleMarkers: [], //<- Allow us to filter out items hidden by users
}
}
function resetProviderState(ignoreKeys = []) {
let newState = newProviderState()
for (var key of Object.keys(newState)) {
if (ignoreKeys.length > 0 && ignoreKeys.includes(key)) {
continue
} else {
providerState[key] = newState[key]
}
}
}
watchEffect(() => {
if (!mapVmRef.value) {
return false
}
if (providerState.shouldRender) {
MapOperations.showRadiusCircle(
providerState.lat && providerState.lng && providerState.radius
)
MapOperations.showLocateAddressResultMarker(
providerState.lat && providerState.lng && providerState.formattedAddress
)
MapOperations.showVehicleMarkers(filteredMapVehiclesList.value.length > 0)
} else {
mapVmRef.value.removeLayerGroupBy((layerGroup) =>
layerGroup.name.includes('nearbyVehicles')
)
}
})
export function useNearbyVehicles() {
return {
setMapVmRef(ref) {
mapVmRef.value = ref ? ref.value : null
},
isProcessing: computed(() => providerState.isProcessing),
isMapRequired: computed(() => providerState.shouldRender),
vehiclesList: computed(() => providerState.vehiclesList),
radiusKM: computed(() => providerState.radius),
clearNearbyVehicles: () => resetProviderState(),
clearNearbyVehiclesResults: () =>
resetProviderState(['lat', 'lng', 'radius', 'formattedAddress']),
updateNearbyVehicles,
isVehicleMarkerVisible: (vehicleId) => {
return !providerState.hiddenVehicleMarkers.includes(vehicleId)
},
toggleVehicleMarkerVisibility(vehicleId) {
if (providerState.hiddenVehicleMarkers.includes(vehicleId)) {
providerState.hiddenVehicleMarkers.splice(
providerState.hiddenVehicleMarkers.indexOf(vehicleId),
1
)
} else {
providerState.hiddenVehicleMarkers.push(vehicleId)
}
},
mapFlyTo(lat, lng) {
if (mapVmRef.value) {
mapVmRef.value.map.flyTo([lat, lng], 15)
}
},
}
}
export function updateNearbyVehicles() {
if (providerState.radius === 0) {
providerState.vehiclesList = []
}
if (!providerState.lat || !providerState.lng || !providerState.radius) {
return console.warn('Lat, Lng, and radius are required')
}
if (providerState.isProcessing) {
return
}
providerState.isProcessing = true
providerState.shouldRender = true
let payload = {
lat: providerState.lat,
lng: providerState.lng,
radius: providerState.radius,
}
let payloadId = window.btoa(JSON.stringify(payload))
if (providerState.lastPayloadId === payloadId) {
//Ignore if result is already in the provider context
return console.log(
'Ignore request, a response with the same payload is already in the provider context'
)
}
fetchNearbyVehicles(payload.lat, payload.lng, payload.radius)
.then((res = []) => {
providerState.vehiclesList = res
})
.finally(() => {
providerState.lastPayloadId = payloadId
providerState.isProcessing = false
})
}
export default {
name: 'NearbyVehiclesFeature',
}
</script>
<style lang="scss" scoped>
.title {
font: normal normal bold 14px/19px Open Sans;
letter-spacing: 0px;
color: var(--color-main);
}
</style>
Source