diff --git a/common/webapp/src/js/BlueMapApp.js b/common/webapp/src/js/BlueMapApp.js index 08a27b8b1..ba3fbc685 100644 --- a/common/webapp/src/js/BlueMapApp.js +++ b/common/webapp/src/js/BlueMapApp.js @@ -76,6 +76,7 @@ export class BlueMapApp { * maps: string[], * scripts: string[], * styles: string[] + * clientDecompression: boolean, * }} **/ this.settings = null; @@ -308,7 +309,7 @@ export class BlueMapApp { // create maps if (settings.maps !== undefined){ let loadingPromises = settings.maps.map(mapId => { - let map = new BlueMapMap(mapId, settings.mapDataRoot + "/" + mapId, settings.liveDataRoot + "/" + mapId, this.loadBlocker, this.mapViewer.events); + let map = new BlueMapMap(mapId, settings.mapDataRoot + "/" + mapId, settings.liveDataRoot + "/" + mapId, this.loadBlocker, this.mapViewer.events, this.settings.clientDecompression); maps.push(map); return map.loadSettings(this.mapViewer.revalidatedUrls) @@ -355,6 +356,7 @@ export class BlueMapApp { ], scripts: [], styles: [], + clientDecompression: false, ...loaded }; } diff --git a/common/webapp/src/js/map/Map.js b/common/webapp/src/js/map/Map.js index bc9e3aa94..e96aeeec0 100644 --- a/common/webapp/src/js/map/Map.js +++ b/common/webapp/src/js/map/Map.js @@ -49,8 +49,9 @@ export class Map { * @param liveDataRoot {string} * @param loadBlocker {function: Promise} * @param events {EventTarget} + * @param clientDecompression {boolean} */ - constructor(id, mapDataRoot, liveDataRoot, loadBlocker, events = null) { + constructor(id, mapDataRoot, liveDataRoot, loadBlocker, events = null, clientDecompression) { Object.defineProperty( this, 'isMap', { value: true } ); this.loadBlocker = loadBlocker; @@ -62,7 +63,7 @@ export class Map { mapDataRoot: mapDataRoot, liveDataRoot: liveDataRoot, settingsUrl: mapDataRoot + "/settings.json", - texturesUrl: mapDataRoot + "/textures.json", + texturesUrl: mapDataRoot + (clientDecompression ? "/textures.json.gz" : "/textures.json"), name: id, startPos: {x: 0, z: 0}, skyColor: new Color(), @@ -82,7 +83,8 @@ export class Map { perspectiveView: false, flatView: false, freeFlightView: false, - views: ["perspective", "flat", "free"] + views: ["perspective", "flat", "free"], + clientDecompression: clientDecompression }); this.raycaster = new Raycaster(); @@ -129,28 +131,29 @@ export class Map { this.hiresMaterial = this.createHiresMaterial(hiresVertexShader, hiresFragmentShader, uniforms, textures); this.hiresTileManager = new TileManager(new TileLoader( - `${this.data.mapDataRoot}/tiles/0/`, - this.hiresMaterial, - this.data.hires, - this.loadBlocker, - revalidatedUrls - ), this.onTileLoad("hires"), this.onTileUnload("hires"), this.events); - this.hiresTileManager.scene.matrixWorldAutoUpdate = false; - - this.lowresTileManager = []; - for (let i = 0; i < this.data.lowres.lodCount; i++) { - this.lowresTileManager[i] = new TileManager(new LowresTileLoader( - `${this.data.mapDataRoot}/tiles/`, - this.data.lowres, - i + 1, - lowresVertexShader, - lowresFragmentShader, - uniforms, - async () => {}, - revalidatedUrls - ), this.onTileLoad("lowres"), this.onTileUnload("lowres"), this.events); - this.lowresTileManager[i].scene.matrixWorldAutoUpdate = false; - } + `${this.data.mapDataRoot}/tiles/0/`, + this.hiresMaterial, + this.data.hires, + this.loadBlocker, + revalidatedUrls, + this.data.clientDecompression + ), this.onTileLoad("hires"), this.onTileUnload("hires"), this.events); + this.hiresTileManager.scene.matrixWorldAutoUpdate = false; + + this.lowresTileManager = []; + for (let i = 0; i < this.data.lowres.lodCount; i++) { + this.lowresTileManager[i] = new TileManager(new LowresTileLoader( + `${this.data.mapDataRoot}/tiles/`, + this.data.lowres, + i + 1, + lowresVertexShader, + lowresFragmentShader, + uniforms, + async () => {}, + revalidatedUrls + ), this.onTileLoad("lowres"), this.onTileUnload("lowres"), this.events); + this.lowresTileManager[i].scene.matrixWorldAutoUpdate = false; + } alert(this.events, `Map '${this.data.id}' is loaded.`, "fine"); }); @@ -285,6 +288,7 @@ export class Map { let loader = new RevalidatingFileLoader(); loader.setRevalidatedUrls(revalidatedUrls); loader.setResponseType("json"); + loader.setClientDecompression(this.data.clientDecompression); loader.load(this.data.texturesUrl, resolve, () => {}, diff --git a/common/webapp/src/js/map/TileLoader.js b/common/webapp/src/js/map/TileLoader.js index 9aadafada..7fedf0047 100644 --- a/common/webapp/src/js/map/TileLoader.js +++ b/common/webapp/src/js/map/TileLoader.js @@ -39,8 +39,9 @@ export class TileLoader { * }} * @param loadBlocker {function: Promise} * @param revalidatedUrls {Set | undefined} + * @param clientDecompression {boolean} */ - constructor(tilePath, material, tileSettings, loadBlocker = () => Promise.resolve(), revalidatedUrls) { + constructor(tilePath, material, tileSettings, loadBlocker = () => Promise.resolve(), revalidatedUrls, clientDecompression) { Object.defineProperty( this, 'isTileLoader', { value: true } ); this.tilePath = tilePath; @@ -54,12 +55,17 @@ export class TileLoader { this.fileLoader = new RevalidatingFileLoader(); this.fileLoader.setResponseType('arraybuffer'); this.fileLoader.setRevalidatedUrls(this.revalidatedUrls); + this.fileLoader.setClientDecompression(clientDecompression); + this.clientDecompression = clientDecompression; this.bufferGeometryLoader = new PRBMLoader(); } load = (tileX, tileZ, cancelCheck = () => false) => { let tileUrl = this.tilePath + pathFromCoords(tileX, tileZ) + '.prbm'; + if (this.clientDecompression) { + tileUrl += '.gz'; + } return new Promise((resolve, reject) => { this.fileLoader.setRevalidatedUrls(this.revalidatedUrls); diff --git a/common/webapp/src/js/util/RevalidatingFileLoader.js b/common/webapp/src/js/util/RevalidatingFileLoader.js index ae64163bb..46048d646 100644 --- a/common/webapp/src/js/util/RevalidatingFileLoader.js +++ b/common/webapp/src/js/util/RevalidatingFileLoader.js @@ -55,6 +55,14 @@ export class RevalidatingFileLoader extends Loader { */ this.responseType = ""; + /** + * Whether client-side decompression is required. + * + * @type {boolean} + * @default false; + */ + this.clientDecompression = false; + /** * Used for aborting requests. * @@ -246,6 +254,15 @@ export class RevalidatingFileLoader extends Loader { ); } }) + .then(async (response) => { + if (this.clientDecompression) { + const ds = new DecompressionStream("gzip"); + const decompressedStream = (await response.blob()).stream().pipeThrough(ds); + const decompressedResponse = new Response(decompressedStream); + return decompressedResponse; + } + return response; + }) .then((response) => { switch (responseType) { case "arraybuffer": @@ -344,6 +361,16 @@ export class RevalidatingFileLoader extends Loader { return this; } + /** + * Sets whether client-side decompression is required. + * @param {boolean} value - True if the client must decompress the loaded file + * @returns {FileLoader} A reference to this file loader. + */ + setClientDecompression(value) { + this.clientDecompression = value; + return this; + } + /** * Aborts ongoing fetch requests. *