diff --git a/README.md b/README.md index ee6b13a..4996175 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Generate a heatmap from GPS tracks. -Drag and drop one or more GPX/TCX/FIT/IGC files or JPEG images into the browser +Drag and drop one or more GPX/TCX/FIT/IGC/SKIZ files or JPEG images into the browser window. No data is ever uploaded, everything is done client side. Loosely inspired by [The Passage Ride](http://thepassageride.com), which you diff --git a/package-lock.json b/package-lock.json index a496162..949e611 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2063,6 +2063,11 @@ "isarray": "^1.0.0" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -2814,6 +2819,14 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "csv-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz", + "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", + "requires": { + "minimist": "^1.2.0" + } + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -3579,12 +3592,25 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-xml-parser": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz", + "integrity": "sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==" + }, "fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", "dev": true }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "requires": { + "pend": "~1.2.0" + } + }, "figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -5070,8 +5096,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { "version": "3.0.2", @@ -5693,6 +5718,11 @@ "sha.js": "^2.4.8" } }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -6498,6 +6528,16 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "skiz-parser": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/skiz-parser/-/skiz-parser-1.4.0.tgz", + "integrity": "sha512-1imVXEfpClP0/XvUJvq2VCF2jI/2A4WX8KoVaHrYYZoCLNUFFj/cCtVoJc57LT5hOH3rGt1iLh+8dmWDMWy8gg==", + "requires": { + "csv-parser": "^3.0.0", + "fast-xml-parser": "^3.19.0", + "yauzl": "^2.10.0" + } + }, "slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", @@ -8263,6 +8303,15 @@ "decamelize": "^1.2.0" } }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "ylru": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", diff --git a/package.json b/package.json index e900b8d..7ccf44f 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "leaflet-providers": "^1.3.1", "pako": "^1.0.6", "picomodal": "^3.0.0", + "skiz-parser": "^1.4.0", "xml2js": "^0.4.19" }, "devDependencies": { diff --git a/src/track.js b/src/track.js index feaa8a0..8650c0b 100644 --- a/src/track.js +++ b/src/track.js @@ -9,6 +9,7 @@ import xml2js from 'xml2js'; import FitParser from 'fit-file-parser'; import Pako from 'pako'; import IGCParser from 'igc-parser'; +import { parseSkizFile } from 'skiz-parser'; const parser = new xml2js.Parser(); @@ -144,6 +145,23 @@ function extractIGCTracks(igc) { return points.length > 0 ? [{timestamp, points, name}] : []; } +function extractSKIZTracks(skiz) { + const points = []; + let timestamp = null; + + for (const node of skiz.trackNodes) { + points.push({ + lat: node.latitude, + lng: node.longitude, + }); + + node.timestamp && (timestamp = node.timestamp); + } + + const name = 'skiz'; + return points.length > 0 ? [{timestamp, points, name}] : []; +} + function readFile(file, encoding, isGzipped) { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -215,6 +233,18 @@ export default function extractTracks(file) { } })); + case 'skiz': + return readFile(file, 'binary', isGzipped) + .then(contents => new Promise((resolve, reject) => { + parseSkizFile(contents, (err, result) => { + if (err) { + reject(err); + } else { + resolve(extractSKIZTracks(result)); + } + }); + })); + default: throw `Unsupported file format: ${format}`; } diff --git a/src/ui.js b/src/ui.js index e80d52d..981e781 100644 --- a/src/ui.js +++ b/src/ui.js @@ -24,7 +24,7 @@ const AVAILABLE_THEMES = [ const MODAL_CONTENT = { help: `
If you use Strava, go to your account download page and click "Request your archive". You'll get an email containing a ZIP diff --git a/webpack.config.js b/webpack.config.js index 98ea64a..8c7fe6d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -23,5 +23,8 @@ module.exports = { } } ] + }, + node: { + fs: 'empty' } };