nakarte

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

wm-utils.js (8525B)


      1 import L from 'leaflet';
      2 
      3 // (1233,130,5) -> "032203"
      4 function getTileId({x, y, z}) {
      5     let id = [];
      6     y = (1 << z) - y - 1;
      7     z += 1;
      8     while (z) {
      9         id.push((x & 1) + (y & 1) * 2);
     10         x >>= 1;
     11         y >>= 1;
     12         z -= 1;
     13     }
     14     return id.reverse().join('');
     15 }
     16 
     17 function makeTilePath(coords) {
     18     const
     19         tileId = getTileId(coords),
     20         urlPath = tileId.replace(/(\d{3})(?!$)/gu, '$1/'); // "033331022" -> "033/331/022"
     21     return `z1/itiles/${urlPath}.xy?342342`;
     22 }
     23 
     24 function tileIdToCoords(tileId) {
     25     const z = tileId.length - 1;
     26     let x = 0,
     27         y = 0;
     28     for (let i = 0; i < tileId.length; i++) {
     29         let c = parseInt(tileId[i], 10);
     30         x <<= 1;
     31         y <<= 1;
     32         x += c & 1;
     33         y += c >> 1;
     34     }
     35     y = (1 << z) - y - 1;
     36     return {x, y, z};
     37 }
     38 
     39 function getWikimapiaTileCoords(coords, viewTileSize) {
     40     let z = coords.z - 2;
     41     if (z < 0) {
     42         z = 0;
     43     }
     44     if (z > 15) {
     45         z = 15;
     46     }
     47     const q = 2 ** (z - coords.z + Math.log2(viewTileSize / 256));
     48     let x = Math.floor(coords.x * q),
     49         y = Math.floor(coords.y * q);
     50     return {x, y, z};
     51 }
     52 
     53 function decodeTitles(s) {
     54     const titles = {};
     55     for (let title of s.split('\x1f')) {
     56         if (title.length > 2) {
     57             let langCode = title.charCodeAt(0) - 32;
     58             titles[langCode.toString()] = title.substring(1);
     59         }
     60     }
     61     return titles;
     62 }
     63 
     64 function tileCoordsEqual(tile1, tile2) {
     65     return (
     66         tile1.x === tile2.x &&
     67         tile1.y === tile2.y &&
     68         tile1.z === tile2.z
     69     );
     70 }
     71 
     72 function chooseTitle(titles) {
     73     var popularLanguages = ['1', '0', '3', '2', '5', '4', '9', '28', '17', '27'];
     74     for (let langCode of popularLanguages) {
     75         if (langCode in titles) {
     76             return titles[langCode];
     77         }
     78     }
     79     for (let langCode of Object.keys(titles)) {
     80         return titles[langCode];
     81     }
     82     return "";
     83 }
     84 
     85 function decodePolygon(s) {
     86     var i = 0,
     87         coords = [],
     88         lat = 0,
     89         lng = 0;
     90     while (i < s.length) {
     91         var p,
     92             l = 0,
     93             c = 0;
     94         do {
     95             p = s.charCodeAt(i++) - 63; // eslint-disable-line no-plusplus
     96             c |= (p & 31) << l;
     97             l += 5;
     98         } while (p >= 32);
     99         lng += c & 1 ? ~(c >> 1) : c >> 1;
    100         l = 0;
    101         c = 0;
    102         do {
    103             p = s.charCodeAt(i++) - 63; // eslint-disable-line no-plusplus
    104             c |= (p & 31) << l;
    105             l += 5;
    106         } while (p >= 32);
    107         lat += c & 1 ? ~(c >> 1) : c >> 1;
    108         coords.push([lat / 1e6, lng / 1e6]);
    109     }
    110     return coords;
    111 }
    112 
    113 function makeCoordsLocal(line, tileCoords, projectObj) {
    114     const {x: tileX, y: tileY, z: tileZ} = tileCoords,
    115         x0 = tileX * 1024,
    116         y0 = tileY * 1024,
    117         localCoords = [];
    118     let latlon, p;
    119     for (let i = 0; i < line.length; i++) {
    120         latlon = line[i];
    121         p = projectObj.project(latlon, tileZ + 2);
    122         p.x -= x0;
    123         p.y -= y0;
    124         localCoords.push(p);
    125     }
    126     return localCoords;
    127 }
    128 
    129 function asap() {
    130     return new Promise((resolve) => setTimeout(resolve, 0));
    131 }
    132 
    133 function simplifyPolygon(latlngs, tileCoords, tileHasChildren, projectObj) {
    134     const
    135         x = tileCoords.x * 1024,
    136         y = tileCoords.y * 1024,
    137         p0 = projectObj.unproject([x, y], tileCoords.z + 2),
    138         p1 = projectObj.unproject([x + 1, y + 1], tileCoords.z + 2);
    139     let pixelDegSize = p0.lat - p1.lat;
    140     if (!tileHasChildren && tileCoords.z < 15) {
    141         pixelDegSize /= (1 << (15 - tileCoords.z));
    142     }
    143     let points = [];
    144     for (let i = 0; i < latlngs.length; i++) {
    145         let ll = latlngs[i];
    146         points.push({x: ll[0], y: ll[1]});
    147     }
    148     points = L.LineUtil.simplify(points, pixelDegSize * 2);
    149     latlngs = [];
    150     for (let i = 0; i < points.length; i++) {
    151         let p = points[i];
    152         latlngs.push([p.x, p.y]);
    153     }
    154     return latlngs;
    155 }
    156 
    157 async function parseTile(s, projectObj, requestedCoords) {
    158     const tile = {};
    159     const places = tile.places = [];
    160     const lines = s.split('\n');
    161     if (lines.length < 1) {
    162         throw new Error('No data in tile');
    163     }
    164     const fields = lines[0].split('|');
    165     const tileId = fields[0];
    166     if (!tileId || !tileId.match(/^[0-3]+$/u)) {
    167         throw new Error('Invalid tile header');
    168     }
    169     tile.tileId = tileId;
    170     tile.coords = tileIdToCoords(tileId);
    171     tile.hasChildren = tileCoordsEqual(requestedCoords, tile.coords);
    172 
    173     // FIXME: ignore some errors
    174     let prevTime = Date.now();
    175     for (let line of lines.slice(2)) {
    176         let curTime = Date.now();
    177         if (curTime - prevTime > 20) {
    178             await asap();
    179             prevTime = Date.now();
    180         }
    181         const place = {};
    182         const fields = line.split('|');
    183         if (fields.length < 6) {
    184             continue;
    185         }
    186         let placeId = fields[0];
    187         if (!placeId.match(/^\d+$/u)) {
    188             // throw new Error('Invalid place id');
    189             continue;
    190         }
    191         place.id = parseInt(placeId, 10);
    192         place.title = chooseTitle(decodeTitles(fields[5]));
    193         if (fields[6] !== '1') {
    194             throw new Error(`Unknown wikimapia polygon encoding type: "${fields[6]}"`);
    195         }
    196 
    197         let bounds = fields[2].match(/^([-\d]+),([-\d]+),([-\d]+),([-\d]+)$/u);
    198         if (!bounds) {
    199             throw new Error('Invalid place bounds');
    200         }
    201         place.boundsWESN = bounds.slice(1).map((x) => parseInt(x, 10) / 1e7);
    202 
    203         let coords = fields.slice(7).join('|');
    204 
    205         coords = decodePolygon(coords);
    206         if (coords.length < 3) {
    207             throw new Error(`Polygon has ${coords.length} points`);
    208         }
    209         let polygon = simplifyPolygon(coords, tile.coords, tile.hasChildren, projectObj);
    210         place.polygon = polygon;
    211         place.localPolygon = makeCoordsLocal(polygon, tile.coords, projectObj);
    212         places.push(place);
    213     }
    214     return tile;
    215 }
    216 
    217 // быстрое проектирование полигонов
    218 // TODO: каждые 50-100 мс отдавать управление
    219 // projectPlacesForZoom: function(tile, viewZoom) {
    220 //     if (viewZoom < tile.coords.z) {
    221 //         throw new Error('viewZoom < tile.zoom');
    222 //     }
    223 //     if (tile.places && tile.places[0] && tile.places[0].projectedPolygons &&
    224 //         tile.places[0].projectedPolygons[viewZoom]) {
    225 //         return;
    226 //     }
    227 //     const virtualTileSize = 256 * (2 ** (viewZoom - tile.coords.z));
    228 //     const p1 = L.point([tile.coords.x * virtualTileSize, tile.coords.y * virtualTileSize]),
    229 //         p2 = L.point([(tile.coords.x + 1) * virtualTileSize, (tile.coords.y + 1) * virtualTileSize]),
    230 //         latlng1 = this._map.unproject(p1, viewZoom),
    231 //         latlng2 = this._map.unproject(p2, viewZoom),
    232 //         lat0 = latlng1.lat,
    233 //         lng0 = latlng1.lng;
    234 //     const qx = (p2.x - p1.x) / (latlng2.lng - latlng1.lng),
    235 //         qy = (p2.y - p1.y) / (latlng2.lat - latlng1.lat);
    236 //
    237 //     const offsets = [],
    238 //         offsetsStep = virtualTileSize / 64;
    239 //     const y0 = p1.y;
    240 //     for (let y = -virtualTileSize; y <= 2 * virtualTileSize; y += offsetsStep) {
    241 //         let lat = y / qy + lat0;
    242 //         let offset = this._map.project([lat, lng0], viewZoom).y - y0 - y;
    243 //         offsets.push(offset);
    244 //     }
    245 //
    246 //     let ll, offset, offsetPos, offsetIndex, offsetIndexDelta, x, y;
    247 //     const x0 = p1.x;
    248 //     for (let place of tile.places) {
    249 //         let polygon = place.polygon;
    250 //         if (polygon.length < 3) {
    251 //             continue;
    252 //         }
    253 //         let projectedPolygon = [];
    254 //         for (let i = 0; i < polygon.length; i++) {
    255 //             ll = polygon[i];
    256 //             x = (ll[1] - lng0) * qx;
    257 //             y = (ll[0] - lat0) * qy;
    258 //             offsetPos = (y + virtualTileSize) / offsetsStep;
    259 //             offsetIndex = Math.floor(offsetPos);
    260 //             offsetIndexDelta = offsetPos - offsetIndex;
    261 //             offset = offsets[offsetIndex] * (1 - offsetIndexDelta) + offsets[offsetIndex + 1] * offsetIndexDelta;
    262 //             projectedPolygon.push([x + x0, y + offset + y0]);
    263 //         }
    264 //         projectedPolygon = projectedPolygon.map(L.point);
    265 //         projectedPolygon = L.LineUtil.simplify(projectedPolygon, 1.5);
    266 //         if (!place.projectedPolygons) {
    267 //             place.projectedPolygons = [];
    268 //         }
    269 //         place.projectedPolygons[viewZoom] = projectedPolygon;
    270 //     }
    271 // },
    272 
    273 export {getTileId, getWikimapiaTileCoords, parseTile, makeTilePath};