Files
dawarich/app/javascript/controllers/maps/maplibre/data_loader.js
Evgenii Burmakin c8242ce902 0.36.3 (#2013)
* fix: move foreman to global gems to fix startup crash (#1971)

* Update exporting code to stream points data to file in batches to red… (#1980)

* Update exporting code to stream points data to file in batches to reduce memory usage

* Update changelog

* Update changelog

* Feature/maplibre frontend (#1953)

* Add a plan to use MapLibre GL JS for the frontend map rendering, replacing Leaflet

* Implement phase 1

* Phases 1-3 + part of 4

* Fix e2e tests

* Phase 6

* Implement fog of war

* Phase 7

* Next step: fix specs, phase 7 done

* Use our own map tiles

* Extract v2 map logic to separate manager classes

* Update settings panel on v2 map

* Update v2 e2e tests structure

* Reimplement location search in maps v2

* Update speed routes

* Implement visits and places creation in v2

* Fix last failing test

* Implement visits merging

* Fix a routes e2e test and simplify the routes layer styling.

* Extract js to modules from maps_v2_controller.js

* Implement area creation

* Fix spec problem

* Fix some e2e tests

* Implement live mode in v2 map

* Update icons and panel

* Extract some styles

* Remove unused file

* Start adding dark theme to popups on MapLibre maps

* Make popups respect dark theme

* Move v2 maps to maplibre namespace

* Update v2 references to maplibre

* Put place, area and visit info into side panel

* Update API to use safe settings config method

* Fix specs

* Fix method name to config in SafeSettings and update usages accordingly

* Add missing public files

* Add handling for real time points

* Fix remembering enabled/disabled layers of the v2 map

* Fix lots of e2e tests

* Add settings to select map version

* Use maps/v2 as main path for MapLibre maps

* Update routing

* Update live mode

* Update maplibre controller

* Update changelog

* Remove some console.log statements

* Pull only necessary data for map v2 points

* Feature/raw data archive (#2009)

* 0.36.2 (#2007)

* fix: move foreman to global gems to fix startup crash (#1971)

* Update exporting code to stream points data to file in batches to red… (#1980)

* Update exporting code to stream points data to file in batches to reduce memory usage

* Update changelog

* Update changelog

* Feature/maplibre frontend (#1953)

* Add a plan to use MapLibre GL JS for the frontend map rendering, replacing Leaflet

* Implement phase 1

* Phases 1-3 + part of 4

* Fix e2e tests

* Phase 6

* Implement fog of war

* Phase 7

* Next step: fix specs, phase 7 done

* Use our own map tiles

* Extract v2 map logic to separate manager classes

* Update settings panel on v2 map

* Update v2 e2e tests structure

* Reimplement location search in maps v2

* Update speed routes

* Implement visits and places creation in v2

* Fix last failing test

* Implement visits merging

* Fix a routes e2e test and simplify the routes layer styling.

* Extract js to modules from maps_v2_controller.js

* Implement area creation

* Fix spec problem

* Fix some e2e tests

* Implement live mode in v2 map

* Update icons and panel

* Extract some styles

* Remove unused file

* Start adding dark theme to popups on MapLibre maps

* Make popups respect dark theme

* Move v2 maps to maplibre namespace

* Update v2 references to maplibre

* Put place, area and visit info into side panel

* Update API to use safe settings config method

* Fix specs

* Fix method name to config in SafeSettings and update usages accordingly

* Add missing public files

* Add handling for real time points

* Fix remembering enabled/disabled layers of the v2 map

* Fix lots of e2e tests

* Add settings to select map version

* Use maps/v2 as main path for MapLibre maps

* Update routing

* Update live mode

* Update maplibre controller

* Update changelog

* Remove some console.log statements

---------

Co-authored-by: Robin Tuszik <mail@robin.gg>

* Remove esbuild scripts from package.json

* Remove sideEffects field from package.json

* Raw data archivation

* Add tests

* Fix tests

* Fix tests

* Update ExceptionReporter

* Add schedule to run raw data archival job monthly

* Change file structure for raw data archival feature

* Update changelog and version for raw data archival feature

---------

Co-authored-by: Robin Tuszik <mail@robin.gg>

* Set raw_data to an empty hash instead of nil when archiving

* Fix storage configuration and file extraction

* Consider MIN_MINUTES_SPENT_IN_CITY during stats calculation (#2018)

* Consider MIN_MINUTES_SPENT_IN_CITY during stats calculation

* Remove raw data from visited cities api endpoint

* Use user timezone to show dates on maps (#2020)

* Fix/pre epoch time (#2019)

* Use user timezone to show dates on maps

* Limit timestamps to valid range to prevent database errors when users enter pre-epoch dates.

* Limit timestamps to valid range to prevent database errors when users enter pre-epoch dates.

* Fix tests failing due to new index on stats table

* Fix failing specs

* Update redis client configuration to support unix socket connection

* Update changelog

* Fix kml kmz import issues (#2023)

* Fix kml kmz import issues

* Refactor KML importer to improve readability and maintainability

* Implement moving points in map v2 and fix route rendering logic to ma… (#2027)

* Implement moving points in map v2 and fix route rendering logic to match map v1.

* Fix route spec

* fix(maplibre): update date format to ISO 8601 (#2029)

* Add verification step to raw data archival process (#2028)

* Add verification step to raw data archival process

* Add actual verification of raw data archives after creation, and only clear raw_data for verified archives.

* Fix failing specs

* Eliminate zip-bomb risk

* Fix potential memory leak in js

* Return .keep files

* Use Toast instead of alert for notifications

* Add help section to navbar dropdown

* Update changelog

* Remove raw_data_archival_job

* Ensure file is being closed properly after reading in Archivable concern

---------

Co-authored-by: Robin Tuszik <mail@robin.gg>
2025-12-14 12:05:59 +01:00

237 lines
6.4 KiB
JavaScript

import { pointsToGeoJSON } from 'maps_maplibre/utils/geojson_transformers'
import { RoutesLayer } from 'maps_maplibre/layers/routes_layer'
import { createCircle } from 'maps_maplibre/utils/geometry'
import { performanceMonitor } from 'maps_maplibre/utils/performance_monitor'
/**
* Handles loading and transforming data from API
*/
export class DataLoader {
constructor(api, apiKey, settings = {}) {
this.api = api
this.apiKey = apiKey
this.settings = settings
}
/**
* Update settings (called when user changes settings)
*/
updateSettings(settings) {
this.settings = settings
}
/**
* Fetch all map data (points, visits, photos, areas, tracks)
*/
async fetchMapData(startDate, endDate, onProgress) {
const data = {}
// Fetch points
performanceMonitor.mark('fetch-points')
data.points = await this.api.fetchAllPoints({
start_at: startDate,
end_at: endDate,
onProgress: onProgress
})
performanceMonitor.measure('fetch-points')
// Transform points to GeoJSON
performanceMonitor.mark('transform-geojson')
data.pointsGeoJSON = pointsToGeoJSON(data.points)
data.routesGeoJSON = RoutesLayer.pointsToRoutes(data.points, {
distanceThresholdMeters: this.settings.metersBetweenRoutes || 1000,
timeThresholdMinutes: this.settings.minutesBetweenRoutes || 60
})
performanceMonitor.measure('transform-geojson')
// Fetch visits
try {
data.visits = await this.api.fetchVisits({
start_at: startDate,
end_at: endDate
})
} catch (error) {
console.warn('Failed to fetch visits:', error)
data.visits = []
}
data.visitsGeoJSON = this.visitsToGeoJSON(data.visits)
// Fetch photos
try {
console.log('[Photos] Fetching photos from:', startDate, 'to', endDate)
data.photos = await this.api.fetchPhotos({
start_at: startDate,
end_at: endDate
})
console.log('[Photos] Fetched photos:', data.photos.length, 'photos')
console.log('[Photos] Sample photo:', data.photos[0])
} catch (error) {
console.error('[Photos] Failed to fetch photos:', error)
data.photos = []
}
data.photosGeoJSON = this.photosToGeoJSON(data.photos)
console.log('[Photos] Converted to GeoJSON:', data.photosGeoJSON.features.length, 'features')
console.log('[Photos] Sample feature:', data.photosGeoJSON.features[0])
// Fetch areas
try {
data.areas = await this.api.fetchAreas()
} catch (error) {
console.warn('Failed to fetch areas:', error)
data.areas = []
}
data.areasGeoJSON = this.areasToGeoJSON(data.areas)
// Fetch places (no date filtering)
try {
data.places = await this.api.fetchPlaces()
} catch (error) {
console.warn('Failed to fetch places:', error)
data.places = []
}
data.placesGeoJSON = this.placesToGeoJSON(data.places)
// Tracks - DISABLED: Backend API not yet implemented
// TODO: Re-enable when /api/v1/tracks endpoint is created
data.tracks = []
data.tracksGeoJSON = this.tracksToGeoJSON(data.tracks)
return data
}
/**
* Convert visits to GeoJSON
*/
visitsToGeoJSON(visits) {
return {
type: 'FeatureCollection',
features: visits.map(visit => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [visit.place.longitude, visit.place.latitude]
},
properties: {
id: visit.id,
name: visit.name,
place_name: visit.place?.name,
status: visit.status,
started_at: visit.started_at,
ended_at: visit.ended_at,
duration: visit.duration
}
}))
}
}
/**
* Convert photos to GeoJSON
*/
photosToGeoJSON(photos) {
return {
type: 'FeatureCollection',
features: photos.map(photo => {
// Construct thumbnail URL
const thumbnailUrl = `/api/v1/photos/${photo.id}/thumbnail.jpg?api_key=${this.apiKey}&source=${photo.source}`
return {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [photo.longitude, photo.latitude]
},
properties: {
id: photo.id,
thumbnail_url: thumbnailUrl,
taken_at: photo.localDateTime,
filename: photo.originalFileName,
city: photo.city,
state: photo.state,
country: photo.country,
type: photo.type,
source: photo.source
}
}
})
}
}
/**
* Convert places to GeoJSON
*/
placesToGeoJSON(places) {
return {
type: 'FeatureCollection',
features: places.map(place => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [place.longitude, place.latitude]
},
properties: {
id: place.id,
name: place.name,
latitude: place.latitude,
longitude: place.longitude,
note: place.note,
// Stringify tags for MapLibre GL JS compatibility
tags: JSON.stringify(place.tags || []),
// Use first tag's color if available
color: place.tags?.[0]?.color || '#6366f1'
}
}))
}
}
/**
* Convert areas to GeoJSON
* Backend returns circular areas with latitude, longitude, radius
*/
areasToGeoJSON(areas) {
return {
type: 'FeatureCollection',
features: areas.map(area => {
// Create circle polygon from center and radius
// Parse as floats since API returns strings
const center = [parseFloat(area.longitude), parseFloat(area.latitude)]
const coordinates = createCircle(center, area.radius)
return {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [coordinates]
},
properties: {
id: area.id,
name: area.name,
color: area.color || '#ef4444',
radius: area.radius
}
}
})
}
}
/**
* Convert tracks to GeoJSON
*/
tracksToGeoJSON(tracks) {
return {
type: 'FeatureCollection',
features: tracks.map(track => ({
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: track.coordinates
},
properties: {
id: track.id,
name: track.name,
color: track.color || '#8b5cf6'
}
}))
}
}
}