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

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

View File

@@ -12,7 +12,7 @@ import {
import { fileStateCollection } from '$lib/logic/file-state';
import { settings } from '$lib/logic/settings';
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';
export class Selection {
@@ -203,11 +203,11 @@ export class Selection {
this._cut.set(false);
}
get copied(): ListItem[] | undefined {
get copied(): Readable<ListItem[] | undefined> {
return this._copied;
}
get cut(): boolean {
get cut(): Readable<boolean> {
return this._cut;
}
}
@@ -219,7 +219,7 @@ export function applyToOrderedItemsFromFile(
callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void,
reverse: boolean = true
) {
settings.fileOrder.value.forEach((fileId) => {
get(settings.fileOrder).forEach((fileId) => {
let level: ListLevel | undefined = undefined;
let items: ListItem[] = [];
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 { GPXFile, GPXStatistics, type Track } from 'gpx';
import { selection } from '$lib/logic/selection';
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 {
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();
});
export class SelectedGPXStatistics {
private _statistics: Writable<GPXStatistics>;
private _files: Map<
string,
{
file: GPXFileState;
unsubscribe: () => void;
}
>;
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 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));
selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let stats = fileStateCollection.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;
}
});
}
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 {
let child = this.statistics[id];
if (child instanceof GPXStatistics) {
statistics.mergeWith(child);
} else if (child !== undefined) {
statistics.mergeWith(child.getStatisticsFor(item));
}
}, false);
this._statistics.set(statistics);
for (let [fileId, entry] of this._files) {
if (
!get(fileStateCollection).has(fileId) ||
!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);
});