nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

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:
Asrc/lib/leaflet.control.track-list/lib/endomondo.js | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/leaflet.control.track-list/lib/geo_file_formats.js | 6+++---
Msrc/lib/leaflet.control.track-list/lib/gpsies.js | 46+++++++++++++++++++++++++++++-----------------
Asrc/lib/leaflet.control.track-list/lib/strava.js | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/leaflet.control.track-list/track-list.js | 61++++++++++++++++++++++++++++++++++++++++---------------------
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('');