// 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'; enableMapSet(); enablePatches(); export class Database extends Dexie { fileids!: Dexie.Table; files!: Dexie.Table; patches!: Dexie.Table<{ patch: Patch[]; inversePatch: Patch[]; index: number }, number>; settings!: Dexie.Table; 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] >; constructor() { super('Database', { cache: 'immutable', }); this.version(1).stores({ fileids: ',&fileid', files: '', patches: ',patch', settings: '', overpasstiles: '[query+x+y],[x+y]', overpassdata: '[query+id]', }); } } export const db = new Database(); // Wrap Dexie live queries in a Svelte store to avoid triggering the query for every subscriber // function dexieStore(querier: () => T | Promise, initial?: T): Readable { // let store = writable(initial); // liveQuery(querier).subscribe((value) => { // if (value !== undefined) { // store.set(value); // } // }); // return { // subscribe: store.subscribe, // }; // } // 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 }; // // 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 & { destroy: () => void } { // let store = writable(undefined); // let query = liveQuery(() => db.files.get(id)).subscribe((value) => { // if (value !== undefined) { // let gpx = new GPXFile(value); // updateAnchorPoints(gpx); // 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 // ); // } // fileState.set(id, gpx); // store.set({ // file: gpx, // statistics, // }); // if (get(selection).hasAnyChildren(new ListFileItem(id))) { // updateAllHidden(); // } // } // }); // return { // subscribe: store.subscribe, // destroy: () => { // fileState.delete(id); // query.unsubscribe(); // }, // }; // } // function updateSelection(updatedFiles: GPXFile[], deletedFileIds: string[]) { // let removedItems: ListItem[] = []; // 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); // }); // } // }); // 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; // }); // } // } // // Commit the changes to the file state to the database // function commitFileStateChange(newFileState: ReadonlyMap, patch: Patch[]) { // let changedFileIds = getChangedFileIds(patch); // let updatedFileIds: string[] = [], // deletedFileIds: string[] = []; // changedFileIds.forEach((id) => { // if (newFileState.has(id)) { // updatedFileIds.push(id); // } else { // deletedFileIds.push(id); // } // }); // let updatedFiles = updatedFileIds // .map((id) => newFileState.get(id)) // .filter((file) => file !== undefined) as GPXFile[]; // updatedFileIds = updatedFiles.map((file) => file._data.id); // updateSelection(updatedFiles, deletedFileIds); // 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); // } // }); // } // export const fileObservers: Writable< // Map & { destroy?: () => void }> // > = writable(new Map()); // const fileState: Map = new Map(); // Used to generate patches // // 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) // ); // // 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; // }); // } // }); // } // export function getFile(fileId: string): GPXFile | undefined { // let fileStore = get(fileObservers).get(fileId); // return fileStore ? get(fileStore)?.file : undefined; // } // export function getStatistics(fileId: string): GPXStatisticsTree | undefined { // let fileStore = get(fileObservers).get(fileId); // return fileStore ? get(fileStore)?.statistics : undefined; // } // const patchIndex: Readable = dexieStore(() => db.settings.get('patchIndex'), -1); // const patchMinMaxIndex: Readable<{ min: number; max: number }> = dexieStore( // () => // (db.patches.orderBy(':id').keys() as Promise).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 = derived( // [patchIndex, patchMinMaxIndex], // ([$patchIndex, $patchMinMaxIndex]) => $patchIndex >= $patchMinMaxIndex.min // ); // export const canRedo: Readable = derived( // [patchIndex, patchMinMaxIndex], // ([$patchIndex, $patchMinMaxIndex]) => $patchIndex < $patchMinMaxIndex.max - 1 // ); // // Helper function to apply a callback to the global file state // function applyGlobal(callback: (files: Map) => void) { // const [newFileState, patch, inversePatch] = produceWithPatches(fileState, callback); // storePatches(patch, inversePatch); // return commitFileStateChange(newFileState, patch); // } // // Helper function to apply a callback to multiple files // function applyToFiles(fileIds: string[], callback: (file: WritableDraft) => void) { // const [newFileState, patch, inversePatch] = produceWithPatches(fileState, (draft) => { // fileIds.forEach((fileId) => { // let file = draft.get(fileId); // if (file) { // callback(file); // } // }); // }); // storePatches(patch, inversePatch); // return commitFileStateChange(newFileState, patch); // } // // Helper function to apply different callbacks to multiple files // function applyEachToFilesAndGlobal( // fileIds: string[], // callbacks: ((file: WritableDraft, context?: any) => void)[], // globalCallback: (files: Map, 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); // }); // storePatches(patch, inversePatch); // return commitFileStateChange(newFileState, patch); // } // 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'); // }); // } // // Apply a patch to the file state // function applyPatch(patch: Patch[]) { // let newFileState = applyPatches(fileState, patch); // return commitFileStateChange(newFileState, patch); // } // // Get the file ids of the files that have changed in the patch // function getChangedFileIds(patch: Patch[]): string[] { // let changedFileIds = new Set(); // for (let p of patch) { // changedFileIds.add(p.path[0] as string); // } // return Array.from(changedFileIds); // } // // 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; // } // // 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) => void) => { // applyToFiles([id], callback); // }, // applyToFiles: (ids: string[], callback: (file: WritableDraft) => void) => { // applyToFiles(ids, callback); // }, // applyEachToFilesAndGlobal: ( // ids: string[], // callbacks: ((file: WritableDraft, context?: any) => void)[], // globalCallback: (files: Map, 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; // } // }); // 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 // ); // } // } // } // 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]; // } // } // 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); // 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); // } // }); // }); // }); // }); // 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); // 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); // } // }); // }); // }); // 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]; // 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; // } // let absoluteIndex = minIndex; // file.forEachSegment((seg, trkIndex, segIndex) => { // if ( // (trkIndex < trackIndex && splitType === SplitType.FILES) || // (trkIndex === trackIndex && segIndex < segmentIndex) // ) { // absoluteIndex += seg.trkpt.length; // } // }); // 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) => { // 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(); // 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])); // } // } // }); // if (points.length === 0) { // return; // } // 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'); // } // }); // } // }, // };