jnx-maker.js (4199B)
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( 59 srcLayer, layerName, maxZoomLevel, latLngBounds, correctZoom, progress 60 ) { 61 const jnxProductId = L.stamp(srcLayer); 62 const jnxZOrder = Math.min(jnxProductId, 100); 63 // scale multiplier for GPSMAP 67 64 const scaleMultiplier = correctZoom ? 3.5 : 1; 65 const writer = new JnxWriter(layerName, jnxProductId, jnxZOrder, scaleMultiplier); 66 const xhrQueue = new XHRQueue(); 67 let doStop = false; 68 let error; 69 const minZoomLevel = minZoom(maxZoomLevel); 70 let progressWeight = 1; 71 for (let zoom = maxZoomLevel; zoom >= minZoomLevel; zoom--) { 72 let pixelBounds = L.bounds( 73 L.CRS.EPSG3857.latLngToPoint(latLngBounds.getNorthWest(), zoom).round(), 74 L.CRS.EPSG3857.latLngToPoint(latLngBounds.getSouthEast(), zoom).round() 75 ); 76 77 let promises = []; 78 let layer = srcLayer.cloneForPrint({xhrQueue}); 79 let tempMap = getTempMap(zoom, layer._rasterizeNeedsFullSizeMap, pixelBounds); 80 tempMap.addLayer(layer); 81 let {iterateTilePromises, count: tilesCount} = await layer.getTilesInfo({ 82 xhrOptions: defaultXHROptions, 83 pixelBounds, 84 rawData: true 85 } 86 ); 87 for (let tilePromiseRec of iterateTilePromises()) { 88 promises.push(tilePromiseRec); 89 } 90 for (let {tilePromise} of promises) { 91 let imageRec; 92 try { 93 imageRec = await tilePromise; 94 } catch (e) { 95 error = e; 96 doStop = true; 97 break; 98 } 99 let xhr = imageRec.image; 100 101 if (xhr === null || xhr.status !== 200 || !xhr.response || !xhr.response.byteLength) { 102 continue; 103 } 104 let image = await ensureImageJpg(xhr.response); 105 if (!image) { 106 error = new Error('Tile image invalid'); 107 doStop = true; 108 break; 109 } 110 writer.addTile(image, zoom, imageRec.latLngBounds); 111 progress(progressWeight / tilesCount, 4 / 3); 112 } 113 disposeMap(tempMap); 114 if (doStop) { 115 promises.forEach((promiseRec) => promiseRec.abortLoading()); 116 throw error; 117 } 118 progressWeight /= 4; 119 } 120 121 return writer.getJnx(); 122 } 123 124 export {makeJnxFromLayer, minZoom};
