2024-04-18 17:11:23 +02:00
|
|
|
import { Coordinates, GPXFileAttributes, GPXFileType, Link, Metadata, TrackExtensions, TrackPointExtensions, TrackPointType, TrackSegmentType, TrackType, WaypointType } from "./types";
|
|
|
|
|
|
|
|
function cloneJSON<T>(obj: T): T {
|
|
|
|
if (obj === null || typeof obj !== 'object') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return JSON.parse(JSON.stringify(obj));
|
|
|
|
}
|
2024-04-16 13:54:48 +02:00
|
|
|
|
2024-04-16 15:41:03 +02:00
|
|
|
// An abstract class that groups functions that need to be computed recursively in the GPX file hierarchy
|
|
|
|
abstract class GPXTreeElement<T extends GPXTreeElement<any>> {
|
|
|
|
|
|
|
|
abstract isLeaf(): boolean;
|
|
|
|
abstract getChildren(): T[];
|
|
|
|
|
|
|
|
abstract reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date): void;
|
|
|
|
|
|
|
|
abstract getStartTimestamp(): Date;
|
|
|
|
abstract getEndTimestamp(): Date;
|
2024-04-16 22:57:28 +02:00
|
|
|
|
|
|
|
abstract toGeoJSON(): any;
|
2024-04-16 15:41:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// An abstract class that can be extended to facilitate functions working similarly with Tracks and TrackSegments
|
|
|
|
abstract class GPXTreeNode<T extends GPXTreeElement<any>> extends GPXTreeElement<T> {
|
|
|
|
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<GPXTreeLeaf> {
|
|
|
|
isLeaf(): boolean {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
getChildren(): GPXTreeLeaf[] {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// A class that represents a GPX file
|
|
|
|
export class GPXFile extends GPXTreeNode<Track>{
|
2024-04-16 13:54:48 +02:00
|
|
|
attributes: GPXFileAttributes;
|
|
|
|
metadata: Metadata;
|
|
|
|
wpt: Waypoint[];
|
|
|
|
trk: Track[];
|
|
|
|
|
|
|
|
constructor(gpx: GPXFileType | GPXFile) {
|
2024-04-16 15:41:03 +02:00
|
|
|
super();
|
2024-04-18 17:11:23 +02:00
|
|
|
this.attributes = cloneJSON(gpx.attributes);
|
|
|
|
this.metadata = cloneJSON(gpx.metadata);
|
|
|
|
this.wpt = gpx.wpt.map((waypoint) => new Waypoint(waypoint));
|
2024-04-16 13:54:48 +02:00
|
|
|
this.trk = gpx.trk.map((track) => new Track(track));
|
|
|
|
}
|
|
|
|
|
2024-04-16 15:41:03 +02:00
|
|
|
getChildren(): Track[] {
|
|
|
|
return this.trk;
|
2024-04-16 13:54:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
clone(): GPXFile {
|
|
|
|
return new GPXFile(structuredClone(this));
|
|
|
|
}
|
2024-04-16 22:57:28 +02:00
|
|
|
|
|
|
|
toGeoJSON(): any {
|
|
|
|
return {
|
|
|
|
type: "FeatureCollection",
|
|
|
|
features: this.getChildren().flatMap((child) => child.toGeoJSON())
|
|
|
|
};
|
|
|
|
}
|
2024-04-16 13:54:48 +02:00
|
|
|
};
|
|
|
|
|
2024-04-16 15:41:03 +02:00
|
|
|
// A class that represents a Track in a GPX file
|
|
|
|
export class Track extends GPXTreeNode<TrackSegment> {
|
2024-04-16 13:54:48 +02:00
|
|
|
name?: string;
|
|
|
|
cmt?: string;
|
|
|
|
desc?: string;
|
|
|
|
src?: string;
|
|
|
|
link?: Link;
|
|
|
|
type?: string;
|
|
|
|
trkseg: TrackSegment[];
|
|
|
|
extensions?: TrackExtensions;
|
|
|
|
|
2024-04-16 15:41:03 +02:00
|
|
|
constructor(track: TrackType | Track) {
|
|
|
|
super();
|
2024-04-16 13:54:48 +02:00
|
|
|
this.name = track.name;
|
|
|
|
this.cmt = track.cmt;
|
|
|
|
this.desc = track.desc;
|
|
|
|
this.src = track.src;
|
2024-04-18 17:11:23 +02:00
|
|
|
this.link = cloneJSON(track.link);
|
2024-04-16 13:54:48 +02:00
|
|
|
this.type = track.type;
|
|
|
|
this.trkseg = track.trkseg.map((seg) => new TrackSegment(seg));
|
2024-04-18 17:11:23 +02:00
|
|
|
this.extensions = cloneJSON(track.extensions);
|
2024-04-16 13:54:48 +02:00
|
|
|
}
|
|
|
|
|
2024-04-16 15:41:03 +02:00
|
|
|
getChildren(): TrackSegment[] {
|
|
|
|
return this.trkseg;
|
2024-04-16 13:54:48 +02:00
|
|
|
}
|
|
|
|
|
2024-04-16 15:41:03 +02:00
|
|
|
clone(): Track {
|
|
|
|
return new Track(structuredClone(this));
|
|
|
|
}
|
2024-04-16 22:57:28 +02:00
|
|
|
|
|
|
|
toGeoJSON(): any {
|
2024-04-17 16:46:51 +02:00
|
|
|
return this.getChildren().map((child) => {
|
|
|
|
let geoJSON = child.toGeoJSON();
|
|
|
|
if (this.extensions && this.extensions['gpx_style:line']) {
|
|
|
|
if (this.extensions['gpx_style:line'].color) {
|
|
|
|
geoJSON.properties['color'] = this.extensions['gpx_style:line'].color;
|
|
|
|
}
|
|
|
|
if (this.extensions['gpx_style:line'].opacity) {
|
|
|
|
geoJSON.properties['opacity'] = this.extensions['gpx_style:line'].opacity;
|
|
|
|
}
|
|
|
|
if (this.extensions['gpx_style:line'].weight) {
|
|
|
|
geoJSON.properties['weight'] = this.extensions['gpx_style:line'].weight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return geoJSON;
|
|
|
|
});
|
2024-04-16 22:57:28 +02:00
|
|
|
}
|
2024-04-16 15:41:03 +02:00
|
|
|
};
|
2024-04-16 13:54:48 +02:00
|
|
|
|
2024-04-16 15:41:03 +02:00
|
|
|
// A class that represents a TrackSegment in a GPX file
|
|
|
|
export class TrackSegment extends GPXTreeLeaf {
|
2024-04-16 13:54:48 +02:00
|
|
|
trkpt: TrackPoint[];
|
|
|
|
|
2024-04-16 15:41:03 +02:00
|
|
|
constructor(segment: TrackSegmentType | TrackSegment) {
|
|
|
|
super();
|
|
|
|
this.trkpt = segment.trkpt.map((point) => new TrackPoint(point));
|
2024-04-16 13:54:48 +02:00
|
|
|
}
|
|
|
|
|
2024-04-16 15:41:03 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-04-16 22:57:28 +02:00
|
|
|
toGeoJSON(): any {
|
|
|
|
return {
|
|
|
|
type: "Feature",
|
|
|
|
geometry: {
|
|
|
|
type: "LineString",
|
|
|
|
coordinates: this.trkpt.map((point) => [point.attributes.lon, point.attributes.lat])
|
|
|
|
},
|
|
|
|
properties: {}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-04-16 15:41:03 +02:00
|
|
|
clone(): TrackSegment {
|
|
|
|
return new TrackSegment(structuredClone(this));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export class TrackPoint {
|
|
|
|
attributes: Coordinates;
|
|
|
|
ele?: number;
|
|
|
|
time?: Date;
|
|
|
|
extensions?: TrackPointExtensions;
|
|
|
|
|
|
|
|
constructor(point: TrackPointType | TrackPoint) {
|
2024-04-18 17:11:23 +02:00
|
|
|
this.attributes = cloneJSON(point.attributes);
|
2024-04-16 15:41:03 +02:00
|
|
|
this.ele = point.ele;
|
|
|
|
if (point.time) {
|
|
|
|
this.time = new Date(point.time.getTime());
|
|
|
|
}
|
2024-04-18 17:11:23 +02:00
|
|
|
this.extensions = cloneJSON(point.extensions);
|
2024-04-16 13:54:48 +02:00
|
|
|
}
|
2024-04-18 17:11:23 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
export class Waypoint {
|
|
|
|
attributes: Coordinates;
|
|
|
|
ele?: number;
|
|
|
|
time?: Date;
|
|
|
|
name?: string;
|
|
|
|
cmt?: string;
|
|
|
|
desc?: string;
|
|
|
|
link?: Link;
|
|
|
|
sym?: string;
|
|
|
|
type?: string;
|
|
|
|
|
|
|
|
constructor(waypoint: WaypointType | Waypoint) {
|
|
|
|
this.attributes = cloneJSON(waypoint.attributes);
|
|
|
|
this.ele = waypoint.ele;
|
|
|
|
if (waypoint.time) {
|
|
|
|
this.time = new Date(waypoint.time.getTime());
|
|
|
|
}
|
|
|
|
this.name = waypoint.name;
|
|
|
|
this.cmt = waypoint.cmt;
|
|
|
|
this.desc = waypoint.desc;
|
|
|
|
this.link = cloneJSON(waypoint.link);
|
|
|
|
this.sym = waypoint.sym;
|
|
|
|
this.type = waypoint.type;
|
|
|
|
}
|
|
|
|
}
|