commit d6263be212d2dc67d7b3de95873b6d14380f26c0
parent bcd71a90179ae246e222232a17701cc0decd1965
Author: Sergej Orlov <wladimirych@gmail.com>
Date: Fri, 8 Dec 2017 21:49:59 +0300
[tracks] added support for getting tracks from Strava and endomondo links; refactored gpsies module and track proecessing
Diffstat:
5 files changed, 205 insertions(+), 41 deletions(-)
diff --git a/src/lib/leaflet.control.track-list/lib/endomondo.js b/src/lib/leaflet.control.track-list/lib/endomondo.js
@@ -0,0 +1,65 @@
+import urlViaCorsProxy from 'lib/CORSProxy';
+
+const regexps = [
+ /^https:\/\/www\.endomondo\.com\/users\/(\d+)\/workouts\/(\d+)/,
+ /^https:\/\/www\.endomondo\.com\/workouts\/(\d+)\/(\d+)/
+];
+
+function isEndomondoUrl(url) {
+ return regexps[0].test(url) || regexps[1].test(url);
+}
+
+function endomonXhrOptions(url) {
+ let m = regexps[0].exec(url);
+ let userId, trackId;
+ if (m) {
+ [userId, trackId] = [m[1], m[2]];
+ } else {
+ m = regexps[1].exec(url);
+ if (!m) {
+ throw new Error('Invalid endomodo url');
+ }
+ [trackId, userId] = [m[1], m[2]];
+ }
+ return [{
+ url: urlViaCorsProxy(`https://www.endomondo.com/rest/v1/users/${userId}/workouts/${trackId}`),
+ options: {responseType: 'binarystring'}
+ }];
+}
+
+function endomondoParser(name, responses) {
+ if (responses.length !== 1) {
+ throw new Error(`Invalid responses array length ${responses.length}`);
+ }
+ let data;
+ try {
+ data = JSON.parse(responses[0].responseBinaryText)
+ } catch (e) {
+ return null;
+ }
+ if (!data.points || !data.points.points) {
+ return null;
+ }
+ const track = data.points.points
+ .filter((p) => p.latitude)
+ .map((p) => {
+ return {
+ lat: p.latitude,
+ lng: p.longitude
+ }
+ }
+ );
+ if (!track.length) {
+ return [{error: 'Endomondo user disabled viewing this workout track'}];
+ }
+
+ let trackName = `${data.local_start_time.split('T')[0]}, ${data.distance.toFixed(1)} km, ${data.author.name}, `;
+ const geodata = {
+ name: trackName,
+ tracks: [track]
+ };
+ return [geodata];
+}
+
+
+export {isEndomondoUrl, endomonXhrOptions, endomondoParser}
diff --git a/src/lib/leaflet.control.track-list/lib/geo_file_formats.js b/src/lib/leaflet.control.track-list/lib/geo_file_formats.js
@@ -759,7 +759,7 @@ function parseNakarteUrl(s) {
return geodataArray;
}
-function parseGeoFile(name, data, preferNameFromFile) {
+function parseGeoFile(name, data) {
var parsers = [
parseTrackUrl,
parseNakarteUrl,
@@ -774,7 +774,7 @@ function parseGeoFile(name, data, preferNameFromFile) {
// parseYandexMap
];
for (var i = 0; i < parsers.length; i++) {
- var parsed = parsers[i](data, name, preferNameFromFile);
+ var parsed = parsers[i](data, name);
if (parsed !== null) {
return parsed;
}
@@ -782,4 +782,4 @@ function parseGeoFile(name, data, preferNameFromFile) {
return [{name: name, error: 'UNSUPPORTED'}];
}
-export {parseGeoFile};
+export {parseGeoFile, parseGpx};
diff --git a/src/lib/leaflet.control.track-list/lib/gpsies.js b/src/lib/leaflet.control.track-list/lib/gpsies.js
@@ -1,4 +1,5 @@
import urlViaCorsProxy from 'lib/CORSProxy';
+import {parseGpx} from './geo_file_formats'
function urlEncode(d) {
return Object.entries(d).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
@@ -10,27 +11,38 @@ function isGpsiesUrl(url) {
return re.test(url);
}
-function gpsiesXhrOptions(url, options) {
+function gpsiesXhrOptions(url) {
const m = re.exec(url);
if (!m) {
throw new Error('Invalid gpsies url');
}
const trackId = m[1];
const newUrl = urlViaCorsProxy('http://www.gpsies.com/download.do');
- return [newUrl, {
- method: 'POST',
- data: urlEncode({
- fileId: trackId,
- speed: '10',
- dataType: '3',
- filetype: 'gpxTrk',
- submitButton: '',
- inappropriate: ''
- }
- ),
- headers: [["Content-type", "application/x-www-form-urlencoded"]],
- responseType: 'binarystring'
- }]
+ return [{
+ url: newUrl,
+ options: {
+ method: 'POST',
+ data: urlEncode({
+ fileId: trackId,
+ speed: '10',
+ dataType: '3',
+ filetype: 'gpxTrk',
+ submitButton: '',
+ inappropriate: ''
+ }
+ ),
+ headers: [["Content-type", "application/x-www-form-urlencoded"]],
+ responseType: 'binarystring'
+ }
+ }];
}
-export {gpsiesXhrOptions, isGpsiesUrl}
-\ No newline at end of file
+
+function gpsiesParser(name, responses) {
+ if (responses.length !== 1) {
+ throw new Error(`Invalid responses array length ${responses.length}`);
+ }
+ return parseGpx(responses[0].responseBinaryText, name, true);
+}
+
+export {gpsiesXhrOptions, isGpsiesUrl, gpsiesParser}
+\ No newline at end of file
diff --git a/src/lib/leaflet.control.track-list/lib/strava.js b/src/lib/leaflet.control.track-list/lib/strava.js
@@ -0,0 +1,67 @@
+import urlViaCorsProxy from 'lib/CORSProxy';
+import {decode as utf8_decode} from 'utf8';
+
+const re = /^https:\/\/www\.strava\.com\/activities\/(\d+)/;
+
+function isStravaUrl(url) {
+ return re.test(url);
+}
+
+function stravaXhrOptions(url) {
+ const m = re.exec(url);
+ if (!m) {
+ throw new Error('Invalid strava url');
+ }
+ const trackId = m[1];
+ return [
+ {
+ url: urlViaCorsProxy(url),
+ options: {responseType: 'binarystring'}
+ },
+ {
+ url: urlViaCorsProxy(`https://www.strava.com/stream/${trackId}?streams%5B%5D=latlng`),
+ options: {responseType: 'binarystring'}
+ }];
+}
+
+
+function stravaParser(_, responses) {
+ if (responses.length !== 2) {
+ throw new Error(`Invalid responses array length ${responses.length}`);
+ }
+
+
+ let data;
+ try {
+ data = JSON.parse(responses[1].responseBinaryText);
+ } catch (e) {
+ return null;
+ }
+ if (!data.latlng) {
+ return null;
+ }
+
+ let name;
+ let s = responses[0].responseBinaryText;
+ s = utf8_decode(s);
+ let m = s.match(/<meta [^>]*twitter:description[^>]*>/);
+ if (m) {
+ m = m[0].match(/content='([^']+)'/);
+ name = m[1];
+ }
+
+ const geodata = {
+ name: name || 'Strava',
+ tracks: [data.latlng.map((p) => {
+ return {
+ lat: p[0],
+ lng: p[1]
+ }
+ }
+ )]
+ };
+ return [geodata];
+}
+
+
+export {isStravaUrl, stravaXhrOptions, stravaParser}
+\ No newline at end of file
diff --git a/src/lib/leaflet.control.track-list/track-list.js b/src/lib/leaflet.control.track-list/track-list.js
@@ -21,8 +21,9 @@ import 'lib/leaflet.polyline-edit';
import 'lib/leaflet.polyline-measure';
import logging from 'lib/logging';
import {notify} from 'lib/notifications';
-import {isGpsiesUrl, gpsiesXhrOptions} from './lib/gpsies';
-
+import {isGpsiesUrl, gpsiesXhrOptions, gpsiesParser} from './lib/gpsies';
+import {isStravaUrl, stravaXhrOptions, stravaParser} from './lib/strava';
+import {isEndomondoUrl, endomonXhrOptions, endomondoParser} from './lib/endomondo';
const TrackSegment = L.MeasuredLine.extend({
includes: L.Polyline.EditMixin,
@@ -37,6 +38,21 @@ const TrackSegment = L.MeasuredLine.extend({
TrackSegment.mergeOptions(L.Polyline.EditMixinOptions);
+function simpleTrackFetchOptions(url) {
+ return [{
+ url: urlViaCorsProxy(url),
+ options: {responseType: 'binarystring'}
+ }];
+}
+
+
+function simpleTrackParser(name, responses) {
+ if (responses.length !== 1) {
+ throw new Error(`Invalid responses array length ${responses.length}`);
+ }
+ return parseGeoFile(name, responses[0].responseBinaryText);
+}
+
L.Control.TrackList = L.Control.extend({
options: {position: 'bottomright'},
includes: L.Mixin.Events,
@@ -69,7 +85,7 @@ L.Control.TrackList = L.Control.extend({
<div class="leaflet-control-content">
<div class="header">
<div class="hint">
- GPX Ozi GoogleEarth ZIP YandexMaps GPSies
+ gpx kml Ozi zip YandexMaps GPSies Strava endomondo
</div>
<div class="button-minimize" data-bind="click: setMinimized"></div>
</div>
@@ -238,7 +254,7 @@ L.Control.TrackList = L.Control.extend({
url = decodeURIComponent(url);
} catch (e) {
}
- var geodata;
+ let geodata;
if (url.length > 0) {
this.readingFiles(true);
this.readProgressDone(undefined);
@@ -253,25 +269,28 @@ L.Control.TrackList = L.Control.extend({
.replace(/\/*$/, '')
.split('/')
.pop();
- let url_for_request, xhrOptions, preferNameFromFile;
+
+ let urlToRequest = simpleTrackFetchOptions;
+ let parser = simpleTrackParser;
+
+
if (isGpsiesUrl(url)) {
- [url_for_request, xhrOptions] = gpsiesXhrOptions(url);
- preferNameFromFile = true;
- } else {
- url_for_request = urlViaCorsProxy(url);
- xhrOptions = {responseType: 'binarystring'};
- preferNameFromFile = false;
+ urlToRequest = gpsiesXhrOptions;
+ parser = gpsiesParser;
+ } else if (isEndomondoUrl(url)) {
+ urlToRequest = endomonXhrOptions;
+ parser = endomondoParser;
+ } else if (isStravaUrl(url)) {
+ urlToRequest = stravaXhrOptions;
+ parser = stravaParser;
}
- fetch(url_for_request, xhrOptions)
- .then(function(xhr) {
- var geodata = parseGeoFile(name, xhr.responseBinaryText, preferNameFromFile);
- this.addTracksFromGeodataArray(geodata);
- }.bind(this),
- function() {
- var geodata = [{name: url, error: 'NETWORK'}];
- this.addTracksFromGeodataArray(geodata);
- }.bind(this)
- );
+ const requests = urlToRequest(url);
+ Promise.all(requests.map((request) => fetch(request.url, request.options)))
+ .then(
+ (responses) => parser(name, responses),
+ () => [{name: url, error: 'NETWORK'}]
+ )
+ .then((geodata) => this.addTracksFromGeodataArray(geodata));
}
}
this.url('');