diff --git a/gpx/src/gpx.ts b/gpx/src/gpx.ts index 90a2cb5b..6884ab82 100644 --- a/gpx/src/gpx.ts +++ b/gpx/src/gpx.ts @@ -412,10 +412,10 @@ export class TrackSegment extends GPXTreeLeaf { // Producers replace(segment: number, start: number, end: number, points: TrackPoint[]) { return produce(this, (draft) => { - let og = getOriginal(draft); + let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let trkpt = og.trkpt.slice(); trkpt.splice(start, end - start + 1, ...points); - draft.trkpt = freeze(trkpt); + draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well }); } diff --git a/website/src/lib/components/toolbar/tools/routing/RoutingControls.ts b/website/src/lib/components/toolbar/tools/routing/RoutingControls.ts index 527f8914..6f068778 100644 --- a/website/src/lib/components/toolbar/tools/routing/RoutingControls.ts +++ b/website/src/lib/components/toolbar/tools/routing/RoutingControls.ts @@ -432,17 +432,18 @@ export class RoutingControls { return false; } - let start = anchors[0].point._data.index + 1; - let end = anchors[anchors.length - 1].point._data.index - 1; - if (anchors[0].point._data.index === 0) { // First anchor is the first point of the segment - anchors[0].point = response[0]; // Update the first anchor in case it was not on a road - start--; // Remove the original first point + anchors[0].point = response[0]; // Update the first anchor + anchors[0].point._data.index = 0; + } else { + response.splice(0, 0, anchors[0].point); // Keep the original start point } if (anchors[anchors.length - 1].point._data.index === segment.trkpt.length - 1) { // Last anchor is the last point of the segment - anchors[anchors.length - 1].point = response[response.length - 1]; // Update the last anchor in case it was not on a road - end++; // Remove the original last point + anchors[anchors.length - 1].point = response[response.length - 1]; // Update the last anchor + anchors[anchors.length - 1].point._data.index = segment.trkpt.length - 1; + } else { + response.push(anchors[anchors.length - 1].point); // Keep the original end point } for (let i = 1; i < anchors.length - 1; i++) { @@ -465,7 +466,7 @@ export class RoutingControls { anchor.point._data.zoom = 0; // Make these anchors permanent }); - dbUtils.applyToFile(this.fileId, (file) => file.replace(anchors[0].segmentIndex, start, end, response)); + dbUtils.applyToFile(this.fileId, (file) => file.replace(anchors[0].segmentIndex, anchors[0].point._data.index, anchors[anchors.length - 1].point._data.index, response)); return true; } diff --git a/website/src/lib/components/toolbar/tools/routing/Simplify.ts b/website/src/lib/components/toolbar/tools/routing/Simplify.ts index 54c13655..5e856100 100644 --- a/website/src/lib/components/toolbar/tools/routing/Simplify.ts +++ b/website/src/lib/components/toolbar/tools/routing/Simplify.ts @@ -26,7 +26,7 @@ export function computeAnchorPoints(segment: TrackSegment) { segment._data.anchors = true; } -export function ramerDouglasPeucker(points: TrackPoint[], epsilon: number = 50, start: number = 0, end: number = points.length - 1): SimplifiedTrackPoint[] { +export function ramerDouglasPeucker(points: readonly TrackPoint[], epsilon: number = 50, start: number = 0, end: number = points.length - 1): SimplifiedTrackPoint[] { if (points.length == 0) { return []; } else if (points.length == 1) { @@ -45,7 +45,7 @@ export function ramerDouglasPeucker(points: TrackPoint[], epsilon: number = 50, return simplified; } -function ramerDouglasPeuckerRecursive(points: TrackPoint[], epsilon: number, start: number, end: number, simplified: SimplifiedTrackPoint[]) { +function ramerDouglasPeuckerRecursive(points: readonly TrackPoint[], epsilon: number, start: number, end: number, simplified: SimplifiedTrackPoint[]) { let largest = { index: 0, distance: 0 diff --git a/website/src/lib/db.ts b/website/src/lib/db.ts index 346f2f5d..cb094f8d 100644 --- a/website/src/lib/db.ts +++ b/website/src/lib/db.ts @@ -1,6 +1,6 @@ import Dexie, { liveQuery } from 'dexie'; import { GPXFile, GPXStatistics } from 'gpx'; -import { enableMapSet, enablePatches, produceWithPatches, applyPatches, type Patch, type WritableDraft, castDraft } from 'immer'; +import { enableMapSet, enablePatches, applyPatches, type Patch, type WritableDraft, castDraft, Immer } from 'immer'; import { writable, get, derived, type Readable, type Writable } from 'svelte/store'; import { fileOrder, initTargetMapBounds, selectedFiles, updateTargetMapBounds } from './stores'; import { mode } from 'mode-watcher'; @@ -9,6 +9,8 @@ import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays enableMapSet(); enablePatches(); +const noFreezeImmer = new Immer({ autoFreeze: false }); // Do not freeze files that are not concerned by an update, otherwise cannot assign anchors for them + class Database extends Dexie { fileids!: Dexie.Table; @@ -210,7 +212,7 @@ export const canRedo: Readable = derived([patchIndex, patchMinMaxIndex] // Helper function to apply a callback to the global file state function applyGlobal(callback: (files: Map) => void) { - const [newFileState, patch, inversePatch] = produceWithPatches(fileState, callback); + const [newFileState, patch, inversePatch] = noFreezeImmer.produceWithPatches(fileState, callback); storePatches(patch, inversePatch); @@ -219,7 +221,7 @@ function applyGlobal(callback: (files: Map) => void) { // Helper function to apply a callback to multiple files function applyToFiles(fileIds: string[], callback: (file: WritableDraft) => GPXFile) { - const [newFileState, patch, inversePatch] = produceWithPatches(fileState, (draft) => { + const [newFileState, patch, inversePatch] = noFreezeImmer.produceWithPatches(fileState, (draft) => { fileIds.forEach((fileId) => { let file = draft.get(fileId); if (file) {