nakarte

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

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:
Msrc/lib/leaflet.control.track-list/lib/geo_file_exporters.js | 4++--
Msrc/lib/leaflet.control.track-list/lib/geo_file_formats.js | 200++-----------------------------------------------------------------------------
Asrc/lib/leaflet.control.track-list/lib/nktk.js | 329+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.control.track-list/lib/nktk.proto | 33+++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.control.track-list/lib/nktk_pb.js | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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); +};