Source: lib/osm/tiles-keeper.js

import {OsmUtils, Vec3, R} from './utils.js'
import {OSM3} from './osm3.js'

// import TileWorker from 'worker-loader!./tiles-worker.js'
import workjs from 'raw-loader!./tiles-worker.js'

/** @class */
class TilesKeeper {

	/**
	 * @param {object} osm OSM3, provid function addTile(mesh), center()
	 * and addTileMesh()
	 * @param {string} path workder js path O3_workerPath = "tiles-keeper.js",
	 * use webpack.config?
	 */
	constructor (osm, path) {
		this.osm = osm;
		this.tiles = {};

		if (path) {
			this.workers = new Array(1);
			var num = 2; // threads
			for (let i = 0; i < num; i++) {
				// var blob = new Blob([path]);
				var blob = new Blob([workjs]);
				var url = window.URL.createObjectURL(blob);
				var w = new Worker(url);

				// var w = new Worker(path);
				// var w = new TileWorker();

				w.addEventListener('message', this.onOsmReady, false);
				w.addEventListener('error', this.onErr, false);
				this.workers[i] = w;
			}
		}
		else {
			console.warn('Worker js is undefined. Testing?');
		}
	}

	/**Asynchronously start an OSM tile loading - post message to worker.
	 * This function always use the first worker.
	 * @param {int[]} xyz [x,y,z]
	 * @method
	 * @memeberof TilesKeeper
	 */
	loasOsmAsync (xyz) {
		this.workers[0].postMessage({cmd: 'osm', xyz: [xyz]});
	}

	/** on tile loaded
	 * @method
	 * @memeberof TilesKeeper
	 */
	onOsmReady (e) {
		if (this.verbose >= 5)
			console.log(e);
		if (e.data.code === 'tile') {
			console.log(e.data);
			{	var blob = new Blob( [ e.data.img ], { type: "image/jpeg" } );
				var imageUrl = window.URL.createObjectURL( blob );
				var img = document.querySelector( "#tile" );
				if (img) {
					img.src = imageUrl;
				}
			}
			// tileKeeper.postMessage({cmd: 'set-ground', xyz: e.data.xyz}, e.data.bytes);

			this.setOsmTexture(e.data.xyz, imageUrl);
		}
	}

	onErr (e) { console.error(e); }

	/**Set loaded osm texture to the ground tile geometry
	 * For three.js texture loader, see
	 * https://threejs.org/docs/index.html#api/en/loaders/TextureLoader
	 * TODO put into work?
	 * @param {object} xyz osm {x, y, z}
	 * @param {Blob} blobUrl should be a blob url - ready for building texture
	 * @method
	 * @memeberof TilesKeeper
	 */
	setOsmTexture (xyz, blobUrl) {
		// instantiate a loader
		var loader = new THREE.TextureLoader();

		// load a resource
		loader.load( blobUrl, // resource URL
			// onLoad callback
			function ( texture ) {
				// in this example we create the material when the texture is loaded
				var material = new THREE.MeshBasicMaterial( {
					map: texture
				 } );

				// groundMeshes.material = material;
				// groundMeshes.material.map.needsUpdate = true;
				var tile = tilesKeeper.getile({x: xyz[0], y: xyz[1], z: xyz[2]});
				if (tile) {
					tile.mesh.material = material;
					tile.mesh.material.map.needsUpdate = true;
				}
				else {
					console.error('No tile for texture?', xyz);
				}
			},

			// onProgress callback currently not supported
			undefined,

			// onError callback
			function ( err ) {
				console.error( 'An error happened loading image' );
			}
		);
	}

	/**
	 * @method
	 * @memeberof TilesKeeper
	 */
	destroy () {
		this.workers.forEach( w => w.destroy());
		this.workers = [];
	}

	/**
	 * @method
	 * @memeberof TilesKeeper
	 */
	ping (ix) {
		var msg = {name: 'function loadingOsm'};
		this.workers[ix].postMessage({cmd: 'ping', msg: msg}); // Start worker
	}

	/**
	 * @method
	 * @memeberof TilesKeeper
	 */
	getile (xyz) {
		if (Array.isArray(xyz)) {
			return tilesKeeper.tiles[xyz[2]][xyz[0]][xyz[1]]
		}
		else {
			var ztiles = tilesKeeper.tiles[xyz.z];
			if (ztiles) {
				if (ztiles[xyz.x]) {
					return ztiles[xyz.x][xyz.y];
				}
			}
		}
	}

	/**
	 * @method
	 * @memeberof TilesKeeper
	 */
	setile (xyz, tile) {
		// collectOsmTiles (tiles, dir, c0, a) {
		if (this.tiles === undefined)
			this.tiles = {};

		var tiles = this.tiles;

		if (tiles[xyz.z] === undefined) {
			tiles[xyz.z] = {};
		}
		if (tiles[xyz.z][xyz.x] === undefined) {
			tiles[xyz.z][xyz.x] = {};
		}
		if (tiles[xyz.z][xyz.x][xyz.y] === undefined) {
			tiles[xyz.z][xyz.x][xyz.y] = {};
		}
		Object.assign(tiles[xyz.z][xyz.x][xyz.y],
			// { lon: longlat.long, lat: longlat.lat });
			tile);
		return this;
	}

	/*
	forEachTile (fun) {
		var tz = this.tiles; // for short
		if (tz) {
			for (var z in tz)
				for (var x in tz[z])
					for (var y in tz[z][x])
						fun(tz[z][x][y], [x, y, z]);
		}
	}
	*/

	/**Find osm tiles according to camera position.
	 * @param {object} osmTiles osm tile collection {z: {x: {y: tileInf}}}<br>
	 * tileInf: lon, lat, mesh, world
	 * @param {object} camWorld:<br>
	 *     target: lookAt, {lat, lon, h = 0}<br>
	 *     position: eye position, {lat, lon, h}<br>
	 *     z: osm z
	 * @param {THREE.Camera} camera
	 * @param {THREE.Matrix4} castMatrix
	 * @return {object} tiles {z: {x: {y: {tileInf}}}}
	 * @method
	 * @memeberof TilesKeeper
	 */
	findTiles (camWorld, camera, castMatrix) {
		var m = camWorld.worldMat4;
		if (camWorld && camWorld.castMatrix) {
			m.multiply(camWorld.castMatrix);
		}

		var eye = OsmUtils.rad2cart(camWorld.position.lon,
							camWorld.position.lat, R + camWorld.position.h);
		eye = [eye.x, eye.y, eye.z];

		var lookAt = OsmUtils.rad2cart(camWorld.target.lon, camWorld.target.lat,
							camWorld.target.h === undefined ? R : R + camWorld.target.h);
		lookAt= [lookAt.x, lookAt.y, lookAt.z];

		// var dir = Vec3.minus(Vec3.add([x, y, z], lookAt), eye);
		if (this.dirs === undefined) {
			this.dirs = ThreeWrapper.getRayDirs(camera, {w: 4, h: 3});
		}
		else {
			console.error('TODO')
		}

		for ( var ix = 0; ix < this.dirs.count; ix++ ) {
			var x = this.dirs.getX(ix);
			var y = this.dirs.getY(ix);
			var z = this.dirs.getZ(ix);

			// find tile by ray casting from eye to dir [x, y, z]
			// var dir = Vec3.minus(Vec3.add([x, y, z], lookAt), eye);
			var looks = [x, y, z];
			TilesKeeper.collectOsmTiles(this, looks, eye);
		}

		if (this.verbose >= 5) {
			console.log('osmTiles', this.tiles);
		}

		return this;
	}

	/**
	 * @method
	 * @memeberof TilesKeeper
	 */
	update (center) {
		if (center.lon && center.lat) {
			this.geoCenter = ceneter;
		}
		else {
			console.error("expecting center = {lon, lat, h(optinal)}, but get ",
							center)
			return
		}

		/*
		this.forEachTile((t, xyz) => {
			if (t.mesh === undefined) {
				t.mesh = new THREE.Mesh(t.geom(), blankMaterial);
				t.mesh.name = `${xyz[2]}-${xyz[0]}-${xyz[1]}`
				s3.scene.add(t.mesh);
				// this.tileskeepr.postMessage({cmd: 'osm', xyz: [xyz]});
				this.loasOsmAsync(xyz);
			}
		});
		*/
		this.loadOsmTilesAsync()
		// TODO purge
	}

	/**
	 * @method
	 * @memeberof TilesKeeper
	 */
	loadOsmTilesAsync () {
		var tz = this.tiles; // for short
		for (var z in tz)
			for (var x in tz[z])
				for (var t in tz[z][x]) {
					// fun(tz[z][x][y], [x, y, z]);
					if (t.mesh === undefined) {
						/*
						t.mesh = new THREE.Mesh(t.geom(), blankMaterial);
						t.mesh.name = `${xyz[2]}-${xyz[0]}-${xyz[1]}`
						s3.scene.add(t.mesh);
						// this.tileskeepr.postMessage({cmd: 'osm', xyz: [xyz]});
						this.loasOsmAsync(xyz);
						*/
						t.mesh = ThreeWrapper.createMesh(this.osm.center(), t, xyz)
						// s3.scene.add(t.mesh);
						this.osm.addTileMesh(t.mesh, xyz); // xyz here for test
					}
				}
	}

	/**If xyz is not in tiles, add new xyz to tiles.
	 * @param {tilesKeeper} tkeeper
	 * {z: {x0: {y00: world00, ...}, {x1: {y10: world10, ...}, ...}}}
	 * @param {vec3} dir [x, y, z] dir
	 * @param {vec3} c0 [x, y, z] center / eye
	 * @param {float} a radius
	 * @return {object} tiles
	 * @method
	 * @memeberof TilesKeeper
	 */
	static collectOsmTiles (tkeeper, dir, c0) {
		// dir = this.normalize(dir, [c0.x, c0.y, c0.z]);
		// dir = this.normalize(dir, c0);
		var p = OsmUtils.castPosition (c0, dir);
		if (p) {
			var longlat = OsmUtils.cart2rad(p);
			longlat.lon *= 180 / Math.PI;
			longlat.lat *= 180 / Math.PI;
			var osmz = OsmUtils.stepz(p.w, a);
			var xyz = {
				x: OsmUtils.long2tile(longlat.lon, osmz),
				y: OsmUtils.lat2tile(longlat.lat, osmz),
				z: osmz };

			if (OsmUtils.verbose >= 5) {
				console.log('dir, p, long-lat, xyz', dir, p, longlat, xyz);
			}

			tkeeper.setile(xyz, { lon: longlat.lon, lat: longlat.lat });
		}
		return tkeeper;
	}
}

export {TilesKeeper}