nakarte

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

nktk.js (9491B)


      1 import Pbf from 'pbf';
      2 import {TrackView} from './nktk_pb';
      3 import {arrayBufferToString, stringToArrayBuffer} from '~/lib/binary-strings';
      4 import utf8 from 'utf8';
      5 import * as urlSafeBase64 from './urlSafeBase64';
      6 
      7 const arcUnit = ((1 << 24) - 1) / 360;
      8 
      9 function PackedStreamReader(s) {
     10     this._string = s;
     11     this.position = 0;
     12 }
     13 
     14 function unpackNumber(s, position) {
     15     var x,
     16         n = 0;
     17     x = s.charCodeAt(position);
     18     if (isNaN(x)) {
     19         throw new Error('Unexpected end of line while unpacking number');
     20     }
     21     if (x < 128) {
     22         n = x - 64;
     23         return [n, 1];
     24     }
     25     n = x & 0x7f;
     26     x = s.charCodeAt(position + 1);
     27     if (isNaN(x)) {
     28         throw new Error('Unexpected end of line while unpacking number');
     29     }
     30     if (x < 128) {
     31         n |= x << 7;
     32         n -= 8192;
     33         return [n, 2];
     34     }
     35     n |= (x & 0x7f) << 7;
     36     x = s.charCodeAt(position + 2);
     37     if (isNaN(x)) {
     38         throw new Error('Unexpected end of line while unpacking number');
     39     }
     40     if (x < 128) {
     41         n |= x << 14;
     42         n -= 1048576;
     43         return [n, 3];
     44     }
     45     n |= (x & 0x7f) << 14;
     46     x = s.charCodeAt(position + 3);
     47     if (isNaN(x)) {
     48         throw new Error('Unexpected end of line while unpacking number');
     49     }
     50     n |= x << 21;
     51     n -= 268435456;
     52     return [n, 4];
     53 }
     54 
     55 PackedStreamReader.prototype.readNumber = function() {
     56     var n = unpackNumber(this._string, this.position);
     57     this.position += n[1];
     58     return n[0];
     59 };
     60 
     61 PackedStreamReader.prototype.readString = function(size) {
     62     var s = this._string.slice(this.position, this.position + size);
     63     this.position += size;
     64     return s;
     65 };
     66 
     67 function deltaEncodeSegment(points) {
     68     let deltaLats = [],
     69         deltaLons = [];
     70     let lastLon = 0,
     71         lastLat = 0,
     72         lon, lat;
     73     for (let i = 0, len = points.length; i < len; i++) {
     74         let p = points[i];
     75         lon = Math.round(p.lng * arcUnit);
     76         lat = Math.round(p.lat * arcUnit);
     77         let deltaLon = lon - lastLon;
     78         let deltaLat = lat - lastLat;
     79         deltaLats.push(deltaLat);
     80         deltaLons.push(deltaLon);
     81         lastLon = lon;
     82         lastLat = lat;
     83     }
     84     return {deltaLats, deltaLons};
     85 }
     86 
     87 function deltaDecodeSegment(deltaLats, deltaLons) {
     88     let encodedLat = 0,
     89         encodedLon = 0;
     90     const points = [];
     91     for (let i = 0; i < deltaLats.length; i++) {
     92         encodedLat += deltaLats[i];
     93         encodedLon += deltaLons[i];
     94         points.push({lat: encodedLat / arcUnit, lng: encodedLon / arcUnit});
     95     }
     96     return points;
     97 }
     98 
     99 function saveNktk(segments, name, color, measureTicksShown, waypoints, trackHidden) {
    100     const trackView = {
    101         view: {
    102             color,
    103             shown: !trackHidden,
    104             ticksShown: measureTicksShown,
    105         }
    106     };
    107     const track = trackView.track = {name};
    108     if (segments && segments.length) {
    109         let deltaEncodedSegments = [];
    110         for (let segment of segments) {
    111             let {deltaLats, deltaLons} = deltaEncodeSegment(segment);
    112             deltaEncodedSegments.push({lats: deltaLats, lons: deltaLons});
    113         }
    114         track.segments = deltaEncodedSegments;
    115     }
    116     if (waypoints && waypoints.length) {
    117         let midLon = 0,
    118             midLat = 0;
    119         waypoints.forEach((wp) => {
    120                 midLon += wp.latlng.lng;
    121                 midLat += wp.latlng.lat;
    122             }
    123         );
    124         midLon = Math.round(midLon * arcUnit / waypoints.length);
    125         midLat = Math.round(midLat * arcUnit / waypoints.length);
    126         track.waypoints = {
    127             midLat, midLon
    128         };
    129 
    130         let packedWaypoints = [];
    131         for (let waypoint of waypoints) {
    132             packedWaypoints.push({
    133                 name: waypoint.label,
    134                 lat: Math.round(waypoint.latlng.lat * arcUnit) - midLat,
    135                 lon: Math.round(waypoint.latlng.lng * arcUnit) - midLon
    136             });
    137         }
    138         track.waypoints.waypoints = packedWaypoints;
    139     }
    140     const pbf = new Pbf();
    141     const versionStr = String.fromCharCode(4 + 64);
    142     TrackView.write(trackView, pbf);
    143     const s = versionStr + arrayBufferToString(pbf.finish());
    144     return urlSafeBase64.encode(s);
    145 }
    146 
    147 function parseNktkOld(s, version) {
    148     var name,
    149         n,
    150         segments = [],
    151         segment,
    152         segmentsCount,
    153         pointsCount,
    154         arcUnit = ((1 << 24) - 1) / 360,
    155         x, y,
    156         error, midX, midY, /* symbol,*/ waypointName,
    157         wayPoints = [],
    158         color, measureTicksShown,
    159         trackHidden = false;
    160     s = new PackedStreamReader(s);
    161     try {
    162         n = s.readNumber();
    163         name = s.readString(n);
    164         name = utf8.decode(name);
    165         segmentsCount = s.readNumber();
    166         for (let i = 0; i < segmentsCount; i++) {
    167             segment = [];
    168             pointsCount = s.readNumber();
    169             x = 0;
    170             y = 0;
    171             for (let j = 0; j < pointsCount; j++) {
    172                 x += s.readNumber();
    173                 y += s.readNumber();
    174                 segment.push({lng: x / arcUnit, lat: y / arcUnit});
    175             }
    176             segments.push(segment);
    177             segment = null;
    178         }
    179     } catch (e) {
    180         if (e.message.match('Unexpected end of line while unpacking number')) {
    181             error = ['CORRUPT'];
    182             if (segment) {
    183                 segments.push(segment);
    184             }
    185         } else {
    186             throw e;
    187         }
    188     }
    189     try {
    190         color = s.readNumber();
    191         measureTicksShown = s.readNumber();
    192     } catch (e) {
    193         if (e.message.match('Unexpected end of line while unpacking number')) {
    194             color = 0;
    195             measureTicksShown = 0;
    196             if (version > 0) {
    197                 error = ['CORRUPT'];
    198             }
    199         } else {
    200             throw e;
    201         }
    202     }
    203     if (version >= 3) {
    204         try {
    205             trackHidden = Boolean(s.readNumber());
    206         } catch (e) {
    207             if (e.message.match('Unexpected end of line while unpacking number')) {
    208                 error = ['CORRUPT'];
    209             } else {
    210                 throw e;
    211             }
    212         }
    213     }
    214     if (version >= 2) {
    215         try {
    216             pointsCount = s.readNumber();
    217             if (pointsCount) {
    218                 midX = s.readNumber();
    219                 midY = s.readNumber();
    220             }
    221             for (let i = 0; i < pointsCount; i++) {
    222                 n = s.readNumber();
    223                 waypointName = s.readString(n);
    224                 waypointName = utf8.decode(waypointName);
    225 
    226                 // let symbol = s.readNumber();
    227                 s.readNumber();
    228 
    229                 x = s.readNumber() + midX;
    230                 y = s.readNumber() + midY;
    231                 wayPoints.push({
    232                         name: waypointName,
    233                         lat: y / arcUnit,
    234                         lng: x / arcUnit,
    235 
    236                     }
    237                 );
    238             }
    239         } catch (e) {
    240             if (e.message.match('Unexpected end of line while unpacking number')) {
    241                 error = ['CORRUPT'];
    242             } else {
    243                 throw e;
    244             }
    245         }
    246     }
    247     var geoData = {
    248         name: name || "Text encoded track",
    249         tracks: segments,
    250         error: error,
    251         points: wayPoints,
    252         color: color,
    253         measureTicksShown: measureTicksShown,
    254         trackHidden: trackHidden
    255     };
    256     return [geoData];
    257 }
    258 
    259 function parseTrackUrlData(s) {
    260     s = urlSafeBase64.decode(s);
    261     if (!s) {
    262         return [{name: 'Text encoded track', error: ['CORRUPT']}];
    263     }
    264     return parseNktkOld(s, 0);
    265 }
    266 
    267 function parseNktkProtobuf(s) {
    268     const pbf = new Pbf(stringToArrayBuffer(s));
    269     let trackView;
    270     try {
    271         trackView = TrackView.read(pbf);
    272     } catch (e) {
    273         return [{name: 'Text encoded track', error: ['CORRUPT']}];
    274     }
    275     const geoData = {
    276         name: trackView.track.name || "Text encoded track",
    277         color: trackView.view.color,
    278         trackHidden: !trackView.view.shown,
    279         measureTicksShown: trackView.view.ticksShown,
    280     };
    281     const segments = trackView.track.segments;
    282     if (segments && segments.length) {
    283         geoData.tracks = segments.map((segment) => deltaDecodeSegment(segment.lats, segment.lons));
    284     }
    285     if (trackView.track.waypoints && trackView.track.waypoints.waypoints.length) {
    286         const waypoints = geoData.points = [];
    287         for (let waypoint of trackView.track.waypoints.waypoints) {
    288             waypoints.push({
    289                 name: waypoint.name,
    290                 lat: (waypoint.lat + trackView.track.waypoints.midLat) / arcUnit,
    291                 lng: (waypoint.lon + trackView.track.waypoints.midLon) / arcUnit
    292             });
    293         }
    294     }
    295     return [geoData];
    296 }
    297 
    298 function parseNktkFragment(s) {
    299     s = urlSafeBase64.decode(s);
    300     if (!s) {
    301         return [{name: 'Text encoded track', error: ['CORRUPT']}];
    302     }
    303     const reader = new PackedStreamReader(s);
    304     let version = reader.readNumber();
    305     if (version === 1 || version === 2 || version === 3) {
    306         return parseNktkOld(s.substring(reader.position), version);
    307     } else if (version === 4) {
    308         return parseNktkProtobuf(s.substring(reader.position));
    309     }
    310     return [{name: 'Text encoded track', error: ['CORRUPT']}];
    311 }
    312 
    313 function parseNktkSequence(s) {
    314     return s.split('/')
    315         .map(parseNktkFragment)
    316         .reduce((acc, cur) => {
    317             acc.push(...cur);
    318             return acc;
    319         });
    320 }
    321 
    322 export {saveNktk, parseTrackUrlData, parseNktkSequence, parseNktkFragment};