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 = ` ` 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' } } }