diff --git a/gpx/src/gpx.ts b/gpx/src/gpx.ts index 0f33e59f..10c3b029 100644 --- a/gpx/src/gpx.ts +++ b/gpx/src/gpx.ts @@ -1,23 +1,80 @@ -import { GPXFileAttributes, GPXFileType, Link, Metadata, TrackExtensions, TrackPoint, TrackSegmentType, TrackType, Waypoint } from "./types"; +import { Coordinates, GPXFileAttributes, GPXFileType, Link, Metadata, TrackExtensions, TrackPointExtensions, TrackPointType, TrackSegmentType, TrackType, Waypoint } from "./types"; -export class GPXFile { +// An abstract class that groups functions that need to be computed recursively in the GPX file hierarchy +abstract class GPXTreeElement> { + + abstract isLeaf(): boolean; + abstract getChildren(): T[]; + + abstract reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date): void; + + abstract getStartTimestamp(): Date; + abstract getEndTimestamp(): Date; +} + +// An abstract class that can be extended to facilitate functions working similarly with Tracks and TrackSegments +abstract class GPXTreeNode> extends GPXTreeElement { + isLeaf(): boolean { + return false; + } + + reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date): void { + const children = this.getChildren(); + + if (!originalNextTimestamp && !newPreviousTimestamp) { + originalNextTimestamp = children[children.length - 1].getEndTimestamp(); + newPreviousTimestamp = children[0].getStartTimestamp(); + } + + children.reverse(); + + for (let i = 0; i < children.length; i++) { + let originalStartTimestamp = children[i].getStartTimestamp(); + + children[i].reverse(originalNextTimestamp, newPreviousTimestamp); + + originalNextTimestamp = originalStartTimestamp; + newPreviousTimestamp = children[i].getEndTimestamp(); + } + } + + getStartTimestamp(): Date { + return this.getChildren()[0].getStartTimestamp(); + } + + getEndTimestamp(): Date { + return this.getChildren()[this.getChildren().length - 1].getEndTimestamp(); + } +} + +// An abstract class that TrackSegment extends to implement the GPXTreeElement interface +abstract class GPXTreeLeaf extends GPXTreeElement { + isLeaf(): boolean { + return true; + } + + getChildren(): GPXTreeLeaf[] { + return []; + } +} + +// A class that represents a GPX file +export class GPXFile extends GPXTreeNode{ attributes: GPXFileAttributes; metadata: Metadata; wpt: Waypoint[]; trk: Track[]; constructor(gpx: GPXFileType | GPXFile) { + super(); this.attributes = gpx.attributes; this.metadata = gpx.metadata; this.wpt = gpx.wpt; this.trk = gpx.trk.map((track) => new Track(track)); } - reverse(): void { - for (let i = 0; i < this.trk.length; i++) { - this.trk[i].reverse(); - } - this.trk.reverse(); + getChildren(): Track[] { + return this.trk; } clone(): GPXFile { @@ -25,7 +82,8 @@ export class GPXFile { } }; -export class Track { +// A class that represents a Track in a GPX file +export class Track extends GPXTreeNode { name?: string; cmt?: string; desc?: string; @@ -35,7 +93,8 @@ export class Track { trkseg: TrackSegment[]; extensions?: TrackExtensions; - constructor(track: TrackType) { + constructor(track: TrackType | Track) { + super(); this.name = track.name; this.cmt = track.cmt; this.desc = track.desc; @@ -46,23 +105,68 @@ export class Track { this.extensions = track.extensions; } - reverse(): void { - for (let i = 0; i < this.trkseg.length; i++) { - this.trkseg[i].reverse(); - } - this.trkseg.reverse(); + getChildren(): TrackSegment[] { + return this.trkseg; + } + + clone(): Track { + return new Track(structuredClone(this)); } }; - -export class TrackSegment { +// A class that represents a TrackSegment in a GPX file +export class TrackSegment extends GPXTreeLeaf { trkpt: TrackPoint[]; - constructor(segment: TrackSegmentType) { - this.trkpt = segment.trkpt; + constructor(segment: TrackSegmentType | TrackSegment) { + super(); + this.trkpt = segment.trkpt.map((point) => new TrackPoint(point)); } - reverse(): void { - this.trkpt.reverse(); + reverse(originalNextTimestamp: Date | undefined, newPreviousTimestamp: Date | undefined): void { + if (originalNextTimestamp !== undefined && newPreviousTimestamp !== undefined) { + let originalEndTimestamp = this.getEndTimestamp(); + let newStartTimestamp = new Date( + newPreviousTimestamp.getTime() + originalNextTimestamp.getTime() - originalEndTimestamp.getTime() + ); + + this.trkpt.reverse(); + + for (let i = 0; i < this.trkpt.length; i++) { + this.trkpt[i].time = new Date( + newStartTimestamp.getTime() + (originalEndTimestamp.getTime() - this.trkpt[i].time.getTime()) + ); + } + } else { + this.trkpt.reverse(); + } + } + + getStartTimestamp(): Date { + return this.trkpt[0].time; + } + + getEndTimestamp(): Date { + return this.trkpt[this.trkpt.length - 1].time; + } + + clone(): TrackSegment { + return new TrackSegment(structuredClone(this)); + } +}; + +export class TrackPoint { + attributes: Coordinates; + ele?: number; + time?: Date; + extensions?: TrackPointExtensions; + + constructor(point: TrackPointType | TrackPoint) { + this.attributes = point.attributes; + this.ele = point.ele; + if (point.time) { + this.time = new Date(point.time.getTime()); + } + this.extensions = point.extensions; } }; \ No newline at end of file diff --git a/gpx/src/types.ts b/gpx/src/types.ts index c98dec33..0256c012 100644 --- a/gpx/src/types.ts +++ b/gpx/src/types.ts @@ -67,10 +67,10 @@ export type LineStyleExtension = { }; export type TrackSegmentType = { - trkpt: TrackPoint[]; + trkpt: TrackPointType[]; }; -export type TrackPoint = { +export type TrackPointType = { attributes: Coordinates; ele?: number; time?: Date; diff --git a/gpx/test-data/with_tracks_and_segments.gpx b/gpx/test-data/with_tracks_and_segments.gpx index 739d5515..f10e720a 100644 --- a/gpx/test-data/with_tracks_and_segments.gpx +++ b/gpx/test-data/with_tracks_and_segments.gpx @@ -17,155 +17,205 @@ 109.0 + 110.8 + 110.3 + 110.0 + 110.3 + 109.3 + 107.0 + 106.0 + 108.5 + 109.8 + 110.8 + 112.0 + 112.8 + 113.5 + 114.3 + 115.3 + 115.0 + 114.8 + 114.3 + 114.3 + 114.5 + 115.0 + 115.8 + 115.8 + 116.0 + 115.8 + 115.5 + 114.5 + 112.5 + 110.8 + 109.0 + 106.3 + 104.3 + 104.0 + 103.8 + 103.3 + 103.5 + 103.8 + 104.8 + 105.8 + 106.8 + 107.8 + 108.5 + 109.3 + 110.0 + 110.5 + 110.8 + 111.8 + 112.8 + 113.3 + @@ -175,92 +225,121 @@ 115.5 + 115.8 + 118.5 + 119.5 + 121.3 + 122.0 + 122.8 + 123.5 + 126.3 + 128.0 + 129.0 + 130.0 + 130.8 + 131.8 + 132.3 + 132.8 + 135.8 + 135.5 + 132.5 + 133.3 + 134.0 + 136.8 + 137.5 + 137.3 + 137.5 + 137.3 + 134.8 + 132.3 + 129.5 + diff --git a/gpx/test/gpx.test.ts b/gpx/test/gpx.test.ts index caca3f7e..54c60f22 100644 --- a/gpx/test/gpx.test.ts +++ b/gpx/test/gpx.test.ts @@ -26,6 +26,9 @@ describe('GPX operations', () => { let reversed = original.clone(); reversed.reverse(); + expect(original.getStartTimestamp().getTime()).toBe(reversed.getStartTimestamp().getTime()); + expect(original.getEndTimestamp().getTime()).toBe(reversed.getEndTimestamp().getTime()); + expect(reversed.trk.length).toBe(original.trk.length); for (let i = 0; i < original.trk.length; i++) { @@ -47,6 +50,8 @@ describe('GPX operations', () => { expect(reversedPoint.attributes.lat).toBe(originalPoint.attributes.lat); expect(reversedPoint.attributes.lon).toBe(originalPoint.attributes.lon); expect(reversedPoint.ele).toBe(originalPoint.ele); + + expect(reversed.getEndTimestamp().getTime() - reversedPoint.time.getTime()).toBe(originalPoint.time.getTime() - original.getStartTimestamp().getTime()); } } }