diff --git a/src/World.js b/src/World.js index e818217..2a89265 100644 --- a/src/World.js +++ b/src/World.js @@ -242,6 +242,8 @@ class World extends EventEmitter { } addLayer(layer) { + // Is is right to assume that there will always be some other layer + // managing layers with output set to false? this._layers.push(layer); if (layer.isOutput() && layer.isOutputToScene()) { @@ -256,7 +258,11 @@ class World extends EventEmitter { if (layer._options.attribution) { this._addAttribution(layer._options.id, layer._options.attribution); } + + // TODO: Consider moving this so it doesn't fire for layers that are + // actually managed by a parent layer (eg. tiles) this.emit('layerAdded', layer); + resolve(this); }).catch(reject); }); diff --git a/src/layer/GeoJSONWorkerLayer.js b/src/layer/GeoJSONWorkerLayer.js index e346078..57793b8 100644 --- a/src/layer/GeoJSONWorkerLayer.js +++ b/src/layer/GeoJSONWorkerLayer.js @@ -38,6 +38,7 @@ class GeoJSONWorkerLayer extends Layer { super(_options); + this._aborted = false; this._geojson = geojson; } @@ -80,6 +81,11 @@ class GeoJSONWorkerLayer extends Layer { }).catch(reject); } else if (typeof this._options.filter === 'function' || typeof this._options.onEachFeature === 'function') { GeoJSONWorkerLayer.RequestGeoJSON(geojson).then((res) => { + // if (this._aborted) { + // resolve(); + // return; + // } + var fc = GeoJSON.collectFeatures(res, this._options.topojson); var features = fc.features; @@ -118,16 +124,198 @@ class GeoJSONWorkerLayer extends Layer { Worker.exec('GeoJSONWorkerLayer.Process', [geojson, topojson, headers, originPoint, style, interactive, pointGeometry], transferrables).then((results) => { console.timeEnd('Worker round trip'); + // if (this._aborted) { + // resolve(); + // return; + // } + + var processPromises = []; + if (results.polygons) { - this._processPolygonResults(results.polygons); + processPromises.push(this._processPolygonResults(results.polygons)); } if (results.polylines) { - this._processPolylineResults(results.polylines); + processPromises.push(this._processPolylineResults(results.polylines)); } if (results.points) { - this._processPointResults(results.points); + processPromises.push(this._processPointResults(results.points)); + } + + if (processPromises.length > 0) { + Promise.all(processPromises).then(() => { + resolve(); + }); + } else { + resolve(); + } + }); + }); + } + + // TODO: Dedupe with polyline method + _processPolygonResults(results) { + return new Promise((resolve, reject) => { + var splitPositions = Buffer.splitFloat32Array(results.attributes.positions); + var splitNormals = Buffer.splitFloat32Array(results.attributes.normals); + var splitColors = Buffer.splitFloat32Array(results.attributes.colors); + var splitTops = Buffer.splitFloat32Array(results.attributes.tops); + + var splitOutlinePositions; + var splitOutlineColors; + + if (results.outlineAttributes) { + splitOutlinePositions = Buffer.splitFloat32Array(results.outlineAttributes.positions); + splitOutlineColors = Buffer.splitFloat32Array(results.outlineAttributes.colors); + } + + var splitProperties; + if (results.properties) { + splitProperties = Buffer.splitUint8Array(results.properties); + } + + var flats = results.flats; + + var objects = []; + var outlineObjects = []; + + var obj; + var pickingId; + var pickingIds; + var properties; + + var polygonAttributeLengths = { + positions: 3, + normals: 3, + colors: 3, + tops: 1 + }; + + var polygonOutlineAttributeLengths = { + positions: 3, + colors: 3 + }; + + for (var i = 0; i < splitPositions.length; i++) { + if (splitProperties && splitProperties[i]) { + properties = JSON.parse(Buffer.uint8ArrayToString(splitProperties[i])); + } else { + properties = {}; + } + + // WORKERS: obj.attributes should actually an array of polygons for + // the feature, though the current logic isn't aware of that + obj = { + attributes: [{ + positions: splitPositions[i], + normals: splitNormals[i], + colors: splitColors[i], + tops: splitTops[i] + }], + properties: properties, + flat: flats[i] + }; + + // WORKERS: If interactive, generate unique ID for each feature, create + // the buffer attributes and set up event listeners + if (this._options.interactive) { + pickingId = this.getPickingId(); + + pickingIds = new Float32Array(splitPositions[i].length / 3); + pickingIds.fill(pickingId); + + obj.attributes[0].pickingIds = pickingIds; + + polygonAttributeLengths.pickingIds = 1; + + this._addPicking(pickingId, properties); + } + + // TODO: Make this specific to polygon attributes + if (typeof this._options.onAddAttributes === 'function') { + var customAttributes = this._options.onAddAttributes(obj.attributes[0], properties); + var customAttribute; + for (var key in customAttributes) { + customAttribute = customAttributes[key]; + obj.attributes[0][key] = customAttribute.value; + polygonAttributeLengths[key] = customAttribute.length; + } + } + + objects.push(obj); + } + + for (var i = 0; i < splitOutlinePositions.length; i++) { + obj = { + attributes: [{ + positions: splitOutlinePositions[i], + colors: splitOutlineColors[i] + }], + flat: true + }; + + outlineObjects.push(obj); + } + + var polygonAttributes = []; + var polygonOutlineAttributes = []; + + var polygonFlat = true; + + for (var i = 0; i < objects.length; i++) { + obj = objects[i]; + + if (polygonFlat && !obj.flat) { + polygonFlat = false; + } + + var bufferAttributes = Buffer.mergeAttributes(obj.attributes); + polygonAttributes.push(bufferAttributes); + }; + + for (var i = 0; i < outlineObjects.length; i++) { + obj = outlineObjects[i]; + + var bufferAttributes = Buffer.mergeAttributes(obj.attributes); + polygonOutlineAttributes.push(bufferAttributes); + }; + + var outputPromises = []; + + if (polygonAttributes.length > 0) { + var mergedPolygonAttributes = Buffer.mergeAttributes(polygonAttributes); + + // TODO: Make this work when style is a function per feature + var style = (typeof this._options.style === 'function') ? this._options.style(objects[0]) : this._options.style; + style = extend({}, GeoJSON.defaultStyle, style); + + outputPromises.push(this._setPolygonMesh(mergedPolygonAttributes, polygonAttributeLengths, style, polygonFlat)); + } + + if (polygonOutlineAttributes.length > 0) { + var mergedPolygonOutlineAttributes = Buffer.mergeAttributes(polygonOutlineAttributes); + + var style = (typeof this._options.style === 'function') ? this._options.style(objects[0]) : this._options.style; + style = extend({}, GeoJSON.defaultStyle, style); + + outputPromises.push(this._setPolylineMesh(mergedPolygonOutlineAttributes, polygonOutlineAttributeLengths, style, true)); + } + + Promise.all(outputPromises).then((results) => { + var [polygonResult, outlineResult] = results; + + if (polygonResult) { + this._polygonMesh = polygonResult.mesh; + this.add(this._polygonMesh); + + if (polygonResult.pickingMesh) { + this._pickingMesh.add(polygonResult.pickingMesh); + } + } + + if (outlineResult) { + this.add(outlineResult.mesh); } resolve(); @@ -135,370 +323,224 @@ class GeoJSONWorkerLayer extends Layer { }); } - // TODO: Dedupe with polyline method - _processPolygonResults(results) { - var splitPositions = Buffer.splitFloat32Array(results.attributes.positions); - var splitNormals = Buffer.splitFloat32Array(results.attributes.normals); - var splitColors = Buffer.splitFloat32Array(results.attributes.colors); - var splitTops = Buffer.splitFloat32Array(results.attributes.tops); - - var splitOutlinePositions; - var splitOutlineColors; - - if (results.outlineAttributes) { - splitOutlinePositions = Buffer.splitFloat32Array(results.outlineAttributes.positions); - splitOutlineColors = Buffer.splitFloat32Array(results.outlineAttributes.colors); - } - - var splitProperties; - if (results.properties) { - splitProperties = Buffer.splitUint8Array(results.properties); - } - - var flats = results.flats; - - var objects = []; - var outlineObjects = []; - - var obj; - var pickingId; - var pickingIds; - var properties; - - var polygonAttributeLengths = { - positions: 3, - normals: 3, - colors: 3, - tops: 1 - }; - - var polygonOutlineAttributeLengths = { - positions: 3, - colors: 3 - }; - - for (var i = 0; i < splitPositions.length; i++) { - if (splitProperties && splitProperties[i]) { - properties = JSON.parse(Buffer.uint8ArrayToString(splitProperties[i])); - } else { - properties = {}; - } - - // WORKERS: obj.attributes should actually an array of polygons for - // the feature, though the current logic isn't aware of that - obj = { - attributes: [{ - positions: splitPositions[i], - normals: splitNormals[i], - colors: splitColors[i], - tops: splitTops[i] - }], - properties: properties, - flat: flats[i] - }; - - // WORKERS: If interactive, generate unique ID for each feature, create - // the buffer attributes and set up event listeners - if (this._options.interactive) { - pickingId = this.getPickingId(); - - pickingIds = new Float32Array(splitPositions[i].length / 3); - pickingIds.fill(pickingId); - - obj.attributes[0].pickingIds = pickingIds; - - polygonAttributeLengths.pickingIds = 1; - - this._addPicking(pickingId, properties); - } - - // TODO: Make this specific to polygon attributes - if (typeof this._options.onAddAttributes === 'function') { - var customAttributes = this._options.onAddAttributes(obj.attributes[0], properties); - var customAttribute; - for (var key in customAttributes) { - customAttribute = customAttributes[key]; - obj.attributes[0][key] = customAttribute.value; - polygonAttributeLengths[key] = customAttribute.length; - } - } - - objects.push(obj); - } - - for (var i = 0; i < splitOutlinePositions.length; i++) { - obj = { - attributes: [{ - positions: splitOutlinePositions[i], - colors: splitOutlineColors[i] - }], - flat: true - }; - - outlineObjects.push(obj); - } - - var polygonAttributes = []; - var polygonOutlineAttributes = []; - - var polygonFlat = true; - - var obj; - for (var i = 0; i < objects.length; i++) { - obj = objects[i]; - - if (polygonFlat && !obj.flat) { - polygonFlat = false; - } - - var bufferAttributes = Buffer.mergeAttributes(obj.attributes); - polygonAttributes.push(bufferAttributes); - }; - - for (var i = 0; i < outlineObjects.length; i++) { - obj = outlineObjects[i]; - - var bufferAttributes = Buffer.mergeAttributes(obj.attributes); - polygonOutlineAttributes.push(bufferAttributes); - }; - - if (polygonAttributes.length > 0) { - var mergedPolygonAttributes = Buffer.mergeAttributes(polygonAttributes); - - // TODO: Make this work when style is a function per feature - var style = (typeof this._options.style === 'function') ? this._options.style(objects[0]) : this._options.style; - style = extend({}, GeoJSON.defaultStyle, style); - - this._setPolygonMesh(mergedPolygonAttributes, polygonAttributeLengths, style, polygonFlat).then((result) => { - this._polygonMesh = result.mesh; - this.add(this._polygonMesh); - - if (result.pickingMesh) { - this._pickingMesh.add(result.pickingMesh); - } - }); - } - - if (polygonOutlineAttributes.length > 0) { - var mergedPolygonOutlineAttributes = Buffer.mergeAttributes(polygonOutlineAttributes); - - var style = (typeof this._options.style === 'function') ? this._options.style(objects[0]) : this._options.style; - style = extend({}, GeoJSON.defaultStyle, style); - - this._setPolylineMesh(mergedPolygonOutlineAttributes, polygonOutlineAttributeLengths, style, true).then((result) => { - this.add(result.mesh); - }); - } - } - // TODO: Dedupe with polygon method _processPolylineResults(results) { - var splitPositions = Buffer.splitFloat32Array(results.attributes.positions); - var splitColors = Buffer.splitFloat32Array(results.attributes.colors); + return new Promise((resolve, reject) => { + var splitPositions = Buffer.splitFloat32Array(results.attributes.positions); + var splitColors = Buffer.splitFloat32Array(results.attributes.colors); - var splitProperties; - if (results.properties) { - splitProperties = Buffer.splitUint8Array(results.properties); - } - - var flats = results.flats; - - var objects = []; - var obj; - var pickingId; - var pickingIds; - var properties; - - var polylineAttributeLengths = { - positions: 3, - colors: 3 - }; - - for (var i = 0; i < splitPositions.length; i++) { - if (splitProperties && splitProperties[i]) { - properties = JSON.parse(Buffer.uint8ArrayToString(splitProperties[i])); - } else { - properties = {}; + var splitProperties; + if (results.properties) { + splitProperties = Buffer.splitUint8Array(results.properties); } - // WORKERS: obj.attributes should actually an array of polygons for - // the feature, though the current logic isn't aware of that - obj = { - attributes: [{ - positions: splitPositions[i], - colors: splitColors[i] - }], - properties: properties, - flat: flats[i] + var flats = results.flats; + + var objects = []; + var obj; + var pickingId; + var pickingIds; + var properties; + + var polylineAttributeLengths = { + positions: 3, + colors: 3 }; - // WORKERS: If interactive, generate unique ID for each feature, create - // the buffer attributes and set up event listeners - if (this._options.interactive) { - pickingId = this.getPickingId(); - - pickingIds = new Float32Array(splitPositions[i].length / 3); - pickingIds.fill(pickingId); - - obj.attributes[0].pickingIds = pickingIds; - - polylineAttributeLengths.pickingIds = 1; - - this._addPicking(pickingId, properties); - } - - // TODO: Make this specific to polyline attributes - if (typeof this._options.onAddAttributes === 'function') { - var customAttributes = this._options.onAddAttributes(obj.attributes[0], properties); - var customAttribute; - for (var key in customAttributes) { - customAttribute = customAttributes[key]; - obj.attributes[0][key] = customAttribute.value; - polylineAttributeLengths[key] = customAttribute.length; + for (var i = 0; i < splitPositions.length; i++) { + if (splitProperties && splitProperties[i]) { + properties = JSON.parse(Buffer.uint8ArrayToString(splitProperties[i])); + } else { + properties = {}; } - } - objects.push(obj); - } + // WORKERS: obj.attributes should actually an array of polygons for + // the feature, though the current logic isn't aware of that + obj = { + attributes: [{ + positions: splitPositions[i], + colors: splitColors[i] + }], + properties: properties, + flat: flats[i] + }; - var polylineAttributes = []; + // WORKERS: If interactive, generate unique ID for each feature, create + // the buffer attributes and set up event listeners + if (this._options.interactive) { + pickingId = this.getPickingId(); - var polylineFlat = true; + pickingIds = new Float32Array(splitPositions[i].length / 3); + pickingIds.fill(pickingId); - var obj; - for (var i = 0; i < objects.length; i++) { - obj = objects[i]; + obj.attributes[0].pickingIds = pickingIds; - if (polylineFlat && !obj.flat) { - polylineFlat = false; - } + polylineAttributeLengths.pickingIds = 1; - var bufferAttributes = Buffer.mergeAttributes(obj.attributes); - polylineAttributes.push(bufferAttributes); - }; - - if (polylineAttributes.length > 0) { - var mergedPolylineAttributes = Buffer.mergeAttributes(polylineAttributes); - - // TODO: Make this work when style is a function per feature - var style = (typeof this._options.style === 'function') ? this._options.style(objects[0]) : this._options.style; - style = extend({}, GeoJSON.defaultStyle, style); - - this._setPolylineMesh(mergedPolylineAttributes, polylineAttributeLengths, style, polylineFlat).then((result) => { - this._polylineMesh = result.mesh; - this.add(this._polylineMesh); - - if (result.pickingMesh) { - this._pickingMesh.add(result.pickingMesh); + this._addPicking(pickingId, properties); } - }); - } + + // TODO: Make this specific to polyline attributes + if (typeof this._options.onAddAttributes === 'function') { + var customAttributes = this._options.onAddAttributes(obj.attributes[0], properties); + var customAttribute; + for (var key in customAttributes) { + customAttribute = customAttributes[key]; + obj.attributes[0][key] = customAttribute.value; + polylineAttributeLengths[key] = customAttribute.length; + } + } + + objects.push(obj); + } + + var polylineAttributes = []; + + var polylineFlat = true; + + for (var i = 0; i < objects.length; i++) { + obj = objects[i]; + + if (polylineFlat && !obj.flat) { + polylineFlat = false; + } + + var bufferAttributes = Buffer.mergeAttributes(obj.attributes); + polylineAttributes.push(bufferAttributes); + }; + + if (polylineAttributes.length > 0) { + var mergedPolylineAttributes = Buffer.mergeAttributes(polylineAttributes); + + // TODO: Make this work when style is a function per feature + var style = (typeof this._options.style === 'function') ? this._options.style(objects[0]) : this._options.style; + style = extend({}, GeoJSON.defaultStyle, style); + + this._setPolylineMesh(mergedPolylineAttributes, polylineAttributeLengths, style, polylineFlat).then((result) => { + this._polylineMesh = result.mesh; + this.add(this._polylineMesh); + + if (result.pickingMesh) { + this._pickingMesh.add(result.pickingMesh); + } + + resolve(); + }); + } else { + resolve(); + } + }); } _processPointResults(results) { - var splitPositions = Buffer.splitFloat32Array(results.attributes.positions); - var splitNormals = Buffer.splitFloat32Array(results.attributes.normals); - var splitColors = Buffer.splitFloat32Array(results.attributes.colors); + return new Promise((resolve, reject) => { + var splitPositions = Buffer.splitFloat32Array(results.attributes.positions); + var splitNormals = Buffer.splitFloat32Array(results.attributes.normals); + var splitColors = Buffer.splitFloat32Array(results.attributes.colors); - var splitProperties; - if (results.properties) { - splitProperties = Buffer.splitUint8Array(results.properties); - } - - var flats = results.flats; - - var objects = []; - var obj; - var pickingId; - var pickingIds; - var properties; - - var pointAttributeLengths = { - positions: 3, - normals: 3, - colors: 3 - }; - - for (var i = 0; i < splitPositions.length; i++) { - if (splitProperties && splitProperties[i]) { - properties = JSON.parse(Buffer.uint8ArrayToString(splitProperties[i])); - } else { - properties = {}; + var splitProperties; + if (results.properties) { + splitProperties = Buffer.splitUint8Array(results.properties); } - // WORKERS: obj.attributes should actually an array of polygons for - // the feature, though the current logic isn't aware of that - obj = { - attributes: [{ - positions: splitPositions[i], - normals: splitNormals[i], - colors: splitColors[i] - }], - properties: properties, - flat: flats[i] + var flats = results.flats; + + var objects = []; + var obj; + var pickingId; + var pickingIds; + var properties; + + var pointAttributeLengths = { + positions: 3, + normals: 3, + colors: 3 }; - // WORKERS: If interactive, generate unique ID for each feature, create - // the buffer attributes and set up event listeners - if (this._options.interactive) { - pickingId = this.getPickingId(); - - pickingIds = new Float32Array(splitPositions[i].length / 3); - pickingIds.fill(pickingId); - - obj.attributes[0].pickingIds = pickingIds; - - pointAttributeLengths.pickingIds = 1; - - this._addPicking(pickingId, properties); - } - - // TODO: Make this specific to polygon attributes - if (typeof this._options.onAddAttributes === 'function') { - var customAttributes = this._options.onAddAttributes(obj.attributes[0], properties); - var customAttribute; - for (var key in customAttributes) { - customAttribute = customAttributes[key]; - obj.attributes[0][key] = customAttribute.value; - pointAttributeLengths[key] = customAttribute.length; + for (var i = 0; i < splitPositions.length; i++) { + if (splitProperties && splitProperties[i]) { + properties = JSON.parse(Buffer.uint8ArrayToString(splitProperties[i])); + } else { + properties = {}; } - } - objects.push(obj); - } + // WORKERS: obj.attributes should actually an array of polygons for + // the feature, though the current logic isn't aware of that + obj = { + attributes: [{ + positions: splitPositions[i], + normals: splitNormals[i], + colors: splitColors[i] + }], + properties: properties, + flat: flats[i] + }; - var pointAttributes = []; + // WORKERS: If interactive, generate unique ID for each feature, create + // the buffer attributes and set up event listeners + if (this._options.interactive) { + pickingId = this.getPickingId(); - var pointFlat = true; + pickingIds = new Float32Array(splitPositions[i].length / 3); + pickingIds.fill(pickingId); - var obj; - for (var i = 0; i < objects.length; i++) { - obj = objects[i]; + obj.attributes[0].pickingIds = pickingIds; - if (pointFlat && !obj.flat) { - pointFlat = false; - } + pointAttributeLengths.pickingIds = 1; - var bufferAttributes = Buffer.mergeAttributes(obj.attributes); - pointAttributes.push(bufferAttributes); - }; - - if (pointAttributes.length > 0) { - var mergedPointAttributes = Buffer.mergeAttributes(pointAttributes); - - // TODO: Make this work when style is a function per feature - var style = (typeof this._options.style === 'function') ? this._options.style(objects[0]) : this._options.style; - style = extend({}, GeoJSON.defaultStyle, style); - - this._setPointMesh(mergedPointAttributes, pointAttributeLengths, style, pointFlat).then((result) => { - this._pointMesh = result.mesh; - this.add(this._pointMesh); - - if (result.pickingMesh) { - this._pickingMesh.add(result.pickingMesh); + this._addPicking(pickingId, properties); } - }); - } + + // TODO: Make this specific to polygon attributes + if (typeof this._options.onAddAttributes === 'function') { + var customAttributes = this._options.onAddAttributes(obj.attributes[0], properties); + var customAttribute; + for (var key in customAttributes) { + customAttribute = customAttributes[key]; + obj.attributes[0][key] = customAttribute.value; + pointAttributeLengths[key] = customAttribute.length; + } + } + + objects.push(obj); + } + + var pointAttributes = []; + + var pointFlat = true; + + for (var i = 0; i < objects.length; i++) { + obj = objects[i]; + + if (pointFlat && !obj.flat) { + pointFlat = false; + } + + var bufferAttributes = Buffer.mergeAttributes(obj.attributes); + pointAttributes.push(bufferAttributes); + }; + + if (pointAttributes.length > 0) { + var mergedPointAttributes = Buffer.mergeAttributes(pointAttributes); + + // TODO: Make this work when style is a function per feature + var style = (typeof this._options.style === 'function') ? this._options.style(objects[0]) : this._options.style; + style = extend({}, GeoJSON.defaultStyle, style); + + this._setPointMesh(mergedPointAttributes, pointAttributeLengths, style, pointFlat).then((result) => { + this._pointMesh = result.mesh; + this.add(this._pointMesh); + + if (result.pickingMesh) { + this._pickingMesh.add(result.pickingMesh); + } + + resolve(); + }); + } else { + resolve(); + } + }); } // TODO: At some point this needs to return all the features to the main thread diff --git a/src/layer/tile/GeoJSONTile.js b/src/layer/tile/GeoJSONTile.js index 649b4fb..353f271 100644 --- a/src/layer/tile/GeoJSONTile.js +++ b/src/layer/tile/GeoJSONTile.js @@ -89,11 +89,10 @@ class GeoJSONTile extends Tile { setTimeout(() => { if (!this._mesh) { this._mesh = this._createMesh(); - // this._shadowCanvas = this._createShadowCanvas(); - - this._requestTile(); } + + this._requestTile(); }, 0); } @@ -233,6 +232,8 @@ class GeoJSONTile extends Tile { var url = this._getTileURL(urlParams); + this._aborted = false; + if (!this._options.workers) { this._request = reqwest({ url: url, @@ -244,8 +245,6 @@ class GeoJSONTile extends Tile { this._request = null; this._processTileData(res); }).catch(err => { - console.error(err); - // Clear request reference this._request = null; }); @@ -255,13 +254,37 @@ class GeoJSONTile extends Tile { } _processTileData(data) { - console.time(this._tile); + // console.time(this._tile); var GeoJSONClass = (!this._options.workers) ? GeoJSONLayer : GeoJSONWorkerLayer; // Using this creates a huge amount of memory due to the quantity of tiles this._geojsonLayer = GeoJSONClass(data, this._options); this._geojsonLayer.addTo(this._world).then(() => { + // TODO: This never seems to be called on worker layers. Find out why. + if (this.isAborted()) { + // this._geojsonLayer._aborted = true; + // this._geojsonLayer = null; + return; + } + + // TODO: This is a hack to stop old tile meshes hanging around. Fix or + // move to somewhere more robust. + // + // Could potentially just overwrite mesh on first index each time + // + // This makes some worker tiles to not appear properly – showing the + // points mesh but not the polygon mesh, etc. + // + // Only do this for non-worker layers for now as it seems to cause issues + // with worker tiles showing for a moment and then disappearing forever + if (!this._options.workers) { + this.destroyMesh(this._mesh); + } + + // TOSO: Work out if the picking mesh needs destroying here + // this.destroyMesh(this._pickingMesh); + this._mesh.add(this._geojsonLayer._object3D); this._pickingMesh = this._geojsonLayer._pickingMesh; @@ -325,16 +348,20 @@ class GeoJSONTile extends Tile { // this._mesh.add(mesh); this._ready = true; - console.timeEnd(this._tile); + // console.timeEnd(this._tile); }); } _abortRequest() { - if (!this._request) { + if ((!this._request && !this._options.workers) || this._ready) { return; } - this._request.abort(); + this._aborted = true; + + if (this._request) { + this._request.abort(); + } } } diff --git a/src/layer/tile/GeoJSONTileLayer.js b/src/layer/tile/GeoJSONTileLayer.js index 3847c73..30ff016 100644 --- a/src/layer/tile/GeoJSONTileLayer.js +++ b/src/layer/tile/GeoJSONTileLayer.js @@ -108,7 +108,10 @@ class GeoJSONTileLayer extends TileLayer { } _createTile(quadcode, layer) { - var newOptions = extend({}, this.defaults, this._options); + var newOptions = extend({}, this.defaults, this._options, { + outputToScene: false + }); + delete newOptions.attribution; return new GeoJSONTile(quadcode, this._path, layer, newOptions); diff --git a/src/layer/tile/ImageTile.js b/src/layer/tile/ImageTile.js index 21b65f4..cfd5757 100644 --- a/src/layer/tile/ImageTile.js +++ b/src/layer/tile/ImageTile.js @@ -15,8 +15,9 @@ class ImageTile extends Tile { setTimeout(() => { if (!this._mesh) { this._mesh = this._createMesh(); - this._requestTile(); } + + this._requestTile(); }, 0); } @@ -131,7 +132,13 @@ class ImageTile extends Tile { var image = document.createElement('img'); + this._aborted = false; + image.addEventListener('load', event => { + if (this.isAborted()) { + return; + } + var texture = new THREE.Texture(); texture.image = image; @@ -172,10 +179,12 @@ class ImageTile extends Tile { } _abortRequest() { - if (!this._image) { + if (!this._image || this._ready) { return; } + this._aborted = true; + this._image.src = ''; } } diff --git a/src/layer/tile/Tile.js b/src/layer/tile/Tile.js index c81b5ef..f4865ec 100644 --- a/src/layer/tile/Tile.js +++ b/src/layer/tile/Tile.js @@ -18,6 +18,7 @@ class Tile { this._path = path; this._ready = false; + this._aborted = false; this._tile = this._quadcodeToTile(quadcode); @@ -46,6 +47,10 @@ class Tile { return this._ready; } + isAborted() { + return this._aborted; + } + // Request data for the tile requestTileAsync() {} @@ -78,6 +83,8 @@ class Tile { // Ensure that this leaves no trace of the tile – no textures, no meshes, // nothing in memory or the GPU destroy() { + // console.log('Destroying tile', this._quadcode); + // Delete reference to layer and world this._layer = null; this._world = null; @@ -88,35 +95,42 @@ class Tile { this._center = null; // Done if no mesh - if (!this._mesh) { + if (!this._mesh && !this._pickingMesh) { return; } - if (this._mesh.children) { - // Dispose of mesh and materials - this._mesh.children.forEach(child => { - child.geometry.dispose(); - child.geometry = null; + this.destroyMesh(this._mesh); + this.destroyMesh(this._pickingMesh); - if (child.material.map) { - child.material.map.dispose(); - child.material.map = null; - } + this._mesh = null; + this._pickingMesh = null; + } - child.material.dispose(); - child.material = null; - }); - } else { - this._mesh.geometry.dispose(); - this._mesh.geometry = null; - - if (this._mesh.material.map) { - this._mesh.material.map.dispose(); - this._mesh.material.map = null; + destroyMesh(mesh, dispose = true) { + if (mesh) { + if (mesh.children) { + mesh.children.forEach((child) => { + mesh.remove(child); + this.destroyMesh(child); + }); } - this._mesh.material.dispose(); - this._mesh.material = null; + if (dispose) { + if (mesh.geometry) { + mesh.geometry.dispose(); + mesh.geometry = null; + } + + if (mesh.material) { + if (mesh.material.map) { + mesh.material.map.dispose(); + mesh.material.map = null; + } + + mesh.material.dispose(); + mesh.material = null; + } + } } } diff --git a/src/layer/tile/TileLayer.js b/src/layer/tile/TileLayer.js index 450e3be..c24c4f2 100644 --- a/src/layer/tile/TileLayer.js +++ b/src/layer/tile/TileLayer.js @@ -168,7 +168,7 @@ class TileLayer extends Layer { }); // 5. Filter the tiles remaining in the check list - this._tileList = checkList.filter((tile, index) => { + var tileList = checkList.filter((tile, index) => { // Skip tile if it's not in the current view frustum if (!this._tileInFrustum(tile)) { return false; @@ -189,7 +189,7 @@ class TileLayer extends Layer { // // If yes, continue // If no, generate tile mesh, request texture and skip - if (!tile.getMesh()) { + if (!tile.getMesh() || tile.isAborted()) { tile.requestTileAsync(); } @@ -207,6 +207,17 @@ class TileLayer extends Layer { // this._tiles.add(tile.getMesh()); }); + // Get list of tiles that were in the previous update but not the + // current one (for aborting requests, etc) + var missingTiles = this._tileList.filter((item) => { + return !tileList.includes(item); + }); + + // Abort tiles that are no longer in view + missingTiles.forEach((tile) => tile._abortRequest()); + + this._tileList = tileList; + // console.log(performance.now() - start); }