UNPKG

@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
"use strict"; 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;