Files
gpx.studio/website/src/lib/db.ts

1299 lines
57 KiB
TypeScript
Raw Normal View History

2025-06-21 21:07:36 +02:00
// import Dexie, { liveQuery } from 'dexie';
// import {
// GPXFile,
// GPXStatistics,
// Track,
// TrackSegment,
// Waypoint,
// TrackPoint,
// type Coordinates,
// distance,
// type LineStyleExtension,
// type WaypointType,
// } from 'gpx';
// import {
// enableMapSet,
// enablePatches,
// applyPatches,
// type Patch,
// type WritableDraft,
// freeze,
// produceWithPatches,
// } from 'immer';
// import { writable, get, derived, type Readable, type Writable } from 'svelte/store';
// import { gpxStatistics, updateAllHidden } from './stores';
// import { initTargetMapBounds, updateTargetMapBounds } from '$lib/components/map/utils.svelte';
// import {
// applyToOrderedItemsFromFile,
// applyToOrderedSelectedItemsFromFile,
// selection,
// } from '$lib/components/file-list/Selection';
// import {
// ListFileItem,
// ListItem,
// ListTrackItem,
// ListLevel,
// ListTrackSegmentItem,
// ListWaypointItem,
// ListRootItem,
// } from '$lib/components/file-list/FileList';
// import { updateAnchorPoints } from '$lib/components/toolbar/tools/routing/Simplify';
// import { SplitType } from '$lib/components/toolbar/tools/scissors/utils.svelte';
// import { getClosestLinePoint, getElevation } from '$lib/utils';
import Dexie from 'dexie';
import type { GPXFile } from 'gpx';
import { enableMapSet, enablePatches, type Patch } from 'immer';
2024-05-02 19:51:08 +02:00
2024-05-07 18:14:47 +02:00
enableMapSet();
enablePatches();
2025-06-21 21:07:36 +02:00
export class Database extends Dexie {
2024-05-03 15:59:34 +02:00
fileids!: Dexie.Table<string, string>;
2024-05-07 18:14:47 +02:00
files!: Dexie.Table<GPXFile, string>;
patches!: Dexie.Table<{ patch: Patch[]; inversePatch: Patch[]; index: number }, number>;
2024-05-02 19:51:08 +02:00
settings!: Dexie.Table<any, string>;
overpasstiles!: Dexie.Table<
{ query: string; x: number; y: number; time: number },
[string, number, number]
>;
overpassdata!: Dexie.Table<
{ query: string; id: number; poi: GeoJSON.Feature },
[string, number]
>;
2024-05-02 19:51:08 +02:00
constructor() {
super('Database', {
cache: 'immutable',
2024-05-03 15:59:34 +02:00
});
2024-05-02 19:51:08 +02:00
this.version(1).stores({
2024-05-03 15:59:34 +02:00
fileids: ',&fileid',
files: '',
patches: ',patch',
2024-07-16 23:57:17 +02:00
settings: '',
overpasstiles: '[query+x+y],[x+y]',
2024-07-17 14:19:04 +02:00
overpassdata: '[query+id]',
2024-05-02 19:51:08 +02:00
});
}
}
2024-07-16 23:57:17 +02:00
export const db = new Database();
2024-05-02 19:51:08 +02:00
2024-05-04 14:27:12 +02:00
// Wrap Dexie live queries in a Svelte store to avoid triggering the query for every subscriber
2025-06-21 21:07:36 +02:00
// function dexieStore<T>(querier: () => T | Promise<T>, initial?: T): Readable<T> {
// let store = writable<T>(initial);
// liveQuery(querier).subscribe((value) => {
// if (value !== undefined) {
// store.set(value);
// }
// });
// return {
// subscribe: store.subscribe,
// };
// }
2024-05-03 15:59:34 +02:00
2025-06-21 21:07:36 +02:00
// export class GPXStatisticsTree {
// level: ListLevel;
// statistics: {
// [key: string]: GPXStatisticsTree | GPXStatistics;
// } = {};
2024-05-22 16:05:31 +02:00
2025-06-21 21:07:36 +02:00
// 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();
// });
// }
// }
2024-05-22 16:05:31 +02:00
2025-06-21 21:07:36 +02:00
// 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 };
2024-05-08 21:31:54 +02:00
2025-06-21 21:07:36 +02:00
// // Wrap Dexie live queries in a Svelte store to avoid triggering the query for every subscriber, also takes care of the conversion to a GPXFile object
// function dexieGPXFileStore(id: string): Readable<GPXFileWithStatistics> & { destroy: () => void } {
// let store = writable<GPXFileWithStatistics>(undefined);
// let query = liveQuery(() => db.files.get(id)).subscribe((value) => {
// if (value !== undefined) {
// let gpx = new GPXFile(value);
// updateAnchorPoints(gpx);
2024-05-22 16:05:31 +02:00
2025-06-21 21:07:36 +02:00
// let statistics = new GPXStatisticsTree(gpx);
// if (!fileState.has(id)) {
// // Update the map bounds for new files
// updateTargetMapBounds(
// id,
// statistics.getStatisticsFor(new ListFileItem(id)).global.bounds
// );
// }
2024-05-22 16:05:31 +02:00
2025-06-21 21:07:36 +02:00
// fileState.set(id, gpx);
// store.set({
// file: gpx,
// statistics,
// });
2024-07-02 20:04:17 +02:00
2025-06-21 21:07:36 +02:00
// if (get(selection).hasAnyChildren(new ListFileItem(id))) {
// updateAllHidden();
// }
// }
// });
// return {
// subscribe: store.subscribe,
// destroy: () => {
// fileState.delete(id);
// query.unsubscribe();
// },
// };
// }
2024-05-03 15:59:34 +02:00
2025-06-21 21:07:36 +02:00
// function updateSelection(updatedFiles: GPXFile[], deletedFileIds: string[]) {
// let removedItems: ListItem[] = [];
2024-07-02 20:04:17 +02:00
2025-06-21 21:07:36 +02:00
// applyToOrderedItemsFromFile(get(selection).getSelected(), (fileId, level, items) => {
// let file = updatedFiles.find((file) => file._data.id === fileId);
// if (file) {
// items.forEach((item) => {
// if (item instanceof ListTrackItem) {
// let newTrackIndex = file.trk.findIndex(
// (track) => track._data.trackIndex === item.getTrackIndex()
// );
// if (newTrackIndex === -1) {
// removedItems.push(item);
// }
// } else if (item instanceof ListTrackSegmentItem) {
// let newTrackIndex = file.trk.findIndex(
// (track) => track._data.trackIndex === item.getTrackIndex()
// );
// if (newTrackIndex === -1) {
// removedItems.push(item);
// } else {
// let newSegmentIndex = file.trk[newTrackIndex].trkseg.findIndex(
// (segment) => segment._data.segmentIndex === item.getSegmentIndex()
// );
// if (newSegmentIndex === -1) {
// removedItems.push(item);
// }
// }
// } else if (item instanceof ListWaypointItem) {
// let newWaypointIndex = file.wpt.findIndex(
// (wpt) => wpt._data.index === item.getWaypointIndex()
// );
// if (newWaypointIndex === -1) {
// removedItems.push(item);
// }
// }
// });
// } else if (deletedFileIds.includes(fileId)) {
// items.forEach((item) => {
// removedItems.push(item);
// });
// }
// });
2024-07-02 20:04:17 +02:00
2025-06-21 21:07:36 +02:00
// if (removedItems.length > 0) {
// selection.update(($selection) => {
// removedItems.forEach((item) => {
// if (item instanceof ListFileItem) {
// $selection.deleteChild(item.getFileId());
// } else {
// $selection.set(item, false);
// }
// });
// return $selection;
// });
// }
// }
2024-07-02 20:04:17 +02:00
2025-06-21 21:07:36 +02:00
// // Commit the changes to the file state to the database
// function commitFileStateChange(newFileState: ReadonlyMap<string, GPXFile>, patch: Patch[]) {
// let changedFileIds = getChangedFileIds(patch);
// let updatedFileIds: string[] = [],
// deletedFileIds: string[] = [];
2024-07-02 20:04:17 +02:00
2025-06-21 21:07:36 +02:00
// changedFileIds.forEach((id) => {
// if (newFileState.has(id)) {
// updatedFileIds.push(id);
// } else {
// deletedFileIds.push(id);
// }
// });
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// let updatedFiles = updatedFileIds
// .map((id) => newFileState.get(id))
// .filter((file) => file !== undefined) as GPXFile[];
// updatedFileIds = updatedFiles.map((file) => file._data.id);
2024-06-08 17:19:22 +02:00
2025-06-21 21:07:36 +02:00
// updateSelection(updatedFiles, deletedFileIds);
2024-07-02 20:04:17 +02:00
2025-06-21 21:07:36 +02:00
// return db.transaction('rw', db.fileids, db.files, async () => {
// if (updatedFileIds.length > 0) {
// await db.fileids.bulkPut(updatedFileIds, updatedFileIds);
// await db.files.bulkPut(updatedFiles, updatedFileIds);
// }
// if (deletedFileIds.length > 0) {
// await db.fileids.bulkDelete(deletedFileIds);
// await db.files.bulkDelete(deletedFileIds);
// }
// });
// }
2024-05-03 15:59:34 +02:00
2025-06-21 21:07:36 +02:00
// export const fileObservers: Writable<
// Map<string, Readable<GPXFileWithStatistics | undefined> & { destroy?: () => void }>
// > = writable(new Map());
// const fileState: Map<string, GPXFile> = new Map(); // Used to generate patches
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// // Observe the file ids in the database, and maintain a map of file observers for the corresponding files
// export function observeFilesFromDatabase(fitBounds: boolean) {
// let initialize = true;
// liveQuery(() => db.fileids.toArray()).subscribe((dbFileIds) => {
// if (initialize) {
// if (fitBounds && dbFileIds.length > 0) {
// initTargetMapBounds(dbFileIds);
// }
// initialize = false;
// }
// // Find new files to observe
// let newFiles = dbFileIds
// .filter((id) => !get(fileObservers).has(id))
// .sort((a, b) => parseInt(a.split('-')[1]) - parseInt(b.split('-')[1]));
// // Find deleted files to stop observing
// let deletedFiles = Array.from(get(fileObservers).keys()).filter(
// (id) => !dbFileIds.find((fileId) => fileId === id)
// );
2024-05-08 21:31:54 +02:00
2025-06-21 21:07:36 +02:00
// // Update the store
// if (newFiles.length > 0 || deletedFiles.length > 0) {
// fileObservers.update(($files) => {
// newFiles.forEach((id) => {
// $files.set(id, dexieGPXFileStore(id));
// });
// deletedFiles.forEach((id) => {
// $files.get(id)?.destroy?.();
// $files.delete(id);
// });
// return $files;
// });
// settings.fileOrder.update((order) => {
// newFiles.forEach((fileId) => {
// if (!order.includes(fileId)) {
// order.push(fileId);
// }
// });
// deletedFiles.forEach((fileId) => {
// let index = order.indexOf(fileId);
// if (index !== -1) {
// order.splice(index, 1);
// }
// });
// return order;
// });
// }
// });
// }
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// export function getFile(fileId: string): GPXFile | undefined {
// let fileStore = get(fileObservers).get(fileId);
// return fileStore ? get(fileStore)?.file : undefined;
// }
2024-06-15 18:44:17 +02:00
2025-06-21 21:07:36 +02:00
// export function getStatistics(fileId: string): GPXStatisticsTree | undefined {
// let fileStore = get(fileObservers).get(fileId);
// return fileStore ? get(fileStore)?.statistics : undefined;
// }
2024-06-15 18:44:17 +02:00
2025-06-21 21:07:36 +02:00
// const patchIndex: Readable<number> = dexieStore(() => db.settings.get('patchIndex'), -1);
// const patchMinMaxIndex: Readable<{ min: number; max: number }> = dexieStore(
// () =>
// (db.patches.orderBy(':id').keys() as Promise<number[]>).then((keys) => {
// if (keys.length === 0) {
// return { min: 0, max: 0 };
// } else {
// return { min: keys[0], max: keys[keys.length - 1] + 1 };
// }
// }),
// { min: 0, max: 0 }
// );
// export const canUndo: Readable<boolean> = derived(
// [patchIndex, patchMinMaxIndex],
// ([$patchIndex, $patchMinMaxIndex]) => $patchIndex >= $patchMinMaxIndex.min
// );
// export const canRedo: Readable<boolean> = derived(
// [patchIndex, patchMinMaxIndex],
// ([$patchIndex, $patchMinMaxIndex]) => $patchIndex < $patchMinMaxIndex.max - 1
// );
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// // Helper function to apply a callback to the global file state
// function applyGlobal(callback: (files: Map<string, GPXFile>) => void) {
// const [newFileState, patch, inversePatch] = produceWithPatches(fileState, callback);
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// storePatches(patch, inversePatch);
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// return commitFileStateChange(newFileState, patch);
// }
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// // Helper function to apply a callback to multiple files
// function applyToFiles(fileIds: string[], callback: (file: WritableDraft<GPXFile>) => void) {
// const [newFileState, patch, inversePatch] = produceWithPatches(fileState, (draft) => {
// fileIds.forEach((fileId) => {
// let file = draft.get(fileId);
// if (file) {
// callback(file);
// }
// });
// });
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// storePatches(patch, inversePatch);
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// return commitFileStateChange(newFileState, patch);
// }
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// // Helper function to apply different callbacks to multiple files
// function applyEachToFilesAndGlobal(
// fileIds: string[],
// callbacks: ((file: WritableDraft<GPXFile>, context?: any) => void)[],
// globalCallback: (files: Map<string, GPXFile>, context?: any) => void,
// context?: any
// ) {
// const [newFileState, patch, inversePatch] = produceWithPatches(fileState, (draft) => {
// fileIds.forEach((fileId, index) => {
// let file = draft.get(fileId);
// if (file) {
// callbacks[index](file, context);
// }
// });
// globalCallback(draft, context);
// });
2024-06-04 16:11:47 +02:00
2025-06-21 21:07:36 +02:00
// storePatches(patch, inversePatch);
2024-06-04 16:11:47 +02:00
2025-06-21 21:07:36 +02:00
// return commitFileStateChange(newFileState, patch);
// }
2024-06-04 16:11:47 +02:00
2025-06-21 21:07:36 +02:00
// const MAX_PATCHES = 100;
// // Store the new patches in the database
// async function storePatches(patch: Patch[], inversePatch: Patch[]) {
// if (get(patchIndex) !== undefined) {
// db.patches.where(':id').above(get(patchIndex)).delete(); // Delete all patches after the current patch to avoid redoing them
// let minmax = get(patchMinMaxIndex);
// if (minmax.max - minmax.min + 1 > MAX_PATCHES) {
// db.patches
// .where(':id')
// .belowOrEqual(get(patchMinMaxIndex).max - MAX_PATCHES)
// .delete();
// }
// }
// db.transaction('rw', db.patches, db.settings, async () => {
// let index = get(patchIndex) + 1;
// await db.patches.put(
// {
// patch,
// inversePatch,
// index,
// },
// index
// );
// await db.settings.put(index, 'patchIndex');
// });
// }
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// // Apply a patch to the file state
// function applyPatch(patch: Patch[]) {
// let newFileState = applyPatches(fileState, patch);
// return commitFileStateChange(newFileState, patch);
// }
2024-05-03 15:59:34 +02:00
2025-06-21 21:07:36 +02:00
// // Get the file ids of the files that have changed in the patch
// function getChangedFileIds(patch: Patch[]): string[] {
// let changedFileIds = new Set<string>();
// for (let p of patch) {
// changedFileIds.add(p.path[0] as string);
// }
// return Array.from(changedFileIds);
// }
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// // Generate unique file ids, different from the ones in the database
// export function getFileIds(n: number) {
// let ids = [];
// for (let index = 0; ids.length < n; index++) {
// let id = `gpx-${index}`;
// if (!get(fileObservers).has(id)) {
// ids.push(id);
// }
// }
// return ids;
// }
2024-05-02 19:51:08 +02:00
2025-06-21 21:07:36 +02:00
// // Helper functions for file operations
// export const dbUtils = {
// add: (file: GPXFile) => {
// if (file._data.id === undefined) {
// file._data.id = getFileIds(1)[0];
// }
// return applyGlobal((draft) => {
// draft.set(file._data.id, freeze(file));
// });
// },
// addMultiple: (files: GPXFile[]) => {
// let ids = getFileIds(files.length);
// applyGlobal((draft) => {
// files.forEach((file, index) => {
// file._data.id = ids[index];
// draft.set(file._data.id, freeze(file));
// });
// });
// return ids;
// },
// applyToFile: (id: string, callback: (file: WritableDraft<GPXFile>) => void) => {
// applyToFiles([id], callback);
// },
// applyToFiles: (ids: string[], callback: (file: WritableDraft<GPXFile>) => void) => {
// applyToFiles(ids, callback);
// },
// applyEachToFilesAndGlobal: (
// ids: string[],
// callbacks: ((file: WritableDraft<GPXFile>, context?: any) => void)[],
// globalCallback: (files: Map<string, GPXFile>, context?: any) => void,
// context?: any
// ) => {
// applyEachToFilesAndGlobal(ids, callbacks, globalCallback, context);
// },
// duplicateSelection: () => {
// if (get(selection).size === 0) {
// return;
// }
// applyGlobal((draft) => {
// let ids = getFileIds(get(settings.fileOrder).length);
// let index = 0;
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// if (level === ListLevel.FILE) {
// let file = getFile(fileId);
// if (file) {
// let newFile = file.clone();
// newFile._data.id = ids[index++];
// draft.set(newFile._data.id, freeze(newFile));
// }
// } else {
// let file = draft.get(fileId);
// if (file) {
// if (level === ListLevel.TRACK) {
// for (let item of items) {
// let trackIndex = (item as ListTrackItem).getTrackIndex();
// file.replaceTracks(trackIndex + 1, trackIndex, [
// file.trk[trackIndex].clone(),
// ]);
// }
// } else if (level === ListLevel.SEGMENT) {
// for (let item of items) {
// let trackIndex = (item as ListTrackSegmentItem).getTrackIndex();
// let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex();
// file.replaceTrackSegments(
// trackIndex,
// segmentIndex + 1,
// segmentIndex,
// [file.trk[trackIndex].trkseg[segmentIndex].clone()]
// );
// }
// } else if (level === ListLevel.WAYPOINTS) {
// file.replaceWaypoints(
// file.wpt.length,
// file.wpt.length - 1,
// file.wpt.map((wpt) => wpt.clone())
// );
// } else if (level === ListLevel.WAYPOINT) {
// for (let item of items) {
// let waypointIndex = (item as ListWaypointItem).getWaypointIndex();
// file.replaceWaypoints(waypointIndex + 1, waypointIndex, [
// file.wpt[waypointIndex].clone(),
// ]);
// }
// }
// }
// }
// });
// });
// },
// addNewTrack: (fileId: string) => {
// dbUtils.applyToFile(fileId, (file) =>
// file.replaceTracks(file.trk.length, file.trk.length, [new Track()])
// );
// },
// addNewSegment: (fileId: string, trackIndex: number) => {
// dbUtils.applyToFile(fileId, (file) => {
// let track = file.trk[trackIndex];
// track.replaceTrackSegments(track.trkseg.length, track.trkseg.length, [
// new TrackSegment(),
// ]);
// });
// },
// reverseSelection: () => {
// if (
// !get(selection).hasAnyChildren(new ListRootItem(), true, ['waypoints']) ||
// get(gpxStatistics).local.points?.length <= 1
// ) {
// return;
// }
// applyGlobal((draft) => {
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// let file = draft.get(fileId);
// if (file) {
// if (level === ListLevel.FILE) {
// file.reverse();
// } else if (level === ListLevel.TRACK) {
// for (let item of items) {
// let trackIndex = (item as ListTrackItem).getTrackIndex();
// file.reverseTrack(trackIndex);
// }
// } else if (level === ListLevel.SEGMENT) {
// for (let item of items) {
// let trackIndex = (item as ListTrackSegmentItem).getTrackIndex();
// let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex();
// file.reverseTrackSegment(trackIndex, segmentIndex);
// }
// }
// }
// });
// });
// },
// createRoundTripForSelection() {
// if (!get(selection).hasAnyChildren(new ListRootItem(), true, ['waypoints'])) {
// return;
// }
// applyGlobal((draft) => {
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// let file = draft.get(fileId);
// if (file) {
// if (level === ListLevel.FILE) {
// file.roundTrip();
// } else if (level === ListLevel.TRACK) {
// for (let item of items) {
// let trackIndex = (item as ListTrackItem).getTrackIndex();
// file.roundTripTrack(trackIndex);
// }
// } else if (level === ListLevel.SEGMENT) {
// for (let item of items) {
// let trackIndex = (item as ListTrackSegmentItem).getTrackIndex();
// let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex();
// file.roundTripTrackSegment(trackIndex, segmentIndex);
// }
// }
// }
// });
// });
// },
// mergeSelection: (mergeTraces: boolean, removeGaps: boolean) => {
// applyGlobal((draft) => {
// let first = true;
// let target: ListItem = new ListRootItem();
// let targetFile: GPXFile | undefined = undefined;
// let toMerge: {
// trk: Track[];
// trkseg: TrackSegment[];
// wpt: Waypoint[];
// } = {
// trk: [],
// trkseg: [],
// wpt: [],
// };
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// let file = draft.get(fileId);
// let originalFile = getFile(fileId);
// if (file && originalFile) {
// if (level === ListLevel.FILE) {
// toMerge.trk.push(...originalFile.trk.map((track) => track.clone()));
// for (const wpt of originalFile.wpt) {
// if (!toMerge.wpt.some((w) => w.equals(wpt))) {
// toMerge.wpt.push(wpt.clone());
// }
// }
// if (first) {
// target = items[0];
// targetFile = file;
// } else {
// draft.delete(fileId);
// }
// } else {
// if (level === ListLevel.TRACK) {
// items.forEach((item, index) => {
// let trackIndex = (item as ListTrackItem).getTrackIndex();
// toMerge.trkseg.splice(
// 0,
// 0,
// ...originalFile.trk[trackIndex].trkseg.map((segment) =>
// segment.clone()
// )
// );
// if (index === items.length - 1) {
// // Order is reversed, so the last track is the first one and the one to keep
// target = item;
// file.trk[trackIndex].trkseg = [];
// } else {
// file.trk.splice(trackIndex, 1);
// }
// });
// } else if (level === ListLevel.SEGMENT) {
// items.forEach((item, index) => {
// let trackIndex = (item as ListTrackSegmentItem).getTrackIndex();
// let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex();
// if (index === items.length - 1) {
// // Order is reversed, so the last segment is the first one and the one to keep
// target = item;
// }
// toMerge.trkseg.splice(
// 0,
// 0,
// originalFile.trk[trackIndex].trkseg[segmentIndex].clone()
// );
// file.trk[trackIndex].trkseg.splice(segmentIndex, 1);
// });
// }
// targetFile = file;
// }
// first = false;
// }
// });
2024-06-08 17:19:22 +02:00
2025-06-21 21:07:36 +02:00
// if (mergeTraces) {
// let statistics = get(gpxStatistics);
// let speed =
// statistics.global.speed.moving > 0 ? statistics.global.speed.moving : undefined;
// let startTime: Date | undefined = undefined;
// if (speed !== undefined) {
// if (
// statistics.local.points.length > 0 &&
// statistics.local.points[0].time !== undefined
// ) {
// startTime = statistics.local.points[0].time;
// } else {
// let index = statistics.local.points.findIndex(
// (point) => point.time !== undefined
// );
// if (index !== -1 && statistics.local.points[index].time) {
// startTime = new Date(
// statistics.local.points[index].time.getTime() -
// (1000 * 3600 * statistics.local.distance.total[index]) / speed
// );
// }
// }
// }
2024-06-18 12:35:24 +02:00
2025-06-21 21:07:36 +02:00
// if (toMerge.trk.length > 0 && toMerge.trk[0].trkseg.length > 0) {
// let s = new TrackSegment();
// toMerge.trk.map((track) => {
// track.trkseg.forEach((segment) => {
// s.replaceTrackPoints(
// s.trkpt.length,
// s.trkpt.length,
// segment.trkpt.slice(),
// speed,
// startTime,
// removeGaps
// );
// });
// });
// toMerge.trk = [toMerge.trk[0]];
// toMerge.trk[0].trkseg = [s];
// }
// if (toMerge.trkseg.length > 0) {
// let s = new TrackSegment();
// toMerge.trkseg.forEach((segment) => {
// s.replaceTrackPoints(
// s.trkpt.length,
// s.trkpt.length,
// segment.trkpt.slice(),
// speed,
// startTime,
// removeGaps
// );
// });
// toMerge.trkseg = [s];
// }
// }
2024-06-08 17:19:22 +02:00
2025-06-21 21:07:36 +02:00
// if (targetFile) {
// if (target instanceof ListFileItem) {
// targetFile.replaceTracks(0, targetFile.trk.length - 1, toMerge.trk);
// targetFile.replaceWaypoints(0, targetFile.wpt.length - 1, toMerge.wpt);
// } else if (target instanceof ListTrackItem) {
// let trackIndex = target.getTrackIndex();
// targetFile.replaceTrackSegments(trackIndex, 0, -1, toMerge.trkseg);
// } else if (target instanceof ListTrackSegmentItem) {
// let trackIndex = target.getTrackIndex();
// let segmentIndex = target.getSegmentIndex();
// targetFile.replaceTrackSegments(
// trackIndex,
// segmentIndex,
// segmentIndex - 1,
// toMerge.trkseg
// );
// }
// }
// });
// },
// cropSelection: (start: number, end: number) => {
// if (get(selection).size === 0) {
// return;
// }
// applyGlobal((draft) => {
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// let file = draft.get(fileId);
// if (file) {
// if (level === ListLevel.FILE) {
// let length = file.getNumberOfTrackPoints();
// if (start >= length || end < 0) {
// draft.delete(fileId);
// } else if (start > 0 || end < length - 1) {
// file.crop(Math.max(0, start), Math.min(length - 1, end));
// }
// start -= length;
// end -= length;
// } else if (level === ListLevel.TRACK) {
// let trackIndices = items.map((item) =>
// (item as ListTrackItem).getTrackIndex()
// );
// file.crop(start, end, trackIndices);
// } else if (level === ListLevel.SEGMENT) {
// let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()];
// let segmentIndices = items.map((item) =>
// (item as ListTrackSegmentItem).getSegmentIndex()
// );
// file.crop(start, end, trackIndices, segmentIndices);
// }
// }
// }, false);
// });
// },
// extractSelection: () => {
// return applyGlobal((draft) => {
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// if (level === ListLevel.FILE) {
// let file = getFile(fileId);
// if (file) {
// if (file.trk.length > 1) {
// let fileIds = getFileIds(file.trk.length);
2024-06-15 18:44:17 +02:00
2025-06-21 21:07:36 +02:00
// let closest = file.wpt.map((wpt, wptIndex) => {
// return {
// wptIndex: wptIndex,
// index: [0],
// distance: Number.MAX_VALUE,
// };
// });
// file.trk.forEach((track, index) => {
// track.getSegments().forEach((segment) => {
// segment.trkpt.forEach((point) => {
// file.wpt.forEach((wpt, wptIndex) => {
// let dist = distance(
// point.getCoordinates(),
// wpt.getCoordinates()
// );
// if (dist < closest[wptIndex].distance) {
// closest[wptIndex].distance = dist;
// closest[wptIndex].index = [index];
// } else if (dist === closest[wptIndex].distance) {
// closest[wptIndex].index.push(index);
// }
// });
// });
// });
// });
2024-06-15 18:44:17 +02:00
2025-06-21 21:07:36 +02:00
// file.trk.forEach((track, index) => {
// let newFile = file.clone();
// let tracks = track.trkseg.map((segment, segmentIndex) => {
// let t = track.clone();
// t.replaceTrackSegments(0, track.trkseg.length - 1, [segment]);
// if (track.name) {
// t.name = `${track.name} (${segmentIndex + 1})`;
// }
// return t;
// });
// newFile.replaceTracks(0, file.trk.length - 1, tracks);
// newFile.replaceWaypoints(
// 0,
// file.wpt.length - 1,
// closest
// .filter((c) => c.index.includes(index))
// .map((c) => file.wpt[c.wptIndex])
// );
// newFile._data.id = fileIds[index];
// newFile.metadata.name =
// track.name ?? `${file.metadata.name} (${index + 1})`;
// draft.set(newFile._data.id, freeze(newFile));
// });
// } else if (file.trk.length === 1) {
// let fileIds = getFileIds(file.trk[0].trkseg.length);
2024-06-15 18:44:17 +02:00
2025-06-21 21:07:36 +02:00
// let closest = file.wpt.map((wpt, wptIndex) => {
// return {
// wptIndex: wptIndex,
// index: [0],
// distance: Number.MAX_VALUE,
// };
// });
// file.trk[0].trkseg.forEach((segment, index) => {
// segment.trkpt.forEach((point) => {
// file.wpt.forEach((wpt, wptIndex) => {
// let dist = distance(
// point.getCoordinates(),
// wpt.getCoordinates()
// );
// if (dist < closest[wptIndex].distance) {
// closest[wptIndex].distance = dist;
// closest[wptIndex].index = [index];
// } else if (dist === closest[wptIndex].distance) {
// closest[wptIndex].index.push(index);
// }
// });
// });
// });
2024-06-15 18:44:17 +02:00
2025-06-21 21:07:36 +02:00
// file.trk[0].trkseg.forEach((segment, index) => {
// let newFile = file.clone();
// newFile.replaceTrackSegments(0, 0, file.trk[0].trkseg.length - 1, [
// segment,
// ]);
// newFile.replaceWaypoints(
// 0,
// file.wpt.length - 1,
// closest
// .filter((c) => c.index.includes(index))
// .map((c) => file.wpt[c.wptIndex])
// );
// newFile._data.id = fileIds[index];
// newFile.metadata.name = `${file.trk[0].name ?? file.metadata.name} (${index + 1})`;
// draft.set(newFile._data.id, freeze(newFile));
// });
// }
// draft.delete(fileId);
// }
// } else if (level === ListLevel.TRACK) {
// let file = draft.get(fileId);
// if (file) {
// for (let item of items) {
// let trackIndex = (item as ListTrackItem).getTrackIndex();
// let track = file.trk[trackIndex];
// let tracks = track.trkseg.map((segment, segmentIndex) => {
// let t = track.clone();
// t.replaceTrackSegments(0, track.trkseg.length - 1, [segment]);
// if (track.name) {
// t.name = `${track.name} (${segmentIndex + 1})`;
// }
// return t;
// });
// file.replaceTracks(trackIndex, trackIndex, tracks);
// }
// }
// }
// });
// });
// },
// split(
// splitType: SplitType,
// fileId: string,
// trackIndex: number,
// segmentIndex: number,
// coordinates: Coordinates,
// trkptIndex?: number
// ) {
// return applyGlobal((draft) => {
// let file = getFile(fileId);
// if (file) {
// let segment = file.trk[trackIndex].trkseg[segmentIndex];
2024-06-10 20:03:57 +02:00
2025-06-21 21:07:36 +02:00
// let minIndex = 0;
// if (trkptIndex === undefined) {
// // Find the point closest to split
// let closest = getClosestLinePoint(segment.trkpt, coordinates);
// minIndex = closest._data.index;
// } else {
// minIndex = trkptIndex;
// }
2024-06-10 20:03:57 +02:00
2025-06-21 21:07:36 +02:00
// let absoluteIndex = minIndex;
// file.forEachSegment((seg, trkIndex, segIndex) => {
// if (
// (trkIndex < trackIndex && splitType === SplitType.FILES) ||
// (trkIndex === trackIndex && segIndex < segmentIndex)
// ) {
// absoluteIndex += seg.trkpt.length;
// }
// });
2024-06-10 20:03:57 +02:00
2025-06-21 21:07:36 +02:00
// if (splitType === SplitType.FILES) {
// let newFile = draft.get(fileId);
// if (newFile) {
// newFile.crop(0, absoluteIndex);
// let newFile2 = file.clone();
// newFile2._data.id = getFileIds(1)[0];
// newFile2.crop(absoluteIndex, file.getNumberOfTrackPoints() - 1);
// draft.set(newFile2._data.id, freeze(newFile2));
// }
// } else if (splitType === SplitType.TRACKS) {
// let newFile = draft.get(fileId);
// if (newFile) {
// let start = file.trk[trackIndex].clone();
// start.crop(0, absoluteIndex);
// let end = file.trk[trackIndex].clone();
// end.crop(absoluteIndex, file.trk[trackIndex].getNumberOfTrackPoints() - 1);
// newFile.replaceTracks(trackIndex, trackIndex, [start, end]);
// }
// } else if (splitType === SplitType.SEGMENTS) {
// let newFile = draft.get(fileId);
// if (newFile) {
// let start = segment.clone();
// start.crop(0, minIndex);
// let end = segment.clone();
// end.crop(minIndex, segment.trkpt.length - 1);
// newFile.replaceTrackSegments(trackIndex, segmentIndex, segmentIndex, [
// start,
// end,
// ]);
// }
// }
// }
// });
// },
// cleanSelection: (
// bounds: [Coordinates, Coordinates],
// inside: boolean,
// deleteTrackPoints: boolean,
// deleteWaypoints: boolean
// ) => {
// if (get(selection).size === 0) {
// return;
// }
// applyGlobal((draft) => {
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// let file = draft.get(fileId);
// if (file) {
// if (level === ListLevel.FILE) {
// file.clean(bounds, inside, deleteTrackPoints, deleteWaypoints);
// } else if (level === ListLevel.TRACK) {
// let trackIndices = items.map((item) =>
// (item as ListTrackItem).getTrackIndex()
// );
// file.clean(
// bounds,
// inside,
// deleteTrackPoints,
// deleteWaypoints,
// trackIndices
// );
// } else if (level === ListLevel.SEGMENT) {
// let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()];
// let segmentIndices = items.map((item) =>
// (item as ListTrackSegmentItem).getSegmentIndex()
// );
// file.clean(
// bounds,
// inside,
// deleteTrackPoints,
// deleteWaypoints,
// trackIndices,
// segmentIndices
// );
// } else if (level === ListLevel.WAYPOINTS) {
// file.clean(bounds, inside, false, deleteWaypoints);
// } else if (level === ListLevel.WAYPOINT) {
// let waypointIndices = items.map((item) =>
// (item as ListWaypointItem).getWaypointIndex()
// );
// file.clean(bounds, inside, false, deleteWaypoints, [], [], waypointIndices);
// }
// }
// });
// });
// },
// reduce: (itemsAndPoints: Map<ListItem, TrackPoint[]>) => {
// if (itemsAndPoints.size === 0) {
// return;
// }
// applyGlobal((draft) => {
// let allItems = Array.from(itemsAndPoints.keys());
// applyToOrderedItemsFromFile(allItems, (fileId, level, items) => {
// let file = draft.get(fileId);
// if (file) {
// for (let item of items) {
// if (item instanceof ListTrackSegmentItem) {
// let trackIndex = item.getTrackIndex();
// let segmentIndex = item.getSegmentIndex();
// let points = itemsAndPoints.get(item);
// if (points) {
// file.replaceTrackPoints(
// trackIndex,
// segmentIndex,
// 0,
// file.trk[trackIndex].trkseg[
// segmentIndex
// ].getNumberOfTrackPoints() - 1,
// points
// );
// }
// }
// }
// }
// });
// });
// },
// addOrUpdateWaypoint: (waypoint: WaypointType, item?: ListWaypointItem) => {
// getElevation([waypoint.attributes]).then((elevation) => {
// if (item) {
// dbUtils.applyToFile(item.getFileId(), (file) => {
// let wpt = file.wpt[item.getWaypointIndex()];
// wpt.name = waypoint.name;
// wpt.desc = waypoint.desc;
// wpt.cmt = waypoint.cmt;
// wpt.sym = waypoint.sym;
// wpt.link = waypoint.link;
// wpt.setCoordinates(waypoint.attributes);
// wpt.ele = elevation[0];
// });
// } else {
// let fileIds = new Set<string>();
// get(selection)
// .getSelected()
// .forEach((item) => {
// fileIds.add(item.getFileId());
// });
// let wpt = new Waypoint(waypoint);
// wpt.ele = elevation[0];
// dbUtils.applyToFiles(Array.from(fileIds), (file) =>
// file.replaceWaypoints(file.wpt.length, file.wpt.length, [wpt])
// );
// }
// });
// },
// setStyleToSelection: (style: LineStyleExtension) => {
// if (get(selection).size === 0) {
// return;
// }
// applyGlobal((draft) => {
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// let file = draft.get(fileId);
// if (file && (level === ListLevel.FILE || level === ListLevel.TRACK)) {
// if (level === ListLevel.FILE) {
// file.setStyle(style);
// } else if (level === ListLevel.TRACK) {
// if (items.length === file.trk.length) {
// file.setStyle(style);
// } else {
// for (let item of items) {
// let trackIndex = (item as ListTrackItem).getTrackIndex();
// file.trk[trackIndex].setStyle(style);
// }
// }
// }
// }
// });
// });
// },
// setHiddenToSelection: (hidden: boolean) => {
// if (get(selection).size === 0) {
// return;
// }
// applyGlobal((draft) => {
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// let file = draft.get(fileId);
// if (file) {
// if (level === ListLevel.FILE) {
// file.setHidden(hidden);
// } else if (level === ListLevel.TRACK) {
// let trackIndices = items.map((item) =>
// (item as ListTrackItem).getTrackIndex()
// );
// file.setHidden(hidden, trackIndices);
// } else if (level === ListLevel.SEGMENT) {
// let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()];
// let segmentIndices = items.map((item) =>
// (item as ListTrackSegmentItem).getSegmentIndex()
// );
// file.setHidden(hidden, trackIndices, segmentIndices);
// } else if (level === ListLevel.WAYPOINTS) {
// file.setHiddenWaypoints(hidden);
// } else if (level === ListLevel.WAYPOINT) {
// let waypointIndices = items.map((item) =>
// (item as ListWaypointItem).getWaypointIndex()
// );
// file.setHiddenWaypoints(hidden, waypointIndices);
// }
// }
// });
// });
// },
// deleteSelection: () => {
// if (get(selection).size === 0) {
// return;
// }
// applyGlobal((draft) => {
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// if (level === ListLevel.FILE) {
// draft.delete(fileId);
// } else {
// let file = draft.get(fileId);
// if (file) {
// if (level === ListLevel.TRACK) {
// for (let item of items) {
// let trackIndex = (item as ListTrackItem).getTrackIndex();
// file.replaceTracks(trackIndex, trackIndex, []);
// }
// } else if (level === ListLevel.SEGMENT) {
// for (let item of items) {
// let trackIndex = (item as ListTrackSegmentItem).getTrackIndex();
// let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex();
// file.replaceTrackSegments(
// trackIndex,
// segmentIndex,
// segmentIndex,
// []
// );
// }
// } else if (level === ListLevel.WAYPOINTS) {
// file.replaceWaypoints(0, file.wpt.length - 1, []);
// } else if (level === ListLevel.WAYPOINT) {
// for (let item of items) {
// let waypointIndex = (item as ListWaypointItem).getWaypointIndex();
// file.replaceWaypoints(waypointIndex, waypointIndex, []);
// }
// }
// }
// }
// });
// });
// },
// addElevationToSelection: async (map: mapboxgl.Map) => {
// if (get(selection).size === 0) {
// return;
// }
// let points: (TrackPoint | Waypoint)[] = [];
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// let file = fileState.get(fileId);
// if (file) {
// if (level === ListLevel.FILE) {
// points.push(...file.getTrackPoints());
// points.push(...file.wpt);
// } else if (level === ListLevel.TRACK) {
// let trackIndices = items.map((item) => (item as ListTrackItem).getTrackIndex());
// trackIndices.forEach((trackIndex) => {
// points.push(...file.trk[trackIndex].getTrackPoints());
// });
// } else if (level === ListLevel.SEGMENT) {
// let trackIndex = (items[0] as ListTrackSegmentItem).getTrackIndex();
// let segmentIndices = items.map((item) =>
// (item as ListTrackSegmentItem).getSegmentIndex()
// );
// segmentIndices.forEach((segmentIndex) => {
// points.push(...file.trk[trackIndex].trkseg[segmentIndex].getTrackPoints());
// });
// } else if (level === ListLevel.WAYPOINTS) {
// points.push(...file.wpt);
// } else if (level === ListLevel.WAYPOINT) {
// let waypointIndices = items.map((item) =>
// (item as ListWaypointItem).getWaypointIndex()
// );
// points.push(...waypointIndices.map((waypointIndex) => file.wpt[waypointIndex]));
// }
// }
// });
2024-07-19 13:18:38 +02:00
2025-06-21 21:07:36 +02:00
// if (points.length === 0) {
// return;
// }
2024-09-10 17:42:36 +02:00
2025-06-21 21:07:36 +02:00
// getElevation(points).then((elevations) => {
// applyGlobal((draft) => {
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// let file = draft.get(fileId);
// if (file) {
// if (level === ListLevel.FILE) {
// file.addElevation(elevations);
// } else if (level === ListLevel.TRACK) {
// let trackIndices = items.map((item) =>
// (item as ListTrackItem).getTrackIndex()
// );
// file.addElevation(elevations, trackIndices, undefined, []);
// } else if (level === ListLevel.SEGMENT) {
// let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()];
// let segmentIndices = items.map((item) =>
// (item as ListTrackSegmentItem).getSegmentIndex()
// );
// file.addElevation(elevations, trackIndices, segmentIndices, []);
// } else if (level === ListLevel.WAYPOINTS) {
// file.addElevation(elevations, [], [], undefined);
// } else if (level === ListLevel.WAYPOINT) {
// let waypointIndices = items.map((item) =>
// (item as ListWaypointItem).getWaypointIndex()
// );
// file.addElevation(elevations, [], [], waypointIndices);
// }
// }
// });
// });
// });
// },
// deleteSelectedFiles: () => {
// if (get(selection).size === 0) {
// return;
// }
// applyGlobal((draft) => {
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
// draft.delete(fileId);
// });
// });
// },
// deleteAllFiles: () => {
// applyGlobal((draft) => {
// draft.clear();
// });
// },
// // undo-redo
// undo: () => {
// if (get(canUndo)) {
// let index = get(patchIndex);
// db.patches.get(index).then((patch) => {
// if (patch) {
// applyPatch(patch.inversePatch);
// db.settings.put(index - 1, 'patchIndex');
// }
// });
// }
// },
// redo: () => {
// if (get(canRedo)) {
// let index = get(patchIndex) + 1;
// db.patches.get(index).then((patch) => {
// if (patch) {
// applyPatch(patch.patch);
// db.settings.put(index, 'patchIndex');
// }
// });
// }
// },
// };