nakarte

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

commit d63628b4f72c692d3db0dd9c8b7f9011c18d00ef
parent 6a553f8168fe664c9a0076afc42cd320fc55b314
Author: Sergej Orlov <wladimirych@gmail.com>
Date:   Thu,  6 Dec 2018 22:37:02 +0100

[tracks] load tracks from urls in url hash #131

new hash params added:
 * nktu -- urlencoded urls
 * nktp -- point
 * ntkj -- json strictures with links, track data, point data and view params

Diffstat:
Msrc/App.js | 21++++++++++++++++-----
Msrc/lib/leaflet.control.track-list/lib/parsers/nktk.js | 33++++++++-------------------------
Msrc/lib/leaflet.control.track-list/lib/services/index.js | 5++---
Dsrc/lib/leaflet.control.track-list/lib/services/nakarte.js | 62--------------------------------------------------------------
Asrc/lib/leaflet.control.track-list/lib/services/nakarte/index.js | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.control.track-list/lib/services/nakarte/loadTracksFromJson.js | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/leaflet.control.track-list/track-list.hash-state.js | 36+++++++++++-------------------------
Msrc/lib/leaflet.control.track-list/track-list.js | 8+++++++-
Msrc/lib/leaflet.control.track-list/track-list.localstorage.js | 5+++--
Msrc/lib/leaflet.hashState/hashState.js | 52++++++++++++++++++++++++++++------------------------
10 files changed, 312 insertions(+), 147 deletions(-)

diff --git a/src/App.js b/src/App.js @@ -135,14 +135,25 @@ function setUp() { return tracklist.tracks().map((track) => track.name()); } tracklist.addTo(map); - if (!hashState.getState('nktk') && !hashState.getState('nktl')) { + const tracksHashParams = tracklist.hashParams(); + + let hasTrackParamsInHash = false; + for (let param of tracksHashParams) { + if (hashState.hasKey(param)) { + hasTrackParamsInHash = true; + break; + } + } + if (!hasTrackParamsInHash) { tracklist.loadTracksFromStorage(); } startInfo.tracksAfterLoadFromStorage = trackNames(); - bindHashStateReadOnly('nktk', tracklist.loadNktkFromHash.bind(tracklist)); - startInfo.tracksAfterLoadFromNktk = trackNames(); - bindHashStateReadOnly('nktl', tracklist.loadNktlFromHash.bind(tracklist)); - startInfo.tracksAfterLoadFromNktl = trackNames(); + + for (let param of tracksHashParams ) { + bindHashStateReadOnly(param, tracklist.loadTrackFromParam.bind(tracklist, param)); + } + startInfo.tracksAfterLoadFromHash = trackNames(); + if (!validPositionInHash) { tracklist.whenLoadDone(() => tracklist.setViewToAllTracks(true)); } diff --git a/src/lib/leaflet.control.track-list/lib/parsers/nktk.js b/src/lib/leaflet.control.track-list/lib/parsers/nktk.js @@ -311,29 +311,12 @@ function parseNktkFragment(s) { } function parseNktkSequence(s) { - if (typeof s === "string") { - s = s.split('/'); - } - var geodataArray = []; - for (let i = 0; i < s.length; i++) { - if (s[i]) { - geodataArray.push.apply(geodataArray, parseNktkFragment(s[i])); - } - } - return geodataArray; -} - -function parseNktkParam(s) { - let i = s.indexOf('#'); - if (i === -1) { - return null; - } - i = s.indexOf('nktk=', i + 1); - if (i === -1) { - return null; - } - s = s.substring(i + 5); - return parseNktkSequence(s) + return s.split('/') + .map(parseNktkFragment) + .reduce((acc, cur) => { + acc.push(...cur); + return acc; + }); } -export {saveNktk, parseNktkParam, parseTrackUrlData, parseNktkSequence}; -\ No newline at end of file +export {saveNktk, parseTrackUrlData, parseNktkSequence, parseNktkFragment}; +\ No newline at end of file diff --git a/src/lib/leaflet.control.track-list/lib/services/index.js b/src/lib/leaflet.control.track-list/lib/services/index.js @@ -5,14 +5,13 @@ import Gpslib from './gpslib'; import Osm from './osm'; import Strava from './strava'; import {YandexRuler} from './yandex'; -import {NakarteTrack, NakarteNktk, NakarteNktl} from './nakarte'; +import {NakarteTrack, NakarteUrl} from './nakarte'; import {MovescountMove, MovescountRoute} from './movescount'; export default [ YandexRuler, NakarteTrack, - NakarteNktk, - NakarteNktl, + NakarteUrl, Endomondo, Gpsies, Gpslib, diff --git a/src/lib/leaflet.control.track-list/lib/services/nakarte.js b/src/lib/leaflet.control.track-list/lib/services/nakarte.js @@ -1,62 +0,0 @@ -import BaseService from './baseService'; -import {parseNktkParam, parseNktkSequence, parseTrackUrlData} from '../parsers/nktk'; -import config from 'config'; - -class NakarteTrack extends BaseService { - isOurUrl() { - return this.origUrl.indexOf('track://') > -1; - } - - requestOptions() { - return []; - } - - parseResponse() { - const i = this.origUrl.indexOf('track://'); - return parseTrackUrlData(this.origUrl.substring(i + 8)); - } -} - -class NakarteNktk extends BaseService { - constructor(url) { - super(null); - this._geoData = parseNktkParam(url); - } - - isOurUrl() { - return !!this._geoData; - } - - requestOptions() { - return []; - } - - parseResponse() { - return this._geoData; - } - -} - -class NakarteNktl extends BaseService { - urlRe = /#.*nktl=([A-Za-z0-9_-]+)/; - - isOurUrl() { - return this.urlRe.test(this.origUrl); - } - - requestOptions() { - const m = this.urlRe.exec(this.origUrl); - const trackId = m[1]; - return [{ - url: (`${config.tracksStorageServer}/track/${trackId}`), - options: {responseType: 'binarystring'} - }]; - } - - parseResponse(responses) { - return parseNktkSequence(responses[0].responseBinaryText); - } -} - - -export {NakarteTrack, NakarteNktk, NakarteNktl}; diff --git a/src/lib/leaflet.control.track-list/lib/services/nakarte/index.js b/src/lib/leaflet.control.track-list/lib/services/nakarte/index.js @@ -0,0 +1,120 @@ +import BaseService from '../baseService'; +import {parseNktkSequence, parseTrackUrlData, parseNktkFragment} from '../../parsers/nktk'; +import config from 'config'; +import {parseHashParams} from 'lib/leaflet.hashState/hashState'; +import loadFromUrl from '../../loadFromUrl'; +import loadTracksFromJson from './loadTracksFromJson'; +import {fetch} from 'lib/xhr-promise'; + +function flattenArray(ar) { + return ar.reduce((cur, acc) => { + acc.push(...cur); + return acc; + }, []); + +} + +class NakarteTrack extends BaseService { + isOurUrl() { + return this.origUrl.indexOf('track://') > -1; + } + + requestOptions() { + return []; + } + + parseResponse() { + const i = this.origUrl.indexOf('track://'); + return parseTrackUrlData(this.origUrl.substring(i + 8)); + } +} + +class NakarteUrlLoader { + constructor() { + this.loaders = { + nktk: this.loadFromTextEncodedTrack, + nktl: this.loadFromTextEncodedTrackId, + nktu: this.loadFromUrlencodedUrls, + nktp: this.loadPoint, + nktj: this.loadFromJSON + }; + } + + paramNames() { + return Object.keys(this.loaders); + } + + async geoData(paramName, values) { + const loader = this.loaders[paramName]; + return loader.call(this, values); + } + + async loadFromTextEncodedTrack(values) { + return flattenArray(values.map(parseNktkFragment)); + } + + async loadFromTextEncodedTrackId(values) { + const requests = values.map((trackId) => + fetch( + `${config.tracksStorageServer}/track/${trackId}`, + {responseType: 'binarystring'} + ) + ); + let responses; + try { + responses = await Promise.all(requests); + } catch (e) { + return [{name: 'Track from nakarte server', error: 'NETWORK'}]; + } + return flattenArray(responses.map((r) => parseNktkSequence(r.responseBinaryText))); + } + + async loadFromJSON(values) { + return loadTracksFromJson(values); + } + + async loadFromUrlencodedUrls(values) { + return flattenArray(await Promise.all(values.map(loadFromUrl))); + } + + async loadPoint(values) { + return parsePointFromHashValues(values) + } +} + +class NakarteUrl { + constructor(url) { + const paramNames = new NakarteUrlLoader().paramNames(); + this._params = Object.entries(parseHashParams(url)) + .filter(([name, ]) => paramNames.includes(name)); + } + + isOurUrl(url) { + return this._params.length > 0; + } + + async geoData() { + const promises = this._params.map(([paramName, value]) => { + return new NakarteUrlLoader().geoData(paramName, value); + }); + return flattenArray(await Promise.all(promises)); + } +} + + +function parsePointFromHashValues(values) { + if (values.length < 2) { + return [{name: 'Point in url', error: 'CORRUPT'}] + } + const lat = parseFloat(values[0]); + const lng = parseFloat(values[1]); + if (isNaN(lat) || isNaN(lng) || lat < -90 || lat > 90 || lng < -180 || lng > 180) { + return [{name: 'Point in url', error: 'CORRUPT'}] + } + const name = ((values[2] || '').trim()) || 'Point'; + return [{name, points: [{name, lat, lng}]}]; + +} + + +export {NakarteTrack, NakarteUrl, NakarteUrlLoader}; diff --git a/src/lib/leaflet.control.track-list/lib/services/nakarte/loadTracksFromJson.js b/src/lib/leaflet.control.track-list/lib/services/nakarte/loadTracksFromJson.js @@ -0,0 +1,116 @@ +import urlSafeBase64 from '../../parsers/urlSafeBase64'; +import {TRACKLIST_TRACK_COLORS} from '../../../track-list'; +import loadFromUrl from '../../loadFromUrl'; + +function parseWaypoint(rawPoint) { + let name = rawPoint.n; + let lat = Number(rawPoint.lt); + let lng = Number(rawPoint.ln); + if (typeof name !== 'string' || !name || isNaN(lat) || isNaN(lng) || + lat < -90 || lat > 90 || lng < -180 || lng > 180) { + return {valid: false} + } + return { + valid: true, + point: {lat, lng, name} + } +} + +function parseTrack(rawTrack) { + if (!rawTrack.length) { + return {valid: false}; + } + const track = []; + for (let rawSegment of rawTrack) { + let segment = []; + if (!rawSegment || !rawSegment.length) { + return {valid: false}; + } + for (let rawPoint of rawSegment) { + if (!rawPoint || rawPoint.length !== 2) { + return {valid: false}; + } + let [lat, lng] = rawPoint.map(Number); + if (isNaN(lat) || isNaN(lng) || lat < -90 || lat > 90) { + return {valid: false}; + } + segment.push({lat, lng}); + } + track.push(segment); + } + return {valid: true, track}; + +} + +async function loadTracksFromJson(values) { + const errCorrupt = [{name: 'Track in url', error: 'CORRUPT'}]; + if (values.length < 1) { + return errCorrupt; + } + const jsonString = urlSafeBase64.decode(values[0]); + let data; + try { + data = JSON.parse(jsonString) + } catch (e) { + return errCorrupt; + } + if (!data || !data.length) { + return errCorrupt; + } + const geoDataArray = []; + + for (let el of data) { + // Each track should contain either url or at least ono of tracks and points + if (!el.u && !(el.p || el.t)) { + return errCorrupt; + } + let geodata; + if (el.u) { + geodata = await + loadFromUrl(el.u); + if (el.n && geodata.length === 1 && !geodata[0].error) { + geodata[0].name = el.n; + } + } else { + geodata = {}; + geodata.name = el.name || 'Track'; + if (el.t) { + const res = parseTrack(el.t); + if (!res.valid) { + return errCorrupt; + } + geodata.tracks = res.track; + } + if (el.p) { + geodata.points = []; + for (let rawPoint of el.p) { + let res = parseWaypoint(rawPoint); + if (!res.valid) { + return errCorrupt; + } + geodata.points.push(res.point); + } + } + geodata = [geodata]; + } + let viewProps = {}; + if ('c' in el) { + let color = Number(el.c); + if (color < 0 || color >= TRACKLIST_TRACK_COLORS.length) { + return errCorrupt; + } + viewProps.color = color; + } + if ('v' in el) { + viewProps.trackHidden = !el.v; + } + if ('m' in el) { + viewProps.measureTicksShown = !!el.m; + } + geodata.forEach((el) => Object.assign(el, viewProps)); + geoDataArray.push(...geodata) + } + return geoDataArray; +} + +export default loadTracksFromJson; +\ No newline at end of file diff --git a/src/lib/leaflet.control.track-list/track-list.hash-state.js b/src/lib/leaflet.control.track-list/track-list.hash-state.js @@ -1,38 +1,24 @@ import L from 'leaflet'; -import loadFromUrl from './lib/loadFromUrl'; -import logging from 'lib/logging'; -import {parseNktkSequence} from './lib/parsers/nktk'; +import {NakarteUrlLoader} from './lib/services/nakarte' L.Control.TrackList.include({ - loadNktkFromHash: function(values) { - if (!values || !(values.length)) { + hashParams: function() { + return new NakarteUrlLoader().paramNames(); + }, + + loadTrackFromParam: async function(paramName, values) { + if (!values || !values.length) { return false; } - logging.captureBreadcrumb({message: 'load nktk from hashState'}); - const geodata = parseNktkSequence(values); - const notEmpty = this.addTracksFromGeodataArray(geodata, {href: window.location.href}); + this.readingFiles(this.readingFiles() + 1); + const geodata = await new NakarteUrlLoader().geoData(paramName, values); + const notEmpty = this.addTracksFromGeodataArray(geodata, {paramName, values}); + this.readingFiles(this.readingFiles() - 1); if (notEmpty) { this.setExpanded(); } }, - - loadNktlFromHash: function(values) { - if (!values || !(values.length)) { - return false; - } - logging.captureBreadcrumb({message: 'load nktl from hashState'}); - const url = `#nktl=${values[0]}`; - const href = window.location.href; - loadFromUrl(url).then( - (geodata) => { - const notEmpty = this.addTracksFromGeodataArray(geodata, {href}); - if (notEmpty) { - this.setExpanded(); - } - } - ); - } } ); diff --git a/src/lib/leaflet.control.track-list/track-list.js b/src/lib/leaflet.control.track-list/track-list.js @@ -25,6 +25,9 @@ import config from 'config'; import md5 from './lib/md5'; import {wrapLatLngToTarget, wrapLatLngBoundsToTarget} from 'lib/leaflet.fixes/fixWorldCopyJump'; +const TRACKLIST_TRACK_COLORS = ['#77f', '#f95', '#0ff', '#f77', '#f7f', '#ee5']; + + const TrackSegment = L.MeasuredLine.extend({ includes: L.Polyline.EditMixin, @@ -43,7 +46,7 @@ L.Control.TrackList = L.Control.extend({ options: {position: 'bottomright'}, includes: L.Mixin.Events, - colors: ['#77f', '#f95', '#0ff', '#f77', '#f7f', '#ee5'], + colors: TRACKLIST_TRACK_COLORS, initialize: function() { @@ -1181,3 +1184,5 @@ L.Control.TrackList = L.Control.extend({ } } ); + +export {TRACKLIST_TRACK_COLORS}; +\ No newline at end of file diff --git a/src/lib/leaflet.control.track-list/track-list.localstorage.js b/src/lib/leaflet.control.track-list/track-list.localstorage.js @@ -1,6 +1,6 @@ import './track-list' import L from 'leaflet'; -import {parseNktkParam} from './lib/parsers/nktk'; +import {parseNktkSequence} from './lib/parsers/nktk'; import safeLocalStorage from 'lib/safe-localstorage'; import logging from 'lib/logging'; @@ -80,7 +80,8 @@ L.Control.TrackList.include({ safeLocalStorage.removeItem(key); if (s) { logging.captureBreadcrumb({message: 'load track from localStorage'}); - geodata = parseNktkParam(s); + s = s.slice(6); // remove "#nktk=" prefix + geodata = parseNktkSequence(s); this.addTracksFromGeodataArray(geodata, {localStorage: {key, value: s}}); } } diff --git a/src/lib/leaflet.hashState/hashState.js b/src/lib/leaflet.hashState/hashState.js @@ -9,6 +9,30 @@ function arrayItemsEqual(l1, l2) { return true; } + +function parseHashParams(s) { + const args = {}, + i = s.indexOf('#'); + if (i >= 0) { + s = s.substr(i + 1).trim(); + let m, key, value; + for (let pair of s.split('&')) { + m = /^([^=]+?)(?:=(.*))?$/.exec(pair); + if (m) { + [, key, value] = m; + if (value) { + value = value.split('/'); + value = value.map(decodeURIComponent); + } else { + value = []; + } + args[key] = value; + } + } + } + return args; +} + const hashState = { _listeners: [], _state: {}, @@ -45,28 +69,8 @@ const hashState = { return this._state[key]; }, - _parseHash: function() { - let hash = location.hash; - const args = {}, - i = hash.indexOf('#'); - if (i >= 0) { - hash = hash.substr(i + 1).trim(); - let m, key, value; - for (let pair of hash.split('&')) { - m = /^([^=]+?)(?:=(.*))?$/.exec(pair); - if (m) { - [, key, value] = m; - if (value) { - value = value.split('/'); - value = value.map(decodeURIComponent); - } else { - value = []; - } - args[key] = value; - } - } - } - return args; + hasKey: function(key) { + return this._state.hasOwnProperty(key); }, _saveStateToHash: function() { @@ -93,7 +97,7 @@ const hashState = { if (this._ignoreChanges) { return; } - const newState = this._parseHash(); + const newState = parseHashParams(location.hash); const changedKeys = {}; for (let key of Object.keys(newState)) { if (!(key in this._state) || !arrayItemsEqual(newState[key], this._state[key])) { @@ -130,5 +134,5 @@ window.addEventListener('hashchange', hashState.onHashChanged.bind(hashState)); hashState.onHashChanged(); -export {hashState, bindHashStateReadOnly}; +export {hashState, bindHashStateReadOnly, parseHashParams};