statistics

This commit is contained in:
vcoppe
2025-10-18 00:31:14 +02:00
parent a73da0d81d
commit de81a8940e
13 changed files with 151 additions and 122 deletions

View File

@@ -7,23 +7,26 @@
import { i18n } from '$lib/i18n.svelte'; import { i18n } from '$lib/i18n.svelte';
import type { GPXStatistics } from 'gpx'; import type { GPXStatistics } from 'gpx';
import type { Writable } from 'svelte/store'; import type { Readable } from 'svelte/store';
import { settings } from '$lib/db'; import { settings } from '$lib/logic/settings';
export let gpxStatistics: Writable<GPXStatistics>;
export let slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>;
export let orientation: 'horizontal' | 'vertical';
export let panelSize: number;
const { velocityUnits } = settings; const { velocityUnits } = settings;
let statistics: GPXStatistics; let {
gpxStatistics,
slicedGPXStatistics,
orientation,
panelSize,
}: {
gpxStatistics: Readable<GPXStatistics>;
slicedGPXStatistics: Readable<[GPXStatistics, number, number] | undefined>;
orientation: 'horizontal' | 'vertical';
panelSize: number;
} = $props();
$: if ($slicedGPXStatistics !== undefined) { let statistics = $derived(
statistics = $slicedGPXStatistics[0]; $slicedGPXStatistics !== undefined ? $slicedGPXStatistics[0] : $gpxStatistics
} else { );
statistics = $gpxStatistics;
}
</script> </script>
<Card.Root <Card.Root

View File

@@ -22,7 +22,7 @@
} from './file-list'; } from './file-list';
import { i18n } from '$lib/i18n.svelte'; import { i18n } from '$lib/i18n.svelte';
import { settings } from '$lib/logic/settings'; import { settings } from '$lib/logic/settings';
import type { GPXFileWithStatistics } from '$lib/logic/statistics'; import type { GPXFileWithStatistics } from '$lib/logic/statistics-tree';
import { selection } from '$lib/logic/selection'; import { selection } from '$lib/logic/selection';
let { let {

View File

@@ -20,7 +20,7 @@
type ListItem, type ListItem,
} from './file-list'; } from './file-list';
import { isMac } from '$lib/utils'; import { isMac } from '$lib/utils';
import type { GPXFileWithStatistics } from '$lib/logic/statistics'; import type { GPXFileWithStatistics } from '$lib/logic/statistics-tree';
import { settings } from '$lib/logic/settings'; import { settings } from '$lib/logic/settings';
import { getFileIds, moveItems } from '$lib/logic/file-actions'; import { getFileIds, moveItems } from '$lib/logic/file-actions';

View File

@@ -5,7 +5,7 @@
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import type { Readable } from 'svelte/store'; import type { Readable } from 'svelte/store';
import { ListFileItem } from './file-list'; import { ListFileItem } from './file-list';
import type { GPXFileWithStatistics } from '$lib/logic/statistics'; import type { GPXFileWithStatistics } from '$lib/logic/statistics-tree';
let { let {
file, file,

View File

@@ -21,7 +21,7 @@ import {
import { selectedWaypoint } from '$lib/components/toolbar/tools/waypoint/waypoint'; import { selectedWaypoint } from '$lib/components/toolbar/tools/waypoint/waypoint';
import { MapPin, Square } from 'lucide-static'; import { MapPin, Square } from 'lucide-static';
import { getSymbolKey, symbols } from '$lib/assets/symbols'; import { getSymbolKey, symbols } from '$lib/assets/symbols';
import type { GPXFileWithStatistics } from '$lib/logic/statistics'; import type { GPXFileWithStatistics } from '$lib/logic/statistics-tree';
import { selection } from '$lib/logic/selection'; import { selection } from '$lib/logic/selection';
import { settings } from '$lib/logic/settings'; import { settings } from '$lib/logic/settings';
import { currentTool, Tool } from '$lib/components/toolbar/tools'; import { currentTool, Tool } from '$lib/components/toolbar/tools';

View File

@@ -9,7 +9,7 @@ import {
ListTrackSegmentItem, ListTrackSegmentItem,
} from '$lib/components/file-list/file-list'; } from '$lib/components/file-list/file-list';
import { getClosestLinePoint, resetCursor, setGrabbingCursor } from '$lib/utils'; import { getClosestLinePoint, resetCursor, setGrabbingCursor } from '$lib/utils';
import type { GPXFileWithStatistics } from '$lib/logic/statistics'; import type { GPXFileWithStatistics } from '$lib/logic/statistics-tree';
// const { streetViewSource } = settings; // const { streetViewSource } = settings;
export const canChangeStart = writable(false); export const canChangeStart = writable(false);

View File

@@ -2,12 +2,12 @@ import { updateAnchorPoints } from '$lib/components/toolbar/tools/routing/Simpli
import { db, type Database } from '$lib/db'; import { db, type Database } from '$lib/db';
import { liveQuery } from 'dexie'; import { liveQuery } from 'dexie';
import { GPXFile } from 'gpx'; import { GPXFile } from 'gpx';
import { GPXStatisticsTree, type GPXFileWithStatistics } from '$lib/logic/statistics'; import { GPXStatisticsTree, type GPXFileWithStatistics } from '$lib/logic/statistics-tree';
import { settings } from '$lib/logic/settings'; import { settings } from '$lib/logic/settings';
import { get, writable, type Subscriber, type Writable } from 'svelte/store'; import { get, writable, type Subscriber, type Writable } from 'svelte/store';
// Observe a single file from the database, and maintain its statistics // Observe a single file from the database, and maintain its statistics
class GPXFileState { export class GPXFileState {
private _file: Writable<GPXFileWithStatistics | undefined>; private _file: Writable<GPXFileWithStatistics | undefined>;
private _subscription: { unsubscribe: () => void } | undefined; private _subscription: { unsubscribe: () => void } | undefined;
@@ -119,6 +119,10 @@ export class GPXFileStateCollection {
return get(this._files).size; return get(this._files).size;
} }
getFileState(fileId: string): GPXFileState | undefined {
return get(this._files).get(fileId);
}
getFile(fileId: string): GPXFile | undefined { getFile(fileId: string): GPXFile | undefined {
let fileState = get(this._files).get(fileId); let fileState = get(this._files).get(fileId);
return fileState?.file; return fileState?.file;

View File

@@ -12,7 +12,7 @@ import {
import { fileStateCollection } from '$lib/logic/file-state'; import { fileStateCollection } from '$lib/logic/file-state';
import { settings } from '$lib/logic/settings'; import { settings } from '$lib/logic/settings';
import type { GPXFile } from 'gpx'; import type { GPXFile } from 'gpx';
import { get, writable, type Writable } from 'svelte/store'; import { get, writable, type Readable, type Writable } from 'svelte/store';
import { SelectionTreeType } from '$lib/logic/selection-tree'; import { SelectionTreeType } from '$lib/logic/selection-tree';
export class Selection { export class Selection {
@@ -203,11 +203,11 @@ export class Selection {
this._cut.set(false); this._cut.set(false);
} }
get copied(): ListItem[] | undefined { get copied(): Readable<ListItem[] | undefined> {
return this._copied; return this._copied;
} }
get cut(): boolean { get cut(): Readable<boolean> {
return this._cut; return this._cut;
} }
} }
@@ -219,7 +219,7 @@ export function applyToOrderedItemsFromFile(
callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void, callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void,
reverse: boolean = true reverse: boolean = true
) { ) {
settings.fileOrder.value.forEach((fileId) => { get(settings.fileOrder).forEach((fileId) => {
let level: ListLevel | undefined = undefined; let level: ListLevel | undefined = undefined;
let items: ListItem[] = []; let items: ListItem[] = [];
selectedItems.forEach((item) => { selectedItems.forEach((item) => {

View File

@@ -0,0 +1,46 @@
import { ListItem, ListLevel } from '$lib/components/file-list/file-list';
import { GPXFile, GPXStatistics, type Track } from 'gpx';
export class GPXStatisticsTree {
level: ListLevel;
statistics: {
[key: string]: GPXStatisticsTree | GPXStatistics;
} = {};
constructor(element: GPXFile | Track) {
if (element instanceof GPXFile) {
this.level = ListLevel.FILE;
element.children.forEach((child, index) => {
this.statistics[index] = new GPXStatisticsTree(child);
});
} else {
this.level = ListLevel.TRACK;
element.children.forEach((child, index) => {
this.statistics[index] = child.getStatistics();
});
}
}
getStatisticsFor(item: ListItem): GPXStatistics {
let statistics = new GPXStatistics();
let id = item.getIdAtLevel(this.level);
if (id === undefined || id === 'waypoints') {
Object.keys(this.statistics).forEach((key) => {
if (this.statistics[key] instanceof GPXStatistics) {
statistics.mergeWith(this.statistics[key]);
} else {
statistics.mergeWith(this.statistics[key].getStatisticsFor(item));
}
});
} else {
let child = this.statistics[id];
if (child instanceof GPXStatistics) {
statistics.mergeWith(child);
} else if (child !== undefined) {
statistics.mergeWith(child.getStatisticsFor(item));
}
}
return statistics;
}
}
export type GPXFileWithStatistics = { file: GPXFile; statistics: GPXStatisticsTree };

View File

@@ -1,46 +1,80 @@
import { ListItem, ListLevel } from '$lib/components/file-list/file-list'; import { selection } from '$lib/logic/selection';
import { GPXFile, GPXStatistics, type Track } from 'gpx'; import { GPXStatistics } from 'gpx';
import { fileStateCollection, GPXFileState } from '$lib/logic/file-state';
import {
ListFileItem,
ListWaypointItem,
ListWaypointsItem,
} from '$lib/components/file-list/file-list';
import { get, writable, type Writable } from 'svelte/store';
export class GPXStatisticsTree { export class SelectedGPXStatistics {
level: ListLevel; private _statistics: Writable<GPXStatistics>;
statistics: { private _files: Map<
[key: string]: GPXStatisticsTree | GPXStatistics; string,
} = {}; {
file: GPXFileState;
constructor(element: GPXFile | Track) { unsubscribe: () => void;
if (element instanceof GPXFile) {
this.level = ListLevel.FILE;
element.children.forEach((child, index) => {
this.statistics[index] = new GPXStatisticsTree(child);
});
} else {
this.level = ListLevel.TRACK;
element.children.forEach((child, index) => {
this.statistics[index] = child.getStatistics();
});
} }
>;
constructor() {
this._statistics = writable(new GPXStatistics());
this._files = new Map();
selection.subscribe(() => this.update());
} }
getStatisticsFor(item: ListItem): GPXStatistics { subscribe(run: (value: GPXStatistics) => void, invalidate?: (value?: GPXStatistics) => void) {
return this._statistics.subscribe(run, invalidate);
}
update() {
let statistics = new GPXStatistics(); let statistics = new GPXStatistics();
let id = item.getIdAtLevel(this.level); selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
if (id === undefined || id === 'waypoints') { let stats = fileStateCollection.getStatistics(fileId);
Object.keys(this.statistics).forEach((key) => { if (stats) {
if (this.statistics[key] instanceof GPXStatistics) { let first = true;
statistics.mergeWith(this.statistics[key]); items.forEach((item) => {
} else { if (
statistics.mergeWith(this.statistics[key].getStatisticsFor(item)); !(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) ||
first
) {
statistics.mergeWith(stats.getStatisticsFor(item));
first = false;
}
});
}
if (!this._files.has(fileId)) {
let file = fileStateCollection.getFileState(fileId);
if (file) {
let first = true;
let unsubscribe = file.subscribe(() => {
if (first) first = false;
else this.update();
});
this._files.set(fileId, { file, unsubscribe });
} }
}); }
} else { }, false);
let child = this.statistics[id]; this._statistics.set(statistics);
if (child instanceof GPXStatistics) { for (let [fileId, entry] of this._files) {
statistics.mergeWith(child); if (
} else if (child !== undefined) { !get(fileStateCollection).has(fileId) ||
statistics.mergeWith(child.getStatisticsFor(item)); !get(selection).hasAnyChildren(new ListFileItem(fileId))
) {
entry.unsubscribe();
this._files.delete(fileId);
} }
} }
return statistics;
} }
} }
export type GPXFileWithStatistics = { file: GPXFile; statistics: GPXStatisticsTree };
export const gpxStatistics = new SelectedGPXStatistics();
export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> =
writable(undefined);
gpxStatistics.subscribe(() => {
slicedGPXStatistics.set(undefined);
});

View File

@@ -22,64 +22,6 @@
// export const embedding = writable(false); // export const embedding = writable(false);
// export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({}); // export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({});
// export const gpxStatistics: Writable<GPXStatistics> = writable(new GPXStatistics());
// export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> =
// writable(undefined);
// export function updateGPXData() {
// let statistics = new GPXStatistics();
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// let stats = getStatistics(fileId);
// if (stats) {
// let first = true;
// items.forEach((item) => {
// if (
// !(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) ||
// first
// ) {
// statistics.mergeWith(stats.getStatisticsFor(item));
// first = false;
// }
// });
// }
// }, false);
// gpxStatistics.set(statistics);
// }
// let unsubscribes: Map<string, () => void> = new Map();
// selection.subscribe(($selection) => {
// // Maintain up-to-date statistics for the current selection
// updateGPXData();
// while (unsubscribes.size > 0) {
// let [fileId, unsubscribe] = unsubscribes.entries().next().value;
// unsubscribe();
// unsubscribes.delete(fileId);
// }
// $selection.forEach((item) => {
// let fileId = item.getFileId();
// if (!unsubscribes.has(fileId)) {
// let fileObserver = get(fileObservers).get(fileId);
// if (fileObserver) {
// let first = true;
// unsubscribes.set(
// fileId,
// fileObserver.subscribe(() => {
// if (first) first = false;
// else updateGPXData();
// })
// );
// }
// }
// });
// });
// gpxStatistics.subscribe(() => {
// slicedGPXStatistics.set(undefined);
// });
// export const gpxLayers: Map<string, GPXLayer> = new Map();
// export const routingControls: Map<string, RoutingControls> = new Map(); // export const routingControls: Map<string, RoutingControls> = new Map();
// export function selectFileWhenLoaded(fileId: string) { // export function selectFileWhenLoaded(fileId: string) {

View File

@@ -2,7 +2,7 @@
import GPXLayers from '$lib/components/map/gpx-layer/GPXLayers.svelte'; import GPXLayers from '$lib/components/map/gpx-layer/GPXLayers.svelte';
// import ElevationProfile from '$lib/components/ElevationProfile.svelte'; // import ElevationProfile from '$lib/components/ElevationProfile.svelte';
// import FileList from '$lib/components/file-list/FileList.svelte'; // import FileList from '$lib/components/file-list/FileList.svelte';
// import GPXStatistics from '$lib/components/GPXStatistics.svelte'; import GPXStatistics from '$lib/components/GPXStatistics.svelte';
import Map from '$lib/components/map/Map.svelte'; import Map from '$lib/components/map/Map.svelte';
import Menu from '$lib/components/Menu.svelte'; import Menu from '$lib/components/Menu.svelte';
// import Toolbar from '$lib/components/toolbar/Toolbar.svelte'; // import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
@@ -11,7 +11,6 @@
// import CoordinatesPopup from '$lib/components/map/CoordinatesPopup.svelte'; // import CoordinatesPopup from '$lib/components/map/CoordinatesPopup.svelte';
import Resizer from '$lib/components/Resizer.svelte'; import Resizer from '$lib/components/Resizer.svelte';
import { Toaster } from '$lib/components/ui/sonner'; import { Toaster } from '$lib/components/ui/sonner';
// import { gpxStatistics, loadFiles, slicedGPXStatistics } from '$lib/stores';
// import { onMount } from 'svelte'; // import { onMount } from 'svelte';
// import { page } from '$app/state'; // import { page } from '$app/state';
import { languages } from '$lib/languages'; import { languages } from '$lib/languages';
@@ -23,6 +22,7 @@
import { loadFiles } from '$lib/logic/file-actions'; import { loadFiles } from '$lib/logic/file-actions';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { page } from '$app/state'; import { page } from '$app/state';
import { gpxStatistics, slicedGPXStatistics } from '$lib/logic/statistics';
const { const {
treeFileView, treeFileView,
@@ -127,12 +127,12 @@
class="{$elevationProfile ? '' : 'h-10'} flex flex-row gap-2 px-2 sm:px-4" class="{$elevationProfile ? '' : 'h-10'} flex flex-row gap-2 px-2 sm:px-4"
style={$elevationProfile ? `height: ${$bottomPanelSize}px` : ''} style={$elevationProfile ? `height: ${$bottomPanelSize}px` : ''}
> >
<!-- <GPXStatistics <GPXStatistics
{gpxStatistics} {gpxStatistics}
{slicedGPXStatistics} {slicedGPXStatistics}
panelSize={$bottomPanelSize} panelSize={$bottomPanelSize}
orientation={$elevationProfile ? 'vertical' : 'horizontal'} orientation={$elevationProfile ? 'vertical' : 'horizontal'}
/> --> />
<!-- {#if $elevationProfile} <!-- {#if $elevationProfile}
<ElevationProfile <ElevationProfile
{gpxStatistics} {gpxStatistics}