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};