From 0baa956160518dd5a47ea29c49105695797d0b83 Mon Sep 17 00:00:00 2001 From: vcoppe Date: Mon, 16 Sep 2024 10:57:10 +0200 Subject: [PATCH] commit dist folder --- gpx/.gitignore | 3 +- gpx/dist/gpx.d.ts | 250 ++++++++ gpx/dist/gpx.js | 1328 ++++++++++++++++++++++++++++++++++++++++ gpx/dist/index.d.ts | 4 + gpx/dist/index.js | 3 + gpx/dist/io.d.ts | 3 + gpx/dist/io.js | 105 ++++ gpx/dist/simplify.d.ts | 9 + gpx/dist/simplify.js | 126 ++++ gpx/dist/types.d.ts | 108 ++++ gpx/dist/types.js | 1 + 11 files changed, 1938 insertions(+), 2 deletions(-) create mode 100644 gpx/dist/gpx.d.ts create mode 100644 gpx/dist/gpx.js create mode 100644 gpx/dist/index.d.ts create mode 100644 gpx/dist/index.js create mode 100644 gpx/dist/io.d.ts create mode 100644 gpx/dist/io.js create mode 100644 gpx/dist/simplify.d.ts create mode 100644 gpx/dist/simplify.js create mode 100644 gpx/dist/types.d.ts create mode 100644 gpx/dist/types.js diff --git a/gpx/.gitignore b/gpx/.gitignore index 76add878..b512c09d 100644 --- a/gpx/.gitignore +++ b/gpx/.gitignore @@ -1,2 +1 @@ -node_modules -dist \ No newline at end of file +node_modules \ No newline at end of file diff --git a/gpx/dist/gpx.d.ts b/gpx/dist/gpx.d.ts new file mode 100644 index 00000000..02526c4d --- /dev/null +++ b/gpx/dist/gpx.d.ts @@ -0,0 +1,250 @@ +import { Coordinates, GPXFileAttributes, GPXFileType, LineStyleExtension, Link, Metadata, RouteType, TrackExtensions, TrackPointExtensions, TrackPointType, TrackSegmentType, TrackType, WaypointType } from "./types"; +import { immerable } from "immer"; +export declare abstract class GPXTreeElement> { + _data: { + [key: string]: any; + }; + abstract isLeaf(): boolean; + abstract get children(): Array; + abstract getNumberOfTrackPoints(): number; + abstract getStartTimestamp(): Date | undefined; + abstract getEndTimestamp(): Date | undefined; + abstract getStatistics(): GPXStatistics; + abstract getSegments(): TrackSegment[]; + abstract getTrackPoints(): TrackPoint[]; + abstract toGeoJSON(): GeoJSON.Feature | GeoJSON.Feature[] | GeoJSON.FeatureCollection | GeoJSON.FeatureCollection[]; + abstract _reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date): any; +} +export type AnyGPXTreeElement = GPXTreeElement>; +declare abstract class GPXTreeNode> extends GPXTreeElement { + isLeaf(): boolean; + getNumberOfTrackPoints(): number; + getStartTimestamp(): Date | undefined; + getEndTimestamp(): Date | undefined; + getStatistics(): GPXStatistics; + getSegments(): TrackSegment[]; + getTrackPoints(): TrackPoint[]; + _reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date): void; +} +declare abstract class GPXTreeLeaf extends GPXTreeElement { + isLeaf(): boolean; + get children(): Array; +} +export declare class GPXFile extends GPXTreeNode { + [immerable]: boolean; + attributes: GPXFileAttributes; + metadata: Metadata; + wpt: Waypoint[]; + trk: Track[]; + rte: RouteType[]; + constructor(gpx?: GPXFileType & { + _data?: any; + } | GPXFile); + get children(): Array; + getSegment(trackIndex: number, segmentIndex: number): TrackSegment; + forEachSegment(callback: (segment: TrackSegment, trackIndex: number, segmentIndex: number) => void): void; + getStyle(): MergedLineStyles; + clone(): GPXFile; + toGeoJSON(): GeoJSON.FeatureCollection; + toGPXFileType(exclude?: string[]): GPXFileType; + replaceTracks(start: number, end: number, tracks: Track[]): void; + replaceTrackSegments(trackIndex: number, start: number, end: number, segments: TrackSegment[]): void; + replaceTrackPoints(trackIndex: number, segmentIndex: number, start: number, end: number, points: TrackPoint[], speed?: number, startTime?: Date): void; + replaceWaypoints(start: number, end: number, waypoints: Waypoint[]): void; + reverse(): void; + reverseTrack(trackIndex: number): void; + reverseTrackSegment(trackIndex: number, segmentIndex: number): void; + roundTrip(): void; + roundTripTrack(trackIndex: number): void; + roundTripTrackSegment(trackIndex: number, segmentIndex: number): void; + crop(start: number, end: number, trackIndices?: number[], segmentIndices?: number[]): void; + clean(bounds: [Coordinates, Coordinates], inside: boolean, deleteTrackPoints: boolean, deleteWaypoints: boolean, trackIndices?: number[], segmentIndices?: number[], waypointIndices?: number[]): void; + changeTimestamps(startTime: Date, speed: number, ratio: number, trackIndex?: number, segmentIndex?: number): void; + createArtificialTimestamps(startTime: Date, totalTime: number, trackIndex?: number, segmentIndex?: number): void; + addElevation(elevations: number[], trackIndices?: number[], segmentIndices?: number[], waypointIndices?: number[]): void; + setStyle(style: LineStyleExtension): void; + setHidden(hidden: boolean, trackIndices?: number[], segmentIndices?: number[]): void; + setHiddenWaypoints(hidden: boolean, waypointIndices?: number[]): void; +} +export declare class Track extends GPXTreeNode { + [immerable]: boolean; + name?: string; + cmt?: string; + desc?: string; + src?: string; + link?: Link; + type?: string; + extensions?: TrackExtensions; + trkseg: TrackSegment[]; + constructor(track?: TrackType & { + _data?: any; + } | Track); + get children(): Array; + clone(): Track; + getStyle(): LineStyleExtension | undefined; + toGeoJSON(): GeoJSON.Feature[]; + toTrackType(exclude?: string[]): TrackType; + replaceTrackSegments(start: number, end: number, segments: TrackSegment[]): void; + replaceTrackPoints(segmentIndex: number, start: number, end: number, points: TrackPoint[], speed?: number, startTime?: Date): void; + reverseTrackSegment(segmentIndex: number): void; + roundTrip(): void; + roundTripTrackSegment(segmentIndex: number): void; + crop(start: number, end: number, segmentIndices?: number[]): void; + clean(bounds: [Coordinates, Coordinates], inside: boolean, segmentIndices?: number[]): void; + changeTimestamps(startTime: Date, speed: number, ratio: number, lastPoint?: TrackPoint, segmentIndex?: number): void; + createArtificialTimestamps(startTime: Date, totalTime: number, lastPoint: TrackPoint | undefined, segmentIndex?: number): void; + setStyle(style: LineStyleExtension, force?: boolean): void; + setHidden(hidden: boolean, segmentIndices?: number[]): void; +} +export declare class TrackSegment extends GPXTreeLeaf { + [immerable]: boolean; + trkpt: TrackPoint[]; + constructor(segment?: TrackSegmentType & { + _data?: any; + } | TrackSegment); + _computeStatistics(): GPXStatistics; + _computeSmoothedElevation(): number[]; + _computeSlope(): number[]; + _computeSlopeSegments(statistics: GPXStatistics): [number[], number[]]; + getNumberOfTrackPoints(): number; + getStartTimestamp(): Date | undefined; + getEndTimestamp(): Date | undefined; + getStatistics(): GPXStatistics; + getSegments(): TrackSegment[]; + getTrackPoints(): TrackPoint[]; + toGeoJSON(): GeoJSON.Feature; + toTrackSegmentType(exclude?: string[]): TrackSegmentType; + clone(): TrackSegment; + replaceTrackPoints(start: number, end: number, points: TrackPoint[], speed?: number, startTime?: Date): void; + _reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date): void; + roundTrip(): void; + crop(start: number, end: number): void; + clean(bounds: [Coordinates, Coordinates], inside: boolean): void; + changeTimestamps(startTime: Date, speed: number, ratio: number, lastPoint?: TrackPoint): void; + createArtificialTimestamps(startTime: Date, totalTime: number, lastPoint: TrackPoint | undefined): void; + setHidden(hidden: boolean): void; +} +export declare class TrackPoint { + [immerable]: boolean; + attributes: Coordinates; + ele?: number; + time?: Date; + extensions?: TrackPointExtensions; + _data: { + [key: string]: any; + }; + constructor(point: TrackPointType & { + _data?: any; + } | TrackPoint); + getCoordinates(): Coordinates; + setCoordinates(coordinates: Coordinates): void; + getLatitude(): number; + getLongitude(): number; + getTemperature(): number; + getHeartRate(): number; + getCadence(): number; + getPower(): number; + getSurface(): string; + setSurface(surface: string): void; + toTrackPointType(exclude?: string[]): TrackPointType; + clone(): TrackPoint; +} +export declare class Waypoint { + [immerable]: boolean; + attributes: Coordinates; + ele?: number; + time?: Date; + name?: string; + cmt?: string; + desc?: string; + link?: Link; + sym?: string; + type?: string; + _data: { + [key: string]: any; + }; + constructor(waypoint: WaypointType & { + _data?: any; + } | Waypoint); + getCoordinates(): Coordinates; + setCoordinates(coordinates: Coordinates): void; + getLatitude(): number; + getLongitude(): number; + toWaypointType(exclude?: string[]): WaypointType; + clone(): Waypoint; + setHidden(hidden: boolean): void; +} +export declare class GPXStatistics { + global: { + distance: { + moving: number; + total: number; + }; + time: { + start: Date | undefined; + end: Date | undefined; + moving: number; + total: number; + }; + speed: { + moving: number; + total: number; + }; + elevation: { + gain: number; + loss: number; + }; + bounds: { + southWest: Coordinates; + northEast: Coordinates; + }; + atemp: { + avg: number; + count: number; + }; + hr: { + avg: number; + count: number; + }; + cad: { + avg: number; + count: number; + }; + power: { + avg: number; + count: number; + }; + }; + local: { + points: TrackPoint[]; + distance: { + moving: number[]; + total: number[]; + }; + time: { + moving: number[]; + total: number[]; + }; + speed: number[]; + elevation: { + smoothed: number[]; + gain: number[]; + loss: number[]; + }; + slope: { + at: number[]; + segment: number[]; + length: number[]; + }; + }; + constructor(); + mergeWith(other: GPXStatistics): void; + slice(start: number, end: number): GPXStatistics; +} +export declare function distance(coord1: TrackPoint | Coordinates, coord2: TrackPoint | Coordinates): number; +export type MergedLineStyles = { + color: string[]; + opacity: number[]; + weight: number[]; +}; +export {}; diff --git a/gpx/dist/gpx.js b/gpx/dist/gpx.js new file mode 100644 index 00000000..d34c9a4e --- /dev/null +++ b/gpx/dist/gpx.js @@ -0,0 +1,1328 @@ +var _a, _b, _c, _d, _e; +import { ramerDouglasPeucker } from "./simplify"; +import { immerable, isDraft, original, freeze } from "immer"; +function cloneJSON(obj) { + if (obj === null || typeof obj !== 'object') { + return null; + } + return JSON.parse(JSON.stringify(obj)); +} +// An abstract class that groups functions that need to be computed recursively in the GPX file hierarchy +export class GPXTreeElement { + constructor() { + this._data = {}; + } +} +// An abstract class that can be extended to facilitate functions working similarly with Tracks and TrackSegments +class GPXTreeNode extends GPXTreeElement { + isLeaf() { + return false; + } + getNumberOfTrackPoints() { + return this.children.reduce((acc, child) => acc + child.getNumberOfTrackPoints(), 0); + } + getStartTimestamp() { + if (this.children.length === 0) { + return undefined; + } + return this.children[0].getStartTimestamp(); + } + getEndTimestamp() { + if (this.children.length === 0) { + return undefined; + } + return this.children[this.children.length - 1].getEndTimestamp(); + } + getStatistics() { + let statistics = new GPXStatistics(); + for (let child of this.children) { + statistics.mergeWith(child.getStatistics()); + } + return statistics; + } + getSegments() { + return this.children.flatMap((child) => child.getSegments()); + } + getTrackPoints() { + return this.children.flatMap((child) => child.getTrackPoints()); + } + // Producers + _reverse(originalNextTimestamp, newPreviousTimestamp) { + let og = getOriginal(this); + if (!originalNextTimestamp && !newPreviousTimestamp) { + originalNextTimestamp = og.getEndTimestamp(); + newPreviousTimestamp = og.getStartTimestamp(); + } + this.children.reverse(); + for (let i = 0; i < og.children.length; i++) { + let originalStartTimestamp = og.children[og.children.length - i - 1].getStartTimestamp(); + this.children[i]._reverse(originalNextTimestamp, newPreviousTimestamp); + originalNextTimestamp = originalStartTimestamp; + newPreviousTimestamp = this.children[i].getEndTimestamp(); + } + } +} +// An abstract class that TrackSegment extends to implement the GPXTreeElement interface +class GPXTreeLeaf extends GPXTreeElement { + isLeaf() { + return true; + } + get children() { + return []; + } +} +// A class that represents a GPX file +export class GPXFile extends GPXTreeNode { + constructor(gpx) { + var _f; + super(); + this[_a] = true; + if (gpx) { + this.attributes = gpx.attributes; + this.metadata = (_f = gpx.metadata) !== null && _f !== void 0 ? _f : {}; + this.metadata.author = { + name: 'gpx.studio', + link: { + attributes: { + href: 'https://gpx.studio', + } + } + }; + this.wpt = gpx.wpt ? gpx.wpt.map((waypoint) => new Waypoint(waypoint)) : []; + this.trk = gpx.trk ? gpx.trk.map((track) => new Track(track)) : []; + if (gpx.rte && gpx.rte.length > 0) { + this.trk = this.trk.concat(gpx.rte.map((route) => convertRouteToTrack(route))); + } + if (gpx.hasOwnProperty('_data')) { + this._data = gpx._data; + if (!this._data.hasOwnProperty('style')) { + let style = this.getStyle(); + let fileStyle = {}; + if (style.color.length === 1) { + fileStyle['color'] = style.color[0]; + } + if (style.weight.length === 1) { + fileStyle['weight'] = style.weight[0]; + } + if (style.opacity.length === 1) { + fileStyle['opacity'] = style.opacity[0]; + } + if (Object.keys(fileStyle).length > 0) { + this.setStyle(fileStyle); + } + } + } + } + else { + this.attributes = {}; + this.metadata = {}; + this.wpt = []; + this.trk = [new Track()]; + } + this.trk.forEach((track, trackIndex) => { + track._data['trackIndex'] = trackIndex; + track.trkseg.forEach((segment, segmentIndex) => { + segment._data['trackIndex'] = trackIndex; + segment._data['segmentIndex'] = segmentIndex; + }); + }); + this.wpt.forEach((waypoint, waypointIndex) => { + waypoint._data['index'] = waypointIndex; + }); + } + get children() { + return this.trk; + } + getSegment(trackIndex, segmentIndex) { + return this.trk[trackIndex].children[segmentIndex]; + } + forEachSegment(callback) { + this.trk.forEach((track, trackIndex) => { + track.children.forEach((segment, segmentIndex) => { + callback(segment, trackIndex, segmentIndex); + }); + }); + } + getStyle() { + return this.trk.map((track) => track.getStyle()).reduce((acc, style) => { + if (style) { + if (style.color && !acc.color.includes(style.color)) { + acc.color.push(style.color); + } + if (style.opacity && !acc.opacity.includes(style.opacity)) { + acc.opacity.push(style.opacity); + } + if (style.weight && !acc.weight.includes(style.weight)) { + acc.weight.push(style.weight); + } + } + return acc; + }, { + color: [], + opacity: [], + weight: [] + }); + } + clone() { + return new GPXFile({ + attributes: cloneJSON(this.attributes), + metadata: cloneJSON(this.metadata), + wpt: this.wpt.map((waypoint) => waypoint.clone()), + trk: this.trk.map((track) => track.clone()), + rte: [], + _data: cloneJSON(this._data), + }); + } + toGeoJSON() { + return { + type: "FeatureCollection", + features: this.children.flatMap((child) => child.toGeoJSON()) + }; + } + toGPXFileType(exclude = []) { + let file = { + attributes: cloneJSON(this.attributes), + metadata: {}, + wpt: this.wpt.map((wpt) => wpt.toWaypointType(exclude)), + trk: this.trk.map((track) => track.toTrackType(exclude)), + rte: [], + }; + if (this.metadata) { + if (this.metadata.name) { + file.metadata.name = this.metadata.name; + } + if (this.metadata.desc) { + file.metadata.desc = this.metadata.desc; + } + if (this.metadata.author) { + file.metadata.author = cloneJSON(this.metadata.author); + } + if (this.metadata.link) { + file.metadata.link = cloneJSON(this.metadata.link); + } + if (this.metadata.time && !exclude.includes('time')) { + file.metadata.time = this.metadata.time; + } + } + return file; + } + // Producers + replaceTracks(start, end, tracks) { + if (this._data.style) { + tracks.forEach((track) => track.setStyle(this._data.style, false)); + } + this.trk.splice(start, end - start + 1, ...tracks); + } + replaceTrackSegments(trackIndex, start, end, segments) { + this.trk[trackIndex].replaceTrackSegments(start, end, segments); + } + replaceTrackPoints(trackIndex, segmentIndex, start, end, points, speed, startTime) { + this.trk[trackIndex].replaceTrackPoints(segmentIndex, start, end, points, speed, startTime); + } + replaceWaypoints(start, end, waypoints) { + this.wpt.splice(start, end - start + 1, ...waypoints); + } + reverse() { + return this._reverse(this.getEndTimestamp(), this.getStartTimestamp()); + } + reverseTrack(trackIndex) { + this.trk[trackIndex]._reverse(); + } + reverseTrackSegment(trackIndex, segmentIndex) { + this.trk[trackIndex].reverseTrackSegment(segmentIndex); + } + roundTrip() { + this.trk.forEach((track) => { + track.roundTrip(); + }); + } + roundTripTrack(trackIndex) { + this.trk[trackIndex].roundTrip(); + } + roundTripTrackSegment(trackIndex, segmentIndex) { + this.trk[trackIndex].roundTripTrackSegment(segmentIndex); + } + crop(start, end, trackIndices, segmentIndices) { + let i = 0; + let trackIndex = 0; + 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, inside, deleteTrackPoints, deleteWaypoints, trackIndices, segmentIndices, waypointIndices) { + if (deleteTrackPoints) { + let i = 0; + let trackIndex = 0; + while (i < this.trk.length) { + if (trackIndices === undefined || trackIndices.includes(trackIndex)) { + this.trk[i].clean(bounds, inside, segmentIndices); + if (this.trk[i].getNumberOfTrackPoints() === 0) { + this.trk.splice(i, 1); + } + else { + i++; + } + } + else { + i++; + } + trackIndex++; + } + } + 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) => { + 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; + } + }); + this.wpt = freeze(wpt); // Pre-freeze the array, faster as well + } + } + changeTimestamps(startTime, speed, ratio, trackIndex, segmentIndex) { + let lastPoint = undefined; + this.trk.forEach((track, index) => { + if (trackIndex === undefined || trackIndex === index) { + track.changeTimestamps(startTime, speed, ratio, lastPoint, segmentIndex); + } + }); + } + createArtificialTimestamps(startTime, totalTime, trackIndex, segmentIndex) { + let lastPoint = undefined; + this.trk.forEach((track, index) => { + if (trackIndex === undefined || trackIndex === index) { + track.createArtificialTimestamps(startTime, totalTime, lastPoint, segmentIndex); + } + }); + } + addElevation(elevations, trackIndices, segmentIndices, waypointIndices) { + let index = 0; + this.trk.forEach((track, trackIndex) => { + if (trackIndices === undefined || trackIndices.includes(trackIndex)) { + track.trkseg.forEach((segment, segmentIndex) => { + if (segmentIndices === undefined || segmentIndices.includes(segmentIndex)) { + segment.trkpt.forEach((point) => { + point.ele = elevations[index++]; + }); + } + }); + } + }); + this.wpt.forEach((waypoint, waypointIndex) => { + if (waypointIndices === undefined || waypointIndices.includes(waypointIndex)) { + waypoint.ele = elevations[index++]; + } + }); + elevations.splice(0, index); + } + setStyle(style) { + this.trk.forEach((track) => { + track.setStyle(style); + }); + if (!this._data.style) { + this._data.style = {}; + } + if (style.color) { + this._data.style.color = style.color.replace('#', ''); + } + if (style.opacity) { + this._data.style.opacity = style.opacity; + } + if (style.weight) { + this._data.style.weight = style.weight; + } + } + setHidden(hidden, trackIndices, segmentIndices) { + let allHidden = hidden; + this.trk.forEach((track, index) => { + if (trackIndices === undefined || trackIndices.includes(index)) { + track.setHidden(hidden, segmentIndices); + } + else { + allHidden = allHidden && (track._data.hidden === true); + } + }); + 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, waypointIndices) { + let allHiddenWpt = hidden; + this.wpt.forEach((waypoint, index) => { + if (waypointIndices === undefined || waypointIndices.includes(index)) { + waypoint.setHidden(hidden); + } + else { + allHiddenWpt = allHiddenWpt && (waypoint._data.hidden === true); + } + }); + let allHiddenTrk = true; + this.trk.forEach((track) => { + allHiddenTrk = allHiddenTrk && (track._data.hidden === true); + }); + this._data.hiddenWpt = allHiddenWpt; + this._data.hidden = allHiddenTrk && allHiddenWpt; + } +} +_a = immerable; +; +// A class that represents a Track in a GPX file +export class Track extends GPXTreeNode { + constructor(track) { + super(); + this[_b] = true; + if (track) { + this.name = track.name; + this.cmt = track.cmt; + this.desc = track.desc; + this.src = track.src; + this.link = track.link; + this.type = track.type; + this.trkseg = track.trkseg ? track.trkseg.map((seg) => new TrackSegment(seg)) : []; + this.extensions = track.extensions; + if (track.hasOwnProperty('_data')) { + this._data = track._data; + } + } + else { + this.trkseg = [new TrackSegment()]; + } + } + get children() { + return this.trkseg; + } + clone() { + return new Track({ + name: this.name, + cmt: this.cmt, + desc: this.desc, + src: this.src, + link: cloneJSON(this.link), + type: this.type, + extensions: cloneJSON(this.extensions), + trkseg: this.trkseg.map((seg) => seg.clone()), + _data: cloneJSON(this._data), + }); + } + getStyle() { + if (this.extensions && this.extensions['gpx_style:line']) { + if (this.extensions["gpx_style:line"].color) { + return Object.assign(Object.assign({}, this.extensions["gpx_style:line"]), { color: `#${this.extensions["gpx_style:line"].color}` }); + } + return this.extensions['gpx_style:line']; + } + return undefined; + } + toGeoJSON() { + return this.children.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; + }); + } + toTrackType(exclude = []) { + return { + name: this.name, + cmt: this.cmt, + desc: this.desc, + src: this.src, + link: this.link, + type: this.type, + extensions: this.extensions, + trkseg: this.trkseg.map((seg) => seg.toTrackSegmentType(exclude)), + }; + } + // Producers + replaceTrackSegments(start, end, segments) { + this.trkseg.splice(start, end - start + 1, ...segments); + } + replaceTrackPoints(segmentIndex, start, end, points, speed, startTime) { + this.trkseg[segmentIndex].replaceTrackPoints(start, end, points, speed, startTime); + } + reverseTrackSegment(segmentIndex) { + this.trkseg[segmentIndex]._reverse(this.trkseg[segmentIndex].getEndTimestamp(), this.trkseg[segmentIndex].getStartTimestamp()); + } + roundTrip() { + this.trkseg.forEach((segment) => { + segment.roundTrip(); + }); + } + roundTripTrackSegment(segmentIndex) { + this.trkseg[segmentIndex].roundTrip(); + } + crop(start, end, segmentIndices) { + let i = 0; + let segmentIndex = 0; + while (i < this.trkseg.length) { + let length = this.trkseg[i].getNumberOfTrackPoints(); + if (segmentIndices === undefined || segmentIndices.includes(segmentIndex)) { + if (start >= length || end < 0) { + this.trkseg.splice(i, 1); + } + else { + if (start > 0 || end < length - 1) { + this.trkseg[i].crop(Math.max(0, start), Math.min(length - 1, end)); + } + i++; + } + start -= length; + end -= length; + } + else { + i++; + } + segmentIndex++; + } + } + clean(bounds, inside, segmentIndices) { + let i = 0; + let segmentIndex = 0; + while (i < this.trkseg.length) { + if (segmentIndices === undefined || segmentIndices.includes(segmentIndex)) { + this.trkseg[i].clean(bounds, inside); + if (this.trkseg[i].getNumberOfTrackPoints() === 0) { + this.trkseg.splice(i, 1); + } + else { + i++; + } + } + else { + i++; + } + segmentIndex++; + } + } + changeTimestamps(startTime, speed, ratio, lastPoint, segmentIndex) { + this.trkseg.forEach((segment, index) => { + if (segmentIndex === undefined || segmentIndex === index) { + segment.changeTimestamps(startTime, speed, ratio, lastPoint); + if (segment.trkpt.length > 0) { + lastPoint = segment.trkpt[segment.trkpt.length - 1]; + } + } + }); + } + createArtificialTimestamps(startTime, totalTime, lastPoint, segmentIndex) { + this.trkseg.forEach((segment, index) => { + if (segmentIndex === undefined || segmentIndex === index) { + segment.createArtificialTimestamps(startTime, totalTime, lastPoint); + if (segment.trkpt.length > 0) { + lastPoint = segment.trkpt[segment.trkpt.length - 1]; + } + } + }); + } + setStyle(style, force = true) { + if (!this.extensions) { + this.extensions = {}; + } + if (!this.extensions['gpx_style:line']) { + this.extensions['gpx_style:line'] = {}; + } + if (style.color !== undefined && (force || this.extensions['gpx_style:line'].color === undefined)) { + this.extensions['gpx_style:line'].color = style.color.replace('#', ''); + } + if (style.opacity !== undefined && (force || this.extensions['gpx_style:line'].opacity === undefined)) { + this.extensions['gpx_style:line'].opacity = style.opacity; + } + if (style.weight !== undefined && (force || this.extensions['gpx_style:line'].weight === undefined)) { + this.extensions['gpx_style:line'].weight = style.weight; + } + } + setHidden(hidden, segmentIndices) { + let allHidden = hidden; + this.trkseg.forEach((segment, index) => { + if (segmentIndices === undefined || segmentIndices.includes(index)) { + segment.setHidden(hidden); + } + else { + allHidden = allHidden && (segment._data.hidden === true); + } + }); + this._data.hidden = allHidden; + } +} +_b = immerable; +// A class that represents a TrackSegment in a GPX file +export class TrackSegment extends GPXTreeLeaf { + constructor(segment) { + super(); + this[_c] = true; + if (segment) { + this.trkpt = segment.trkpt.map((point) => new TrackPoint(point)); + if (segment.hasOwnProperty('_data')) { + this._data = segment._data; + } + } + else { + this.trkpt = []; + } + } + _computeStatistics() { + let statistics = new GPXStatistics(); + statistics.local.points = this.trkpt.map((point) => point); + statistics.local.elevation.smoothed = this._computeSmoothedElevation(); + statistics.local.slope.at = this._computeSlope(); + const points = this.trkpt; + for (let i = 0; i < points.length; i++) { + points[i]._data['index'] = i; + // distance + let dist = 0; + if (i > 0) { + dist = distance(points[i - 1].getCoordinates(), points[i].getCoordinates()) / 1000; + statistics.global.distance.total += dist; + } + statistics.local.distance.total.push(statistics.global.distance.total); + // elevation + if (i > 0) { + const ele = statistics.local.elevation.smoothed[i] - statistics.local.elevation.smoothed[i - 1]; + if (ele > 0) { + statistics.global.elevation.gain += ele; + } + else if (ele < 0) { + statistics.global.elevation.loss -= ele; + } + } + statistics.local.elevation.gain.push(statistics.global.elevation.gain); + statistics.local.elevation.loss.push(statistics.global.elevation.loss); + // time + if (points[i].time === undefined) { + statistics.local.time.total.push(0); + } + else { + if (statistics.global.time.start === undefined) { + statistics.global.time.start = points[i].time; + } + statistics.global.time.end = points[i].time; + statistics.local.time.total.push((points[i].time.getTime() - statistics.global.time.start.getTime()) / 1000); + } + // speed + let speed = 0; + if (i > 0 && points[i - 1].time !== undefined && points[i].time !== undefined) { + const time = (points[i].time.getTime() - points[i - 1].time.getTime()) / 1000; + speed = dist / (time / 3600); + if (speed >= 0.5 && speed <= 1500) { + statistics.global.distance.moving += dist; + statistics.global.time.moving += time; + } + } + statistics.local.distance.moving.push(statistics.global.distance.moving); + statistics.local.time.moving.push(statistics.global.time.moving); + // bounds + statistics.global.bounds.southWest.lat = Math.min(statistics.global.bounds.southWest.lat, points[i].attributes.lat); + statistics.global.bounds.southWest.lon = Math.min(statistics.global.bounds.southWest.lon, points[i].attributes.lon); + statistics.global.bounds.northEast.lat = Math.max(statistics.global.bounds.northEast.lat, points[i].attributes.lat); + statistics.global.bounds.northEast.lon = Math.max(statistics.global.bounds.northEast.lon, points[i].attributes.lon); + // extensions + if (points[i].extensions) { + if (points[i].extensions["gpxtpx:TrackPointExtension"] && points[i].extensions["gpxtpx:TrackPointExtension"]["gpxtpx:atemp"]) { + let atemp = points[i].extensions["gpxtpx:TrackPointExtension"]["gpxtpx:atemp"]; + statistics.global.atemp.avg = (statistics.global.atemp.count * statistics.global.atemp.avg + atemp) / (statistics.global.atemp.count + 1); + statistics.global.atemp.count++; + } + if (points[i].extensions["gpxtpx:TrackPointExtension"] && points[i].extensions["gpxtpx:TrackPointExtension"]["gpxtpx:hr"]) { + let hr = points[i].extensions["gpxtpx:TrackPointExtension"]["gpxtpx:hr"]; + statistics.global.hr.avg = (statistics.global.hr.count * statistics.global.hr.avg + hr) / (statistics.global.hr.count + 1); + statistics.global.hr.count++; + } + if (points[i].extensions["gpxtpx:TrackPointExtension"] && points[i].extensions["gpxtpx:TrackPointExtension"]["gpxtpx:cad"]) { + let cad = points[i].extensions["gpxtpx:TrackPointExtension"]["gpxtpx:cad"]; + statistics.global.cad.avg = (statistics.global.cad.count * statistics.global.cad.avg + cad) / (statistics.global.cad.count + 1); + statistics.global.cad.count++; + } + if (points[i].extensions["gpxpx:PowerExtension"] && points[i].extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"]) { + let power = points[i].extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"]; + statistics.global.power.avg = (statistics.global.power.count * statistics.global.power.avg + power) / (statistics.global.power.count + 1); + statistics.global.power.count++; + } + } + } + [statistics.local.slope.segment, statistics.local.slope.length] = this._computeSlopeSegments(statistics); + statistics.global.time.total = statistics.global.time.start && statistics.global.time.end ? (statistics.global.time.end.getTime() - statistics.global.time.start.getTime()) / 1000 : 0; + statistics.global.speed.total = statistics.global.time.total > 0 ? statistics.global.distance.total / (statistics.global.time.total / 3600) : 0; + statistics.global.speed.moving = statistics.global.time.moving > 0 ? statistics.global.distance.moving / (statistics.global.time.moving / 3600) : 0; + statistics.local.speed = distanceWindowSmoothingWithDistanceAccumulator(points, 200, (accumulated, start, end) => (points[start].time && points[end].time) ? 3600 * accumulated / (points[end].time.getTime() - points[start].time.getTime()) : undefined); + return statistics; + } + _computeSmoothedElevation() { + var _f, _g; + const points = this.trkpt; + let smoothed = distanceWindowSmoothing(points, 100, (index) => { var _f; return (_f = points[index].ele) !== null && _f !== void 0 ? _f : 0; }, (accumulated, start, end) => accumulated / (end - start + 1)); + if (points.length > 0) { + smoothed[0] = (_f = points[0].ele) !== null && _f !== void 0 ? _f : 0; + smoothed[points.length - 1] = (_g = points[points.length - 1].ele) !== null && _g !== void 0 ? _g : 0; + } + return smoothed; + } + _computeSlope() { + const points = this.trkpt; + return distanceWindowSmoothingWithDistanceAccumulator(points, 50, (accumulated, start, end) => { var _f, _g; return 100 * (((_f = points[end].ele) !== null && _f !== void 0 ? _f : 0) - ((_g = points[start].ele) !== null && _g !== void 0 ? _g : 0)) / (accumulated > 0 ? accumulated : 1); }); + } + _computeSlopeSegments(statistics) { + var _f, _g; + // x-coordinates are given by: statistics.local.distance.total[point._data.index] * 1000 + // y-coordinates are given by: point.ele + // Compute the distance between point3 and the line defined by point1 and point2 + function elevationDistance(point1, point2, point3) { + if (point1.ele === undefined || point2.ele === undefined || point3.ele === undefined) { + return 0; + } + let x1 = statistics.local.distance.total[point1._data.index] * 1000; + let x2 = statistics.local.distance.total[point2._data.index] * 1000; + let x3 = statistics.local.distance.total[point3._data.index] * 1000; + let y1 = point1.ele; + let y2 = point2.ele; + let y3 = point3.ele; + let dist = Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)); + if (dist === 0) { + return Math.sqrt(Math.pow(x3 - x1, 2) + Math.pow(y3 - y1, 2)); + } + return Math.abs((y2 - y1) * x3 - (x2 - x1) * y3 + x2 * y1 - y2 * x1) / dist; + } + let simplified = ramerDouglasPeucker(this.trkpt, 20, elevationDistance); + let slope = []; + let length = []; + for (let i = 0; i < simplified.length - 1; i++) { + let start = simplified[i].point._data.index; + let end = simplified[i + 1].point._data.index; + let dist = statistics.local.distance.total[end] - statistics.local.distance.total[start]; + let ele = ((_f = simplified[i + 1].point.ele) !== null && _f !== void 0 ? _f : 0) - ((_g = simplified[i].point.ele) !== null && _g !== void 0 ? _g : 0); + for (let j = start; j < end + (i + 1 === simplified.length - 1 ? 1 : 0); j++) { + slope.push(0.1 * ele / dist); + length.push(dist); + } + } + return [slope, length]; + } + getNumberOfTrackPoints() { + return this.trkpt.length; + } + getStartTimestamp() { + if (this.trkpt.length === 0) { + return undefined; + } + return this.trkpt[0].time; + } + getEndTimestamp() { + if (this.trkpt.length === 0) { + return undefined; + } + return this.trkpt[this.trkpt.length - 1].time; + } + getStatistics() { + return this._computeStatistics(); + } + getSegments() { + return [this]; + } + getTrackPoints() { + return this.trkpt; + } + toGeoJSON() { + return { + type: "Feature", + geometry: { + type: "LineString", + coordinates: this.trkpt.map((point) => [point.attributes.lon, point.attributes.lat]) + }, + properties: {} + }; + } + toTrackSegmentType(exclude = []) { + return { + trkpt: this.trkpt.map((point) => point.toTrackPointType(exclude)) + }; + } + clone() { + return new TrackSegment({ + trkpt: this.trkpt.map((point) => point.clone()), + _data: cloneJSON(this._data), + }); + } + // Producers + replaceTrackPoints(start, end, points, speed, startTime) { + let og = getOriginal(this); // Read as much as possible from the original object because it is faster + let trkpt = og.trkpt.slice(); + if (speed !== undefined || (trkpt.length > 0 && trkpt[0].time !== undefined)) { + if (start > 0 && trkpt[0].time === undefined) { + trkpt.splice(0, 0, ...withTimestamps(trkpt.splice(0, start), speed, undefined, startTime)); + } + if (points.length > 0) { + let last = start > 0 ? trkpt[start - 1] : undefined; + if (points[0].time === undefined || (points.length > 1 && points[1].time === undefined)) { + points = withTimestamps(points, speed, last, startTime); + } + else if (last !== undefined && points[0].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) { + trkpt.splice(end + 1, 0, ...withShiftedAndCompressedTimestamps(trkpt.splice(end + 1), speed, 1, last)); + } + } + } + trkpt.splice(start, end - start + 1, ...points); + this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well + } + _reverse(originalNextTimestamp, newPreviousTimestamp) { + let og = getOriginal(this); // Read as much as possible from the original object because it is faster + let originalStartTimestamp = og.getStartTimestamp(); + 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 trkpt = og.trkpt.map((point, i) => new TrackPoint({ + 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 { + this.trkpt.reverse(); + } + } + roundTrip() { + let og = getOriginal(this); // Read as much as possible from the original object because it is faster + let newSegment = og.clone(); + newSegment._reverse(newSegment.getEndTimestamp(), newSegment.getEndTimestamp()); + this.replaceTrackPoints(this.trkpt.length, this.trkpt.length, newSegment.trkpt); + } + crop(start, end) { + this.trkpt = this.trkpt.slice(start, end + 1); + } + clean(bounds, inside) { + let og = getOriginal(this); // Read as much as possible from the original object because it is faster + 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; + return inBounds !== inside; + }); + this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well + } + changeTimestamps(startTime, speed, ratio, lastPoint) { + if (lastPoint === undefined && this.trkpt.length > 0) { + lastPoint = this.trkpt[0].clone(); + lastPoint.time = startTime; + } + 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) { + let trkpt = withTimestamps(og.trkpt, speed, lastPoint, startTime); + this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well + } + else { + let trkpt = withShiftedAndCompressedTimestamps(og.trkpt, speed, ratio, lastPoint); + this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well + } + } + createArtificialTimestamps(startTime, totalTime, lastPoint) { + let og = getOriginal(this); // Read as much as possible from the original object because it is faster + let slope = og._computeSlope(); + let trkpt = withArtificialTimestamps(og.trkpt, totalTime, lastPoint, startTime, slope); + this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well + } + setHidden(hidden) { + this._data.hidden = hidden; + } +} +_c = immerable; +; +export class TrackPoint { + constructor(point) { + this[_d] = true; + this._data = {}; + this.attributes = point.attributes; + this.ele = point.ele; + this.time = point.time; + this.extensions = point.extensions; + if (point.hasOwnProperty('_data')) { + this._data = point._data; + } + } + getCoordinates() { + return this.attributes; + } + setCoordinates(coordinates) { + this.attributes = coordinates; + } + getLatitude() { + return this.attributes.lat; + } + getLongitude() { + return this.attributes.lon; + } + getTemperature() { + return this.extensions && this.extensions['gpxtpx:TrackPointExtension'] && this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:atemp'] ? this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:atemp'] : undefined; + } + getHeartRate() { + return this.extensions && this.extensions['gpxtpx:TrackPointExtension'] && this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:hr'] ? this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:hr'] : undefined; + } + getCadence() { + return this.extensions && this.extensions['gpxtpx:TrackPointExtension'] && this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:cad'] ? this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:cad'] : undefined; + } + getPower() { + return this.extensions && this.extensions["gpxpx:PowerExtension"] && this.extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"] ? this.extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"] : undefined; + } + getSurface() { + return this.extensions && this.extensions["gpxtpx:TrackPointExtension"] && this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"] && this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"].surface ? this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"].surface : undefined; + } + setSurface(surface) { + if (!this.extensions) { + this.extensions = {}; + } + if (!this.extensions["gpxtpx:TrackPointExtension"]) { + this.extensions["gpxtpx:TrackPointExtension"] = {}; + } + if (!this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"]) { + this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"] = {}; + } + this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"]["surface"] = surface; + } + toTrackPointType(exclude = []) { + let trkpt = { + attributes: this.attributes, + ele: this.ele, + }; + if (!exclude.includes('time')) { + trkpt = Object.assign(Object.assign({}, trkpt), { time: this.time }); + } + if (this.extensions) { + trkpt = Object.assign(Object.assign({}, trkpt), { extensions: { + "gpxtpx:TrackPointExtension": {}, + "gpxpx:PowerExtension": {}, + } }); + if (this.extensions["gpxtpx:TrackPointExtension"] && this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:atemp"] && !exclude.includes('atemp')) { + trkpt.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:atemp"] = this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:atemp"]; + } + if (this.extensions["gpxtpx:TrackPointExtension"] && this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:hr"] && !exclude.includes('hr')) { + trkpt.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:hr"] = this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:hr"]; + } + if (this.extensions["gpxtpx:TrackPointExtension"] && this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:cad"] && !exclude.includes('cad')) { + trkpt.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:cad"] = this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:cad"]; + } + if (this.extensions["gpxpx:PowerExtension"] && this.extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"] && !exclude.includes('power')) { + trkpt.extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"] = this.extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"]; + } + if (this.extensions["gpxtpx:TrackPointExtension"] && this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"] && this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"].surface && !exclude.includes('surface')) { + trkpt.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"] = { surface: this.extensions["gpxtpx:TrackPointExtension"]["gpxtpx:Extensions"].surface }; + } + } + return trkpt; + } + clone() { + return new TrackPoint({ + attributes: cloneJSON(this.attributes), + ele: this.ele, + time: this.time ? new Date(this.time.getTime()) : undefined, + extensions: cloneJSON(this.extensions), + _data: cloneJSON(this._data), + }); + } +} +_d = immerable; +; +export class Waypoint { + constructor(waypoint) { + this[_e] = true; + this._data = {}; + this.attributes = waypoint.attributes; + this.ele = waypoint.ele; + this.time = waypoint.time; + this.name = waypoint.name; + this.cmt = waypoint.cmt; + this.desc = waypoint.desc; + this.link = waypoint.link; + this.sym = waypoint.sym; + this.type = waypoint.type; + if (waypoint.hasOwnProperty('_data')) { + this._data = waypoint._data; + } + } + getCoordinates() { + return this.attributes; + } + setCoordinates(coordinates) { + this.attributes = coordinates; + } + getLatitude() { + return this.attributes.lat; + } + getLongitude() { + return this.attributes.lon; + } + toWaypointType(exclude = []) { + if (!exclude.includes('time')) { + return { + attributes: this.attributes, + ele: this.ele, + time: this.time, + name: this.name, + cmt: this.cmt, + desc: this.desc, + link: this.link, + sym: this.sym, + type: this.type, + }; + } + else { + return { + attributes: this.attributes, + ele: this.ele, + name: this.name, + cmt: this.cmt, + desc: this.desc, + link: this.link, + sym: this.sym, + type: this.type, + }; + } + } + clone() { + return new Waypoint({ + attributes: cloneJSON(this.attributes), + ele: this.ele, + time: this.time ? new Date(this.time.getTime()) : undefined, + name: this.name, + cmt: this.cmt, + desc: this.desc, + link: cloneJSON(this.link), + sym: this.sym, + type: this.type, + }); + } + // Producers + setHidden(hidden) { + this._data.hidden = hidden; + } +} +_e = immerable; +export class GPXStatistics { + constructor() { + this.global = { + distance: { + moving: 0, + total: 0, + }, + time: { + start: undefined, + end: undefined, + moving: 0, + total: 0, + }, + speed: { + moving: 0, + total: 0, + }, + elevation: { + gain: 0, + loss: 0, + }, + bounds: { + southWest: { + lat: 90, + lon: 180, + }, + northEast: { + lat: -90, + lon: -180, + }, + }, + atemp: { + avg: 0, + count: 0, + }, + hr: { + avg: 0, + count: 0, + }, + cad: { + avg: 0, + count: 0, + }, + power: { + avg: 0, + count: 0, + } + }; + this.local = { + points: [], + distance: { + moving: [], + total: [], + }, + time: { + moving: [], + total: [], + }, + speed: [], + elevation: { + smoothed: [], + gain: [], + loss: [], + }, + slope: { + at: [], + segment: [], + length: [], + } + }; + } + mergeWith(other) { + var _f, _g; + this.local.points = this.local.points.concat(other.local.points); + this.local.distance.total = this.local.distance.total.concat(other.local.distance.total.map((distance) => distance + this.global.distance.total)); + this.local.distance.moving = this.local.distance.moving.concat(other.local.distance.moving.map((distance) => distance + this.global.distance.moving)); + this.local.time.total = this.local.time.total.concat(other.local.time.total.map((time) => time + this.global.time.total)); + this.local.time.moving = this.local.time.moving.concat(other.local.time.moving.map((time) => time + this.global.time.moving)); + this.local.elevation.gain = this.local.elevation.gain.concat(other.local.elevation.gain.map((gain) => gain + this.global.elevation.gain)); + this.local.elevation.loss = this.local.elevation.loss.concat(other.local.elevation.loss.map((loss) => loss + this.global.elevation.loss)); + this.local.speed = this.local.speed.concat(other.local.speed); + this.local.elevation.smoothed = this.local.elevation.smoothed.concat(other.local.elevation.smoothed); + this.local.slope.at = this.local.slope.at.concat(other.local.slope.at); + this.local.slope.segment = this.local.slope.segment.concat(other.local.slope.segment); + this.local.slope.length = this.local.slope.length.concat(other.local.slope.length); + this.global.distance.total += other.global.distance.total; + this.global.distance.moving += other.global.distance.moving; + this.global.time.start = this.global.time.start !== undefined && other.global.time.start !== undefined ? new Date(Math.min(this.global.time.start.getTime(), other.global.time.start.getTime())) : (_f = this.global.time.start) !== null && _f !== void 0 ? _f : other.global.time.start; + this.global.time.end = this.global.time.end !== undefined && other.global.time.end !== undefined ? new Date(Math.max(this.global.time.end.getTime(), other.global.time.end.getTime())) : (_g = this.global.time.end) !== null && _g !== void 0 ? _g : other.global.time.end; + this.global.time.total += other.global.time.total; + this.global.time.moving += other.global.time.moving; + this.global.speed.moving = this.global.time.moving > 0 ? this.global.distance.moving / (this.global.time.moving / 3600) : 0; + this.global.speed.total = this.global.time.total > 0 ? this.global.distance.total / (this.global.time.total / 3600) : 0; + this.global.elevation.gain += other.global.elevation.gain; + this.global.elevation.loss += other.global.elevation.loss; + this.global.bounds.southWest.lat = Math.min(this.global.bounds.southWest.lat, other.global.bounds.southWest.lat); + this.global.bounds.southWest.lon = Math.min(this.global.bounds.southWest.lon, other.global.bounds.southWest.lon); + this.global.bounds.northEast.lat = Math.max(this.global.bounds.northEast.lat, other.global.bounds.northEast.lat); + this.global.bounds.northEast.lon = Math.max(this.global.bounds.northEast.lon, other.global.bounds.northEast.lon); + this.global.atemp.avg = (this.global.atemp.count * this.global.atemp.avg + other.global.atemp.count * other.global.atemp.avg) / Math.max(1, this.global.atemp.count + other.global.atemp.count); + this.global.atemp.count += other.global.atemp.count; + this.global.hr.avg = (this.global.hr.count * this.global.hr.avg + other.global.hr.count * other.global.hr.avg) / Math.max(1, this.global.hr.count + other.global.hr.count); + this.global.hr.count += other.global.hr.count; + this.global.cad.avg = (this.global.cad.count * this.global.cad.avg + other.global.cad.count * other.global.cad.avg) / Math.max(1, this.global.cad.count + other.global.cad.count); + this.global.cad.count += other.global.cad.count; + this.global.power.avg = (this.global.power.count * this.global.power.avg + other.global.power.count * other.global.power.avg) / Math.max(1, this.global.power.count + other.global.power.count); + this.global.power.count += other.global.power.count; + } + slice(start, end) { + if (start < 0) { + start = 0; + } + else if (start >= this.local.points.length) { + return new GPXStatistics(); + } + if (end < start) { + return new GPXStatistics(); + } + else if (end >= this.local.points.length) { + end = this.local.points.length - 1; + } + let statistics = new GPXStatistics(); + statistics.local.points = this.local.points.slice(start, end + 1); + statistics.global.distance.total = this.local.distance.total[end] - this.local.distance.total[start]; + statistics.global.distance.moving = this.local.distance.moving[end] - this.local.distance.moving[start]; + statistics.global.time.start = this.local.points[start].time; + statistics.global.time.end = this.local.points[end].time; + statistics.global.time.total = this.local.time.total[end] - this.local.time.total[start]; + statistics.global.time.moving = this.local.time.moving[end] - this.local.time.moving[start]; + statistics.global.speed.moving = statistics.global.time.moving > 0 ? statistics.global.distance.moving / (statistics.global.time.moving / 3600) : 0; + statistics.global.speed.total = statistics.global.time.total > 0 ? statistics.global.distance.total / (statistics.global.time.total / 3600) : 0; + statistics.global.elevation.gain = this.local.elevation.gain[end] - this.local.elevation.gain[start]; + statistics.global.elevation.loss = this.local.elevation.loss[end] - this.local.elevation.loss[start]; + statistics.global.bounds.southWest.lat = this.global.bounds.southWest.lat; + statistics.global.bounds.southWest.lon = this.global.bounds.southWest.lon; + statistics.global.bounds.northEast.lat = this.global.bounds.northEast.lat; + statistics.global.bounds.northEast.lon = this.global.bounds.northEast.lon; + statistics.global.atemp = this.global.atemp; + statistics.global.hr = this.global.hr; + statistics.global.cad = this.global.cad; + statistics.global.power = this.global.power; + return statistics; + } +} +const earthRadius = 6371008.8; +export function distance(coord1, coord2) { + if (coord1 instanceof TrackPoint) { + coord1 = coord1.getCoordinates(); + } + if (coord2 instanceof TrackPoint) { + coord2 = coord2.getCoordinates(); + } + const rad = Math.PI / 180; + const lat1 = coord1.lat * rad; + const lat2 = coord2.lat * rad; + const a = Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos((coord2.lon - coord1.lon) * rad); + const maxMeters = earthRadius * Math.acos(Math.min(a, 1)); + return maxMeters; +} +function distanceWindowSmoothing(points, distanceWindow, accumulate, compute, remove) { + let result = []; + let start = 0, end = 0, accumulated = 0; + for (var i = 0; i < points.length; i++) { + while (start + 1 < i && distance(points[start].getCoordinates(), points[i].getCoordinates()) > distanceWindow) { + if (remove) { + accumulated -= remove(start); + } + else { + accumulated -= accumulate(start); + } + start++; + } + while (end < points.length && distance(points[i].getCoordinates(), points[end].getCoordinates()) <= distanceWindow) { + accumulated += accumulate(end); + end++; + } + result[i] = compute(accumulated, start, end - 1); + } + return result; +} +function distanceWindowSmoothingWithDistanceAccumulator(points, distanceWindow, compute) { + 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())); +} +function withTimestamps(points, speed, lastPoint, startTime) { + let last = lastPoint; + if (last === undefined) { + last = points[0].clone(); + last.time = startTime; + } + return points.map((point) => { + let time = getTimestamp(last, point, speed); + last = point.clone(); + last.time = time; + return last; + }); +} +function withShiftedAndCompressedTimestamps(points, speed, ratio, lastPoint) { + let start = getTimestamp(lastPoint, points[0], speed); + let last = points[0]; + return points.map((point) => { + let pt = point.clone(); + if (point.time === undefined) { + pt.time = getTimestamp(last, point, speed); + } + else { + pt.time = new Date(start.getTime() + ratio * (point.time.getTime() - points[0].time.getTime())); + } + last = pt; + return pt; + }); +} +function withArtificialTimestamps(points, totalTime, lastPoint, startTime, slope) { + let weight = []; + let totalWeight = 0; + for (let i = 0; i < points.length - 1; i++) { + let dist = distance(points[i].getCoordinates(), points[i + 1].getCoordinates()); + let w = dist * (0.5 + 1 / (1 + Math.exp(-0.2 * slope[i]))); + weight.push(w); + totalWeight += w; + } + let last = lastPoint; + return points.map((point, i) => { + var _f; + let pt = point.clone(); + if (i === 0) { + pt.time = (_f = lastPoint === null || lastPoint === void 0 ? void 0 : lastPoint.time) !== null && _f !== void 0 ? _f : startTime; + } + else { + pt.time = new Date(last.time.getTime() + totalTime * 1000 * weight[i - 1] / totalWeight); + } + last = pt; + return pt; + }); +} +function getTimestamp(a, b, speed) { + let dist = distance(a.getCoordinates(), b.getCoordinates()) / 1000; + return new Date(a.time.getTime() + 1000 * 3600 * dist / speed); +} +function getOriginal(obj) { + while (isDraft(obj)) { + obj = original(obj); + } + return obj; +} +function convertRouteToTrack(route) { + const track = new Track({ + name: route.name, + cmt: route.cmt, + desc: route.desc, + src: route.src, + link: route.link, + type: route.type, + extensions: route.extensions, + trkseg: [], + }); + if (route.rtept) { + const segment = new TrackSegment(); + route.rtept.forEach((rpt) => { + if (rpt.extensions && rpt.extensions['gpxx:RoutePointExtension'] && rpt.extensions['gpxx:RoutePointExtension']["gpxx:rpt"]) { + rpt.extensions['gpxx:RoutePointExtension']["gpxx:rpt"].forEach((rptExtension) => { + segment.trkpt.push(new TrackPoint({ + attributes: rptExtension.attributes, + })); + }); + } + else { + segment.trkpt.push(new TrackPoint({ + attributes: rpt.attributes, + ele: rpt.ele, + time: rpt.time, + })); + } + }); + track.trkseg.push(segment); + } + return track; +} diff --git a/gpx/dist/index.d.ts b/gpx/dist/index.d.ts new file mode 100644 index 00000000..1d79b1bc --- /dev/null +++ b/gpx/dist/index.d.ts @@ -0,0 +1,4 @@ +export * from './gpx'; +export { Coordinates, LineStyleExtension, WaypointType } from './types'; +export { parseGPX, buildGPX } from './io'; +export * from './simplify'; diff --git a/gpx/dist/index.js b/gpx/dist/index.js new file mode 100644 index 00000000..c454b836 --- /dev/null +++ b/gpx/dist/index.js @@ -0,0 +1,3 @@ +export * from './gpx'; +export { parseGPX, buildGPX } from './io'; +export * from './simplify'; diff --git a/gpx/dist/io.d.ts b/gpx/dist/io.d.ts new file mode 100644 index 00000000..5d6baf2c --- /dev/null +++ b/gpx/dist/io.d.ts @@ -0,0 +1,3 @@ +import { GPXFile } from "./gpx"; +export declare function parseGPX(gpxData: string): GPXFile; +export declare function buildGPX(file: GPXFile, exclude: string[]): string; diff --git a/gpx/dist/io.js b/gpx/dist/io.js new file mode 100644 index 00000000..930f69fa --- /dev/null +++ b/gpx/dist/io.js @@ -0,0 +1,105 @@ +import { XMLParser, XMLBuilder } from "fast-xml-parser"; +import { GPXFile } from "./gpx"; +export function parseGPX(gpxData) { + const parser = new XMLParser({ + ignoreAttributes: false, + attributeNamePrefix: "", + attributesGroupName: 'attributes', + isArray(name) { + return name === 'trk' || name === 'trkseg' || name === 'trkpt' || name === 'wpt' || name === 'rte' || name === 'rtept' || name === 'gpxx:rpt'; + }, + attributeValueProcessor(attrName, attrValue, jPath) { + if (attrName === 'lat' || attrName === 'lon') { + return parseFloat(attrValue); + } + return attrValue; + }, + transformTagName(tagName) { + if (tagName === 'power') { + // Transform the simple tag to the more complex tag, the nested tag is then handled by the tagValueProcessor + return 'gpxpx:PowerExtension'; + } + return tagName; + }, + parseTagValue: false, + tagValueProcessor(tagName, tagValue, jPath, hasAttributes, isLeafNode) { + if (isLeafNode) { + if (tagName === 'ele') { + return parseFloat(tagValue); + } + if (tagName === 'time') { + return new Date(tagValue); + } + if (tagName === 'gpxtpx:atemp' || tagName === 'gpxtpx:hr' || tagName === 'gpxtpx:cad' || tagName === 'gpxpx:PowerInWatts' || tagName === 'opacity' || tagName === 'weight') { + return parseFloat(tagValue); + } + if (tagName === 'gpxpx:PowerExtension') { + // Finish the transformation of the simple tag to the more complex tag + // Note that this only targets the transformed tag, since it must be a leaf node + return { + 'gpxpx:PowerInWatts': parseFloat(tagValue) + }; + } + } + return tagValue; + }, + }); + const parsed = parser.parse(gpxData).gpx; + // @ts-ignore + if (parsed.metadata === "") { + parsed.metadata = {}; + } + return new GPXFile(parsed); +} +export function buildGPX(file, exclude) { + var _a; + const gpx = file.toGPXFileType(exclude); + const builder = new XMLBuilder({ + format: true, + ignoreAttributes: false, + attributeNamePrefix: "", + attributesGroupName: 'attributes', + suppressEmptyNode: true, + tagValueProcessor: (tagName, tagValue) => { + if (tagValue instanceof Date) { + return tagValue.toISOString(); + } + return tagValue.toString(); + }, + }); + gpx.attributes.creator = (_a = gpx.attributes.creator) !== null && _a !== void 0 ? _a : 'https://gpx.studio'; + gpx.attributes['version'] = '1.1'; + gpx.attributes['xmlns'] = 'http://www.topografix.com/GPX/1/1'; + gpx.attributes['xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance'; + gpx.attributes['xsi:schemaLocation'] = 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/PowerExtension/v1 http://www.garmin.com/xmlschemas/PowerExtensionv1.xsd http://www.topografix.com/GPX/gpx_style/0/2 http://www.topografix.com/GPX/gpx_style/0/2/gpx_style.xsd'; + gpx.attributes['xmlns:gpxtpx'] = 'http://www.garmin.com/xmlschemas/TrackPointExtension/v1'; + gpx.attributes['xmlns:gpxx'] = 'http://www.garmin.com/xmlschemas/GpxExtensions/v3'; + gpx.attributes['xmlns:gpxpx'] = 'http://www.garmin.com/xmlschemas/PowerExtension/v1'; + gpx.attributes['xmlns:gpx_style'] = 'http://www.topografix.com/GPX/gpx_style/0/2'; + if (gpx.trk.length === 1 && (gpx.trk[0].name === undefined || gpx.trk[0].name === '')) { + gpx.trk[0].name = gpx.metadata.name; + } + return builder.build({ + "?xml": { + attributes: { + version: "1.0", + encoding: "UTF-8", + } + }, + gpx: removeEmptyElements(gpx) + }); +} +function removeEmptyElements(obj) { + for (const key in obj) { + if (obj[key] === null || obj[key] === undefined || obj[key] === '' || (Array.isArray(obj[key]) && obj[key].length === 0)) { + delete obj[key]; + } + else if (typeof obj[key] === 'object' && !(obj[key] instanceof Date)) { + removeEmptyElements(obj[key]); + if (Object.keys(obj[key]).length === 0) { + delete obj[key]; + } + } + } + return obj; +} diff --git a/gpx/dist/simplify.d.ts b/gpx/dist/simplify.d.ts new file mode 100644 index 00000000..201daa47 --- /dev/null +++ b/gpx/dist/simplify.d.ts @@ -0,0 +1,9 @@ +import { TrackPoint } from "./gpx"; +import { Coordinates } from "./types"; +export type SimplifiedTrackPoint = { + point: TrackPoint; + distance?: number; +}; +export declare function ramerDouglasPeucker(points: TrackPoint[], epsilon?: number, measure?: (a: TrackPoint, b: TrackPoint, c: TrackPoint) => number): SimplifiedTrackPoint[]; +export declare function crossarcDistance(point1: TrackPoint, point2: TrackPoint, point3: TrackPoint | Coordinates): number; +export declare function projectedPoint(point1: TrackPoint, point2: TrackPoint, point3: TrackPoint | Coordinates): Coordinates; diff --git a/gpx/dist/simplify.js b/gpx/dist/simplify.js new file mode 100644 index 00000000..0bb7f174 --- /dev/null +++ b/gpx/dist/simplify.js @@ -0,0 +1,126 @@ +import { TrackPoint } from "./gpx"; +const earthRadius = 6371008.8; +export function ramerDouglasPeucker(points, epsilon = 50, measure = crossarcDistance) { + if (points.length == 0) { + return []; + } + else if (points.length == 1) { + return [{ + point: points[0] + }]; + } + let simplified = [{ + point: points[0] + }]; + ramerDouglasPeuckerRecursive(points, epsilon, measure, 0, points.length - 1, simplified); + simplified.push({ + point: points[points.length - 1] + }); + return simplified; +} +function ramerDouglasPeuckerRecursive(points, epsilon, measure, start, end, simplified) { + let largest = { + index: 0, + distance: 0 + }; + for (let i = start + 1; i < end; i++) { + let distance = measure(points[start], points[end], points[i]); + if (distance > largest.distance) { + largest.index = i; + largest.distance = distance; + } + } + if (largest.distance > epsilon && largest.index != 0) { + ramerDouglasPeuckerRecursive(points, epsilon, measure, start, largest.index, simplified); + simplified.push({ point: points[largest.index], distance: largest.distance }); + ramerDouglasPeuckerRecursive(points, epsilon, measure, largest.index, end, simplified); + } +} +export function crossarcDistance(point1, point2, point3) { + return crossarc(point1.getCoordinates(), point2.getCoordinates(), point3 instanceof TrackPoint ? point3.getCoordinates() : point3); +} +function crossarc(coord1, coord2, coord3) { + // Calculates the shortest distance in meters + // between an arc (defined by p1 and p2) and a third point, p3. + // Input lat1,lon1,lat2,lon2,lat3,lon3 in degrees. + const rad = Math.PI / 180; + const lat1 = coord1.lat * rad; + const lat2 = coord2.lat * rad; + const lat3 = coord3.lat * rad; + const lon1 = coord1.lon * rad; + const lon2 = coord2.lon * rad; + const lon3 = coord3.lon * rad; + // Prerequisites for the formulas + const bear12 = bearing(lat1, lon1, lat2, lon2); + const bear13 = bearing(lat1, lon1, lat3, lon3); + let dis13 = distance(lat1, lon1, lat3, lon3); + let diff = Math.abs(bear13 - bear12); + if (diff > Math.PI) { + diff = 2 * Math.PI - diff; + } + // Is relative bearing obtuse? + if (diff > (Math.PI / 2)) { + return dis13; + } + // Find the cross-track distance. + let dxt = Math.asin(Math.sin(dis13 / earthRadius) * Math.sin(bear13 - bear12)) * earthRadius; + // Is p4 beyond the arc? + let dis12 = distance(lat1, lon1, lat2, lon2); + let dis14 = Math.acos(Math.cos(dis13 / earthRadius) / Math.cos(dxt / earthRadius)) * earthRadius; + if (dis14 > dis12) { + return distance(lat2, lon2, lat3, lon3); + } + else { + return Math.abs(dxt); + } +} +function distance(latA, lonA, latB, lonB) { + // Finds the distance between two lat / lon points. + return Math.acos(Math.sin(latA) * Math.sin(latB) + Math.cos(latA) * Math.cos(latB) * Math.cos(lonB - lonA)) * earthRadius; +} +function bearing(latA, lonA, latB, lonB) { + // Finds the bearing from one lat / lon point to another. + return Math.atan2(Math.sin(lonB - lonA) * Math.cos(latB), Math.cos(latA) * Math.sin(latB) - Math.sin(latA) * Math.cos(latB) * Math.cos(lonB - lonA)); +} +export function projectedPoint(point1, point2, point3) { + return projected(point1.getCoordinates(), point2.getCoordinates(), point3 instanceof TrackPoint ? point3.getCoordinates() : point3); +} +function projected(coord1, coord2, coord3) { + // Calculates the point on the line defined by p1 and p2 + // that is closest to the third point, p3. + // Input lat1,lon1,lat2,lon2,lat3,lon3 in degrees. + const rad = Math.PI / 180; + const lat1 = coord1.lat * rad; + const lat2 = coord2.lat * rad; + const lat3 = coord3.lat * rad; + const lon1 = coord1.lon * rad; + const lon2 = coord2.lon * rad; + const lon3 = coord3.lon * rad; + // Prerequisites for the formulas + const bear12 = bearing(lat1, lon1, lat2, lon2); + const bear13 = bearing(lat1, lon1, lat3, lon3); + let dis13 = distance(lat1, lon1, lat3, lon3); + let diff = Math.abs(bear13 - bear12); + if (diff > Math.PI) { + diff = 2 * Math.PI - diff; + } + // Is relative bearing obtuse? + if (diff > (Math.PI / 2)) { + return coord1; + } + // Find the cross-track distance. + let dxt = Math.asin(Math.sin(dis13 / earthRadius) * Math.sin(bear13 - bear12)) * earthRadius; + // Is p4 beyond the arc? + let dis12 = distance(lat1, lon1, lat2, lon2); + let dis14 = Math.acos(Math.cos(dis13 / earthRadius) / Math.cos(dxt / earthRadius)) * earthRadius; + if (dis14 > dis12) { + return coord2; + } + else { + // Determine the closest point (p4) on the great circle + const f = dis14 / earthRadius; + const lat4 = Math.asin(Math.sin(lat1) * Math.cos(f) + Math.cos(lat1) * Math.sin(f) * Math.cos(bear12)); + const lon4 = lon1 + Math.atan2(Math.sin(bear12) * Math.sin(f) * Math.cos(lat1), Math.cos(f) - Math.sin(lat1) * Math.sin(lat4)); + return { lat: lat4 / rad, lon: lon4 / rad }; + } +} diff --git a/gpx/dist/types.d.ts b/gpx/dist/types.d.ts new file mode 100644 index 00000000..77cacdd1 --- /dev/null +++ b/gpx/dist/types.d.ts @@ -0,0 +1,108 @@ +export type GPXFileType = { + attributes: GPXFileAttributes; + metadata: Metadata; + wpt: WaypointType[]; + trk: TrackType[]; + rte: RouteType[]; +}; +export type GPXFileAttributes = { + creator?: string; + [key: string]: string; +}; +export type Metadata = { + name?: string; + desc?: string; + author?: Author; + link?: Link; + time?: Date; +}; +export type Link = { + attributes: LinkAttributes; + text?: string; + type?: string; +}; +export type LinkAttributes = { + href: string; +}; +export type WaypointType = { + attributes: Coordinates; + ele?: number; + time?: Date; + name?: string; + cmt?: string; + desc?: string; + link?: Link; + sym?: string; + type?: string; + extensions?: WaypointExtensions; +}; +export type WaypointExtensions = { + 'gpxx:RoutePointExtension'?: RoutePointExtension; +}; +export type Coordinates = { + lat: number; + lon: number; +}; +export type TrackType = { + name?: string; + cmt?: string; + desc?: string; + src?: string; + link?: Link; + type?: string; + extensions?: TrackExtensions; + trkseg: TrackSegmentType[]; +}; +export type TrackExtensions = { + 'gpx_style:line'?: LineStyleExtension; +}; +export type LineStyleExtension = { + color?: string; + opacity?: number; + weight?: number; +}; +export type TrackSegmentType = { + trkpt: TrackPointType[]; +}; +export type TrackPointType = { + attributes: Coordinates; + ele?: number; + time?: Date; + extensions?: TrackPointExtensions; +}; +export type TrackPointExtensions = { + 'gpxtpx:TrackPointExtension'?: TrackPointExtension; + 'gpxpx:PowerExtension'?: PowerExtension; +}; +export type TrackPointExtension = { + 'gpxtpx:atemp'?: number; + 'gpxtpx:hr'?: number; + 'gpxtpx:cad'?: number; + 'gpxtpx:Extensions'?: { + surface?: string; + }; +}; +export type PowerExtension = { + 'gpxpx:PowerInWatts'?: number; +}; +export type Author = { + name?: string; + email?: string; + link?: Link; +}; +export type RouteType = { + name?: string; + cmt?: string; + desc?: string; + src?: string; + link?: Link; + type?: string; + extensions?: TrackExtensions; + rtept: WaypointType[]; +}; +export type RoutePointExtension = { + 'gpxx:rpt'?: GPXXRoutePoint[]; +}; +export type GPXXRoutePoint = { + attributes: Coordinates; +}; diff --git a/gpx/dist/types.js b/gpx/dist/types.js new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/gpx/dist/types.js @@ -0,0 +1 @@ +export {};