@sports-alliance/sports-lib
Version:
A Library to for importing / exporting and processing GPX, TCX, FIT and JSON files from services such as Strava, Movescount, Garmin, Polar etc
175 lines (174 loc) • 8.97 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouteImporterGPX = void 0;
const activity_types_1 = require("../../../../activities/activity.types");
const creator_1 = require("../../../../creators/creator");
const data_altitude_1 = require("../../../../data/data.altitude");
const data_latitude_degrees_1 = require("../../../../data/data.latitude-degrees");
const data_longitude_degrees_1 = require("../../../../data/data.longitude-degrees");
const file_type_enum_1 = require("../../../../events/adapters/file-type.enum");
const gx_parser_1 = require("../../../../events/adapters/importers/gpx/gx-parser");
const helpers_1 = require("../../../../events/utilities/helpers");
const parsing_event_lib_error_1 = require("../../../../errors/parsing-event-lib.error");
const route_1 = require("../../../route");
const route_file_1 = require("../../../route-file");
const route_file_utilities_1 = require("../../../route-file.utilities");
const route_parsing_options_1 = require("../../../route-parsing-options");
const route_stream_1 = require("../../../route-stream");
const route_utilities_1 = require("../../../route.utilities");
class RouteImporterGPX {
static getFromString(gpx, domParser, options = route_parsing_options_1.RouteParsingOptions.DEFAULT, name = 'New Route File') {
return new Promise((resolve, reject) => {
const parsedGPX = new gx_parser_1.GXParser(gpx, domParser);
const creator = new creator_1.Creator(parsedGPX.creator || 'Unknown Device', undefined, parsedGPX.version);
const metadata = RouteImporterGPX.toArray(parsedGPX.metadata)[0] || {};
const routeFileName = RouteImporterGPX.getText(metadata.name) || name;
const routeFile = new route_file_1.RouteFile(routeFileName, file_type_enum_1.FileType.GPX, creator, [], RouteImporterGPX.toArray(parsedGPX.wpt).map(waypoint => RouteImporterGPX.getPointFromGPXNode(waypoint)), RouteImporterGPX.getDate(metadata.time));
const routes = RouteImporterGPX.getRouteCandidates(parsedGPX, options)
.map(routeCandidate => {
const points = routeCandidate.pointNodes
.map(routePoint => RouteImporterGPX.getPointFromGPXNode(routePoint))
.filter(point => (0, helpers_1.isNumber)(point.latitudeDegrees) && (0, helpers_1.isNumber)(point.longitudeDegrees));
if (!points.length) {
return null;
}
const route = new route_1.Route(creator, options, RouteImporterGPX.getText(routeCandidate.routeNode.name) || null, RouteImporterGPX.getActivityType(routeCandidate.routeNode), points, {
comment: RouteImporterGPX.getText(routeCandidate.routeNode.cmt),
description: RouteImporterGPX.getText(routeCandidate.routeNode.desc),
number: RouteImporterGPX.getNumber(routeCandidate.routeNode.number),
links: RouteImporterGPX.getLinks(routeCandidate.routeNode.link),
extensions: routeCandidate.routeNode.extensions
});
route.addStream(new route_stream_1.RouteStream(data_latitude_degrees_1.DataLatitudeDegrees.type, points.map(point => point.latitudeDegrees)));
route.addStream(new route_stream_1.RouteStream(data_longitude_degrees_1.DataLongitudeDegrees.type, points.map(point => point.longitudeDegrees)));
if (points.some(point => (0, helpers_1.isNumber)(point.altitude))) {
route.addStream(new route_stream_1.RouteStream(data_altitude_1.DataAltitude.type, points.map(point => ((0, helpers_1.isNumber)(point.altitude) ? point.altitude : null))));
}
route_utilities_1.RouteUtilities.generateMissingStreamsAndStatsForRoute(route);
return route;
})
.filter((route) => route !== null);
if (!routes.length) {
reject(new parsing_event_lib_error_1.ParsingEventLibError('No routes found in GPX'));
return;
}
routeFile.addRoutes(routes);
route_file_utilities_1.RouteFileUtilities.reGenerateStatsForRouteFile(routeFile);
resolve(routeFile);
});
}
static getRouteCandidates(parsedGPX, options) {
const explicitRoutes = RouteImporterGPX.toArray(parsedGPX.rte).map(routeNode => ({
routeNode,
pointNodes: RouteImporterGPX.toArray(routeNode.rtept)
}));
const importTimedTracksAsRoutes = options.gpx.importTimedTracksAsRoutes && explicitRoutes.length === 0;
const trackRoutes = RouteImporterGPX.toArray(parsedGPX.trk)
.map(routeNode => {
const pointNodes = RouteImporterGPX.toArray(routeNode.trkseg).reduce((points, trkseg) => {
return points.concat(RouteImporterGPX.toArray(trkseg.trkpt));
}, []);
if (!pointNodes.length) {
return null;
}
const hasTimedPoints = pointNodes.some(point => RouteImporterGPX.toArray(point.time).length > 0);
if (hasTimedPoints && !importTimedTracksAsRoutes) {
return null;
}
return {
routeNode,
pointNodes
};
})
.filter((routeCandidate) => routeCandidate !== null);
return RouteImporterGPX.dedupeRouteCandidates(explicitRoutes.concat(trackRoutes));
}
static dedupeRouteCandidates(routeCandidates) {
const seenSignatures = new Set();
return routeCandidates.filter(routeCandidate => {
const signature = RouteImporterGPX.getRouteCandidateSignature(routeCandidate);
if (seenSignatures.has(signature)) {
return false;
}
seenSignatures.add(signature);
return true;
});
}
static getRouteCandidateSignature(routeCandidate) {
return routeCandidate.pointNodes
.map(pointNode => {
const latitude = Number.parseFloat(pointNode.lat);
const longitude = Number.parseFloat(pointNode.lon);
const altitude = RouteImporterGPX.getNumber(pointNode.ele);
return [
Number.isFinite(latitude) ? latitude.toFixed(7) : '',
Number.isFinite(longitude) ? longitude.toFixed(7) : '',
altitude === null ? '' : altitude.toFixed(2)
].join(',');
})
.join('|');
}
static getPointFromGPXNode(pointNode) {
return {
latitudeDegrees: parseFloat(pointNode.lat),
longitudeDegrees: parseFloat(pointNode.lon),
altitude: RouteImporterGPX.getNumber(pointNode.ele),
name: RouteImporterGPX.getText(pointNode.name),
comment: RouteImporterGPX.getText(pointNode.cmt),
description: RouteImporterGPX.getText(pointNode.desc),
symbol: RouteImporterGPX.getText(pointNode.sym),
type: RouteImporterGPX.getText(pointNode.type),
links: RouteImporterGPX.getLinks(pointNode.link),
extensions: pointNode.extensions
};
}
static getActivityType(routeNode) {
return activity_types_1.ActivityTypesHelper.resolveActivityType(RouteImporterGPX.getText(routeNode.type));
}
static getText(value) {
const textValue = RouteImporterGPX.toArray(value)[0];
if (textValue === null || textValue === undefined) {
return null;
}
const stringValue = String(textValue);
return stringValue.length ? stringValue : null;
}
static getNumber(value) {
const textValue = RouteImporterGPX.getText(value);
if (textValue === null) {
return null;
}
const numberValue = parseFloat(textValue);
return Number.isFinite(numberValue) ? numberValue : null;
}
static getDate(value) {
const textValue = RouteImporterGPX.getText(value);
if (textValue === null) {
return null;
}
const date = new Date(textValue);
return Number.isNaN(date.getTime()) ? null : date;
}
static getLinks(value) {
return RouteImporterGPX.toArray(value)
.map((linkNode) => {
const href = RouteImporterGPX.getText(linkNode.href) || linkNode.href;
if (!href) {
return null;
}
return {
href: String(href),
text: RouteImporterGPX.getText(linkNode.text),
type: RouteImporterGPX.getText(linkNode.type)
};
})
.filter((link) => link !== null);
}
static toArray(value) {
if (value === undefined || value === null) {
return [];
}
return Array.isArray(value) ? value : [value];
}
}
exports.RouteImporterGPX = RouteImporterGPX;