real incremental immer patches

This commit is contained in:
vcoppe
2024-07-04 02:17:50 +02:00
parent affc3ed946
commit 8643abfafa
16 changed files with 469 additions and 630 deletions

View File

@@ -68,33 +68,31 @@ abstract class GPXTreeNode<T extends GPXTreeElement<any>> extends GPXTreeElement
// Producers // Producers
_reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date) { _reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date) {
return produce(this, (draft: Draft<GPXTreeNode<T>>) => { let og = getOriginal(this);
let og = getOriginal(draft); if (!originalNextTimestamp && !newPreviousTimestamp) {
if (!originalNextTimestamp && !newPreviousTimestamp) { originalNextTimestamp = og.getEndTimestamp();
originalNextTimestamp = og.getEndTimestamp(); newPreviousTimestamp = og.getStartTimestamp();
newPreviousTimestamp = og.getStartTimestamp(); }
}
let children = og.children.slice(); let children = og.children.slice();
children.reverse(); children.reverse();
for (let i = 0; i < og.children.length; i++) { for (let i = 0; i < og.children.length; i++) {
let originalStartTimestamp = og.children[og.children.length - i - 1].getStartTimestamp(); let originalStartTimestamp = og.children[og.children.length - i - 1].getStartTimestamp();
children[i] = children[i]._reverse(originalNextTimestamp, newPreviousTimestamp); children[i]._reverse(originalNextTimestamp, newPreviousTimestamp);
originalNextTimestamp = originalStartTimestamp; originalNextTimestamp = originalStartTimestamp;
newPreviousTimestamp = children[i].getEndTimestamp(); newPreviousTimestamp = children[i].getEndTimestamp();
} }
if (this instanceof GPXFile) { if (this instanceof GPXFile) {
// @ts-ignore // @ts-ignore
draft.trk = freeze(children); this.trk = freeze(children);
} else if (this instanceof Track) { } else if (this instanceof Track) {
// @ts-ignore // @ts-ignore
draft.trkseg = freeze(children); this.trkseg = freeze(children);
} }
});
} }
} }
@@ -115,8 +113,8 @@ export class GPXFile extends GPXTreeNode<Track>{
attributes: GPXFileAttributes; attributes: GPXFileAttributes;
metadata: Metadata; metadata: Metadata;
readonly wpt: ReadonlyArray<Readonly<Waypoint>>; wpt: Waypoint[];
readonly trk: ReadonlyArray<Track>; trk: Track[];
constructor(gpx?: GPXFileType & { _data?: any } | GPXFile) { constructor(gpx?: GPXFileType & { _data?: any } | GPXFile) {
super(); super();
@@ -211,51 +209,23 @@ export class GPXFile extends GPXTreeNode<Track>{
} }
// Producers // Producers
replaceTracks(start: number, end: number, tracks: Track[]): [GPXFile, Track[]] { replaceTracks(start: number, end: number, tracks: Track[]): Track[] {
let removed = []; if (this._data.style) {
let result = produce(this, (draft) => { tracks.forEach((track) => track.setStyle(this._data.style, false));
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster }
if (og._data.style) { return this.trk.splice(start, end - start + 1, ...tracks);
tracks = tracks.map((track) => track.setStyle(og._data.style, false));
}
let trk = og.trk.slice();
removed = trk.splice(start, end - start + 1, ...tracks);
draft.trk = freeze(trk); // Pre-freeze the array, faster as well
});
return [result, removed];
} }
replaceTrackSegments(trackIndex: number, start: number, end: number, segments: TrackSegment[]): [GPXFile, TrackSegment[]] { replaceTrackSegments(trackIndex: number, start: number, end: number, segments: TrackSegment[]): TrackSegment[] {
let removed = []; return this.trk[trackIndex].replaceTrackSegments(start, end, segments);
let result = produce(this, (draft) => {
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let trk = og.trk.slice();
let [result, rmv] = trk[trackIndex].replaceTrackSegments(start, end, segments);
trk[trackIndex] = result;
removed = rmv;
draft.trk = freeze(trk); // Pre-freeze the array, faster as well
});
return [result, removed];
} }
replaceTrackPoints(trackIndex: number, segmentIndex: number, start: number, end: number, points: TrackPoint[], speed?: number, startTime?: Date) { replaceTrackPoints(trackIndex: number, segmentIndex: number, start: number, end: number, points: TrackPoint[], speed?: number, startTime?: Date) {
return produce(this, (draft) => { this.trk[trackIndex].replaceTrackPoints(segmentIndex, start, end, points, speed, startTime);
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let trk = og.trk.slice();
trk[trackIndex] = trk[trackIndex].replaceTrackPoints(segmentIndex, start, end, points, speed, startTime);
draft.trk = freeze(trk); // Pre-freeze the array, faster as well
});
} }
replaceWaypoints(start: number, end: number, waypoints: Waypoint[]): [GPXFile, Waypoint[]] { replaceWaypoints(start: number, end: number, waypoints: Waypoint[]): Waypoint[] {
let removed = []; return this.wpt.splice(start, end - start + 1, ...waypoints);
let result = produce(this, (draft) => {
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let wpt = og.wpt.slice();
removed = wpt.splice(start, end - start + 1, ...waypoints);
draft.wpt = freeze(wpt); // Pre-freeze the array, faster as well
});
return [result, removed];
} }
reverse() { reverse() {
@@ -263,152 +233,117 @@ export class GPXFile extends GPXTreeNode<Track>{
} }
reverseTrack(trackIndex: number) { reverseTrack(trackIndex: number) {
return produce(this, (draft) => { this.trk[trackIndex]._reverse();
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let trk = og.trk.slice();
trk[trackIndex] = trk[trackIndex]._reverse();
draft.trk = freeze(trk); // Pre-freeze the array, faster as well
});
} }
reverseTrackSegment(trackIndex: number, segmentIndex: number) { reverseTrackSegment(trackIndex: number, segmentIndex: number) {
return produce(this, (draft) => { this.trk[trackIndex].reverseTrackSegment(segmentIndex);
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let trk = og.trk.slice();
trk[trackIndex] = trk[trackIndex].reverseTrackSegment(segmentIndex);
draft.trk = freeze(trk); // Pre-freeze the array, faster as well
});
} }
crop(start: number, end: number, trackIndices?: number[], segmentIndices?: number[]) { crop(start: number, end: number, trackIndices?: number[], segmentIndices?: number[]) {
return produce(this, (draft) => { let i = 0;
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let trackIndex = 0;
let trk = og.trk.slice(); while (i < this.trk.length) {
let length = this.trk[i].getNumberOfTrackPoints();
if (trackIndices === undefined || trackIndices.includes(trackIndex)) {
if (start >= length || end < 0) {
this.trk.splice(i, 1);
} else {
if (start > 0 || end < length - 1) {
this.trk[i].crop(Math.max(0, start), Math.min(length - 1, end), segmentIndices);
}
i++;
}
start -= length;
end -= length;
} else {
i++;
}
trackIndex++;
}
}
clean(bounds: [Coordinates, Coordinates], inside: boolean, deleteTrackPoints: boolean, deleteWaypoints: boolean, trackIndices?: number[], segmentIndices?: number[], waypointIndices?: number[]) {
if (deleteTrackPoints) {
let i = 0; let i = 0;
let trackIndex = 0; let trackIndex = 0;
while (i < trk.length) { while (i < this.trk.length) {
let length = trk[i].getNumberOfTrackPoints();
if (trackIndices === undefined || trackIndices.includes(trackIndex)) { if (trackIndices === undefined || trackIndices.includes(trackIndex)) {
if (start >= length || end < 0) { this.trk[i].clean(bounds, inside, segmentIndices);
trk.splice(i, 1); if (this.trk[i].getNumberOfTrackPoints() === 0) {
this.trk.splice(i, 1);
} else { } else {
if (start > 0 || end < length - 1) {
trk[i] = trk[i].crop(Math.max(0, start), Math.min(length - 1, end), segmentIndices);
}
i++; i++;
} }
start -= length;
end -= length;
} else { } else {
i++; i++;
} }
trackIndex++; trackIndex++;
} }
draft.trk = freeze(trk); // Pre-freeze the array, faster as well }
}); if (deleteWaypoints) {
} let og = getOriginal(this); // Read as much as possible from the original object because it is faster
let wpt = og.wpt.filter((point, waypointIndex) => {
clean(bounds: [Coordinates, Coordinates], inside: boolean, deleteTrackPoints: boolean, deleteWaypoints: boolean, trackIndices?: number[], segmentIndices?: number[], waypointIndices?: number[]) { if (waypointIndices === undefined || waypointIndices.includes(waypointIndex)) {
return produce(this, (draft) => { let inBounds = point.attributes.lat >= bounds[0].lat && point.attributes.lat <= bounds[1].lat && point.attributes.lon >= bounds[0].lon && point.attributes.lon <= bounds[1].lon;
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster return inBounds !== inside;
if (deleteTrackPoints) { } else {
let trk = og.trk.slice(); return true;
let i = 0;
let trackIndex = 0;
while (i < trk.length) {
if (trackIndices === undefined || trackIndices.includes(trackIndex)) {
trk[i] = trk[i].clean(bounds, inside, segmentIndices);
if (trk[i].getNumberOfTrackPoints() === 0) {
trk.splice(i, 1);
} else {
i++;
}
} else {
i++;
}
trackIndex++;
} }
draft.trk = freeze(trk); // Pre-freeze the array, faster as well });
} this.wpt = freeze(wpt); // Pre-freeze the array, faster as well
if (deleteWaypoints) { }
let wpt = og.wpt.filter((point, waypointIndex) => {
if (waypointIndices === undefined || waypointIndices.includes(waypointIndex)) {
let inBounds = point.attributes.lat >= bounds[0].lat && point.attributes.lat <= bounds[1].lat && point.attributes.lon >= bounds[0].lon && point.attributes.lon <= bounds[1].lon;
return inBounds !== inside;
} else {
return true;
}
});
draft.wpt = freeze(wpt); // Pre-freeze the array, faster as well
}
});
} }
changeTimestamps(startTime: Date, speed: number, ratio: number, trackIndex?: number, segmentIndex?: number) { changeTimestamps(startTime: Date, speed: number, ratio: number, trackIndex?: number, segmentIndex?: number) {
let lastPoint = undefined; let lastPoint = undefined;
return produce(this, (draft) => { this.trk.forEach((track, index) => {
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster if (trackIndex === undefined || trackIndex === index) {
let trk = og.trk.map((track, index) => { track.changeTimestamps(startTime, speed, ratio, lastPoint, segmentIndex);
if (trackIndex === undefined || trackIndex === index) { }
return track.changeTimestamps(startTime, speed, ratio, lastPoint, segmentIndex);
} else {
return track;
}
});
draft.trk = freeze(trk); // Pre-freeze the array, faster as well
}); });
} }
setStyle(style: LineStyleExtension) { setStyle(style: LineStyleExtension) {
return produce(this, (draft) => { this.trk.forEach((track) => {
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster track.setStyle(style);
let trk = og.trk.map((track) => track.setStyle(style));
draft.trk = freeze(trk); // Pre-freeze the array, faster as well
if (!draft._data.style) {
draft._data.style = {};
}
if (style.color) {
draft._data.style.color = style.color;
}
if (style.opacity) {
draft._data.style.opacity = style.opacity;
}
if (style.weight) {
draft._data.style.weight = style.weight;
}
}); });
if (!this._data.style) {
this._data.style = {};
}
if (style.color) {
this._data.style.color = style.color;
}
if (style.opacity) {
this._data.style.opacity = style.opacity;
}
if (style.weight) {
this._data.style.weight = style.weight;
}
} }
setHidden(hidden: boolean, trackIndices?: number[], segmentIndices?: number[]) { setHidden(hidden: boolean, trackIndices?: number[], segmentIndices?: number[]) {
return produce(this, (draft) => { let allHidden = hidden;
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster this.trk.forEach((track, index) => {
let allHidden = hidden; if (trackIndices === undefined || trackIndices.includes(index)) {
let trk = og.trk.map((track, index) => { track.setHidden(hidden, segmentIndices);
if (trackIndices === undefined || trackIndices.includes(index)) { } else {
return track.setHidden(hidden, segmentIndices); allHidden = allHidden && (track._data.hidden === true);
} else {
allHidden = allHidden && (track._data.hidden === true);
return track;
}
});
draft.trk = freeze(trk); // Pre-freeze the array, faster as well
let wpt = og.wpt.map((waypoint) => {
if (trackIndices === undefined && segmentIndices === undefined) {
return waypoint.setHidden(hidden);
} else {
allHidden = allHidden && (waypoint._data.hidden === true);
return waypoint;
}
});
draft.wpt = freeze(wpt); // Pre-freeze the array, faster as well
if (trackIndices === undefined && segmentIndices === undefined) {
draft._data.hiddenWpt = hidden;
} }
draft._data.hidden = allHidden;
}); });
this.wpt.forEach((waypoint) => {
if (trackIndices === undefined && segmentIndices === undefined) {
waypoint.setHidden(hidden);
} else {
allHidden = allHidden && (waypoint._data.hidden === true);
}
});
if (trackIndices === undefined && segmentIndices === undefined) {
this._data.hiddenWpt = hidden;
}
this._data.hidden = allHidden;
} }
setHiddenWaypoints(hidden: boolean, waypointIndices?: number[]) { setHiddenWaypoints(hidden: boolean, waypointIndices?: number[]) {
@@ -441,14 +376,14 @@ export class GPXFile extends GPXTreeNode<Track>{
export class Track extends GPXTreeNode<TrackSegment> { export class Track extends GPXTreeNode<TrackSegment> {
[immerable] = true; [immerable] = true;
readonly name?: string; name?: string;
readonly cmt?: string; cmt?: string;
readonly desc?: string; desc?: string;
readonly src?: string; src?: string;
readonly link?: Link; link?: Link;
readonly type?: string; type?: string;
readonly trkseg: ReadonlyArray<TrackSegment>; trkseg: TrackSegment[];
readonly extensions?: TrackExtensions; extensions?: TrackExtensions;
constructor(track?: TrackType & { _data?: any } | Track) { constructor(track?: TrackType & { _data?: any } | Track) {
super(); super();
@@ -523,74 +458,50 @@ export class Track extends GPXTreeNode<TrackSegment> {
} }
// Producers // Producers
replaceTrackSegments(start: number, end: number, segments: TrackSegment[]): [Track, TrackSegment[]] { replaceTrackSegments(start: number, end: number, segments: TrackSegment[]): TrackSegment[] {
let removed = []; return this.trkseg.splice(start, end - start + 1, ...segments);
let result = produce(this, (draft) => {
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let trkseg = og.trkseg.slice();
removed = trkseg.splice(start, end - start + 1, ...segments);
draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well
});
return [result, removed];
} }
replaceTrackPoints(segmentIndex: number, start: number, end: number, points: TrackPoint[], speed?: number, startTime?: Date) { replaceTrackPoints(segmentIndex: number, start: number, end: number, points: TrackPoint[], speed?: number, startTime?: Date) {
return produce(this, (draft) => { this.trkseg[segmentIndex].replaceTrackPoints(start, end, points, speed, startTime);
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let trkseg = og.trkseg.slice();
trkseg[segmentIndex] = trkseg[segmentIndex].replaceTrackPoints(start, end, points, speed, startTime);
draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well
});
} }
reverseTrackSegment(segmentIndex: number) { reverseTrackSegment(segmentIndex: number) {
return produce(this, (draft) => { this.trkseg[segmentIndex]._reverse(this.trkseg[segmentIndex].getEndTimestamp(), this.trkseg[segmentIndex].getStartTimestamp());
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let trkseg = og.trkseg.slice();
trkseg[segmentIndex] = trkseg[segmentIndex]._reverse(trkseg[segmentIndex].getEndTimestamp(), trkseg[segmentIndex].getStartTimestamp());
draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well
});
} }
crop(start: number, end: number, segmentIndices?: number[]) { crop(start: number, end: number, segmentIndices?: number[]) {
return produce(this, (draft) => { let i = 0;
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let segmentIndex = 0;
let trkseg = og.trkseg.slice(); while (i < this.trkseg.length) {
let i = 0; let length = this.trkseg[i].getNumberOfTrackPoints();
let segmentIndex = 0; if (segmentIndices === undefined || segmentIndices.includes(segmentIndex)) {
while (i < trkseg.length) { if (start >= length || end < 0) {
let length = trkseg[i].getNumberOfTrackPoints(); this.trkseg.splice(i, 1);
if (segmentIndices === undefined || segmentIndices.includes(segmentIndex)) {
if (start >= length || end < 0) {
trkseg.splice(i, 1);
} else {
if (start > 0 || end < length - 1) {
trkseg[i] = trkseg[i].crop(Math.max(0, start), Math.min(length - 1, end));
}
i++;
}
start -= length;
end -= length;
} else { } else {
if (start > 0 || end < length - 1) {
this.trkseg[i].crop(Math.max(0, start), Math.min(length - 1, end));
}
i++; i++;
} }
segmentIndex++; start -= length;
end -= length;
} else {
i++;
} }
draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well segmentIndex++;
}); }
} }
clean(bounds: [Coordinates, Coordinates], inside: boolean, segmentIndices?: number[]) { clean(bounds: [Coordinates, Coordinates], inside: boolean, segmentIndices?: number[]) {
return produce(this, (draft) => { return produce(this, (draft) => {
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let trkseg = og.trkseg.slice();
let i = 0; let i = 0;
let segmentIndex = 0; let segmentIndex = 0;
while (i < trkseg.length) { while (i < this.trkseg.length) {
if (segmentIndices === undefined || segmentIndices.includes(segmentIndex)) { if (segmentIndices === undefined || segmentIndices.includes(segmentIndex)) {
trkseg[i] = trkseg[i].clean(bounds, inside); this.trkseg[i].clean(bounds, inside);
if (trkseg[i].getNumberOfTrackPoints() === 0) { if (this.trkseg[i].getNumberOfTrackPoints() === 0) {
trkseg.splice(i, 1); this.trkseg.splice(i, 1);
} else { } else {
i++; i++;
} }
@@ -599,64 +510,48 @@ export class Track extends GPXTreeNode<TrackSegment> {
} }
segmentIndex++; segmentIndex++;
} }
draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well
}); });
} }
changeTimestamps(startTime: Date, speed: number, ratio: number, lastPoint?: TrackPoint, segmentIndex?: number) { changeTimestamps(startTime: Date, speed: number, ratio: number, lastPoint?: TrackPoint, segmentIndex?: number) {
return produce(this, (draft) => { this.trkseg.forEach((segment, index) => {
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster if (segmentIndex === undefined || segmentIndex === index) {
let trkseg = og.trkseg.slice(); segment.changeTimestamps(startTime, speed, ratio, lastPoint);
trkseg = trkseg.map((segment, index) => { if (segment.trkpt.length > 0) {
if (segmentIndex === undefined || segmentIndex === index) { lastPoint = segment.trkpt[segment.trkpt.length - 1];
let seg = segment.changeTimestamps(startTime, speed, ratio, lastPoint);
if (seg.trkpt.length > 0) {
lastPoint = seg.trkpt[seg.trkpt.length - 1];
}
return seg;
} else {
return segment;
} }
}); }
draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well
}); });
} }
setStyle(style: LineStyleExtension, force: boolean = true) { setStyle(style: LineStyleExtension, force: boolean = true) {
return produce(this, (draft) => { if (!this.extensions) {
if (!draft.extensions) { this.extensions = {};
draft.extensions = {}; }
} if (!this.extensions['gpx_style:line']) {
if (!draft.extensions['gpx_style:line']) { this.extensions['gpx_style:line'] = {};
draft.extensions['gpx_style:line'] = {}; }
} if (style.color !== undefined && (force || this.extensions['gpx_style:line'].color === undefined)) {
if (style.color !== undefined && (force || draft.extensions['gpx_style:line'].color === undefined)) { this.extensions['gpx_style:line'].color = style.color;
draft.extensions['gpx_style:line'].color = style.color; }
} if (style.opacity !== undefined && (force || this.extensions['gpx_style:line'].opacity === undefined)) {
if (style.opacity !== undefined && (force || draft.extensions['gpx_style:line'].opacity === undefined)) { this.extensions['gpx_style:line'].opacity = style.opacity;
draft.extensions['gpx_style:line'].opacity = style.opacity; }
} if (style.weight !== undefined && (force || this.extensions['gpx_style:line'].weight === undefined)) {
if (style.weight !== undefined && (force || draft.extensions['gpx_style:line'].weight === undefined)) { this.extensions['gpx_style:line'].weight = style.weight;
draft.extensions['gpx_style:line'].weight = style.weight; }
}
});
} }
setHidden(hidden: boolean, segmentIndices?: number[]) { setHidden(hidden: boolean, segmentIndices?: number[]) {
return produce(this, (draft) => { let allHidden = hidden;
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster this.trkseg.forEach((segment, index) => {
let allHidden = hidden; if (segmentIndices === undefined || segmentIndices.includes(index)) {
let trkseg = og.trkseg.map((segment, index) => { segment.setHidden(hidden);
if (segmentIndices === undefined || segmentIndices.includes(index)) { } else {
return segment.setHidden(hidden); allHidden = allHidden && (segment._data.hidden === true);
} else { }
allHidden = allHidden && (segment._data.hidden === true);
return segment;
}
});
draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well
draft._data.hidden = allHidden;
}); });
this._data.hidden = allHidden;
} }
} }
@@ -664,7 +559,7 @@ export class Track extends GPXTreeNode<TrackSegment> {
export class TrackSegment extends GPXTreeLeaf { export class TrackSegment extends GPXTreeLeaf {
[immerable] = true; [immerable] = true;
readonly trkpt: ReadonlyArray<Readonly<TrackPoint>>; trkpt: TrackPoint[];
constructor(segment?: TrackSegmentType & { _data?: any } | TrackSegment) { constructor(segment?: TrackSegmentType & { _data?: any } | TrackSegment) {
super(); super();
@@ -871,83 +766,79 @@ export class TrackSegment extends GPXTreeLeaf {
// Producers // Producers
replaceTrackPoints(start: number, end: number, points: TrackPoint[], speed?: number, startTime?: Date) { replaceTrackPoints(start: number, end: number, points: TrackPoint[], speed?: number, startTime?: Date) {
return produce(this, (draft) => { let og = getOriginal(this); // Read as much as possible from the original object because it is faster
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let trkpt = og.trkpt.slice();
let trkpt = og.trkpt.slice();
if (speed !== undefined || (trkpt.length > 0 && trkpt[0].time !== undefined)) { if (speed !== undefined || (trkpt.length > 0 && trkpt[0].time !== undefined)) {
if (start > 0 && trkpt[0].time === undefined) { if (start > 0 && trkpt[0].time === undefined) {
trkpt.splice(0, 0, withTimestamps(trkpt.splice(0, start), speed, undefined, startTime)); trkpt.splice(0, 0, ...withTimestamps(trkpt.splice(0, start), speed, undefined, startTime));
} }
if (points.length > 0) { if (points.length > 0) {
let last = start > 0 ? trkpt[start - 1] : undefined; let last = start > 0 ? trkpt[start - 1] : undefined;
if (points[0].time === undefined || (points.length > 1 && points[1].time === undefined)) { if (points[0].time === undefined || (points.length > 1 && points[1].time === undefined)) {
points = withTimestamps(points, speed, last, startTime); points = withTimestamps(points, speed, last, startTime);
} else if (last !== undefined && points[0].time < last.time) { } else if (last !== undefined && points[0].time < last.time) {
points = withShiftedAndCompressedTimestamps(points, speed, 1, last); points = withShiftedAndCompressedTimestamps(points, speed, 1, last);
}
}
if (end < trkpt.length - 1) {
let last = points.length > 0 ? points[points.length - 1] : start > 0 ? trkpt[start - 1] : undefined;
if (trkpt[end + 1].time === undefined) {
trkpt.splice(end + 1, 0, withTimestamps(trkpt.splice(end + 1), speed, last, startTime));
} else if (last !== undefined && trkpt[end + 1].time < last.time) {
points = withShiftedAndCompressedTimestamps(points, speed, 1, last);
}
} }
} }
if (end < trkpt.length - 1) {
let last = points.length > 0 ? points[points.length - 1] : start > 0 ? trkpt[start - 1] : undefined;
if (trkpt[end + 1].time === undefined) {
trkpt.splice(end + 1, 0, ...withTimestamps(trkpt.splice(end + 1), speed, last, startTime));
} else if (last !== undefined && trkpt[end + 1].time < last.time) {
points = withShiftedAndCompressedTimestamps(points, speed, 1, last);
}
}
}
trkpt.splice(start, end - start + 1, ...points); trkpt.splice(start, end - start + 1, ...points);
draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well
});
} }
_reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date) { _reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date) {
return produce(this, (draft) => { let og = getOriginal(this); // Read as much as possible from the original object because it is faster
if (originalNextTimestamp !== undefined && newPreviousTimestamp !== undefined) { let originalStartTimestamp = og.getStartTimestamp();
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let originalEndTimestamp = og.getEndTimestamp();
if (!newPreviousTimestamp) {
newPreviousTimestamp = originalStartTimestamp;
}
if (newPreviousTimestamp && originalEndTimestamp && !originalNextTimestamp) {
originalNextTimestamp = new Date(newPreviousTimestamp.getTime() + originalEndTimestamp.getTime() - originalStartTimestamp.getTime());
}
if (originalNextTimestamp !== undefined && newPreviousTimestamp !== undefined && originalEndTimestamp !== undefined) {
let newStartTimestamp = new Date(
newPreviousTimestamp.getTime() + originalNextTimestamp.getTime() - originalEndTimestamp.getTime()
);
let originalEndTimestamp = og.getEndTimestamp(); let trkpt = og.trkpt.map((point, i) => new TrackPoint({
let newStartTimestamp = new Date( attributes: cloneJSON(point.attributes),
newPreviousTimestamp.getTime() + originalNextTimestamp.getTime() - originalEndTimestamp.getTime() ele: point.ele,
); time: new Date(
newStartTimestamp.getTime() + (originalEndTimestamp.getTime() - og.trkpt[i].time.getTime())
),
extensions: cloneJSON(point.extensions),
_data: cloneJSON(point._data),
}));
let trkpt = og.trkpt.map((point, i) => new TrackPoint({ trkpt.reverse();
attributes: cloneJSON(point.attributes),
ele: point.ele,
time: new Date(
newStartTimestamp.getTime() + (originalEndTimestamp.getTime() - og.trkpt[i].time.getTime())
),
extensions: cloneJSON(point.extensions),
_data: cloneJSON(point._data),
}));
trkpt.reverse(); this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well
} else {
draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well this.trkpt.reverse();
} else { }
draft.trkpt.reverse();
}
});
} }
crop(start: number, end: number) { crop(start: number, end: number) {
return produce(this, (draft) => { this.trkpt = this.trkpt.slice(start, end + 1);
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let trkpt = og.trkpt.slice(start, end + 1);
draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well
});
} }
clean(bounds: [Coordinates, Coordinates], inside: boolean) { clean(bounds: [Coordinates, Coordinates], inside: boolean) {
return produce(this, (draft) => { let og = getOriginal(this); // Read as much as possible from the original object because it is faster
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let trkpt = og.trkpt.filter((point) => {
let trkpt = og.trkpt.filter((point) => { let inBounds = point.attributes.lat >= bounds[0].lat && point.attributes.lat <= bounds[1].lat && point.attributes.lon >= bounds[0].lon && point.attributes.lon <= bounds[1].lon;
let inBounds = point.attributes.lat >= bounds[0].lat && point.attributes.lat <= bounds[1].lat && point.attributes.lon >= bounds[0].lon && point.attributes.lon <= bounds[1].lon; return inBounds !== inside;
return inBounds !== inside;
});
draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well
}); });
this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well
} }
changeTimestamps(startTime: Date, speed: number, ratio: number, lastPoint?: TrackPoint) { changeTimestamps(startTime: Date, speed: number, ratio: number, lastPoint?: TrackPoint) {
@@ -955,21 +846,18 @@ export class TrackSegment extends GPXTreeLeaf {
lastPoint = this.trkpt[0].clone(); lastPoint = this.trkpt[0].clone();
lastPoint.time = startTime; lastPoint.time = startTime;
} }
return produce(this, (draft) => {
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let og = getOriginal(this); // Read as much as possible from the original object because it is faster
if (og.trkpt.length > 0 && og.trkpt[0].time === undefined) { if (og.trkpt.length > 0 && og.trkpt[0].time === undefined) {
let trkpt = withTimestamps(og.trkpt, speed, lastPoint, startTime); let trkpt = withTimestamps(og.trkpt, speed, lastPoint, startTime);
draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well
} else { } else {
let trkpt = withShiftedAndCompressedTimestamps(og.trkpt, speed, ratio, lastPoint); let trkpt = withShiftedAndCompressedTimestamps(og.trkpt, speed, ratio, lastPoint);
draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well
} }
});
} }
setHidden(hidden: boolean) { setHidden(hidden: boolean) {
return produce(this, (draft) => { this._data.hidden = hidden;
draft._data.hidden = hidden;
});
} }
}; };
@@ -1122,9 +1010,7 @@ export class Waypoint {
// Producers // Producers
setHidden(hidden: boolean) { setHidden(hidden: boolean) {
return produce(this, (draft) => { this._data.hidden = hidden;
draft._data.hidden = hidden;
});
} }
} }
@@ -1307,7 +1193,7 @@ export function distance(coord1: Coordinates, coord2: Coordinates): number {
return maxMeters; return maxMeters;
} }
function distanceWindowSmoothing(points: ReadonlyArray<Readonly<TrackPoint>>, distanceWindow: number, accumulate: (index: number) => number, compute: (accumulated: number, start: number, end: number) => number, remove?: (index: number) => number): number[] { function distanceWindowSmoothing(points: TrackPoint[], distanceWindow: number, accumulate: (index: number) => number, compute: (accumulated: number, start: number, end: number) => number, remove?: (index: number) => number): number[] {
let result = []; let result = [];
let start = 0, end = 0, accumulated = 0; let start = 0, end = 0, accumulated = 0;
@@ -1330,7 +1216,7 @@ function distanceWindowSmoothing(points: ReadonlyArray<Readonly<TrackPoint>>, di
return result; return result;
} }
function distanceWindowSmoothingWithDistanceAccumulator(points: ReadonlyArray<Readonly<TrackPoint>>, distanceWindow: number, compute: (accumulated: number, start: number, end: number) => number): number[] { function distanceWindowSmoothingWithDistanceAccumulator(points: TrackPoint[], distanceWindow: number, compute: (accumulated: number, start: number, end: number) => number): number[] {
return distanceWindowSmoothing(points, distanceWindow, (index) => index > 0 ? distance(points[index - 1].getCoordinates(), points[index].getCoordinates()) : 0, compute, (index) => distance(points[index].getCoordinates(), points[index + 1].getCoordinates())); return distanceWindowSmoothing(points, distanceWindow, (index) => index > 0 ? distance(points[index - 1].getCoordinates(), points[index].getCoordinates()) : 0, compute, (index) => distance(points[index].getCoordinates(), points[index + 1].getCoordinates()));
} }

View File

@@ -5,7 +5,7 @@ export type SimplifiedTrackPoint = { point: TrackPoint, distance?: number };
const earthRadius = 6371008.8; const earthRadius = 6371008.8;
export function ramerDouglasPeucker(points: readonly TrackPoint[], epsilon: number = 50, measure: (a: TrackPoint, b: TrackPoint, c: TrackPoint) => number = computeCrossarc): SimplifiedTrackPoint[] { export function ramerDouglasPeucker(points: TrackPoint[], epsilon: number = 50, measure: (a: TrackPoint, b: TrackPoint, c: TrackPoint) => number = computeCrossarc): SimplifiedTrackPoint[] {
if (points.length == 0) { if (points.length == 0) {
return []; return [];
} else if (points.length == 1) { } else if (points.length == 1) {
@@ -24,7 +24,7 @@ export function ramerDouglasPeucker(points: readonly TrackPoint[], epsilon: numb
return simplified; return simplified;
} }
function ramerDouglasPeuckerRecursive(points: readonly TrackPoint[], epsilon: number, measure: (a: TrackPoint, b: TrackPoint, c: TrackPoint) => number, start: number, end: number, simplified: SimplifiedTrackPoint[]) { function ramerDouglasPeuckerRecursive(points: TrackPoint[], epsilon: number, measure: (a: TrackPoint, b: TrackPoint, c: TrackPoint) => number, start: number, end: number, simplified: SimplifiedTrackPoint[]) {
let largest = { let largest = {
index: 0, index: 0,
distance: 0 distance: 0

View File

@@ -1,8 +1,8 @@
export type GPXFileType = { export type GPXFileType = {
attributes: GPXFileAttributes; attributes: GPXFileAttributes;
metadata: Metadata; metadata: Metadata;
wpt: ReadonlyArray<WaypointType>; wpt: WaypointType[];
trk: ReadonlyArray<TrackType>; trk: TrackType[];
}; };
export type GPXFileAttributes = { export type GPXFileAttributes = {
@@ -52,7 +52,7 @@ export type TrackType = {
src?: string; src?: string;
link?: Link; link?: Link;
type?: string; type?: string;
trkseg: ReadonlyArray<TrackSegmentType>; trkseg: TrackSegmentType[];
extensions?: TrackExtensions; extensions?: TrackExtensions;
}; };
@@ -67,7 +67,7 @@ export type LineStyleExtension = {
}; };
export type TrackSegmentType = { export type TrackSegmentType = {
trkpt: ReadonlyArray<TrackPointType>; trkpt: TrackPointType[];
}; };
export type TrackPointType = { export type TrackPointType = {

View File

@@ -1,4 +1,4 @@
import { dbUtils, getFile, getFileIds } from "$lib/db"; import { dbUtils, getFile } from "$lib/db";
import { castDraft, freeze } from "immer"; import { castDraft, freeze } from "immer";
import { GPXFile, Track, TrackSegment, Waypoint } from "gpx"; import { GPXFile, Track, TrackSegment, Waypoint } from "gpx";
import { selection } from "./Selection"; import { selection } from "./Selection";
@@ -353,59 +353,41 @@ export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: L
let files = [fromParent.getFileId(), toParent.getFileId()]; let files = [fromParent.getFileId(), toParent.getFileId()];
let callbacks = [ let callbacks = [
(file, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => { (file, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
let newFile = file;
fromItems.forEach((item) => { fromItems.forEach((item) => {
if (item instanceof ListTrackItem) { if (item instanceof ListTrackItem) {
let [result, removed] = newFile.replaceTracks(item.getTrackIndex(), item.getTrackIndex(), []); context.push(...file.replaceTracks(item.getTrackIndex(), item.getTrackIndex(), []));
newFile = castDraft(result);
context.push(...removed);
} else if (item instanceof ListTrackSegmentItem) { } else if (item instanceof ListTrackSegmentItem) {
let [result, removed] = newFile.replaceTrackSegments(item.getTrackIndex(), item.getSegmentIndex(), item.getSegmentIndex(), []); context.push(...file.replaceTrackSegments(item.getTrackIndex(), item.getSegmentIndex(), item.getSegmentIndex(), []));
newFile = castDraft(result);
context.push(...removed);
} else if (item instanceof ListWaypointsItem) { } else if (item instanceof ListWaypointsItem) {
let [result, removed] = newFile.replaceWaypoints(0, newFile.wpt.length - 1, []); context.push(file.replaceWaypoints(0, newFile.wpt.length - 1, []));
newFile = castDraft(result);
context.push(removed);
} else if (item instanceof ListWaypointItem) { } else if (item instanceof ListWaypointItem) {
let [result, removed] = newFile.replaceWaypoints(item.getWaypointIndex(), item.getWaypointIndex(), []); context.push(...file.replaceWaypoints(item.getWaypointIndex(), item.getWaypointIndex(), []));
newFile = castDraft(result);
context.push(...removed);
} }
}); });
context.reverse(); context.reverse();
return newFile;
}, },
(file, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => { (file, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
let newFile = file;
toItems.forEach((item, i) => { toItems.forEach((item, i) => {
if (item instanceof ListTrackItem) { if (item instanceof ListTrackItem) {
if (context[i] instanceof Track) { if (context[i] instanceof Track) {
let [result, _removed] = newFile.replaceTracks(item.getTrackIndex(), item.getTrackIndex() - 1, [context[i]]); file.replaceTracks(item.getTrackIndex(), item.getTrackIndex() - 1, [context[i]]);
newFile = castDraft(result);
} else if (context[i] instanceof TrackSegment) { } else if (context[i] instanceof TrackSegment) {
let [result, _removed] = newFile.replaceTracks(item.getTrackIndex(), item.getTrackIndex() - 1, [new Track({ file.replaceTracks(item.getTrackIndex(), item.getTrackIndex() - 1, [new Track({
trkseg: [context[i]] trkseg: [context[i]]
})]); })]);
newFile = castDraft(result);
} }
} else if (item instanceof ListTrackSegmentItem && context[i] instanceof TrackSegment) { } else if (item instanceof ListTrackSegmentItem && context[i] instanceof TrackSegment) {
let [result, _removed] = newFile.replaceTrackSegments(item.getTrackIndex(), item.getSegmentIndex(), item.getSegmentIndex() - 1, [context[i]]); file.replaceTrackSegments(item.getTrackIndex(), item.getSegmentIndex(), item.getSegmentIndex() - 1, [context[i]]);
newFile = castDraft(result);
} else if (item instanceof ListWaypointsItem) { } else if (item instanceof ListWaypointsItem) {
if (Array.isArray(context[i]) && context[i].length > 0 && context[i][0] instanceof Waypoint) { if (Array.isArray(context[i]) && context[i].length > 0 && context[i][0] instanceof Waypoint) {
let [result, _removed] = newFile.replaceWaypoints(newFile.wpt.length, newFile.wpt.length - 1, context[i]); file.replaceWaypoints(file.wpt.length, file.wpt.length - 1, context[i]);
newFile = castDraft(result);
} else if (context[i] instanceof Waypoint) { } else if (context[i] instanceof Waypoint) {
let [result, _removed] = newFile.replaceWaypoints(newFile.wpt.length, newFile.wpt.length - 1, [context[i]]); file.replaceWaypoints(file.wpt.length, file.wpt.length - 1, [context[i]]);
newFile = castDraft(result);
} }
} else if (item instanceof ListWaypointItem && context[i] instanceof Waypoint) { } else if (item instanceof ListWaypointItem && context[i] instanceof Waypoint) {
let [result, _removed] = newFile.replaceWaypoints(item.getWaypointIndex(), item.getWaypointIndex() - 1, [context[i]]); file.replaceWaypoints(item.getWaypointIndex(), item.getWaypointIndex() - 1, [context[i]]);
newFile = castDraft(result);
} }
}); });
return newFile;
} }
]; ];
@@ -433,12 +415,13 @@ export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: L
if (context[i].name) { if (context[i].name) {
newFile.metadata.name = context[i].name; newFile.metadata.name = context[i].name;
} }
newFile = newFile.replaceTracks(0, 0, [context[i]])[0]; console.log(context[i]);
newFile.replaceTracks(0, 0, [context[i]])[0];
files.set(item.getFileId(), freeze(newFile)); files.set(item.getFileId(), freeze(newFile));
} else if (context[i] instanceof TrackSegment) { } else if (context[i] instanceof TrackSegment) {
let newFile = newGPXFile(); let newFile = newGPXFile();
newFile._data.id = item.getFileId(); newFile._data.id = item.getFileId();
newFile = newFile.replaceTracks(0, 0, [new Track({ newFile.replaceTracks(0, 0, [new Track({
trkseg: [context[i]] trkseg: [context[i]]
})])[0]; })])[0];
files.set(item.getFileId(), freeze(newFile)); files.set(item.getFileId(), freeze(newFile));

View File

@@ -27,8 +27,8 @@
export let node: export let node:
| Map<string, Readable<GPXFileWithStatistics | undefined>> | Map<string, Readable<GPXFileWithStatistics | undefined>>
| GPXTreeElement<AnyGPXTreeElement> | GPXTreeElement<AnyGPXTreeElement>
| ReadonlyArray<Readonly<Waypoint>> | Waypoint[]
| Readonly<Waypoint>; | Waypoint;
export let item: ListItem; export let item: ListItem;
let recursive = getContext<boolean>('recursive'); let recursive = getContext<boolean>('recursive');

View File

@@ -27,7 +27,7 @@
export let node: export let node:
| Map<string, Readable<GPXFileWithStatistics | undefined>> | Map<string, Readable<GPXFileWithStatistics | undefined>>
| GPXTreeElement<AnyGPXTreeElement> | GPXTreeElement<AnyGPXTreeElement>
| Readonly<Waypoint>; | Waypoint;
export let item: ListItem; export let item: ListItem;
export let waypointRoot: boolean = false; export let waypointRoot: boolean = false;

View File

@@ -51,10 +51,7 @@
import MetadataDialog from './MetadataDialog.svelte'; import MetadataDialog from './MetadataDialog.svelte';
import StyleDialog from './StyleDialog.svelte'; import StyleDialog from './StyleDialog.svelte';
export let node: export let node: GPXTreeElement<AnyGPXTreeElement> | Waypoint[] | Waypoint;
| GPXTreeElement<AnyGPXTreeElement>
| ReadonlyArray<Readonly<Waypoint>>
| Readonly<Waypoint>;
export let item: ListItem; export let item: ListItem;
export let label: string | undefined; export let label: string | undefined;
@@ -232,9 +229,8 @@
<ContextMenu.Item <ContextMenu.Item
disabled={!singleSelection} disabled={!singleSelection}
on:click={() => on:click={() =>
dbUtils.applyToFile( dbUtils.applyToFile(item.getFileId(), (file) =>
item.getFileId(), file.replaceTracks(file.trk.length, file.trk.length, [new Track()])
(file) => file.replaceTracks(file.trk.length, file.trk.length, [new Track()])[0]
)} )}
> >
<Plus size="16" class="mr-1" /> <Plus size="16" class="mr-1" />
@@ -246,15 +242,13 @@
disabled={!singleSelection} disabled={!singleSelection}
on:click={() => { on:click={() => {
let trackIndex = item.getTrackIndex(); let trackIndex = item.getTrackIndex();
dbUtils.applyToFile( dbUtils.applyToFile(item.getFileId(), (file) =>
item.getFileId(), file.replaceTrackSegments(
(file) => trackIndex,
file.replaceTrackSegments( file.trk[trackIndex].trkseg.length,
trackIndex, file.trk[trackIndex].trkseg.length,
file.trk[trackIndex].trkseg.length, [new TrackSegment()]
file.trk[trackIndex].trkseg.length, )
[new TrackSegment()]
)[0]
); );
}} }}
> >

View File

@@ -11,10 +11,7 @@
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
import { editMetadata } from '$lib/stores'; import { editMetadata } from '$lib/stores';
export let node: export let node: GPXTreeElement<AnyGPXTreeElement> | Waypoint[] | Waypoint;
| GPXTreeElement<AnyGPXTreeElement>
| ReadonlyArray<Readonly<Waypoint>>
| Readonly<Waypoint>;
export let item: ListItem; export let item: ListItem;
export let open = false; export let open = false;
@@ -54,7 +51,6 @@
file.trk[item.getTrackIndex()].name = name; file.trk[item.getTrackIndex()].name = name;
file.trk[item.getTrackIndex()].desc = description; file.trk[item.getTrackIndex()].desc = description;
} }
return file;
}); });
open = false; open = false;
}} }}

View File

@@ -6,7 +6,6 @@ import { currentPopupWaypoint, deleteWaypoint, waypointPopup } from "./WaypointP
import { addSelectItem, selectItem, selection } from "$lib/components/file-list/Selection"; import { addSelectItem, selectItem, selection } from "$lib/components/file-list/Selection";
import { ListTrackSegmentItem, ListWaypointItem, ListWaypointsItem, ListTrackItem, ListFileItem, ListRootItem } from "$lib/components/file-list/FileList"; import { ListTrackSegmentItem, ListWaypointItem, ListWaypointsItem, ListTrackItem, ListFileItem, ListRootItem } from "$lib/components/file-list/FileList";
import type { Waypoint } from "gpx"; import type { Waypoint } from "gpx";
import { produce } from "immer";
import { resetCursor, setCursor, setGrabbingCursor, setPointerCursor } from "$lib/utils"; import { resetCursor, setCursor, setGrabbingCursor, setPointerCursor } from "$lib/utils";
import { font } from "$lib/assets/layers"; import { font } from "$lib/assets/layers";
import { selectedWaypoint } from "$lib/components/toolbar/tools/Waypoint.svelte"; import { selectedWaypoint } from "$lib/components/toolbar/tools/Waypoint.svelte";
@@ -231,13 +230,13 @@ export class GPXLayer {
resetCursor(); resetCursor();
marker.getElement().style.cursor = ''; marker.getElement().style.cursor = '';
dbUtils.applyToFile(this.fileId, (file) => { dbUtils.applyToFile(this.fileId, (file) => {
return produce(file, (draft) => { let latLng = marker.getLngLat();
let latLng = marker.getLngLat(); let wpt = file.wpt[marker._waypoint._data.index];
draft.wpt[marker._waypoint._data.index].setCoordinates({ wpt.setCoordinates({
lat: latLng.lat, lat: latLng.lat,
lon: latLng.lng lon: latLng.lng
});
}); });
wpt.ele = this.map.queryTerrainElevation([latLng.lng, latLng.lat], { exaggerated: false }) ?? 0;
}); });
dragEndTimestamp = Date.now() dragEndTimestamp = Date.now()
}); });

View File

@@ -21,5 +21,5 @@ export const waypointPopup = new mapboxgl.Popup({
}); });
export function deleteWaypoint(fileId: string, waypointIndex: number) { export function deleteWaypoint(fileId: string, waypointIndex: number) {
dbUtils.applyToFile(fileId, (file) => file.replaceWaypoints(waypointIndex, waypointIndex, [])[0]); dbUtils.applyToFile(fileId, (file) => file.replaceWaypoints(waypointIndex, waypointIndex, []));
} }

View File

@@ -303,16 +303,16 @@
let fileId = item.getFileId(); let fileId = item.getFileId();
dbUtils.applyToFile(fileId, (file) => { dbUtils.applyToFile(fileId, (file) => {
if (item instanceof ListFileItem) { if (item instanceof ListFileItem) {
return file.changeTimestamps(getDate(startDate, startTime), effectiveSpeed, ratio); file.changeTimestamps(getDate(startDate, startTime), effectiveSpeed, ratio);
} else if (item instanceof ListTrackItem) { } else if (item instanceof ListTrackItem) {
return file.changeTimestamps( file.changeTimestamps(
getDate(startDate, startTime), getDate(startDate, startTime),
effectiveSpeed, effectiveSpeed,
ratio, ratio,
item.getTrackIndex() item.getTrackIndex()
); );
} else if (item instanceof ListTrackSegmentItem) { } else if (item instanceof ListTrackSegmentItem) {
return file.changeTimestamps( file.changeTimestamps(
getDate(startDate, startTime), getDate(startDate, startTime),
effectiveSpeed, effectiveSpeed,
ratio, ratio,

View File

@@ -104,19 +104,16 @@
longitude = parseFloat(longitude.toFixed(6)); longitude = parseFloat(longitude.toFixed(6));
if ($selectedWaypoint) { if ($selectedWaypoint) {
dbUtils.applyToFile($selectedWaypoint[1], (file) => { dbUtils.applyToFile($selectedWaypoint[1], (file) => {
let waypoint = $selectedWaypoint[0].clone(); let wpt = file.wpt[$selectedWaypoint[0]._data.index];
waypoint.name = name; wpt.name = name;
waypoint.desc = description; wpt.desc = description;
waypoint.cmt = description; wpt.cmt = description;
waypoint.setCoordinates({ wpt.setCoordinates({
lat: latitude, lat: latitude,
lon: longitude lon: longitude
}); });
return file.replaceWaypoints( wpt.ele =
$selectedWaypoint[0]._data.index, get(map)?.queryTerrainElevation([longitude, latitude], { exaggerated: false }) ?? 0;
$selectedWaypoint[0]._data.index,
[waypoint]
)[0];
}); });
} else { } else {
let fileIds = new Set<string>(); let fileIds = new Set<string>();
@@ -134,9 +131,8 @@
}); });
waypoint.ele = waypoint.ele =
get(map)?.queryTerrainElevation([longitude, latitude], { exaggerated: false }) ?? 0; get(map)?.queryTerrainElevation([longitude, latitude], { exaggerated: false }) ?? 0;
dbUtils.applyToFiles( dbUtils.applyToFiles(Array.from(fileIds), (file) =>
Array.from(fileIds), file.replaceWaypoints(file.wpt.length, file.wpt.length, [waypoint])
(file) => file.replaceWaypoints(file.wpt.length, file.wpt.length, [waypoint])[0]
); );
} }
selectedWaypoint.set(undefined); selectedWaypoint.set(undefined);

View File

@@ -71,7 +71,7 @@
function createFileWithPoint(e: any) { function createFileWithPoint(e: any) {
if ($selection.size === 0) { if ($selection.size === 0) {
let file = newGPXFile(); let file = newGPXFile();
file = file.replaceTrackPoints(0, 0, 0, 0, [ file.replaceTrackPoints(0, 0, 0, 0, [
new TrackPoint({ new TrackPoint({
attributes: { attributes: {
lat: e.lngLat.lat, lat: e.lngLat.lat,
@@ -79,9 +79,7 @@
} }
}) })
]); ]);
file = produce(file, (draft) => { file._data.id = getFileIds(1)[0];
draft._data.id = getFileIds(1)[0];
});
dbUtils.add(file); dbUtils.add(file);
selectFileWhenLoaded(file._data.id); selectFileWhenLoaded(file._data.id);
} }

View File

@@ -351,7 +351,7 @@ export class RoutingControls {
} else if (nextAnchor === null) { // Last point, remove trackpoints from previousAnchor } else if (nextAnchor === null) { // Last point, remove trackpoints from previousAnchor
dbUtils.applyToFile(this.fileId, (file) => { dbUtils.applyToFile(this.fileId, (file) => {
let segment = file.getSegment(anchor.trackIndex, anchor.segmentIndex); let segment = file.getSegment(anchor.trackIndex, anchor.segmentIndex);
return file.replaceTrackPoints(anchor.trackIndex, anchor.segmentIndex, previousAnchor.point._data.index + 1, segment.trkpt.length - 1, []); file.replaceTrackPoints(anchor.trackIndex, anchor.segmentIndex, previousAnchor.point._data.index + 1, segment.trkpt.length - 1, []);
}); });
} else { // Route between previousAnchor and nextAnchor } else { // Route between previousAnchor and nextAnchor
this.routeBetweenAnchors([previousAnchor, nextAnchor], [previousAnchor.point.getCoordinates(), nextAnchor.point.getCoordinates()]); this.routeBetweenAnchors([previousAnchor, nextAnchor], [previousAnchor.point.getCoordinates(), nextAnchor.point.getCoordinates()]);
@@ -374,8 +374,8 @@ export class RoutingControls {
let segment = anchor.segment; let segment = anchor.segment;
dbUtils.applyToFile(this.fileId, (file) => { dbUtils.applyToFile(this.fileId, (file) => {
let newFile = file.replaceTrackPoints(anchor.trackIndex, anchor.segmentIndex, segment.trkpt.length, segment.trkpt.length - 1, segment.trkpt.slice(0, anchor.point._data.index), speed > 0 ? speed : undefined); file.replaceTrackPoints(anchor.trackIndex, anchor.segmentIndex, segment.trkpt.length, segment.trkpt.length - 1, segment.trkpt.slice(0, anchor.point._data.index), speed > 0 ? speed : undefined);
return newFile.replaceTrackPoints(anchor.trackIndex, anchor.segmentIndex, 0, anchor.point._data.index - 1, []); file.crop(anchor.point._data.index, anchor.point._data.index + segment.trkpt.length - 1, [anchor.trackIndex], [anchor.segmentIndex]);
}); });
} }
@@ -412,14 +412,14 @@ export class RoutingControls {
} }
if (file.trk.length === 0) { if (file.trk.length === 0) {
let track = new Track(); let track = new Track();
track = track.replaceTrackPoints(0, 0, 0, [newPoint]); track.replaceTrackPoints(0, 0, 0, [newPoint]);
return file.replaceTracks(0, 0, [track])[0]; file.replaceTracks(0, 0, [track])[0];
} else if (file.trk[trackIndex].trkseg.length === 0) { } else if (file.trk[trackIndex].trkseg.length === 0) {
let segment = new TrackSegment(); let segment = new TrackSegment();
segment = segment.replaceTrackPoints(0, 0, [newPoint]); segment.replaceTrackPoints(0, 0, [newPoint]);
return file.replaceTrackSegments(trackIndex, 0, 0, [segment])[0]; file.replaceTrackSegments(trackIndex, 0, 0, [segment]);
} else { } else {
return file.replaceTrackPoints(trackIndex, segmentIndex, 0, 0, [newPoint]); file.replaceTrackPoints(trackIndex, segmentIndex, 0, 0, [newPoint]);
} }
}); });
return; return;
@@ -458,11 +458,11 @@ export class RoutingControls {
let lastAnchor = this.anchors[this.anchors.length - 1]; let lastAnchor = this.anchors[this.anchors.length - 1];
let segment = lastAnchor.segment;
dbUtils.applyToFile(this.fileId, (file) => { dbUtils.applyToFile(this.fileId, (file) => {
let segment = original(file).getSegment(lastAnchor.trackIndex, lastAnchor.segmentIndex);
let newSegment = segment.clone(); let newSegment = segment.clone();
newSegment = newSegment._reverse(segment.getEndTimestamp(), segment.getEndTimestamp()); newSegment._reverse(segment.getEndTimestamp(), segment.getEndTimestamp());
return file.replaceTrackPoints(lastAnchor.trackIndex, lastAnchor.segmentIndex, segment.trkpt.length, segment.trkpt.length, newSegment.trkpt.map((point) => point)); file.replaceTrackPoints(lastAnchor.trackIndex, lastAnchor.segmentIndex, segment.trkpt.length, segment.trkpt.length, newSegment.trkpt.map((point) => point));
}); });
} }

View File

@@ -23,15 +23,11 @@ export function updateAnchorPoints(file: GPXFile) {
} }
if (segment.trkpt.length > 0) { if (segment.trkpt.length > 0) {
if (!segment.trkpt[0]._data.anchor) { // First point is not an anchor, make it one // Ensure first and last points are anchors and always visible
segment.trkpt[0]._data.anchor = true; segment.trkpt[0]._data.anchor = true;
segment.trkpt[0]._data.zoom = 0; segment.trkpt[0]._data.zoom = 0;
} segment.trkpt[segment.trkpt.length - 1]._data.anchor = true;
segment.trkpt[segment.trkpt.length - 1]._data.zoom = 0;
if (!segment.trkpt[segment.trkpt.length - 1]._data.anchor) { // Last point is not an anchor, make it one
segment.trkpt[segment.trkpt.length - 1]._data.anchor = true;
segment.trkpt[segment.trkpt.length - 1]._data.zoom = 0;
}
} }
} }
} }

View File

@@ -179,6 +179,7 @@ function dexieGPXFileStore(id: string): Readable<GPXFileWithStatistics> & { dest
let store = writable<GPXFileWithStatistics>(undefined); let store = writable<GPXFileWithStatistics>(undefined);
let query = liveQuery(() => db.files.get(id)).subscribe(value => { let query = liveQuery(() => db.files.get(id)).subscribe(value => {
if (value !== undefined) { if (value !== undefined) {
console.log('File updated', id);
let gpx = new GPXFile(value); let gpx = new GPXFile(value);
updateAnchorPoints(gpx); updateAnchorPoints(gpx);
@@ -259,6 +260,8 @@ function updateSelection(updatedFiles: GPXFile[], deletedFileIds: string[]) {
// Commit the changes to the file state to the database // Commit the changes to the file state to the database
function commitFileStateChange(newFileState: ReadonlyMap<string, GPXFile>, patch: Patch[]) { function commitFileStateChange(newFileState: ReadonlyMap<string, GPXFile>, patch: Patch[]) {
console.log(patch);
let changedFileIds = getChangedFileIds(patch); let changedFileIds = getChangedFileIds(patch);
let updatedFileIds: string[] = [], deletedFileIds: string[] = []; let updatedFileIds: string[] = [], deletedFileIds: string[] = [];
@@ -272,6 +275,7 @@ function commitFileStateChange(newFileState: ReadonlyMap<string, GPXFile>, patch
let updatedFiles = updatedFileIds.map(id => newFileState.get(id)).filter(file => file !== undefined) as GPXFile[]; let updatedFiles = updatedFileIds.map(id => newFileState.get(id)).filter(file => file !== undefined) as GPXFile[];
updatedFileIds = updatedFiles.map(file => file._data.id); updatedFileIds = updatedFiles.map(file => file._data.id);
console.log(updatedFileIds, deletedFileIds);
updateSelection(updatedFiles, deletedFileIds); updateSelection(updatedFiles, deletedFileIds);
@@ -284,6 +288,8 @@ function commitFileStateChange(newFileState: ReadonlyMap<string, GPXFile>, patch
await db.fileids.bulkDelete(deletedFileIds); await db.fileids.bulkDelete(deletedFileIds);
await db.files.bulkDelete(deletedFileIds); await db.files.bulkDelete(deletedFileIds);
} }
}).catch((error) => {
console.error('Error committing file state change', error);
}); });
} }
@@ -309,6 +315,7 @@ liveQuery(() => db.fileids.toArray()).subscribe(dbFileIds => {
deletedFiles.forEach(id => { deletedFiles.forEach(id => {
$files.get(id)?.destroy(); $files.get(id)?.destroy();
$files.delete(id); $files.delete(id);
console.log('File removed', id);
}); });
return $files; return $files;
}); });
@@ -360,12 +367,12 @@ function applyGlobal(callback: (files: Map<string, GPXFile>) => void) {
} }
// Helper function to apply a callback to multiple files // Helper function to apply a callback to multiple files
function applyToFiles(fileIds: string[], callback: (file: WritableDraft<GPXFile>) => GPXFile) { function applyToFiles(fileIds: string[], callback: (file: WritableDraft<GPXFile>) => void) {
const [newFileState, patch, inversePatch] = produceWithPatches(fileState, (draft) => { const [newFileState, patch, inversePatch] = produceWithPatches(fileState, (draft) => {
fileIds.forEach((fileId) => { fileIds.forEach((fileId) => {
let file = draft.get(fileId); let file = draft.get(fileId);
if (file) { if (file) {
draft.set(fileId, castDraft(callback(file))); callback(file);
} }
}); });
}); });
@@ -376,12 +383,12 @@ function applyToFiles(fileIds: string[], callback: (file: WritableDraft<GPXFile>
} }
// Helper function to apply different callbacks to multiple files // Helper function to apply different callbacks to multiple files
function applyEachToFilesAndGlobal(fileIds: string[], callbacks: ((file: WritableDraft<GPXFile>, context?: any) => GPXFile)[], globalCallback: (files: Map<string, GPXFile>, context?: any) => void, context?: any) { 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) => { const [newFileState, patch, inversePatch] = produceWithPatches(fileState, (draft) => {
fileIds.forEach((fileId, index) => { fileIds.forEach((fileId, index) => {
let file = draft.get(fileId); let file = draft.get(fileId);
if (file) { if (file) {
draft.set(fileId, castDraft(callbacks[index](file, context))); callbacks[index](file, context);
} }
}); });
globalCallback(draft, context); globalCallback(draft, context);
@@ -459,13 +466,13 @@ export const dbUtils = {
}); });
}); });
}, },
applyToFile: (id: string, callback: (file: WritableDraft<GPXFile>) => GPXFile) => { applyToFile: (id: string, callback: (file: WritableDraft<GPXFile>) => void) => {
applyToFiles([id], callback); applyToFiles([id], callback);
}, },
applyToFiles: (ids: string[], callback: (file: WritableDraft<GPXFile>) => GPXFile) => { applyToFiles: (ids: string[], callback: (file: WritableDraft<GPXFile>) => void) => {
applyToFiles(ids, callback); applyToFiles(ids, callback);
}, },
applyEachToFilesAndGlobal: (ids: string[], callbacks: ((file: WritableDraft<GPXFile>, context?: any) => GPXFile)[], globalCallback: (files: Map<string, GPXFile>, context?: any) => void, context?: any) => { 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); applyEachToFilesAndGlobal(ids, callbacks, globalCallback, context);
}, },
duplicateSelection: () => { duplicateSelection: () => {
@@ -476,36 +483,36 @@ export const dbUtils = {
let ids = getFileIds(get(settings.fileOrder).length); let ids = getFileIds(get(settings.fileOrder).length);
let index = 0; let index = 0;
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = getFile(fileId); if (level === ListLevel.FILE) {
if (file) { let file = getFile(fileId);
let newFile = file; if (file) {
if (level === ListLevel.FILE) { let newFile = file.clone();
newFile = file.clone();
newFile._data.id = ids[index++]; newFile._data.id = ids[index++];
} else if (level === ListLevel.TRACK) { draft.set(newFile._data.id, freeze(newFile));
for (let item of items) { }
let trackIndex = (item as ListTrackItem).getTrackIndex(); } else {
let [result, _removed] = newFile.replaceTracks(trackIndex + 1, trackIndex, [file.trk[trackIndex].clone()]); let file = draft.get(fileId);
newFile = result; if (file) {
} if (level === ListLevel.TRACK) {
} else if (level === ListLevel.SEGMENT) { for (let item of items) {
for (let item of items) { let trackIndex = (item as ListTrackItem).getTrackIndex();
let trackIndex = (item as ListTrackSegmentItem).getTrackIndex(); file.replaceTracks(trackIndex + 1, trackIndex, [file.trk[trackIndex].clone()]);
let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex(); }
let [result, _removed] = newFile.replaceTrackSegments(trackIndex, segmentIndex + 1, segmentIndex, [file.trk[trackIndex].trkseg[segmentIndex].clone()]); } else if (level === ListLevel.SEGMENT) {
newFile = result; for (let item of items) {
} let trackIndex = (item as ListTrackSegmentItem).getTrackIndex();
} else if (level === ListLevel.WAYPOINTS) { let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex();
let [result, _removed] = newFile.replaceWaypoints(file.wpt.length, file.wpt.length - 1, file.wpt.map((wpt) => wpt.clone())); file.replaceTrackSegments(trackIndex, segmentIndex + 1, segmentIndex, [file.trk[trackIndex].trkseg[segmentIndex].clone()]);
newFile = result; }
} else if (level === ListLevel.WAYPOINT) { } else if (level === ListLevel.WAYPOINTS) {
for (let item of items) { file.replaceWaypoints(file.wpt.length, file.wpt.length - 1, file.wpt.map((wpt) => wpt.clone()));
let waypointIndex = (item as ListWaypointItem).getWaypointIndex(); } else if (level === ListLevel.WAYPOINT) {
let [result, _removed] = newFile.replaceWaypoints(waypointIndex + 1, waypointIndex, [file.wpt[waypointIndex].clone()]); for (let item of items) {
newFile = result; let waypointIndex = (item as ListWaypointItem).getWaypointIndex();
file.replaceWaypoints(waypointIndex + 1, waypointIndex, [file.wpt[waypointIndex].clone()]);
}
} }
} }
draft.set(newFile._data.id, freeze(newFile));
} }
}); });
}); });
@@ -516,24 +523,22 @@ export const dbUtils = {
} }
applyGlobal((draft) => { applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = getFile(fileId); let file = draft.get(fileId);
if (file) { if (file) {
let newFile = file;
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
newFile = file.reverse(); file.reverse();
} else if (level === ListLevel.TRACK) { } else if (level === ListLevel.TRACK) {
for (let item of items) { for (let item of items) {
let trackIndex = (item as ListTrackItem).getTrackIndex(); let trackIndex = (item as ListTrackItem).getTrackIndex();
newFile = newFile.reverseTrack(trackIndex); file.reverseTrack(trackIndex);
} }
} else if (level === ListLevel.SEGMENT) { } else if (level === ListLevel.SEGMENT) {
for (let item of items) { for (let item of items) {
let trackIndex = (item as ListTrackSegmentItem).getTrackIndex(); let trackIndex = (item as ListTrackSegmentItem).getTrackIndex();
let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex(); let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex();
newFile = newFile.reverseTrackSegment(trackIndex, segmentIndex); file.reverseTrackSegment(trackIndex, segmentIndex);
} }
} }
draft.set(newFile._data.id, freeze(newFile));
} }
}); });
}); });
@@ -553,23 +558,14 @@ export const dbUtils = {
wpt: [] wpt: []
}; };
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = getFile(fileId); let file = draft.get(fileId);
if (file) { if (file) {
let newFile = file;
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
{ toMerge.trk.push(...file.replaceTracks(0, file.trk.length - 1, []));
let [result, removed] = newFile.replaceTracks(0, newFile.trk.length - 1, []); toMerge.wpt.push(...file.replaceWaypoints(0, file.wpt.length - 1, []));
toMerge.trk.push(...removed);
newFile = result;
}
{
let [result, removed] = newFile.replaceWaypoints(0, newFile.wpt.length - 1, []);
toMerge.wpt.push(...removed);
newFile = result;
}
if (first) { if (first) {
target = items[0]; target = items[0];
targetFile = newFile; targetFile = file;
} else { } else {
draft.delete(fileId); draft.delete(fileId);
} }
@@ -578,14 +574,11 @@ export const dbUtils = {
items.forEach((item, index) => { items.forEach((item, index) => {
let trackIndex = (item as ListTrackItem).getTrackIndex(); let trackIndex = (item as ListTrackItem).getTrackIndex();
if (index === items.length - 1) { // Order is reversed, so the last track is the first one and the one to keep if (index === items.length - 1) { // Order is reversed, so the last track is the first one and the one to keep
let [result, removed] = newFile.replaceTrackSegments(trackIndex, 0, newFile.trk[trackIndex].trkseg.length - 1, []); toMerge.trkseg.splice(0, 0, ...file.replaceTrackSegments(trackIndex, 0, file.trk[trackIndex].trkseg.length - 1, []));
toMerge.trkseg.splice(0, 0, ...removed);
newFile = result;
target = item; target = item;
} else { } else {
let [result, removed] = newFile.replaceTracks(trackIndex, trackIndex, []); let removed = file.replaceTracks(trackIndex, trackIndex, []);
toMerge.trkseg.push(...removed[0].trkseg); toMerge.trkseg.push(...removed[0].trkseg);
newFile = result;
} }
}); });
} else if (level === ListLevel.SEGMENT) { } else if (level === ListLevel.SEGMENT) {
@@ -595,16 +588,10 @@ export const dbUtils = {
if (index === items.length - 1) { // Order is reversed, so the last segment is the first one and the one to keep if (index === items.length - 1) { // Order is reversed, so the last segment is the first one and the one to keep
target = item; target = item;
} }
let [result, removed] = newFile.replaceTrackSegments(trackIndex, segmentIndex, segmentIndex, []); toMerge.trkseg.splice(0, 0, ...file.replaceTrackSegments(trackIndex, segmentIndex, segmentIndex, []));
toMerge.trkseg.splice(0, 0, ...removed);
newFile = result;
}); });
} }
if (first) { targetFile = file;
targetFile = newFile;
} else {
draft.set(fileId, freeze(newFile));
}
} }
first = false; first = false;
} }
@@ -625,37 +612,47 @@ export const dbUtils = {
} }
} }
if (toMerge.trk.length > 0) { if (toMerge.trk.length > 0 && toMerge.trk[0].trkseg.length > 0) {
let s = new TrackSegment(); let s = toMerge.trk[0].trkseg[0];
toMerge.trk.map((track) => { toMerge.trk.map((track, trackIndex) => {
track.trkseg.forEach((segment) => { track.trkseg.forEach((segment, segmentIndex) => {
s = s.replaceTrackPoints(s.trkpt.length, s.trkpt.length, segment.trkpt.slice(), speed, startTime); if (trackIndex === 0 && segmentIndex === 0) {
return;
}
s.replaceTrackPoints(s.trkpt.length, s.trkpt.length, segment.trkpt.slice(), speed, startTime);
}); });
}); });
toMerge.trk = [toMerge.trk[0].replaceTrackSegments(0, toMerge.trk[0].trkseg.length - 1, [s])[0]]; toMerge.trk = [toMerge.trk[0]];
toMerge.trk[0].trkseg = [s];
} }
if (toMerge.trkseg.length > 0) { if (toMerge.trkseg.length > 0) {
let s = new TrackSegment(); let s = toMerge.trkseg[0];
toMerge.trkseg.forEach((segment) => { toMerge.trkseg.forEach((segment, segmentIndex) => {
s = s.replaceTrackPoints(s.trkpt.length, s.trkpt.length, segment.trkpt.slice(), speed, startTime); if (segmentIndex === 0) {
return;
}
s.replaceTrackPoints(s.trkpt.length, s.trkpt.length, segment.trkpt.slice(), speed, startTime);
}); });
toMerge.trkseg = [s]; toMerge.trkseg = [s];
} }
console.log(toMerge);
} }
if (targetFile) { if (targetFile) {
console.log(toMerge, target, targetFile);
if (target instanceof ListFileItem) { if (target instanceof ListFileItem) {
targetFile = targetFile.replaceTracks(0, targetFile.trk.length - 1, toMerge.trk)[0]; targetFile.replaceTracks(0, targetFile.trk.length - 1, toMerge.trk)[0];
targetFile = targetFile.replaceWaypoints(0, targetFile.wpt.length - 1, toMerge.wpt)[0]; targetFile.replaceWaypoints(0, targetFile.wpt.length - 1, toMerge.wpt)[0];
} else if (target instanceof ListTrackItem) { } else if (target instanceof ListTrackItem) {
let trackIndex = target.getTrackIndex(); let trackIndex = target.getTrackIndex();
targetFile = targetFile.replaceTrackSegments(trackIndex, 0, -1, toMerge.trkseg)[0]; targetFile.replaceTrackSegments(trackIndex, 0, -1, toMerge.trkseg)[0];
} else if (target instanceof ListTrackSegmentItem) { } else if (target instanceof ListTrackSegmentItem) {
let trackIndex = target.getTrackIndex(); let trackIndex = target.getTrackIndex();
let segmentIndex = target.getSegmentIndex(); let segmentIndex = target.getSegmentIndex();
targetFile = targetFile.replaceTrackSegments(trackIndex, segmentIndex, segmentIndex - 1, toMerge.trkseg)[0]; targetFile.replaceTrackSegments(trackIndex, segmentIndex, segmentIndex - 1, toMerge.trkseg)[0];
} }
draft.set(targetFile._data.id, freeze(targetFile));
console.log(targetFile);
} }
}); });
}, },
@@ -665,27 +662,24 @@ export const dbUtils = {
} }
applyGlobal((draft) => { applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = getFile(fileId); let file = draft.get(fileId);
if (file) { if (file) {
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
let length = file.getNumberOfTrackPoints(); let length = file.getNumberOfTrackPoints();
if (start >= length || end < 0) { if (start >= length || end < 0) {
draft.delete(fileId); draft.delete(fileId);
} else if (start > 0 || end < length - 1) { } else if (start > 0 || end < length - 1) {
let newFile = file.crop(Math.max(0, start), Math.min(length - 1, end)); file.crop(Math.max(0, start), Math.min(length - 1, end));
draft.set(newFile._data.id, freeze(newFile));
} }
start -= length; start -= length;
end -= length; end -= length;
} else if (level === ListLevel.TRACK) { } else if (level === ListLevel.TRACK) {
let trackIndices = items.map((item) => (item as ListTrackItem).getTrackIndex()); let trackIndices = items.map((item) => (item as ListTrackItem).getTrackIndex());
let newFile = file.crop(start, end, trackIndices); file.crop(start, end, trackIndices);
draft.set(newFile._data.id, freeze(newFile));
} else if (level === ListLevel.SEGMENT) { } else if (level === ListLevel.SEGMENT) {
let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()]; let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()];
let segmentIndices = items.map((item) => (item as ListTrackSegmentItem).getSegmentIndex()); let segmentIndices = items.map((item) => (item as ListTrackSegmentItem).getSegmentIndex());
let newFile = file.crop(start, end, trackIndices, segmentIndices); file.crop(start, end, trackIndices, segmentIndices);
draft.set(newFile._data.id, freeze(newFile));
} }
} }
}, false); }, false);
@@ -694,9 +688,9 @@ export const dbUtils = {
extractSelection: () => { extractSelection: () => {
return applyGlobal((draft) => { return applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = getFile(fileId); if (level === ListLevel.FILE) {
if (file) { let file = getFile(fileId);
if (level === ListLevel.FILE) { if (file) {
if (file.trk.length > 1) { if (file.trk.length > 1) {
let fileIds = getFileIds(file.trk.length); let fileIds = getFileIds(file.trk.length);
@@ -724,27 +718,24 @@ export const dbUtils = {
}); });
file.trk.forEach((track, index) => { file.trk.forEach((track, index) => {
let newFile = file.clone();
let tracks = track.trkseg.map((segment, segmentIndex) => { let tracks = track.trkseg.map((segment, segmentIndex) => {
let t = track.replaceTrackSegments(0, track.trkseg.length - 1, [segment])[0]; let t = track.clone();
t.replaceTrackSegments(0, track.trkseg.length - 1, [segment])[0];
if (track.name) { if (track.name) {
t = produce(t, (t) => { t.name = `${track.name} (${segmentIndex + 1})`;
t.name = `${track.name} (${segmentIndex + 1})`;
});
} }
return t; return t;
}); });
let newFile = file.replaceTracks(0, file.trk.length - 1, tracks)[0]; newFile.replaceTracks(0, file.trk.length - 1, tracks)[0];
newFile = newFile.replaceWaypoints(0, file.wpt.length - 1, closest.filter((c) => c.index.includes(index)).map((c) => file.wpt[c.wptIndex]))[0]; newFile.replaceWaypoints(0, file.wpt.length - 1, closest.filter((c) => c.index.includes(index)).map((c) => file.wpt[c.wptIndex]))[0];
newFile = produce(newFile, (f) => { newFile._data.id = fileIds[index];
f._data.id = fileIds[index]; newFile.metadata.name = track.name ?? `${file.metadata.name} (${index + 1})`;
f.metadata.name = track.name ?? `${file.metadata.name} (${index + 1})`;
});
draft.set(newFile._data.id, freeze(newFile)); draft.set(newFile._data.id, freeze(newFile));
}); });
} else if (file.trk.length === 1) { } else if (file.trk.length === 1) {
let fileIds = getFileIds(file.trk[0].trkseg.length); let fileIds = getFileIds(file.trk[0].trkseg.length);
let closest = file.wpt.map((wpt, wptIndex) => { let closest = file.wpt.map((wpt, wptIndex) => {
return { return {
wptIndex: wptIndex, wptIndex: wptIndex,
@@ -767,33 +758,32 @@ export const dbUtils = {
}); });
file.trk[0].trkseg.forEach((segment, index) => { file.trk[0].trkseg.forEach((segment, index) => {
let newFile = file.replaceTrackSegments(0, 0, file.trk[0].trkseg.length - 1, [segment])[0]; let newFile = file.clone();
newFile = newFile.replaceWaypoints(0, file.wpt.length - 1, closest.filter((c) => c.index.includes(index)).map((c) => file.wpt[c.wptIndex]))[0]; newFile.replaceTrackSegments(0, 0, file.trk[0].trkseg.length - 1, [segment])[0];
newFile = produce(newFile, (f) => { newFile.replaceWaypoints(0, file.wpt.length - 1, closest.filter((c) => c.index.includes(index)).map((c) => file.wpt[c.wptIndex]))[0];
f._data.id = fileIds[index]; newFile._data.id = fileIds[index];
f.metadata.name = `${file.trk[0].name ?? file.metadata.name} (${index + 1})`; newFile.metadata.name = `${file.trk[0].name ?? file.metadata.name} (${index + 1})`;
});
draft.set(newFile._data.id, freeze(newFile)); draft.set(newFile._data.id, freeze(newFile));
}); });
} }
draft.delete(fileId); draft.delete(fileId);
} else if (level === ListLevel.TRACK) { }
let newFile = file; } else if (level === ListLevel.TRACK) {
let file = draft.get(fileId);
if (file) {
for (let item of items) { for (let item of items) {
let trackIndex = (item as ListTrackItem).getTrackIndex(); let trackIndex = (item as ListTrackItem).getTrackIndex();
let track = file.trk[trackIndex]; let track = file.trk[trackIndex];
let tracks = track.trkseg.map((segment, segmentIndex) => { let tracks = track.trkseg.map((segment, segmentIndex) => {
let t = track.clone().replaceTrackSegments(0, track.trkseg.length - 1, [segment])[0]; let t = track.clone();
t.replaceTrackSegments(0, track.trkseg.length - 1, [segment])[0];
if (track.name) { if (track.name) {
t = produce(t, (t) => { t.name = `${track.name} (${segmentIndex + 1})`;
t.name = `${track.name} (${segmentIndex + 1})`;
});
} }
return t; return t;
}); });
newFile = newFile.replaceTracks(trackIndex, trackIndex, tracks)[0]; file.replaceTracks(trackIndex, trackIndex, tracks)[0];
} }
draft.set(newFile._data.id, freeze(newFile));
} }
} }
}); });
@@ -825,18 +815,32 @@ export const dbUtils = {
}); });
if (splitType === SplitType.FILES) { if (splitType === SplitType.FILES) {
let newFile = file.crop(0, absoluteIndex); let newFile = draft.get(fileId);
draft.set(newFile._data.id, freeze(newFile)); if (newFile) {
let newFile2 = file.clone(); newFile.crop(0, absoluteIndex);
newFile2._data.id = getFileIds(1)[0]; let newFile2 = file.clone();
newFile2 = newFile2.crop(absoluteIndex, file.getNumberOfTrackPoints() - 1); newFile2._data.id = getFileIds(1)[0];
draft.set(newFile2._data.id, freeze(newFile2)); newFile2.crop(absoluteIndex, file.getNumberOfTrackPoints() - 1);
draft.set(newFile2._data.id, freeze(newFile2));
}
} else if (splitType === SplitType.TRACKS) { } else if (splitType === SplitType.TRACKS) {
let newFile = file.replaceTracks(trackIndex, trackIndex, [file.trk[trackIndex].crop(0, absoluteIndex), file.trk[trackIndex].crop(absoluteIndex, file.trk[trackIndex].getNumberOfTrackPoints() - 1)])[0]; let newFile = draft.get(fileId);
draft.set(newFile._data.id, freeze(newFile)); 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) { } else if (splitType === SplitType.SEGMENTS) {
let newFile = file.replaceTrackSegments(trackIndex, segmentIndex, segmentIndex, [segment.crop(0, minIndex), segment.crop(minIndex, segment.trkpt.length - 1)])[0]; let newFile = draft.get(fileId);
draft.set(newFile._data.id, freeze(newFile)); 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]);
}
} }
} }
}); });
@@ -847,25 +851,23 @@ export const dbUtils = {
} }
applyGlobal((draft) => { applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = getFile(fileId); let file = draft.get(fileId);
if (file) { if (file) {
let newFile = file;
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
newFile = file.clean(bounds, inside, deleteTrackPoints, deleteWaypoints); file.clean(bounds, inside, deleteTrackPoints, deleteWaypoints);
} else if (level === ListLevel.TRACK) { } else if (level === ListLevel.TRACK) {
let trackIndices = items.map((item) => (item as ListTrackItem).getTrackIndex()); let trackIndices = items.map((item) => (item as ListTrackItem).getTrackIndex());
newFile = newFile.clean(bounds, inside, deleteTrackPoints, deleteWaypoints, trackIndices); file.clean(bounds, inside, deleteTrackPoints, deleteWaypoints, trackIndices);
} else if (level === ListLevel.SEGMENT) { } else if (level === ListLevel.SEGMENT) {
let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()]; let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()];
let segmentIndices = items.map((item) => (item as ListTrackSegmentItem).getSegmentIndex()); let segmentIndices = items.map((item) => (item as ListTrackSegmentItem).getSegmentIndex());
newFile = newFile.clean(bounds, inside, deleteTrackPoints, deleteWaypoints, trackIndices, segmentIndices); file.clean(bounds, inside, deleteTrackPoints, deleteWaypoints, trackIndices, segmentIndices);
} else if (level === ListLevel.WAYPOINTS) { } else if (level === ListLevel.WAYPOINTS) {
newFile = newFile.clean(bounds, inside, false, deleteWaypoints); file.clean(bounds, inside, false, deleteWaypoints);
} else if (level === ListLevel.WAYPOINT) { } else if (level === ListLevel.WAYPOINT) {
let waypointIndices = items.map((item) => (item as ListWaypointItem).getWaypointIndex()); let waypointIndices = items.map((item) => (item as ListWaypointItem).getWaypointIndex());
newFile = newFile.clean(bounds, inside, false, deleteWaypoints, [], [], waypointIndices); file.clean(bounds, inside, false, deleteWaypoints, [], [], waypointIndices);
} }
draft.set(newFile._data.id, freeze(newFile));
} }
}); });
}); });
@@ -877,20 +879,18 @@ export const dbUtils = {
applyGlobal((draft) => { applyGlobal((draft) => {
let allItems = Array.from(itemsAndPoints.keys()); let allItems = Array.from(itemsAndPoints.keys());
applyToOrderedItemsFromFile(allItems, (fileId, level, items) => { applyToOrderedItemsFromFile(allItems, (fileId, level, items) => {
let file = getFile(fileId); let file = draft.get(fileId);
if (file) { if (file) {
let newFile = file;
for (let item of items) { for (let item of items) {
if (item instanceof ListTrackSegmentItem) { if (item instanceof ListTrackSegmentItem) {
let trackIndex = item.getTrackIndex(); let trackIndex = item.getTrackIndex();
let segmentIndex = item.getSegmentIndex(); let segmentIndex = item.getSegmentIndex();
let points = itemsAndPoints.get(item); let points = itemsAndPoints.get(item);
if (points) { if (points) {
newFile = newFile.replaceTrackPoints(trackIndex, segmentIndex, 0, file.trk[trackIndex].trkseg[segmentIndex].getNumberOfTrackPoints() - 1, points); file.replaceTrackPoints(trackIndex, segmentIndex, 0, file.trk[trackIndex].trkseg[segmentIndex].getNumberOfTrackPoints() - 1, points);
} }
} }
} }
draft.set(newFile._data.id, freeze(newFile));
} }
}); });
}); });
@@ -901,21 +901,20 @@ export const dbUtils = {
} }
applyGlobal((draft) => { applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = getFile(fileId); let file = draft.get(fileId);
if (file && (level === ListLevel.FILE || level === ListLevel.TRACK)) { if (file && (level === ListLevel.FILE || level === ListLevel.TRACK)) {
let newFile = file;
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
newFile = file.setStyle(style); file.setStyle(style);
} else if (level === ListLevel.TRACK) { } else if (level === ListLevel.TRACK) {
for (let item of items) {
let trackIndex = (item as ListTrackItem).getTrackIndex();
newFile = newFile.replaceTracks(trackIndex, trackIndex, [file.trk[trackIndex].setStyle(style)])[0];
}
if (items.length === file.trk.length) { if (items.length === file.trk.length) {
newFile = newFile.setStyle(style); file.setStyle(style);
} else {
for (let item of items) {
let trackIndex = (item as ListTrackItem).getTrackIndex();
file.trk[trackIndex].setStyle(style);
}
} }
} }
draft.set(newFile._data.id, freeze(newFile));
} }
}); });
}); });
@@ -926,25 +925,23 @@ export const dbUtils = {
} }
applyGlobal((draft) => { applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = getFile(fileId); let file = draft.get(fileId);
if (file) { if (file) {
let newFile = file;
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
newFile = file.setHidden(hidden); file.setHidden(hidden);
} else if (level === ListLevel.TRACK) { } else if (level === ListLevel.TRACK) {
let trackIndices = items.map((item) => (item as ListTrackItem).getTrackIndex()); let trackIndices = items.map((item) => (item as ListTrackItem).getTrackIndex());
newFile = newFile.setHidden(hidden, trackIndices); file.setHidden(hidden, trackIndices);
} else if (level === ListLevel.SEGMENT) { } else if (level === ListLevel.SEGMENT) {
let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()]; let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()];
let segmentIndices = items.map((item) => (item as ListTrackSegmentItem).getSegmentIndex()); let segmentIndices = items.map((item) => (item as ListTrackSegmentItem).getSegmentIndex());
newFile = newFile.setHidden(hidden, trackIndices, segmentIndices); file.setHidden(hidden, trackIndices, segmentIndices);
} else if (level === ListLevel.WAYPOINTS) { } else if (level === ListLevel.WAYPOINTS) {
newFile = newFile.setHiddenWaypoints(hidden); file.setHiddenWaypoints(hidden);
} else if (level === ListLevel.WAYPOINT) { } else if (level === ListLevel.WAYPOINT) {
let waypointIndices = items.map((item) => (item as ListWaypointItem).getWaypointIndex()); let waypointIndices = items.map((item) => (item as ListWaypointItem).getWaypointIndex());
newFile = newFile.setHiddenWaypoints(hidden, waypointIndices); file.setHiddenWaypoints(hidden, waypointIndices);
} }
draft.set(newFile._data.id, freeze(newFile));
} }
}); });
}); });
@@ -958,33 +955,27 @@ export const dbUtils = {
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
draft.delete(fileId); draft.delete(fileId);
} else { } else {
let file = getFile(fileId); let file = draft.get(fileId);
if (file) { if (file) {
let newFile = file;
if (level === ListLevel.TRACK) { if (level === ListLevel.TRACK) {
for (let item of items) { for (let item of items) {
let trackIndex = (item as ListTrackItem).getTrackIndex(); let trackIndex = (item as ListTrackItem).getTrackIndex();
let [result, _removed] = newFile.replaceTracks(trackIndex, trackIndex, []); file.replaceTracks(trackIndex, trackIndex, []);
newFile = result;
} }
} else if (level === ListLevel.SEGMENT) { } else if (level === ListLevel.SEGMENT) {
for (let item of items) { for (let item of items) {
let trackIndex = (item as ListTrackSegmentItem).getTrackIndex(); let trackIndex = (item as ListTrackSegmentItem).getTrackIndex();
let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex(); let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex();
let [result, _removed] = newFile.replaceTrackSegments(trackIndex, segmentIndex, segmentIndex, []); file.replaceTrackSegments(trackIndex, segmentIndex, segmentIndex, []);
newFile = result;
} }
} else if (level === ListLevel.WAYPOINTS) { } else if (level === ListLevel.WAYPOINTS) {
let [result, _removed] = newFile.replaceWaypoints(0, newFile.wpt.length - 1, []); file.replaceWaypoints(0, file.wpt.length - 1, []);
newFile = result;
} else if (level === ListLevel.WAYPOINT) { } else if (level === ListLevel.WAYPOINT) {
for (let item of items) { for (let item of items) {
let waypointIndex = (item as ListWaypointItem).getWaypointIndex(); let waypointIndex = (item as ListWaypointItem).getWaypointIndex();
let [result, _removed] = newFile.replaceWaypoints(waypointIndex, waypointIndex, []); file.replaceWaypoints(waypointIndex, waypointIndex, []);
newFile = result;
} }
} }
draft.set(newFile._data.id, freeze(newFile));
} }
} }
}); });