mirror of
https://github.com/Freika/dawarich.git
synced 2025-12-19 20:10:20 -06:00
* 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>
237 lines
6.4 KiB
JavaScript
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'
|
|
}
|
|
}))
|
|
}
|
|
}
|
|
}
|