commit 7534e576ce84bc5da0750c31be6a9e233b3ed404
parent a05fb5b97bf1bcea4a399a8ef39f20d5992d03b4
Author: Sergej Orlov <wladimirych@gmail.com>
Date: Mon, 26 Feb 2018 00:03:57 +0300
[tracks] added new track url version 4 -- uses protobuf
Diffstat:
5 files changed, 460 insertions(+), 199 deletions(-)
diff --git a/src/lib/leaflet.control.track-list/lib/geo_file_exporters.js b/src/lib/leaflet.control.track-list/lib/geo_file_exporters.js
@@ -1,5 +1,6 @@
import utf8 from 'utf8';
import escapeHtml from 'escape-html';
+import {saveNktk} from './nktk';
function saveGpx(segments, name, points) {
var gpx = [],
@@ -210,6 +211,5 @@ function saveToString(segments, name, color, measureTicksShown, wayPoints, track
return encodeUrlSafeBase64(stringified.join(''));
}
-
-export default {saveGpx, saveKml,saveToString};
+export default {saveGpx, saveKml, saveToString: saveNktk};
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
@@ -8,6 +8,7 @@ import urlViaCorsProxy from 'lib/CORSProxy';
import {isGpsiesUrl, gpsiesXhrOptions, gpsiesParser} from './gpsies';
import {isStravaUrl, stravaXhrOptions, stravaParser} from './strava';
import {isEndomondoUrl, endomonXhrOptions, endomondoParser} from './endomondo';
+import {parseTrackUrlData, parseNktk} from './nktk';
function xmlGetNodeText(node) {
@@ -542,208 +543,13 @@ function parseZip(txt, name) {
// return [{name: name, error: error, tracks: segments}];
// }
-function decodeUrlSafeBase64(s) {
- var decoded;
- s = s
- .replace(/[\n\r \t]/g, '')
- .replace(/-/g, '+')
- .replace(/_/g, '/');
- try {
- decoded = atob(s);
- } catch (e) {
- }
- if (decoded && decoded.length) {
- return decoded;
- }
- return null;
-}
-
-function unpackNumber(s, position) {
- var x,
- n = 0;
- x = s.charCodeAt(position);
- if (isNaN(x)) {
- throw new Error('Unexpected end of line while unpacking number');
- }
- if (x < 128) {
- n = x - 64;
- return [n, 1];
- }
- n = x & 0x7f;
- x = s.charCodeAt(position + 1);
- if (isNaN(x)) {
- throw new Error('Unexpected end of line while unpacking number');
- }
- if (x < 128) {
- n |= x << 7;
- n -= 8192;
- return [n, 2];
- }
- n |= (x & 0x7f) << 7;
- x = s.charCodeAt(position + 2);
- if (isNaN(x)) {
- throw new Error('Unexpected end of line while unpacking number');
- }
- if (x < 128) {
- n |= x << 14;
- n -= 1048576;
- return [n, 3];
- }
- n |= (x & 0x7f) << 14;
- x = s.charCodeAt(position + 3);
- if (isNaN(x)) {
- throw new Error('Unexpected end of line while unpacking number');
- }
- n |= x << 21;
- n -= 268435456;
- return [n, 4];
-}
-
-function PackedStreamReader(s) {
- this._string = s;
- this.position = 0;
-}
-
-PackedStreamReader.prototype.readNumber = function() {
- var n = unpackNumber(this._string, this.position);
- this.position += n[1];
- return n[0];
-};
-
-PackedStreamReader.prototype.readString = function(size) {
- var s = this._string.slice(this.position, this.position + size);
- this.position += size;
- return s;
-};
-
-function parseStringified(s, oldVersion) {
- var name,
- n,
- segments = [],
- segment,
- segmentsCount,
- pointsCount,
- arcUnit = ((1 << 24) - 1) / 360,
- x, y,
- error, version, midX, midY, /*symbol,*/ waypointName,
- wayPoints = [], color, measureTicksShown, trackHidden = false;
- s = decodeUrlSafeBase64(s);
- if (!s) {
- return [{name: 'Text encoded track', error: ['CORRUPT']}];
- }
- s = new PackedStreamReader(s);
- try {
- if (oldVersion) {
- version = 0;
- } else {
- version = s.readNumber();
- }
- if (version !== 0 && version !== 1 && version !== 2 && version !== 3) {
- return [{name: 'Text encoded track', error: ['CORRUPT']}];
- }
- n = s.readNumber();
- name = s.readString(n);
- name = utf8_decode(name);
- segmentsCount = s.readNumber();
- for (; segmentsCount--;) {
- segment = [];
- pointsCount = s.readNumber();
- x = 0;
- y = 0;
- for (; pointsCount--;) {
- x += s.readNumber();
- y += s.readNumber();
- segment.push({lng: x / arcUnit, lat: y / arcUnit});
- }
- segments.push(segment);
- segment = null;
- }
- } catch (e) {
- if (e.message.match('Unexpected end of line while unpacking number')) {
- error = ['CORRUPT'];
- if (segment) {
- segments.push(segment);
- }
- } else {
- throw e;
- }
- }
- try {
- color = s.readNumber();
- measureTicksShown = s.readNumber();
- } catch (e) {
- if (e.message.match('Unexpected end of line while unpacking number')) {
- color = 0;
- measureTicksShown = 0;
- if (version > 0) {
- error = ['CORRUPT'];
- }
- } else {
- throw e;
- }
- }
- if (version >= 3) {
- try {
- trackHidden = !!(s.readNumber())
- } catch (e) {
- if (e.message.match('Unexpected end of line while unpacking number')) {
- error = ['CORRUPT'];
- } else {
- throw e;
- }
- }
- }
- if (version >= 2) {
- try {
- pointsCount = s.readNumber();
- if (pointsCount) {
- midX = s.readNumber();
- midY = s.readNumber();
- }
- for (; pointsCount--;) {
- n = s.readNumber();
- waypointName = s.readString(n);
- waypointName = utf8_decode(waypointName);
-
- // let symbol = s.readNumber();
- s.readNumber();
-
- x = s.readNumber() + midX;
- y = s.readNumber() + midY;
- wayPoints.push({
- name: waypointName,
- lat: y / arcUnit,
- lng: x / arcUnit,
-
- }
- );
- }
- } catch (e) {
- if (e.message.match('Unexpected end of line while unpacking number')) {
- error = ['CORRUPT'];
- } else {
- throw e;
- }
- }
- }
- var geoData = {
- name: name || "Text encoded track",
- tracks: segments,
- error: error,
- points: wayPoints,
- color: color,
- measureTicksShown: measureTicksShown,
- trackHidden: trackHidden
- };
- return [geoData];
-}
function parseTrackUrl(s) {
var i = s.indexOf('track://');
if (i === -1) {
return null;
}
- return parseStringified(s.substring(i + 8), true);
+ return parseTrackUrlData(s.substring(i + 8));
}
function parseNakarteUrl(s) {
@@ -759,7 +565,7 @@ function parseNakarteUrl(s) {
var geodataArray = [];
for (i = 0; i < s.length; i++) {
if (s[i]) {
- geodataArray.push.apply(geodataArray, parseStringified(s[i]));
+ geodataArray.push.apply(geodataArray, parseNktk(s[i]));
}
}
return geodataArray;
diff --git a/src/lib/leaflet.control.track-list/lib/nktk.js b/src/lib/leaflet.control.track-list/lib/nktk.js
@@ -0,0 +1,328 @@
+import Pbf from 'pbf';
+import {TrackView} from './nktk_pb';
+import {arrayBufferToString, stringToArrayBuffer} from 'lib/binary-strings';
+import utf8 from 'utf8';
+
+const arcUnit = ((1 << 24) - 1) / 360;
+
+function encodeUrlSafeBase64(s) {
+ return (btoa(s)
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_')
+ // .replace(/=+$/, '')
+ );
+}
+
+function decodeUrlSafeBase64(s) {
+ var decoded;
+ s = s
+ .replace(/[\n\r \t]/g, '')
+ .replace(/-/g, '+')
+ .replace(/_/g, '/');
+ try {
+ decoded = atob(s);
+ } catch (e) {
+ }
+ if (decoded && decoded.length) {
+ return decoded;
+ }
+ return null;
+}
+
+function PackedStreamReader(s) {
+ this._string = s;
+ this.position = 0;
+}
+
+PackedStreamReader.prototype.readNumber = function() {
+ var n = unpackNumber(this._string, this.position);
+ this.position += n[1];
+ return n[0];
+};
+
+PackedStreamReader.prototype.readString = function(size) {
+ var s = this._string.slice(this.position, this.position + size);
+ this.position += size;
+ return s;
+};
+
+function unpackNumber(s, position) {
+ var x,
+ n = 0;
+ x = s.charCodeAt(position);
+ if (isNaN(x)) {
+ throw new Error('Unexpected end of line while unpacking number');
+ }
+ if (x < 128) {
+ n = x - 64;
+ return [n, 1];
+ }
+ n = x & 0x7f;
+ x = s.charCodeAt(position + 1);
+ if (isNaN(x)) {
+ throw new Error('Unexpected end of line while unpacking number');
+ }
+ if (x < 128) {
+ n |= x << 7;
+ n -= 8192;
+ return [n, 2];
+ }
+ n |= (x & 0x7f) << 7;
+ x = s.charCodeAt(position + 2);
+ if (isNaN(x)) {
+ throw new Error('Unexpected end of line while unpacking number');
+ }
+ if (x < 128) {
+ n |= x << 14;
+ n -= 1048576;
+ return [n, 3];
+ }
+ n |= (x & 0x7f) << 14;
+ x = s.charCodeAt(position + 3);
+ if (isNaN(x)) {
+ throw new Error('Unexpected end of line while unpacking number');
+ }
+ n |= x << 21;
+ n -= 268435456;
+ return [n, 4];
+}
+
+function deltaEncodeSegment(points) {
+ let deltaLats = [],
+ deltaLons = [];
+ let lastLon = 0,
+ lastLat = 0,
+ lon, lat;
+ for (let i = 0, len = points.length; i < len; i++) {
+ let p = points[i];
+ lon = Math.round(p.lng * arcUnit);
+ lat = Math.round(p.lat * arcUnit);
+ let deltaLon = lon - lastLon;
+ let deltaLat = lat - lastLat;
+ deltaLats.push(deltaLat);
+ deltaLons.push(deltaLon);
+ lastLon = lon;
+ lastLat = lat;
+ }
+ return {deltaLats, deltaLons};
+}
+
+function deltaDecodeSegment(deltaLats, deltaLons) {
+ let encodedLat = 0,
+ encodedLon = 0;
+ const points = [];
+ for (let i = 0; i < deltaLats.length; i++) {
+ encodedLat += deltaLats[i];
+ encodedLon += deltaLons[i];
+ points.push({lat: encodedLat / arcUnit, lng: encodedLon / arcUnit});
+ }
+ return points;
+}
+
+function saveNktk(segments, name, color, measureTicksShown, waypoints, trackHidden) {
+ const trackView = {
+ color,
+ shown: !trackHidden,
+ ticksShown: measureTicksShown,
+ };
+ const track = trackView.track = {name};
+ if (segments && segments.length) {
+ let deltaEncodedSegments = [];
+ for (let segment of segments) {
+ let {deltaLats, deltaLons} = deltaEncodeSegment(segment);
+ deltaEncodedSegments.push({lats: deltaLats, lons: deltaLons});
+ }
+ track.segments = deltaEncodedSegments;
+ }
+ if (waypoints && waypoints.length) {
+ let midLon = 0,
+ midLat = 0;
+ waypoints.forEach((wp) => {
+ midLon += wp.latlng.lng;
+ midLat += wp.latlng.lat;
+ }
+ );
+ midLon = Math.round(midLon * arcUnit / waypoints.length);
+ midLat = Math.round(midLat * arcUnit / waypoints.length);
+ track.waypoints = {
+ midLat, midLon
+ };
+
+ let packedWaypoints = [];
+ for (let waypoint of waypoints) {
+ packedWaypoints.push({
+ name: waypoint.label,
+ lat: Math.round(waypoint.latlng.lat * arcUnit) - midLat,
+ lon: Math.round(waypoint.latlng.lng * arcUnit) - midLon
+ });
+ }
+ track.waypoints.waypoints = packedWaypoints;
+ }
+ const pbf = new Pbf();
+ const versionStr = String.fromCharCode(4 + 64);
+ TrackView.write(trackView, pbf);
+ const s = versionStr + arrayBufferToString(pbf.finish());
+ return encodeUrlSafeBase64(s);
+}
+
+function parseTrackUrlData(s) {
+ s = decodeUrlSafeBase64(s);
+ if (!s) {
+ return [{name: 'Text encoded track', error: ['CORRUPT']}];
+ }
+ return parseNktkOld(s, 0);
+}
+
+function parseNktkOld(s, version) {
+ var name,
+ n,
+ segments = [],
+ segment,
+ segmentsCount,
+ pointsCount,
+ arcUnit = ((1 << 24) - 1) / 360,
+ x, y,
+ error, midX, midY, /*symbol,*/ waypointName,
+ wayPoints = [], color, measureTicksShown, trackHidden = false;
+ s = new PackedStreamReader(s);
+ try {
+ n = s.readNumber();
+ name = s.readString(n);
+ name = utf8.decode(name);
+ segmentsCount = s.readNumber();
+ for (; segmentsCount--;) {
+ segment = [];
+ pointsCount = s.readNumber();
+ x = 0;
+ y = 0;
+ for (; pointsCount--;) {
+ x += s.readNumber();
+ y += s.readNumber();
+ segment.push({lng: x / arcUnit, lat: y / arcUnit});
+ }
+ segments.push(segment);
+ segment = null;
+ }
+ } catch (e) {
+ if (e.message.match('Unexpected end of line while unpacking number')) {
+ error = ['CORRUPT'];
+ if (segment) {
+ segments.push(segment);
+ }
+ } else {
+ throw e;
+ }
+ }
+ try {
+ color = s.readNumber();
+ measureTicksShown = s.readNumber();
+ } catch (e) {
+ if (e.message.match('Unexpected end of line while unpacking number')) {
+ color = 0;
+ measureTicksShown = 0;
+ if (version > 0) {
+ error = ['CORRUPT'];
+ }
+ } else {
+ throw e;
+ }
+ }
+ if (version >= 3) {
+ try {
+ trackHidden = !!(s.readNumber())
+ } catch (e) {
+ if (e.message.match('Unexpected end of line while unpacking number')) {
+ error = ['CORRUPT'];
+ } else {
+ throw e;
+ }
+ }
+ }
+ if (version >= 2) {
+ try {
+ pointsCount = s.readNumber();
+ if (pointsCount) {
+ midX = s.readNumber();
+ midY = s.readNumber();
+ }
+ for (; pointsCount--;) {
+ n = s.readNumber();
+ waypointName = s.readString(n);
+ waypointName = utf8.decode(waypointName);
+
+ // let symbol = s.readNumber();
+ s.readNumber();
+
+ x = s.readNumber() + midX;
+ y = s.readNumber() + midY;
+ wayPoints.push({
+ name: waypointName,
+ lat: y / arcUnit,
+ lng: x / arcUnit,
+
+ }
+ );
+ }
+ } catch (e) {
+ if (e.message.match('Unexpected end of line while unpacking number')) {
+ error = ['CORRUPT'];
+ } else {
+ throw e;
+ }
+ }
+ }
+ var geoData = {
+ name: name || "Text encoded track",
+ tracks: segments,
+ error: error,
+ points: wayPoints,
+ color: color,
+ measureTicksShown: measureTicksShown,
+ trackHidden: trackHidden
+ };
+ return [geoData];
+}
+
+function parseNktkProtobuf(s) {
+ const pbf = new Pbf(stringToArrayBuffer(s));
+ const data = TrackView.read(pbf);
+ const geoData = {
+ name: data.track.name || "Text encoded track",
+ color: data.color,
+ trackHidden: !data.shown,
+ measureTicksShown: data.ticksShown,
+ };
+ if (data.track.segments && data.track.segments.length) {
+ geoData.tracks = data.track.segments.map((segment) => deltaDecodeSegment(segment.lats, segment.lons));
+ }
+ if (data.track.waypoints && data.track.waypoints.waypoints.length) {
+ const waypoints = geoData.points = [];
+ for (let waypoint of data.track.waypoints.waypoints) {
+ waypoints.push({
+ name: waypoint.name,
+ lat: (waypoint.lat + data.track.waypoints.midLat) / arcUnit,
+ lng: (waypoint.lon + data.track.waypoints.midLon) / arcUnit
+ });
+ }
+ }
+ return [geoData];
+
+}
+
+function parseNktk(s) {
+ s = decodeUrlSafeBase64(s);
+ if (!s) {
+ return [{name: 'Text encoded track', error: ['CORRUPT']}];
+ }
+ const reader = new PackedStreamReader(s);
+ let version = reader.readNumber();
+ if (version === 1 || version === 2 || version === 3) {
+ return parseNktkOld(s.substring(reader.position), version);
+ } else if (version == 4) {
+ return parseNktkProtobuf(s.substring(reader.position));
+ } else {
+ return [{name: 'Text encoded track', error: ['CORRUPT']}];
+ }
+}
+
+export {saveNktk, parseTrackUrlData, parseNktk};
+\ No newline at end of file
diff --git a/src/lib/leaflet.control.track-list/lib/nktk.proto b/src/lib/leaflet.control.track-list/lib/nktk.proto
@@ -0,0 +1,33 @@
+syntax = "proto3";
+
+message Segment {
+ repeated sint32 lats = 1;
+ repeated sint32 lons = 2;
+}
+
+message Waypoint {
+ sint32 lat = 1;
+ sint32 lon = 2;
+ string name = 3;
+}
+
+message Waypoints {
+ sint32 midLat = 1;
+ sint32 midLon = 2;
+ repeated Waypoint waypoints = 3;
+}
+
+message Track {
+ string name = 1;
+ repeated Segment segments = 2;
+ Waypoints waypoints = 3;
+}
+
+message TrackView {
+ Track track = 1;
+ int32 color = 2;
+ bool shown = 3;
+ bool ticksShown = 4;
+}
+
+
diff --git a/src/lib/leaflet.control.track-list/lib/nktk_pb.js b/src/lib/leaflet.control.track-list/lib/nktk_pb.js
@@ -0,0 +1,93 @@
+'use strict'; // code generated by pbf v3.0.5
+
+// Segment ========================================
+
+var self = exports;
+
+var Segment = self.Segment = {};
+
+Segment.read = function (pbf, end) {
+ return pbf.readFields(Segment._readField, {lats: [], lons: []}, end);
+};
+Segment._readField = function (tag, obj, pbf) {
+ if (tag === 1) pbf.readPackedSVarint(obj.lats);
+ else if (tag === 2) pbf.readPackedSVarint(obj.lons);
+};
+Segment.write = function (obj, pbf) {
+ if (obj.lats) pbf.writePackedSVarint(1, obj.lats);
+ if (obj.lons) pbf.writePackedSVarint(2, obj.lons);
+};
+
+// Waypoint ========================================
+
+var Waypoint = self.Waypoint = {};
+
+Waypoint.read = function (pbf, end) {
+ return pbf.readFields(Waypoint._readField, {lat: 0, lon: 0, name: ""}, end);
+};
+Waypoint._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.lat = pbf.readSVarint();
+ else if (tag === 2) obj.lon = pbf.readSVarint();
+ else if (tag === 3) obj.name = pbf.readString();
+};
+Waypoint.write = function (obj, pbf) {
+ if (obj.lat) pbf.writeSVarintField(1, obj.lat);
+ if (obj.lon) pbf.writeSVarintField(2, obj.lon);
+ if (obj.name) pbf.writeStringField(3, obj.name);
+};
+
+// Waypoints ========================================
+
+var Waypoints = self.Waypoints = {};
+
+Waypoints.read = function (pbf, end) {
+ return pbf.readFields(Waypoints._readField, {midLat: 0, midLon: 0, waypoints: []}, end);
+};
+Waypoints._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.midLat = pbf.readSVarint();
+ else if (tag === 2) obj.midLon = pbf.readSVarint();
+ else if (tag === 3) obj.waypoints.push(Waypoint.read(pbf, pbf.readVarint() + pbf.pos));
+};
+Waypoints.write = function (obj, pbf) {
+ if (obj.midLat) pbf.writeSVarintField(1, obj.midLat);
+ if (obj.midLon) pbf.writeSVarintField(2, obj.midLon);
+ if (obj.waypoints) for (var i = 0; i < obj.waypoints.length; i++) pbf.writeMessage(3, Waypoint.write, obj.waypoints[i]);
+};
+
+// Track ========================================
+
+var Track = self.Track = {};
+
+Track.read = function (pbf, end) {
+ return pbf.readFields(Track._readField, {name: "", segments: [], waypoints: null}, end);
+};
+Track._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.name = pbf.readString();
+ else if (tag === 2) obj.segments.push(Segment.read(pbf, pbf.readVarint() + pbf.pos));
+ else if (tag === 3) obj.waypoints = Waypoints.read(pbf, pbf.readVarint() + pbf.pos);
+};
+Track.write = function (obj, pbf) {
+ if (obj.name) pbf.writeStringField(1, obj.name);
+ if (obj.segments) for (var i = 0; i < obj.segments.length; i++) pbf.writeMessage(2, Segment.write, obj.segments[i]);
+ if (obj.waypoints) pbf.writeMessage(3, Waypoints.write, obj.waypoints);
+};
+
+// TrackView ========================================
+
+var TrackView = self.TrackView = {};
+
+TrackView.read = function (pbf, end) {
+ return pbf.readFields(TrackView._readField, {track: null, color: 0, shown: false, ticksShown: false}, end);
+};
+TrackView._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.track = Track.read(pbf, pbf.readVarint() + pbf.pos);
+ else if (tag === 2) obj.color = pbf.readVarint(true);
+ else if (tag === 3) obj.shown = pbf.readBoolean();
+ else if (tag === 4) obj.ticksShown = pbf.readBoolean();
+};
+TrackView.write = function (obj, pbf) {
+ if (obj.track) pbf.writeMessage(1, Track.write, obj.track);
+ if (obj.color) pbf.writeVarintField(2, obj.color);
+ if (obj.shown) pbf.writeBooleanField(3, obj.shown);
+ if (obj.ticksShown) pbf.writeBooleanField(4, obj.ticksShown);
+};