/** @module gpx */ /// /** * @typedef {[String, String, String]} Segment * A tuple of latitude, longtitude and time. */ import { parseXml } from "@rgrove/parse-xml"; import { ERROR, OK } from "./result.js"; /** * Checks if the given JSON object is an element. Utility function for working * with JsonObject converted from XML. * * @param {JsonObject} object object to check. * @returns {boolean} True if the object.type is an element, false otherwise. */ const isElement = (object) => object?.type === "element"; /** * Checks if the given JSON object is an GPS track object. * Utility function for working with JsonObject converted from XML. * * @param {JsonObject} object to check. * @returns {boolean} True if the object.name is trk, false otherwise. */ const isTrack = (object) => object?.name === "trk"; /** * Checks if the given JSON object is an GPS track segment object. * Utility function for working with JsonObject converted from XML. * * @param {JsonObject} object to check. * @returns {boolean} True if the object.name is trkseg, false otherwise. */ const isTrackSegment = (object) => object?.name === "trkseg"; /** * Checks if the given JSON object is an GPS track segment time object. * Utility function for working with JsonObject converted from XML. * * @param {JsonObject} object to check. * @returns {boolean} True if the object.name is time, false otherwise. */ const isSegmentTime = (object) => object?.name === "time"; /** * Checks if the given JSON object have all nesessary data. * Utility function for working with JsonObject converted from XML. * * @param {JsonObject} object to check. * @returns {boolean} True if the object have latitude longtitude and time. */ const isProperSegment = (object) => { const haveLat = object?.attributes?.lat !== undefined; const haveLon = object?.attributes?.lon !== undefined; const children = object?.children ?? []; const timeObject = children.filter(isSegmentTime)?.[0]; const haveTime = timeObject !== undefined && timeObject.children[0].text.length > 0; return haveLat && haveLon && haveTime; }; /** * Take longtitude, latitude and time from JSON object * and return it as Segment tuple. * * @param {JsonObject} object to check. * @returns {Segment} True if the object have latitude longtitude and time. */ const getSegmentData = (object) => { const segmentTime = object.children.filter(isSegmentTime)[0].children[0].text; return [object.attributes.lat, object.attributes.lon, segmentTime]; }; /** * Parse XML document, turn it into JSON and find track segments, * then transform every object to Segment tuple * * @param {String} xmlDoc string representation of XML document. * @returns {Result<[Segment]>} */ function parse(xmlDoc) { // Check input argument if (!xmlDoc || xmlDoc.length === 0) { return [ERROR, "Can not parse empty document"]; } const gpxObject = parseXml(xmlDoc); const root = gpxObject.toJSON().children?.[0]; if (!root) { return [ERROR, "Document doesn't contain root element"]; } const track = root.children.filter(isElement).filter(isTrack)?.[0]; if (!track) { return [ERROR, "Document doesn't contain track element"]; } const segments = track.children .filter(isElement) .filter(isTrackSegment) .reduce( (seg1, seg2) => [...(seg1?.children ?? []), ...(seg2?.children ?? [])], [], ) .filter(isElement) .filter(isProperSegment) .map(getSegmentData); if (segments.length === 0) { return [ERROR, "There are no track segments to process"]; } return [OK, segments]; } export { parse };