UNPKG

signalk-parquet

Version:

Vessel data Parquet file archive with automated value and geospatial triggers. History API compliant with cloud backups and queries.

117 lines 4.49 kB
"use strict"; /** * Minimal GPX parser * * Extracts track points (<trkpt>) from a GPX file. Waypoints and routes are * ignored — only trkpt elements carry the <time> tag needed for partitioning * time-series data into the parquet store. * * Supports GPX 1.0 and 1.1. Uses regex-based extraction rather than a full * XML parser to avoid adding a dependency; this is safe for GPX because the * schema is shallow and well-defined. * * Example input fragment: * <trkpt lat="47.5" lon="8.7"> * <ele>412.5</ele> * <time>2024-06-01T10:15:30Z</time> * <speed>5.14</speed> * <course>180.0</course> * </trkpt> * Produces: { latitude: 47.5, longitude: 8.7, time: Date(...), elevation: 412.5, speedMs: 5.14, courseDeg: 180 } */ Object.defineProperty(exports, "__esModule", { value: true }); exports.parseGpx = parseGpx; // Real-world GPX exporters add namespace prefixes liberally (Garmin // Connect uses "gpxx:", some OpenCPN configs use "ns3:", certain Suunto // and Polar exporters use other ones). The (?:[\w-]+:)? group is // non-capturing so existing capture-group indices stay the same. // Match a single <trkpt ...>...</trkpt> block (possibly namespace-prefixed), // capturing attributes and inner content. Multiline because trkpt contents // span several lines. const TRKPT_RE = /<(?:[\w-]+:)?trkpt\b([^>]*)>([\s\S]*?)<\/(?:[\w-]+:)?trkpt>|<(?:[\w-]+:)?trkpt\b([^>]*)\/>/g; // Match <trk>...</trk>; within each we parse name and trkpt elements. const TRK_RE = /<(?:[\w-]+:)?trk\b[^>]*>([\s\S]*?)<\/(?:[\w-]+:)?trk>/g; const NAME_RE = /<(?:[\w-]+:)?name\b[^>]*>([\s\S]*?)<\/(?:[\w-]+:)?name>/; function extractAttr(attrs, name) { // e.g. extract lat="47.5" from ' lat="47.5" lon="8.7"' const re = new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`); const m = attrs.match(re); return m ? m[1] : undefined; } function extractTag(inner, tag) { // Allow an optional namespace prefix on either the open or close tag, // matching the broader tolerance in TRKPT_RE / TRK_RE / NAME_RE. const re = new RegExp(`<(?:[\\w-]+:)?${tag}\\b[^>]*>([\\s\\S]*?)<\\/(?:[\\w-]+:)?${tag}>`, 'i'); const m = inner.match(re); return m ? m[1].trim() : undefined; } function parseFloatOrUndef(s) { if (s === undefined) return undefined; const n = parseFloat(s); return Number.isFinite(n) ? n : undefined; } function parsePoint(attrs, inner) { const lat = parseFloatOrUndef(extractAttr(attrs, 'lat')); const lon = parseFloatOrUndef(extractAttr(attrs, 'lon')); if (lat === undefined || lon === undefined) { return null; } const timeStr = extractTag(inner, 'time'); let time; if (timeStr) { const d = new Date(timeStr); if (!isNaN(d.getTime())) { time = d; } } return { latitude: lat, longitude: lon, time, elevation: parseFloatOrUndef(extractTag(inner, 'ele')), speedMs: parseFloatOrUndef(extractTag(inner, 'speed')), courseDeg: parseFloatOrUndef(extractTag(inner, 'course')), }; } /** * Parse a GPX XML string into structured tracks. * * Points without a valid lat/lon are skipped. Points without <time> are * still returned (caller may drop them) — the time field is undefined. */ function parseGpx(xml) { const tracks = []; let totalPoints = 0; let firstTime; let lastTime; let trkMatch; TRK_RE.lastIndex = 0; while ((trkMatch = TRK_RE.exec(xml)) !== null) { const trkInner = trkMatch[1]; const nameMatch = trkInner.match(NAME_RE); const name = nameMatch ? nameMatch[1].trim() : undefined; const points = []; let ptMatch; TRKPT_RE.lastIndex = 0; while ((ptMatch = TRKPT_RE.exec(trkInner)) !== null) { // Self-closing form uses group 3; otherwise group 1 + 2 const attrs = ptMatch[1] ?? ptMatch[3] ?? ''; const inner = ptMatch[2] ?? ''; const pt = parsePoint(attrs, inner); if (!pt) continue; points.push(pt); totalPoints++; if (pt.time) { if (!firstTime || pt.time < firstTime) firstTime = pt.time; if (!lastTime || pt.time > lastTime) lastTime = pt.time; } } tracks.push({ name, points }); } return { tracks, totalPoints, firstTime, lastTime }; } //# sourceMappingURL=gpx-parser.js.map