jnx-maker.js (4074B)
1 import L from 'leaflet'; 2 import {JnxWriter} from './jnx-encoder'; 3 import {getTempMap, disposeMap} from '~/lib/leaflet.layer.rasterize'; 4 import {XHRQueue} from '~/lib/xhr-promise'; 5 import {arrayBufferToString, stringToArrayBuffer} from '~/lib/binary-strings'; 6 7 const defaultXHROptions = { 8 responseType: 'arraybuffer', 9 timeout: 20000, 10 isResponseSuccess: (xhr) => xhr.status === 200 || xhr.status === 404 11 }; 12 13 function minZoom(maxZoom) { 14 return Math.max(maxZoom - 4, 0); 15 } 16 17 function imageFromarrayBuffer(arr) { 18 const dataUrl = 'data:image/png;base64,' + btoa(arrayBufferToString(arr)); 19 const image = new Image(); 20 return new Promise(function(resolve, reject) { 21 image.onload = () => resolve(image); 22 image.onerror = () => reject(new Error('Tile image corrupt')); 23 image.src = dataUrl; 24 } 25 ); 26 } 27 28 async function convertToJpeg(image) { 29 try { 30 image = await imageFromarrayBuffer(image); 31 } catch (e) { 32 return null; 33 } 34 const canvas = document.createElement("canvas"); 35 canvas.width = image.width; 36 canvas.height = image.height; 37 const ctx = canvas.getContext("2d"); 38 ctx.drawImage(image, 0, 0); 39 const dataURL = canvas.toDataURL("image/jpeg"); 40 const s = atob(dataURL.replace(/^data:image\/jpeg;base64,/u, "")); 41 return stringToArrayBuffer(s); 42 } 43 44 function ensureImageJpg(image) { 45 if (!image) { 46 return null; 47 } 48 if (arrayBufferToString(image.slice(0, 4)) === '\x89PNG' && 49 arrayBufferToString(image.slice(-8)) === 'IEND\xae\x42\x60\x82') { 50 return convertToJpeg(image); 51 } else if (arrayBufferToString(image.slice(0, 2)) === '\xff\xd8' && 52 arrayBufferToString(image.slice(-2)) === '\xff\xd9') { 53 return Promise.resolve(image); 54 } 55 return null; 56 } 57 58 async function makeJnxFromLayer(srcLayer, layerName, maxZoomLevel, latLngBounds, progress) { 59 const jnxProductId = L.stamp(srcLayer); 60 const jnxZOrder = Math.min(jnxProductId, 100); 61 const writer = new JnxWriter(layerName, jnxProductId, jnxZOrder); 62 const xhrQueue = new XHRQueue(); 63 let doStop = false; 64 let error; 65 const minZoomLevel = minZoom(maxZoomLevel); 66 let progressWeight = 1; 67 for (let zoom = maxZoomLevel; zoom >= minZoomLevel; zoom--) { 68 let pixelBounds = L.bounds( 69 L.CRS.EPSG3857.latLngToPoint(latLngBounds.getNorthWest(), zoom).round(), 70 L.CRS.EPSG3857.latLngToPoint(latLngBounds.getSouthEast(), zoom).round() 71 ); 72 73 let promises = []; 74 let layer = srcLayer.cloneForPrint({xhrQueue}); 75 let tempMap = getTempMap(zoom, layer._rasterizeNeedsFullSizeMap, pixelBounds); 76 tempMap.addLayer(layer); 77 let {iterateTilePromises, count: tilesCount} = await layer.getTilesInfo({ 78 xhrOptions: defaultXHROptions, 79 pixelBounds, 80 rawData: true 81 } 82 ); 83 for (let tilePromiseRec of iterateTilePromises()) { 84 promises.push(tilePromiseRec); 85 } 86 for (let {tilePromise} of promises) { 87 let imageRec; 88 try { 89 imageRec = await tilePromise; 90 } catch (e) { 91 error = e; 92 doStop = true; 93 break; 94 } 95 let xhr = imageRec.image; 96 97 if (xhr === null || xhr.status !== 200 || !xhr.response || !xhr.response.byteLength) { 98 continue; 99 } 100 let image = await ensureImageJpg(xhr.response); 101 if (!image) { 102 error = new Error('Tile image invalid'); 103 doStop = true; 104 break; 105 } 106 writer.addTile(image, zoom, imageRec.latLngBounds); 107 progress(progressWeight / tilesCount, 4 / 3); 108 } 109 disposeMap(tempMap); 110 if (doStop) { 111 promises.forEach((promiseRec) => promiseRec.abortLoading()); 112 throw error; 113 } 114 progressWeight /= 4; 115 } 116 117 return writer.getJnx(); 118 } 119 120 export {makeJnxFromLayer, minZoom};