2016-02-27 14:58:33 +00:00
|
|
|
|
import TileLayer from './TileLayer';
|
|
|
|
|
import extend from 'lodash.assign';
|
|
|
|
|
import GeoJSONTile from './GeoJSONTile';
|
|
|
|
|
import throttle from 'lodash.throttle';
|
|
|
|
|
import THREE from 'three';
|
|
|
|
|
|
2016-02-29 09:33:27 +00:00
|
|
|
|
// 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
|
|
|
|
|
|
2016-02-28 18:40:51 +00:00
|
|
|
|
// TODO: Make sure nothing is left behind in the heap after calling destroy()
|
|
|
|
|
|
2016-02-27 14:58:33 +00:00
|
|
|
|
// 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,
|
2016-09-07 15:01:02 +00:00
|
|
|
|
distance: 30000,
|
|
|
|
|
workers: false
|
2016-02-27 14:58:33 +00:00
|
|
|
|
};
|
|
|
|
|
|
2016-02-28 20:29:52 +00:00
|
|
|
|
options = extend({}, defaults, options);
|
2016-02-27 14:58:33 +00:00
|
|
|
|
|
|
|
|
|
super(options);
|
|
|
|
|
|
2016-10-03 09:36:10 +00:00
|
|
|
|
this.defaults = defaults;
|
|
|
|
|
|
2016-02-27 14:58:33 +00:00
|
|
|
|
this._path = path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onAdd(world) {
|
2016-08-30 15:46:04 +00:00
|
|
|
|
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);
|
|
|
|
|
});
|
2016-02-27 14:58:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_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() {
|
2016-09-07 15:01:02 +00:00
|
|
|
|
if (this._pauseOutput || this._disableOutput) {
|
2016-02-27 14:58:33 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._outputTiles();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update tiles grid after world move, but don't output them
|
|
|
|
|
_onWorldMove(latlon, point) {
|
2016-09-07 15:01:02 +00:00
|
|
|
|
if (this._disableOutput) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-27 14:58:33 +00:00
|
|
|
|
this._pauseOutput = false;
|
|
|
|
|
this._calculateLOD();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pause updates during control movement for less visual jank
|
|
|
|
|
_onControlsMove() {
|
2016-09-07 15:01:02 +00:00
|
|
|
|
if (this._disableOutput) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-27 14:58:33 +00:00
|
|
|
|
this._pauseOutput = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_createTile(quadcode, layer) {
|
2016-10-06 09:11:32 +00:00
|
|
|
|
var newOptions = extend({}, this.defaults, this._options, {
|
|
|
|
|
outputToScene: false
|
|
|
|
|
});
|
|
|
|
|
|
2016-09-16 10:39:02 +00:00
|
|
|
|
delete newOptions.attribution;
|
|
|
|
|
|
|
|
|
|
return new GeoJSONTile(quadcode, this._path, layer, newOptions);
|
2016-02-27 14:58:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-25 11:40:13 +00:00
|
|
|
|
hide() {
|
|
|
|
|
this._pauseOutput = true;
|
|
|
|
|
super.hide();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
show() {
|
|
|
|
|
this._pauseOutput = false;
|
|
|
|
|
super.show();
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-27 14:58:33 +00:00
|
|
|
|
// 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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-01 20:34:53 +00:00
|
|
|
|
export default GeoJSONTileLayer;
|
|
|
|
|
|
|
|
|
|
var noNew = function(path, options) {
|
2016-02-27 14:58:33 +00:00
|
|
|
|
return new GeoJSONTileLayer(path, options);
|
|
|
|
|
};
|
2016-03-01 20:34:53 +00:00
|
|
|
|
|
|
|
|
|
// Initialise without requiring new keyword
|
|
|
|
|
export {noNew as geoJSONTileLayer};
|