import TileLayer from './TileLayer'; import extend from 'lodash.assign'; import GeoJSONTile from './GeoJSONTile'; import throttle from 'lodash.throttle'; import THREE from 'three'; // TODO: Offer on-the-fly slicing of static, non-tile-based GeoJSON files into a // tile grid using geojson-vt // // See: https://github.com/mapbox/geojson-vt // TODO: Make sure nothing is left behind in the heap after calling destroy() // TODO: Consider pausing per-frame output during movement so there's little to // no jank caused by previous tiles still processing // This tile layer only updates the quadtree after world movement has occurred // // Tiles from previous quadtree updates are updated and outputted every frame // (or at least every frame, throttled to some amount) // // This is because the complexity of TopoJSON tiles requires a lot of processing // and so makes movement janky if updates occur every frame – only updating // after movement means frame drops are less obvious due to heavy processing // occurring while the view is generally stationary // // The downside is that until new tiles are requested and outputted you will // see blank spaces as you orbit and move around // // An added benefit is that it dramatically reduces the number of tiles being // requested over a period of time and the time it takes to go from request to // screen output // // It may be possible to perform these updates per-frame once Web Worker // processing is added class GeoJSONTileLayer extends TileLayer { constructor(path, options) { var defaults = { maxLOD: 14, distance: 30000, workers: false }; options = extend({}, defaults, options); super(options); this.defaults = defaults; this._path = path; } _onAdd(world) { return new Promise((resolve, reject) => { super._onAdd(world).then(() => { // Trigger initial quadtree calculation on the next frame // // TODO: This is a hack to ensure the camera is all set up - a better // solution should be found setTimeout(() => { this._calculateLOD(); this._initEvents(); }, 0); resolve(this); }).catch(reject); }); } _initEvents() { // Run LOD calculations based on render calls // // Throttled to 1 LOD calculation per 100ms this._throttledWorldUpdate = throttle(this._onWorldUpdate, 100); this._world.on('preUpdate', this._throttledWorldUpdate, this); this._world.on('move', this._onWorldMove, this); this._world.on('controlsMove', this._onControlsMove, this); } // Update and output tiles each frame (throttled) _onWorldUpdate() { if (this._pauseOutput || this._disableOutput) { return; } this._outputTiles(); } // Update tiles grid after world move, but don't output them _onWorldMove(latlon, point) { if (this._disableOutput) { return; } this._pauseOutput = false; this._calculateLOD(); } // Pause updates during control movement for less visual jank _onControlsMove() { if (this._disableOutput) { return; } this._pauseOutput = true; } _createTile(quadcode, layer) { var newOptions = extend({}, this.defaults, this._options, { outputToScene: false }); delete newOptions.attribution; return new GeoJSONTile(quadcode, this._path, layer, newOptions); } hide() { this._pauseOutput = true; super.hide(); } show() { this._pauseOutput = false; super.show(); } // Destroys the layer and removes it from the scene and memory destroy() { this._world.off('preUpdate', this._throttledWorldUpdate); this._world.off('move', this._onWorldMove); this._throttledWorldUpdate = null; // Run common destruction logic from parent super.destroy(); } } export default GeoJSONTileLayer; var noNew = function(path, options) { return new GeoJSONTileLayer(path, options); }; // Initialise without requiring new keyword export {noNew as geoJSONTileLayer};