import { SettingsManager } from 'maps_maplibre/utils/settings_manager'
import { Toast } from 'maps_maplibre/components/toast'
import { lazyLoader } from 'maps_maplibre/utils/lazy_loader'
/**
* Manages routes-related operations for Maps V2
* Including speed-colored routes, route generation, and layer management
*/
export class RoutesManager {
constructor(controller) {
this.controller = controller
this.map = controller.map
this.layerManager = controller.layerManager
this.settings = controller.settings
}
/**
* Toggle routes layer visibility
*/
toggleRoutes(event) {
const element = event.currentTarget
const visible = element.checked
const routesLayer = this.layerManager.getLayer('routes')
if (routesLayer) {
routesLayer.toggle(visible)
}
if (this.controller.hasRoutesOptionsTarget) {
this.controller.routesOptionsTarget.style.display = visible ? 'block' : 'none'
}
SettingsManager.updateSetting('routesVisible', visible)
}
/**
* Toggle speed-colored routes
*/
async toggleSpeedColoredRoutes(event) {
const enabled = event.target.checked
SettingsManager.updateSetting('speedColoredRoutesEnabled', enabled)
if (this.controller.hasSpeedColorScaleContainerTarget) {
this.controller.speedColorScaleContainerTarget.classList.toggle('hidden', !enabled)
}
await this.reloadRoutes()
}
/**
* Open speed color editor modal
*/
openSpeedColorEditor() {
const currentScale = this.controller.speedColorScaleInputTarget.value ||
'0:#00ff00|15:#00ffff|30:#ff00ff|50:#ffff00|100:#ff3300'
let modal = document.getElementById('speed-color-editor-modal')
if (!modal) {
modal = this.createSpeedColorEditorModal(currentScale)
document.body.appendChild(modal)
} else {
const controller = this.controller.application.getControllerForElementAndIdentifier(modal, 'speed-color-editor')
if (controller) {
controller.colorStopsValue = currentScale
controller.loadColorStops()
}
}
const checkbox = modal.querySelector('.modal-toggle')
if (checkbox) {
checkbox.checked = true
}
}
/**
* Create speed color editor modal element
*/
createSpeedColorEditorModal(currentScale) {
const modal = document.createElement('div')
modal.id = 'speed-color-editor-modal'
modal.setAttribute('data-controller', 'speed-color-editor')
modal.setAttribute('data-speed-color-editor-color-stops-value', currentScale)
modal.setAttribute('data-action', 'speed-color-editor:save->maps--maplibre#handleSpeedColorSave')
modal.innerHTML = `
Edit Speed Color Gradient
`
return modal
}
/**
* Handle speed color save event from editor
*/
handleSpeedColorSave(event) {
const newScale = event.detail.colorStops
this.controller.speedColorScaleInputTarget.value = newScale
SettingsManager.updateSetting('speedColorScale', newScale)
if (this.controller.speedColoredToggleTarget.checked) {
this.reloadRoutes()
}
}
/**
* Reload routes layer
*/
async reloadRoutes() {
this.controller.showLoading('Reloading routes...')
try {
const pointsLayer = this.layerManager.getLayer('points')
const points = pointsLayer?.data?.features?.map(f => ({
latitude: f.geometry.coordinates[1],
longitude: f.geometry.coordinates[0],
timestamp: f.properties.timestamp
})) || []
const distanceThresholdMeters = this.settings.metersBetweenRoutes || 1000
const timeThresholdMinutes = this.settings.minutesBetweenRoutes || 60
const { calculateSpeed, getSpeedColor } = await import('maps_maplibre/utils/speed_colors')
const routesGeoJSON = await this.generateRoutesWithSpeedColors(
points,
{ distanceThresholdMeters, timeThresholdMinutes },
calculateSpeed,
getSpeedColor
)
this.layerManager.updateLayer('routes', routesGeoJSON)
} catch (error) {
console.error('Failed to reload routes:', error)
Toast.error('Failed to reload routes')
} finally {
this.controller.hideLoading()
}
}
/**
* Generate routes with speed coloring
*/
async generateRoutesWithSpeedColors(points, options, calculateSpeed, getSpeedColor) {
const { RoutesLayer } = await import('maps_maplibre/layers/routes_layer')
const useSpeedColors = this.settings.speedColoredRoutesEnabled || false
const speedColorScale = this.settings.speedColorScale || '0:#00ff00|15:#00ffff|30:#ff00ff|50:#ffff00|100:#ff3300'
const routesGeoJSON = RoutesLayer.pointsToRoutes(points, options)
if (!useSpeedColors) {
return routesGeoJSON
}
routesGeoJSON.features = routesGeoJSON.features.map((feature, index) => {
const segment = points.slice(
points.findIndex(p => p.timestamp === feature.properties.startTime),
points.findIndex(p => p.timestamp === feature.properties.endTime) + 1
)
if (segment.length >= 2) {
const speed = calculateSpeed(segment[0], segment[segment.length - 1])
const color = getSpeedColor(speed, useSpeedColors, speedColorScale)
feature.properties.speed = speed
feature.properties.color = color
}
return feature
})
return routesGeoJSON
}
/**
* Toggle heatmap visibility
*/
toggleHeatmap(event) {
const enabled = event.target.checked
SettingsManager.updateSetting('heatmapEnabled', enabled)
const heatmapLayer = this.layerManager.getLayer('heatmap')
if (heatmapLayer) {
if (enabled) {
heatmapLayer.show()
} else {
heatmapLayer.hide()
}
}
}
/**
* Toggle fog of war layer
*/
toggleFog(event) {
const enabled = event.target.checked
SettingsManager.updateSetting('fogEnabled', enabled)
const fogLayer = this.layerManager.getLayer('fog')
if (fogLayer) {
fogLayer.toggle(enabled)
} else {
console.warn('Fog layer not yet initialized')
}
}
/**
* Toggle scratch map layer
*/
async toggleScratch(event) {
const enabled = event.target.checked
SettingsManager.updateSetting('scratchEnabled', enabled)
try {
const scratchLayer = this.layerManager.getLayer('scratch')
if (!scratchLayer && enabled) {
const ScratchLayer = await lazyLoader.loadLayer('scratch')
const newScratchLayer = new ScratchLayer(this.map, {
visible: true,
apiClient: this.controller.api
})
const pointsLayer = this.layerManager.getLayer('points')
const pointsData = pointsLayer?.data || { type: 'FeatureCollection', features: [] }
await newScratchLayer.add(pointsData)
this.layerManager.layers.scratchLayer = newScratchLayer
} else if (scratchLayer) {
if (enabled) {
scratchLayer.show()
} else {
scratchLayer.hide()
}
}
} catch (error) {
console.error('Failed to toggle scratch layer:', error)
Toast.error('Failed to load scratch layer')
}
}
/**
* Toggle photos layer
*/
togglePhotos(event) {
const enabled = event.target.checked
SettingsManager.updateSetting('photosEnabled', enabled)
const photosLayer = this.layerManager.getLayer('photos')
if (photosLayer) {
if (enabled) {
photosLayer.show()
} else {
photosLayer.hide()
}
}
}
/**
* Toggle areas layer
*/
toggleAreas(event) {
const enabled = event.target.checked
SettingsManager.updateSetting('areasEnabled', enabled)
const areasLayer = this.layerManager.getLayer('areas')
if (areasLayer) {
if (enabled) {
areasLayer.show()
} else {
areasLayer.hide()
}
}
}
/**
* Toggle tracks layer
*/
toggleTracks(event) {
const enabled = event.target.checked
SettingsManager.updateSetting('tracksEnabled', enabled)
const tracksLayer = this.layerManager.getLayer('tracks')
if (tracksLayer) {
if (enabled) {
tracksLayer.show()
} else {
tracksLayer.hide()
}
}
}
/**
* Toggle points layer visibility
*/
togglePoints(event) {
const element = event.currentTarget
const visible = element.checked
const pointsLayer = this.layerManager.getLayer('points')
if (pointsLayer) {
pointsLayer.toggle(visible)
}
SettingsManager.updateSetting('pointsVisible', visible)
}
/**
* Toggle family members layer
*/
async toggleFamily(event) {
const enabled = event.target.checked
SettingsManager.updateSetting('familyEnabled', enabled)
const familyLayer = this.layerManager.getLayer('family')
if (familyLayer) {
if (enabled) {
familyLayer.show()
// Load family members data
await this.controller.loadFamilyMembers()
} else {
familyLayer.hide()
}
}
// Show/hide the family members list
if (this.controller.hasFamilyMembersListTarget) {
this.controller.familyMembersListTarget.style.display = enabled ? 'block' : 'none'
}
}
}