Merge pull request #9058 from ahocevar/slim-vectorimagetile
Move all tile loading from the VectorImageTile to the source
This commit is contained in:
@@ -5,6 +5,7 @@ import TileState from './TileState.js';
|
||||
import {easeIn} from './easing.js';
|
||||
import EventTarget from './events/Target.js';
|
||||
import EventType from './events/EventType.js';
|
||||
import {abstract} from './util.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -105,6 +106,14 @@ class Tile extends EventTarget {
|
||||
*/
|
||||
this.interimTile = null;
|
||||
|
||||
/**
|
||||
* The tile is available at the highest possible resolution. Subclasses can
|
||||
* set this to `false` initially. Tile load listeners will not be
|
||||
* unregistered before this is set to `true` and a `#changed()` is called.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.hifi = true;
|
||||
|
||||
/**
|
||||
* A key assigned to the tile. This is used by the tile source to determine
|
||||
* if this tile can effectively be used, or if a new tile should be created
|
||||
@@ -240,7 +249,9 @@ class Tile extends EventTarget {
|
||||
* @abstract
|
||||
* @api
|
||||
*/
|
||||
load() {}
|
||||
load() {
|
||||
abstract();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alpha value for rendering.
|
||||
|
||||
@@ -82,7 +82,7 @@ class TileQueue extends PriorityQueue {
|
||||
handleTileChange(event) {
|
||||
const tile = /** @type {import("./Tile.js").default} */ (event.target);
|
||||
const state = tile.getState();
|
||||
if (state === TileState.LOADED || state === TileState.ERROR ||
|
||||
if (tile.hifi && state === TileState.LOADED || state === TileState.ERROR ||
|
||||
state === TileState.EMPTY || state === TileState.ABORT) {
|
||||
unlisten(tile, EventType.CHANGE, this.handleTileChange, this);
|
||||
const tileKey = tile.getKey();
|
||||
|
||||
@@ -1,362 +0,0 @@
|
||||
/**
|
||||
* @module ol/VectorImageTile
|
||||
*/
|
||||
import {getUid} from './util.js';
|
||||
import Tile from './Tile.js';
|
||||
import TileState from './TileState.js';
|
||||
import {createCanvasContext2D} from './dom.js';
|
||||
import {listen, unlistenByKey} from './events.js';
|
||||
import {getHeight, getIntersection, getWidth} from './extent.js';
|
||||
import EventType from './events/EventType.js';
|
||||
import {loadFeaturesXhr} from './featureloader.js';
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {Object} ReplayState
|
||||
* @property {boolean} dirty
|
||||
* @property {null|import("./render.js").OrderFunction} renderedRenderOrder
|
||||
* @property {number} renderedTileRevision
|
||||
* @property {number} renderedRevision
|
||||
*/
|
||||
|
||||
|
||||
class VectorImageTile extends Tile {
|
||||
|
||||
/**
|
||||
* @param {import("./tilecoord.js").TileCoord} tileCoord Tile coordinate.
|
||||
* @param {TileState} state State.
|
||||
* @param {number} sourceRevision Source revision.
|
||||
* @param {import("./format/Feature.js").default} format Feature format.
|
||||
* @param {import("./Tile.js").LoadFunction} tileLoadFunction Tile load function.
|
||||
* @param {import("./tilecoord.js").TileCoord} urlTileCoord Wrapped tile coordinate for source urls.
|
||||
* @param {import("./Tile.js").UrlFunction} tileUrlFunction Tile url function.
|
||||
* @param {import("./tilegrid/TileGrid.js").default} sourceTileGrid Tile grid of the source.
|
||||
* @param {import("./tilegrid/TileGrid.js").default} tileGrid Tile grid of the renderer.
|
||||
* @param {Object<string, import("./VectorTile.js").default>} sourceTiles Source tiles.
|
||||
* @param {number} pixelRatio Pixel ratio.
|
||||
* @param {import("./proj/Projection.js").default} projection Projection.
|
||||
* @param {typeof import("./VectorTile.js").default} tileClass Class to
|
||||
* instantiate for source tiles.
|
||||
* @param {function(this: import("./source/VectorTile.js").default, import("./events/Event.js").default): void} handleTileChange
|
||||
* Function to call when a source tile's state changes.
|
||||
*/
|
||||
constructor(tileCoord, state, sourceRevision, format, tileLoadFunction,
|
||||
urlTileCoord, tileUrlFunction, sourceTileGrid, tileGrid, sourceTiles,
|
||||
pixelRatio, projection, tileClass, handleTileChange) {
|
||||
|
||||
super(tileCoord, state, {transition: 0});
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {!Object<string, CanvasRenderingContext2D>}
|
||||
*/
|
||||
this.context_ = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {import("./featureloader.js").FeatureLoader}
|
||||
*/
|
||||
this.loader_;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {!Object<string, ReplayState>}
|
||||
*/
|
||||
this.replayState_ = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Object<string, import("./VectorTile.js").default>}
|
||||
*/
|
||||
this.sourceTiles_ = sourceTiles;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {import("./tilegrid/TileGrid.js").default}
|
||||
*/
|
||||
this.sourceTileGrid_ = sourceTileGrid;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.sourceTilesLoaded = false;
|
||||
|
||||
/**
|
||||
* Keys of source tiles used by this tile. Use with {@link #getTile}.
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
this.tileKeys = [];
|
||||
|
||||
/**
|
||||
* @type {import("./extent.js").Extent}
|
||||
*/
|
||||
this.extent = null;
|
||||
|
||||
/**
|
||||
* @type {import("./tilecoord.js").TileCoord}
|
||||
*/
|
||||
this.wrappedTileCoord = urlTileCoord;
|
||||
|
||||
/**
|
||||
* @type {Array<import("./events.js").EventsKey>}
|
||||
*/
|
||||
this.loadListenerKeys_ = [];
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isInterimTile = !sourceTileGrid;
|
||||
|
||||
/**
|
||||
* @type {Array<import("./events.js").EventsKey>}
|
||||
*/
|
||||
this.sourceTileListenerKeys_ = [];
|
||||
|
||||
this.key = sourceRevision.toString();
|
||||
|
||||
if (urlTileCoord && sourceTileGrid) {
|
||||
const extent = this.extent = tileGrid.getTileCoordExtent(urlTileCoord);
|
||||
const resolution = this.resolution_ = tileGrid.getResolution(urlTileCoord[0]);
|
||||
const sourceZ = sourceTileGrid.getZForResolution(resolution);
|
||||
sourceTileGrid.forEachTileCoord(extent, sourceZ, function(sourceTileCoord) {
|
||||
let sharedExtent = getIntersection(extent,
|
||||
sourceTileGrid.getTileCoordExtent(sourceTileCoord));
|
||||
const sourceExtent = sourceTileGrid.getExtent();
|
||||
if (sourceExtent) {
|
||||
sharedExtent = getIntersection(sharedExtent, sourceExtent, sharedExtent);
|
||||
}
|
||||
if (getWidth(sharedExtent) / resolution >= 0.5 &&
|
||||
getHeight(sharedExtent) / resolution >= 0.5) {
|
||||
// only include source tile if overlap is at least 1 pixel
|
||||
const sourceTileKey = sourceTileCoord.toString();
|
||||
let sourceTile = sourceTiles[sourceTileKey];
|
||||
if (!sourceTile) {
|
||||
const tileUrl = tileUrlFunction(sourceTileCoord, pixelRatio, projection);
|
||||
sourceTile = sourceTiles[sourceTileKey] = new tileClass(sourceTileCoord,
|
||||
tileUrl == undefined ? TileState.EMPTY : TileState.IDLE,
|
||||
tileUrl == undefined ? '' : tileUrl,
|
||||
format, tileLoadFunction);
|
||||
this.sourceTileListenerKeys_.push(
|
||||
listen(sourceTile, EventType.CHANGE, handleTileChange));
|
||||
}
|
||||
sourceTile.consumers++;
|
||||
this.tileKeys.push(sourceTileKey);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
disposeInternal() {
|
||||
if (!this.isInterimTile) {
|
||||
this.setState(TileState.ABORT);
|
||||
}
|
||||
if (this.interimTile) {
|
||||
this.interimTile.dispose();
|
||||
this.interimTile = null;
|
||||
}
|
||||
for (let i = 0, ii = this.tileKeys.length; i < ii; ++i) {
|
||||
const sourceTileKey = this.tileKeys[i];
|
||||
const sourceTile = this.getTile(sourceTileKey);
|
||||
sourceTile.consumers--;
|
||||
if (sourceTile.consumers == 0) {
|
||||
delete this.sourceTiles_[sourceTileKey];
|
||||
sourceTile.dispose();
|
||||
}
|
||||
}
|
||||
this.tileKeys.length = 0;
|
||||
this.sourceTiles_ = null;
|
||||
this.loadListenerKeys_.forEach(unlistenByKey);
|
||||
this.loadListenerKeys_.length = 0;
|
||||
this.sourceTileListenerKeys_.forEach(unlistenByKey);
|
||||
this.sourceTileListenerKeys_.length = 0;
|
||||
super.disposeInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||
* @return {CanvasRenderingContext2D} The rendering context.
|
||||
*/
|
||||
getContext(layer) {
|
||||
const key = getUid(layer);
|
||||
if (!(key in this.context_)) {
|
||||
this.context_[key] = createCanvasContext2D();
|
||||
}
|
||||
return this.context_[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||
* @return {boolean} Tile has a rendering context for the given layer.
|
||||
*/
|
||||
hasContext(layer) {
|
||||
return getUid(layer) in this.context_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Canvas for this tile.
|
||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||
* @return {HTMLCanvasElement} Canvas.
|
||||
*/
|
||||
getImage(layer) {
|
||||
return this.hasContext(layer) ? this.getContext(layer).canvas : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return {VectorImageTile} Interim tile.
|
||||
*/
|
||||
getInterimTile() {
|
||||
const sourceTileGrid = this.sourceTileGrid_;
|
||||
const state = this.getState();
|
||||
if (state < TileState.LOADED && !this.interimTile) {
|
||||
let z = this.tileCoord[0];
|
||||
const minZoom = sourceTileGrid.getMinZoom();
|
||||
while (--z > minZoom) {
|
||||
let covered = true;
|
||||
const tileKeys = [];
|
||||
sourceTileGrid.forEachTileCoord(this.extent, z, function(tileCoord) {
|
||||
const key = tileCoord.toString();
|
||||
if (key in this.sourceTiles_ && this.sourceTiles_[key].getState() === TileState.LOADED) {
|
||||
tileKeys.push(key);
|
||||
} else {
|
||||
covered = false;
|
||||
}
|
||||
}.bind(this));
|
||||
if (covered && tileKeys.length) {
|
||||
for (let i = 0, ii = tileKeys.length; i < ii; ++i) {
|
||||
this.sourceTiles_[tileKeys[i]].consumers++;
|
||||
}
|
||||
const tile = new VectorImageTile(this.tileCoord, TileState.IDLE, Number(this.key), null, null,
|
||||
this.wrappedTileCoord, null, null, null, this.sourceTiles_,
|
||||
undefined, null, null, null);
|
||||
tile.extent = this.extent;
|
||||
tile.tileKeys = tileKeys;
|
||||
tile.context_ = this.context_;
|
||||
setTimeout(function() {
|
||||
tile.sourceTilesLoaded = true;
|
||||
tile.changed();
|
||||
}, 16);
|
||||
this.interimTile = tile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const interimTile = /** @type {VectorImageTile} */ (this.interimTile);
|
||||
return state === TileState.LOADED ? this : (interimTile || this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||
* @return {ReplayState} The replay state.
|
||||
*/
|
||||
getReplayState(layer) {
|
||||
const key = getUid(layer);
|
||||
if (!(key in this.replayState_)) {
|
||||
this.replayState_[key] = {
|
||||
dirty: false,
|
||||
renderedRenderOrder: null,
|
||||
renderedRevision: -1,
|
||||
renderedTileRevision: -1
|
||||
};
|
||||
}
|
||||
return this.replayState_[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} tileKey Key (tileCoord) of the source tile.
|
||||
* @return {import("./VectorTile.js").default} Source tile.
|
||||
*/
|
||||
getTile(tileKey) {
|
||||
return this.sourceTiles_[tileKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
load() {
|
||||
// Source tiles with LOADED state - we just count them because once they are
|
||||
// loaded, we're no longer listening to state changes.
|
||||
let leftToLoad = 0;
|
||||
// Source tiles with ERROR state - we track them because they can still have
|
||||
// an ERROR state after another load attempt.
|
||||
const errorSourceTiles = {};
|
||||
|
||||
if (this.state == TileState.IDLE) {
|
||||
this.setState(TileState.LOADING);
|
||||
}
|
||||
if (this.state == TileState.LOADING) {
|
||||
this.tileKeys.forEach(function(sourceTileKey) {
|
||||
const sourceTile = this.getTile(sourceTileKey);
|
||||
if (sourceTile.state == TileState.IDLE) {
|
||||
sourceTile.setLoader(this.loader_);
|
||||
sourceTile.load();
|
||||
}
|
||||
if (sourceTile.state == TileState.LOADING) {
|
||||
const key = listen(sourceTile, EventType.CHANGE, function(e) {
|
||||
const state = sourceTile.getState();
|
||||
if (state == TileState.LOADED ||
|
||||
state == TileState.ERROR) {
|
||||
const uid = getUid(sourceTile);
|
||||
if (state == TileState.ERROR) {
|
||||
errorSourceTiles[uid] = true;
|
||||
} else {
|
||||
--leftToLoad;
|
||||
delete errorSourceTiles[uid];
|
||||
}
|
||||
if (leftToLoad - Object.keys(errorSourceTiles).length == 0) {
|
||||
this.finishLoading_();
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
this.loadListenerKeys_.push(key);
|
||||
++leftToLoad;
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
if (leftToLoad - Object.keys(errorSourceTiles).length == 0) {
|
||||
setTimeout(this.finishLoading_.bind(this), 16);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
finishLoading_() {
|
||||
let loaded = this.tileKeys.length;
|
||||
let empty = 0;
|
||||
for (let i = loaded - 1; i >= 0; --i) {
|
||||
const state = this.getTile(this.tileKeys[i]).getState();
|
||||
if (state != TileState.LOADED) {
|
||||
--loaded;
|
||||
}
|
||||
if (state == TileState.EMPTY) {
|
||||
++empty;
|
||||
}
|
||||
}
|
||||
if (loaded == this.tileKeys.length) {
|
||||
this.loadListenerKeys_.forEach(unlistenByKey);
|
||||
this.loadListenerKeys_.length = 0;
|
||||
this.sourceTilesLoaded = true;
|
||||
this.changed();
|
||||
} else {
|
||||
this.setState(empty == this.tileKeys.length ? TileState.EMPTY : TileState.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default VectorImageTile;
|
||||
|
||||
/**
|
||||
* Sets the loader for a tile.
|
||||
* @param {import("./VectorTile.js").default} tile Vector tile.
|
||||
* @param {string} url URL.
|
||||
*/
|
||||
export function defaultLoadFunction(tile, url) {
|
||||
const loader = loadFeaturesXhr(url, tile.getFormat(), tile.onLoad.bind(tile), tile.onError.bind(tile));
|
||||
tile.setLoader(loader);
|
||||
}
|
||||
168
src/ol/VectorRenderTile.js
Normal file
168
src/ol/VectorRenderTile.js
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* @module ol/VectorRenderTile
|
||||
*/
|
||||
import {getUid} from './util.js';
|
||||
import Tile from './Tile.js';
|
||||
import TileState from './TileState.js';
|
||||
import {createCanvasContext2D} from './dom.js';
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {Object} ReplayState
|
||||
* @property {boolean} dirty
|
||||
* @property {null|import("./render.js").OrderFunction} renderedRenderOrder
|
||||
* @property {number} renderedTileRevision
|
||||
* @property {number} renderedRevision
|
||||
* @property {number} renderedZ
|
||||
* @property {number} renderedTileZ
|
||||
*/
|
||||
|
||||
|
||||
class VectorRenderTile extends Tile {
|
||||
|
||||
/**
|
||||
* @param {import("./tilecoord.js").TileCoord} tileCoord Tile coordinate.
|
||||
* @param {TileState} state State.
|
||||
* @param {import("./tilecoord.js").TileCoord} urlTileCoord Wrapped tile coordinate for source urls.
|
||||
* @param {import("./tilegrid/TileGrid.js").default} sourceTileGrid Tile grid of the source.
|
||||
* @param {function(VectorRenderTile):Array<import("./VectorTile").default>} getSourceTiles Function
|
||||
* to get an source tiles for this tile.
|
||||
* @param {function(VectorRenderTile):void} removeSourceTiles Function to remove this tile from its
|
||||
* source tiles's consumer count.
|
||||
*/
|
||||
constructor(tileCoord, state, urlTileCoord, sourceTileGrid, getSourceTiles, removeSourceTiles) {
|
||||
|
||||
super(tileCoord, state, {transition: 0});
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {!Object<string, CanvasRenderingContext2D>}
|
||||
*/
|
||||
this.context_ = {};
|
||||
|
||||
/**
|
||||
* Executor groups by layer uid. Entries are read/written by the renderer.
|
||||
* @type {Object<string, Array<import("./render/canvas/ExecutorGroup.js").default>>}
|
||||
*/
|
||||
this.executorGroups = {};
|
||||
|
||||
/**
|
||||
* Number of loading source tiles. Read/written by the source.
|
||||
* @type {number}
|
||||
*/
|
||||
this.loadingSourceTiles = 0;
|
||||
|
||||
/**
|
||||
* Tile keys of error source tiles. Read/written by the source.
|
||||
* @type {Object<string, boolean>}
|
||||
*/
|
||||
this.errorSourceTileKeys = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {!Object<string, ReplayState>}
|
||||
*/
|
||||
this.replayState_ = {};
|
||||
|
||||
/**
|
||||
* @type {!function(import("./VectorRenderTile.js").default):Array<import("./VectorTile.js").default>}
|
||||
*/
|
||||
this.getSourceTiles_ = getSourceTiles;
|
||||
|
||||
/**
|
||||
* @type {!function(import("./VectorRenderTile.js").default):void}
|
||||
*/
|
||||
this.removeSourceTiles_ = removeSourceTiles;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {import("./tilegrid/TileGrid.js").default}
|
||||
*/
|
||||
this.sourceTileGrid_ = sourceTileGrid;
|
||||
|
||||
/**
|
||||
* z of the source tiles of the last getSourceTiles call.
|
||||
* @type {number}
|
||||
*/
|
||||
this.sourceZ = -1;
|
||||
|
||||
/**
|
||||
* True when all tiles for this tile's nominal resolution are available.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.hifi = false;
|
||||
|
||||
/**
|
||||
* @type {import("./tilecoord.js").TileCoord}
|
||||
*/
|
||||
this.wrappedTileCoord = urlTileCoord;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
disposeInternal() {
|
||||
this.removeSourceTiles_(this);
|
||||
this.setState(TileState.ABORT);
|
||||
super.disposeInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||
* @return {CanvasRenderingContext2D} The rendering context.
|
||||
*/
|
||||
getContext(layer) {
|
||||
const key = getUid(layer);
|
||||
if (!(key in this.context_)) {
|
||||
this.context_[key] = createCanvasContext2D();
|
||||
}
|
||||
return this.context_[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||
* @return {boolean} Tile has a rendering context for the given layer.
|
||||
*/
|
||||
hasContext(layer) {
|
||||
return getUid(layer) in this.context_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Canvas for this tile.
|
||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||
* @return {HTMLCanvasElement} Canvas.
|
||||
*/
|
||||
getImage(layer) {
|
||||
return this.hasContext(layer) ? this.getContext(layer).canvas : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||
* @return {ReplayState} The replay state.
|
||||
*/
|
||||
getReplayState(layer) {
|
||||
const key = getUid(layer);
|
||||
if (!(key in this.replayState_)) {
|
||||
this.replayState_[key] = {
|
||||
dirty: false,
|
||||
renderedRenderOrder: null,
|
||||
renderedRevision: -1,
|
||||
renderedTileRevision: -1,
|
||||
renderedZ: -1,
|
||||
renderedTileZ: -1
|
||||
};
|
||||
}
|
||||
return this.replayState_[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return {Array<import("./VectorTile.js").default>} Source tiles for this tile.
|
||||
*/
|
||||
load() {
|
||||
return this.getSourceTiles_(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default VectorRenderTile;
|
||||
@@ -61,12 +61,6 @@ class VectorTile extends Tile {
|
||||
*/
|
||||
this.projection_ = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Object<string, import("./render/canvas/ExecutorGroup.js").default>}
|
||||
*/
|
||||
this.executorGroups_ = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {import("./Tile.js").LoadFunction}
|
||||
@@ -85,10 +79,7 @@ class VectorTile extends Tile {
|
||||
* @inheritDoc
|
||||
*/
|
||||
disposeInternal() {
|
||||
this.features_ = null;
|
||||
this.executorGroups_ = {};
|
||||
this.state = TileState.ABORT;
|
||||
this.changed();
|
||||
this.setState(TileState.ABORT);
|
||||
super.disposeInternal();
|
||||
}
|
||||
|
||||
@@ -137,15 +128,6 @@ class VectorTile extends Tile {
|
||||
return this.projection_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} layerId UID of the layer.
|
||||
* @param {string} key Key.
|
||||
* @return {import("./render/canvas/ExecutorGroup.js").default} Executor group.
|
||||
*/
|
||||
getExecutorGroup(layerId, key) {
|
||||
return this.executorGroups_[layerId + ',' + key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -214,15 +196,6 @@ class VectorTile extends Tile {
|
||||
this.projection_ = projection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} layerId UID of the layer.
|
||||
* @param {string} key Key.
|
||||
* @param {import("./render/canvas/ExecutorGroup.js").default} executorGroup Executor group.
|
||||
*/
|
||||
setExecutorGroup(layerId, key, executorGroup) {
|
||||
this.executorGroups_[layerId + ',' + key] = executorGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the feature loader for reading this tile's features.
|
||||
* @param {import("./featureloader.js").FeatureLoader} loader Feature loader.
|
||||
|
||||
@@ -26,7 +26,7 @@ export {default as Tile} from './Tile.js';
|
||||
export {default as TileCache} from './TileCache.js';
|
||||
export {default as TileQueue} from './TileQueue.js';
|
||||
export {default as TileRange} from './TileRange.js';
|
||||
export {default as VectorImageTile} from './VectorImageTile.js';
|
||||
export {default as VectorRenderTile} from './VectorRenderTile.js';
|
||||
export {default as VectorTile} from './VectorTile.js';
|
||||
export {default as View} from './View.js';
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
import {getUid} from '../../util.js';
|
||||
import {createCanvasContext2D} from '../../dom.js';
|
||||
import {getValues} from '../../obj.js';
|
||||
import TileState from '../../TileState.js';
|
||||
import ViewHint from '../../ViewHint.js';
|
||||
import {listen, unlisten, unlistenByKey} from '../../events.js';
|
||||
@@ -30,6 +29,7 @@ import {
|
||||
makeInverse
|
||||
} from '../../transform.js';
|
||||
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
||||
import {isEmpty} from '../../obj.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -125,15 +125,14 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Array<import("../../VectorImageTile.js").default>}
|
||||
* @type {!Object<string, import("../../VectorRenderTile.js").default>}
|
||||
*/
|
||||
this.tilesWithoutImage_ = null;
|
||||
this.renderTileImageQueue_ = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Object<string, import("../../events").EventsKey>}
|
||||
* @type {Object<string, import("../../events.js").EventsKey>}
|
||||
*/
|
||||
this.tileChangeKeys_ = {};
|
||||
this.tileListenerKeys_ = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
@@ -153,34 +152,23 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
*/
|
||||
disposeInternal() {
|
||||
unlisten(labelCache, EventType.CLEAR, this.handleFontsChanged_, this);
|
||||
getValues(this.tileChangeKeys_).forEach(unlistenByKey);
|
||||
super.disposeInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to tile changes and mark tile as loaded when source tiles are loaded.
|
||||
* @param {import("../../VectorImageTile").default} tile Tile to listen on.
|
||||
* @param {import("../../VectorRenderTile.js").default} tile Tile.
|
||||
* @param {number} pixelRatio Pixel ratio.
|
||||
* @param {number} projection Projection.
|
||||
* @private
|
||||
* @param {import("../../proj/Projection").default} projection Projection.
|
||||
*/
|
||||
listenTileChange_(tile, pixelRatio, projection) {
|
||||
const uid = getUid(tile);
|
||||
if (!(uid in this.tileChangeKeys_) && tile.getState() === TileState.IDLE) {
|
||||
this.tileChangeKeys_[uid] = listen(tile, EventType.CHANGE, function() {
|
||||
const state = tile.getState();
|
||||
if (state === TileState.ABORT || tile.sourceTilesLoaded) {
|
||||
unlistenByKey(this.tileChangeKeys_[uid]);
|
||||
delete this.tileChangeKeys_[uid];
|
||||
if (tile.sourceTilesLoaded) {
|
||||
// Create render instructions immediately when all source tiles are available.
|
||||
//TODO Make sure no canvas operations are involved in instruction creation.
|
||||
this.updateExecutorGroup_(tile, pixelRatio, projection);
|
||||
//FIXME This should be done by the tile, and VectorImage tiles should be layer specific
|
||||
tile.setState(TileState.LOADED);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
prepareTile(tile, pixelRatio, projection) {
|
||||
const tileUid = getUid(tile);
|
||||
if (tile.getState() === TileState.LOADED || tile.getState() === TileState.ERROR) {
|
||||
unlistenByKey(this.tileListenerKeys_[tileUid]);
|
||||
delete this.tileListenerKeys_[tileUid];
|
||||
this.updateExecutorGroup_(tile, pixelRatio, projection);
|
||||
if (this.tileImageNeedsRender_(tile, pixelRatio, projection)) {
|
||||
this.renderTileImageQueue_[tileUid] = tile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,40 +176,18 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
* @inheritDoc
|
||||
*/
|
||||
getTile(z, x, y, pixelRatio, projection) {
|
||||
const tile = /** @type {import("../../VectorImageTile.js").default} */ (super.getTile(z, x, y, pixelRatio, projection));
|
||||
this.listenTileChange_(tile, pixelRatio, projection);
|
||||
if (tile.isInterimTile) {
|
||||
// Register change listener also on the original tile
|
||||
const source = /** @type {import("../../source/VectorTile").default} */ (this.getLayer().getSource());
|
||||
const originalTile = /** @type {import("../../VectorImageTile").default} */ (source.getTile(z, x, y, pixelRatio, projection));
|
||||
this.listenTileChange_(originalTile, pixelRatio, projection);
|
||||
}
|
||||
if (tile.getState() === TileState.LOADED) {
|
||||
// Update existing instructions if necessary (e.g. when the style has changed)
|
||||
this.updateExecutorGroup_(tile, pixelRatio, projection);
|
||||
const layer = this.getLayer();
|
||||
if (tile.getReplayState(layer).renderedTileRevision !== -1) {
|
||||
// Update existing tile image if necessary (e.g. when the style has changed)
|
||||
this.renderTileImage_(tile, pixelRatio, projection);
|
||||
} else {
|
||||
// Render new tile images after existing tiles have been drawn to the target canvas.
|
||||
this.tilesWithoutImage_.push(tile);
|
||||
const tile = /** @type {import("../../VectorRenderTile.js").default} */ (super.getTile(z, x, y, pixelRatio, projection));
|
||||
this.prepareTile(tile, pixelRatio, projection);
|
||||
if (tile.getState() < TileState.LOADED) {
|
||||
const tileUid = getUid(tile);
|
||||
if (!(tileUid in this.tileListenerKeys_)) {
|
||||
const listenerKey = listen(tile, EventType.CHANGE, this.prepareTile.bind(this, tile, pixelRatio, projection));
|
||||
this.tileListenerKeys_[tileUid] = listenerKey;
|
||||
}
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
loadedTileCallback(tiles, zoom, tile) {
|
||||
if (!tile.hasContext(this.getLayer())) {
|
||||
this.tilesWithoutImage_.push(tile);
|
||||
return false;
|
||||
}
|
||||
return super.loadedTileCallback(tiles, zoom, tile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
@@ -251,7 +217,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../../VectorImageTile.js").default} tile Tile.
|
||||
* @param {import("../../VectorRenderTile.js").default} tile Tile.
|
||||
* @param {number} pixelRatio Pixel ratio.
|
||||
* @param {import("../../proj/Projection.js").default} projection Projection.
|
||||
* @private
|
||||
@@ -263,7 +229,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
|
||||
const builderState = tile.getReplayState(layer);
|
||||
if (!builderState.dirty && builderState.renderedRevision == revision &&
|
||||
builderState.renderedRenderOrder == renderOrder) {
|
||||
builderState.renderedRenderOrder == renderOrder && builderState.renderedZ === tile.sourceZ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -272,10 +238,13 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
const tileGrid = source.getTileGridForProjection(projection);
|
||||
const zoom = tile.tileCoord[0];
|
||||
const resolution = tileGrid.getResolution(zoom);
|
||||
const tileExtent = tile.extent;
|
||||
const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
|
||||
|
||||
for (let t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
|
||||
const sourceTile = tile.getTile(tile.tileKeys[t]);
|
||||
const sourceTiles = tile.load();
|
||||
const layerUid = getUid(layer);
|
||||
tile.executorGroups[layerUid] = [];
|
||||
for (let t = 0, tt = sourceTiles.length; t < tt; ++t) {
|
||||
const sourceTile = sourceTiles[t];
|
||||
if (sourceTile.getState() != TileState.LOADED) {
|
||||
continue;
|
||||
}
|
||||
@@ -334,9 +303,10 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
const executorGroupInstructions = builderGroup.finish();
|
||||
const renderingReplayGroup = new CanvasExecutorGroup(sharedExtent, resolution,
|
||||
pixelRatio, source.getOverlaps(), this.declutterTree_, executorGroupInstructions, layer.getRenderBuffer());
|
||||
sourceTile.setExecutorGroup(getUid(layer), tile.tileCoord.toString(), renderingReplayGroup);
|
||||
tile.executorGroups[layerUid].push(renderingReplayGroup);
|
||||
}
|
||||
builderState.renderedRevision = revision;
|
||||
builderState.renderedZ = tile.sourceZ;
|
||||
builderState.renderedRenderOrder = renderOrder;
|
||||
}
|
||||
|
||||
@@ -348,26 +318,25 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
const rotation = frameState.viewState.rotation;
|
||||
hitTolerance = hitTolerance == undefined ? 0 : hitTolerance;
|
||||
const layer = this.getLayer();
|
||||
const source = /** @type {import("../../source/VectorTile").default} */ (layer.getSource());
|
||||
const tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
|
||||
/** @type {!Object<string, boolean>} */
|
||||
const features = {};
|
||||
|
||||
const renderedTiles = /** @type {Array<import("../../VectorImageTile.js").default>} */ (this.renderedTiles);
|
||||
const renderedTiles = /** @type {Array<import("../../VectorRenderTile.js").default>} */ (this.renderedTiles);
|
||||
|
||||
let bufferedExtent, found;
|
||||
let i, ii;
|
||||
for (i = 0, ii = renderedTiles.length; i < ii; ++i) {
|
||||
const tile = renderedTiles[i];
|
||||
bufferedExtent = buffer(tile.extent, hitTolerance * resolution, bufferedExtent);
|
||||
const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
|
||||
bufferedExtent = buffer(tileExtent, hitTolerance * resolution, bufferedExtent);
|
||||
if (!containsCoordinate(bufferedExtent, coordinate)) {
|
||||
continue;
|
||||
}
|
||||
for (let t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
|
||||
const sourceTile = tile.getTile(tile.tileKeys[t]);
|
||||
if (sourceTile.getState() != TileState.LOADED) {
|
||||
continue;
|
||||
}
|
||||
const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(getUid(layer),
|
||||
tile.tileCoord.toString()));
|
||||
const executorGroups = tile.executorGroups[getUid(layer)];
|
||||
for (let t = 0, tt = executorGroups.length; t < tt; ++t) {
|
||||
const executorGroup = executorGroups[t];
|
||||
found = found || executorGroup.forEachFeatureAtCoordinate(coordinate, resolution, rotation, hitTolerance, {},
|
||||
/**
|
||||
* @param {import("../../Feature.js").FeatureLike} feature Feature.
|
||||
@@ -437,7 +406,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
* @inheritDoc
|
||||
*/
|
||||
renderFrame(frameState, layerState) {
|
||||
this.tilesWithoutImage_ = [];
|
||||
super.renderFrame(frameState, layerState);
|
||||
|
||||
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
||||
@@ -445,7 +413,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
const hifi = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
||||
const renderMode = layer.getRenderMode();
|
||||
if (renderMode === VectorTileRenderType.IMAGE) {
|
||||
this.renderMissingTileImages_(hifi, frameState);
|
||||
this.renderTileImages_(hifi, frameState);
|
||||
return this.container_;
|
||||
}
|
||||
|
||||
@@ -484,25 +452,23 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
const clips = [];
|
||||
const zs = [];
|
||||
for (let i = tiles.length - 1; i >= 0; --i) {
|
||||
const tile = /** @type {import("../../VectorImageTile.js").default} */ (tiles[i]);
|
||||
const tile = /** @type {import("../../VectorRenderTile.js").default} */ (tiles[i]);
|
||||
if (tile.getState() == TileState.ABORT) {
|
||||
continue;
|
||||
}
|
||||
const tileCoord = tile.tileCoord;
|
||||
const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tile.extent[0];
|
||||
const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
|
||||
const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tileExtent[0];
|
||||
const transform = this.getRenderTransform(frameState, width, height, worldOffset);
|
||||
for (let t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
|
||||
const sourceTile = tile.getTile(tile.tileKeys[t]);
|
||||
if (sourceTile.getState() != TileState.LOADED) {
|
||||
continue;
|
||||
}
|
||||
const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(getUid(layer), tileCoord.toString()));
|
||||
if (!executorGroup || !executorGroup.hasExecutors(replayTypes)) {
|
||||
const executorGroups = tile.executorGroups[getUid(layer)];
|
||||
for (let t = 0, tt = executorGroups.length; t < tt; ++t) {
|
||||
const executorGroup = executorGroups[t];
|
||||
if (!executorGroup.hasExecutors(replayTypes)) {
|
||||
// sourceTile was not yet loaded when this.createReplayGroup_() was
|
||||
// called, or it has no replays of the types we want to render
|
||||
continue;
|
||||
}
|
||||
const currentZ = sourceTile.tileCoord[0];
|
||||
const currentZ = tile.tileCoord[0];
|
||||
const currentClip = executorGroup.getClipCoords(transform);
|
||||
context.save();
|
||||
|
||||
@@ -540,9 +506,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
canvas.style.opacity = opacity;
|
||||
}
|
||||
|
||||
// Now that we have rendered the tiles we have already, let's prepare new tiles for the
|
||||
// next frame
|
||||
this.renderMissingTileImages_(hifi, frameState);
|
||||
// Now that we have rendered the tiles we have already, let's prepare new tile images
|
||||
// for the next frame
|
||||
this.renderTileImages_(hifi, frameState);
|
||||
|
||||
return this.container_;
|
||||
}
|
||||
@@ -551,19 +517,22 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
* @param {boolean} hifi We have time to render a high fidelity map image.
|
||||
* @param {import('../../PluggableMap.js').FrameState} frameState Frame state.
|
||||
*/
|
||||
renderMissingTileImages_(hifi, frameState) {
|
||||
renderTileImages_(hifi, frameState) {
|
||||
// Even when we have time to render hifi, do not spend more than 100 ms in this render frame,
|
||||
// to avoid delays when the user starts interacting again with the map.
|
||||
while (this.tilesWithoutImage_.length && Date.now() - frameState.time < 100) {
|
||||
frameState.animate = true;
|
||||
const tile = this.tilesWithoutImage_.pop();
|
||||
// When we don't have time to render hifi, only render interim tiles until we have used up
|
||||
// half of the frame budget of 16 ms
|
||||
if (hifi || (tile.isInterimTile && Date.now() - frameState.time < 8)) {
|
||||
this.renderTileImage_(tile, frameState.pixelRatio, frameState.viewState.projection);
|
||||
// When we don't have time to render hifi, only render lowres tiles until we have used up
|
||||
// half of the frame budget of 16 ms
|
||||
for (const uid in this.renderTileImageQueue_) {
|
||||
if (Date.now() - frameState.time > (hifi ? 100 : 8)) {
|
||||
break;
|
||||
}
|
||||
const tile = this.renderTileImageQueue_[uid];
|
||||
frameState.animate = true;
|
||||
delete this.renderTileImageQueue_[uid];
|
||||
this.renderTileImage_(tile, frameState.pixelRatio, frameState.viewState.projection);
|
||||
}
|
||||
if (this.tilesWithoutImage_.length) {
|
||||
if (!isEmpty(this.renderTileImageQueue_)) {
|
||||
// If there's items left in the queue, render them in another frame
|
||||
frameState.animate = true;
|
||||
}
|
||||
}
|
||||
@@ -595,7 +564,22 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../../VectorImageTile.js").default} tile Tile.
|
||||
* @param {import("../../VectorRenderTile.js").default} tile Tile.
|
||||
* @param {number} pixelRatio Pixel ratio.
|
||||
* @param {import("../../proj/Projection.js").default} projection Projection.
|
||||
* @return {boolean} A new tile image was rendered.
|
||||
* @private
|
||||
*/
|
||||
tileImageNeedsRender_(tile, pixelRatio, projection) {
|
||||
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
||||
const replayState = tile.getReplayState(layer);
|
||||
const revision = layer.getRevision();
|
||||
const sourceZ = tile.sourceZ;
|
||||
return replayState.renderedTileRevision !== revision || replayState.renderedTileZ !== sourceZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../../VectorRenderTile.js").default} tile Tile.
|
||||
* @param {number} pixelRatio Pixel ratio.
|
||||
* @param {import("../../proj/Projection.js").default} projection Projection.
|
||||
* @private
|
||||
@@ -604,32 +588,26 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
||||
const replayState = tile.getReplayState(layer);
|
||||
const revision = layer.getRevision();
|
||||
const replays = IMAGE_REPLAYS[layer.getRenderMode()];
|
||||
if (replays && replayState.renderedTileRevision !== revision) {
|
||||
replayState.renderedTileRevision = revision;
|
||||
const tileCoord = tile.wrappedTileCoord;
|
||||
const z = tileCoord[0];
|
||||
const source = /** @type {import("../../source/VectorTile.js").default} */ (layer.getSource());
|
||||
const tileGrid = source.getTileGridForProjection(projection);
|
||||
const resolution = tileGrid.getResolution(z);
|
||||
const context = tile.getContext(layer);
|
||||
const size = source.getTilePixelSize(z, pixelRatio, projection);
|
||||
context.canvas.width = size[0];
|
||||
context.canvas.height = size[1];
|
||||
const tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent);
|
||||
for (let i = 0, ii = tile.tileKeys.length; i < ii; ++i) {
|
||||
const sourceTile = tile.getTile(tile.tileKeys[i]);
|
||||
if (sourceTile.getState() != TileState.LOADED) {
|
||||
continue;
|
||||
}
|
||||
const pixelScale = pixelRatio / resolution;
|
||||
const transform = resetTransform(this.tmpTransform_);
|
||||
scaleTransform(transform, pixelScale, -pixelScale);
|
||||
translateTransform(transform, -tileExtent[0], -tileExtent[3]);
|
||||
const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(getUid(layer),
|
||||
tile.tileCoord.toString()));
|
||||
executorGroup.execute(context, transform, 0, {}, true, replays);
|
||||
}
|
||||
const executorGroups = tile.executorGroups[getUid(layer)];
|
||||
replayState.renderedTileRevision = revision;
|
||||
replayState.renderedTileZ = tile.sourceZ;
|
||||
const tileCoord = tile.wrappedTileCoord;
|
||||
const z = tileCoord[0];
|
||||
const source = /** @type {import("../../source/VectorTile.js").default} */ (layer.getSource());
|
||||
const tileGrid = source.getTileGridForProjection(projection);
|
||||
const resolution = tileGrid.getResolution(z);
|
||||
const context = tile.getContext(layer);
|
||||
const size = source.getTilePixelSize(z, pixelRatio, projection);
|
||||
context.canvas.width = size[0];
|
||||
context.canvas.height = size[1];
|
||||
const tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent);
|
||||
for (let i = 0, ii = executorGroups.length; i < ii; ++i) {
|
||||
const executorGroup = executorGroups[i];
|
||||
const pixelScale = pixelRatio / resolution;
|
||||
const transform = resetTransform(this.tmpTransform_);
|
||||
scaleTransform(transform, pixelScale, -pixelScale);
|
||||
translateTransform(transform, -tileExtent[0], -tileExtent[3]);
|
||||
executorGroup.execute(context, transform, 0, {}, true, IMAGE_REPLAYS[layer.getRenderMode()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,18 @@
|
||||
*/
|
||||
|
||||
import TileState from '../TileState.js';
|
||||
import VectorImageTile, {defaultLoadFunction} from '../VectorImageTile.js';
|
||||
import VectorRenderTile from '../VectorRenderTile.js';
|
||||
import Tile from '../VectorTile.js';
|
||||
import {toSize} from '../size.js';
|
||||
import UrlTile from './UrlTile.js';
|
||||
import {getKeyZXY} from '../tilecoord.js';
|
||||
import {getKeyZXY, getKey} from '../tilecoord.js';
|
||||
import {createXYZ, extentFromProjection, createForProjection} from '../tilegrid.js';
|
||||
import {buffer as bufferExtent, getIntersection} from '../extent.js';
|
||||
import {listen, unlistenByKey} from '../events.js';
|
||||
import EventType from '../events/EventType.js';
|
||||
import {loadFeaturesXhr} from '../featureloader.js';
|
||||
import {isEmpty} from '../obj.js';
|
||||
import {equals} from '../array.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Options
|
||||
@@ -110,11 +116,22 @@ class VectorTile extends UrlTile {
|
||||
this.format_ = options.format ? options.format : null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Object<string, Tile>}
|
||||
*/
|
||||
* @type {Object<string, import("./VectorTile").default>}
|
||||
*/
|
||||
this.loadingTiles_ = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Object<string, import("../VectorTile.js").default>}
|
||||
*/
|
||||
this.sourceTiles_ = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Object<string, Array<import("../VectorTile.js").default>>}
|
||||
*/
|
||||
this.sourceTilesByTileKey_ = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {boolean}
|
||||
@@ -149,6 +166,139 @@ class VectorTile extends UrlTile {
|
||||
clear() {
|
||||
this.tileCache.clear();
|
||||
this.sourceTiles_ = {};
|
||||
this.sourceTilesByTileKey_ = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pixelRatio Pixel ratio.
|
||||
* @param {import("../proj/Projection").default} projection Projection.
|
||||
* @param {VectorRenderTile} tile Vector image tile.
|
||||
* @return {Array<import("../VectorTile").default>} Tile keys.
|
||||
*/
|
||||
getSourceTiles(pixelRatio, projection, tile) {
|
||||
const sourceTiles = [];
|
||||
const urlTileCoord = tile.wrappedTileCoord;
|
||||
if (urlTileCoord) {
|
||||
const tileGrid = this.getTileGridForProjection(projection);
|
||||
const extent = tileGrid.getTileCoordExtent(urlTileCoord);
|
||||
const z = urlTileCoord[0];
|
||||
const resolution = tileGrid.getResolution(z);
|
||||
// make extent 1 pixel smaller so we don't load tiles for < 0.5 pixel render space
|
||||
bufferExtent(extent, -1 / resolution, extent);
|
||||
const sourceTileGrid = this.tileGrid;
|
||||
const sourceExtent = sourceTileGrid.getExtent();
|
||||
if (sourceExtent) {
|
||||
getIntersection(extent, sourceTileGrid.getExtent(), extent);
|
||||
}
|
||||
const sourceZ = sourceTileGrid.getZForResolution(resolution);
|
||||
const minZoom = sourceTileGrid.getMinZoom();
|
||||
|
||||
let loadedZ = sourceZ + 1;
|
||||
let covered, empty;
|
||||
do {
|
||||
--loadedZ;
|
||||
covered = true;
|
||||
empty = true;
|
||||
sourceTileGrid.forEachTileCoord(extent, loadedZ, function(sourceTileCoord) {
|
||||
const tileKey = getKey(sourceTileCoord);
|
||||
let sourceTile;
|
||||
if (tileKey in this.sourceTiles_) {
|
||||
sourceTile = this.sourceTiles_[tileKey];
|
||||
const state = sourceTile.getState();
|
||||
if (state === TileState.LOADED || state === TileState.ERROR || state === TileState.EMPTY) {
|
||||
empty = empty && state === TileState.EMPTY;
|
||||
sourceTiles.push(sourceTile);
|
||||
return;
|
||||
}
|
||||
} else if (loadedZ === sourceZ) {
|
||||
const tileUrl = this.tileUrlFunction(sourceTileCoord, pixelRatio, projection);
|
||||
sourceTile = new this.tileClass(sourceTileCoord,
|
||||
tileUrl == undefined ? TileState.EMPTY : TileState.IDLE,
|
||||
tileUrl == undefined ? '' : tileUrl,
|
||||
this.format_, this.tileLoadFunction);
|
||||
this.sourceTiles_[tileKey] = sourceTile;
|
||||
empty = empty && sourceTile.getState() === TileState.EMPTY;
|
||||
listen(sourceTile, EventType.CHANGE, this.handleTileChange, this);
|
||||
sourceTile.load();
|
||||
} else {
|
||||
empty = false;
|
||||
}
|
||||
covered = false;
|
||||
if (!sourceTile) {
|
||||
return;
|
||||
}
|
||||
if (sourceTile.getState() !== TileState.EMPTY && tile.getState() === TileState.IDLE) {
|
||||
tile.loadingSourceTiles++;
|
||||
const key = listen(sourceTile, EventType.CHANGE, function() {
|
||||
const state = sourceTile.getState();
|
||||
const sourceTileKey = getKey(sourceTile.tileCoord);
|
||||
if (state === TileState.LOADED || state === TileState.ERROR) {
|
||||
if (state === TileState.LOADED) {
|
||||
unlistenByKey(key);
|
||||
tile.loadingSourceTiles--;
|
||||
delete tile.errorSourceTileKeys[sourceTileKey];
|
||||
} else if (state === TileState.ERROR) {
|
||||
tile.errorSourceTileKeys[sourceTileKey] = true;
|
||||
}
|
||||
if (tile.loadingSourceTiles - Object.keys(tile.errorSourceTileKeys).length === 0) {
|
||||
tile.hifi = true;
|
||||
tile.sourceZ = sourceZ;
|
||||
tile.setState(isEmpty(tile.errorSourceTileKeys) ? TileState.LOADED : TileState.ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}.bind(this));
|
||||
if (!covered) {
|
||||
sourceTiles.length = 0;
|
||||
}
|
||||
} while (!covered && loadedZ > minZoom);
|
||||
if (!empty && tile.getState() === TileState.IDLE) {
|
||||
tile.setState(TileState.LOADING);
|
||||
}
|
||||
if (covered || empty) {
|
||||
tile.hifi = sourceZ === loadedZ;
|
||||
tile.sourceZ = loadedZ;
|
||||
const previousSourceTiles = this.sourceTilesByTileKey_[getKey(tile.tileCoord)];
|
||||
if (tile.getState() < TileState.LOADED) {
|
||||
tile.setState(empty ? TileState.EMPTY : TileState.LOADED);
|
||||
} else if (!previousSourceTiles || !equals(sourceTiles, previousSourceTiles)) {
|
||||
this.removeSourceTiles(tile);
|
||||
this.addSourceTiles(tile, sourceTiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sourceTiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {VectorRenderTile} tile Tile.
|
||||
* @param {Array<import("../VectorTile").default>} sourceTiles Source tiles.
|
||||
*/
|
||||
addSourceTiles(tile, sourceTiles) {
|
||||
this.sourceTilesByTileKey_[getKey(tile.tileCoord)] = sourceTiles;
|
||||
for (let i = 0, ii = sourceTiles.length; i < ii; ++i) {
|
||||
sourceTiles[i].consumers++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {VectorRenderTile} tile Tile.
|
||||
*/
|
||||
removeSourceTiles(tile) {
|
||||
const tileKey = getKey(tile.tileCoord);
|
||||
if (tileKey in this.sourceTilesByTileKey_) {
|
||||
const sourceTiles = this.sourceTilesByTileKey_[tileKey];
|
||||
for (let i = 0, ii = sourceTiles.length; i < ii; ++i) {
|
||||
const sourceTile = sourceTiles[i];
|
||||
sourceTile.consumers--;
|
||||
if (sourceTile.consumers === 0) {
|
||||
sourceTile.dispose();
|
||||
delete this.sourceTiles_[getKey(sourceTile.tileCoord)];
|
||||
}
|
||||
}
|
||||
}
|
||||
delete this.sourceTilesByTileKey_[tileKey];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,21 +314,20 @@ class VectorTile extends UrlTile {
|
||||
const tileCoord = [z, x, y];
|
||||
const urlTileCoord = this.getTileCoordForTileUrlFunction(
|
||||
tileCoord, projection);
|
||||
const tile = new VectorImageTile(
|
||||
const tile = new VectorRenderTile(
|
||||
tileCoord,
|
||||
urlTileCoord !== null ? TileState.IDLE : TileState.EMPTY,
|
||||
this.getRevision(),
|
||||
this.format_, this.tileLoadFunction, urlTileCoord, this.tileUrlFunction,
|
||||
this.tileGrid, this.getTileGridForProjection(projection),
|
||||
this.sourceTiles_, pixelRatio, projection, this.tileClass,
|
||||
this.handleTileChange.bind(this));
|
||||
urlTileCoord,
|
||||
this.tileGrid,
|
||||
this.getSourceTiles.bind(this, pixelRatio, projection),
|
||||
this.removeSourceTiles.bind(this));
|
||||
|
||||
tile.key = this.getRevision().toString();
|
||||
this.tileCache.set(tileCoordKey, tile);
|
||||
return tile;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -195,7 +344,6 @@ class VectorTile extends UrlTile {
|
||||
return tileGrid;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -203,7 +351,6 @@ class VectorTile extends UrlTile {
|
||||
return pixelRatio;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -216,3 +363,14 @@ class VectorTile extends UrlTile {
|
||||
|
||||
|
||||
export default VectorTile;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the loader for a tile.
|
||||
* @param {import("../VectorTile.js").default} tile Vector tile.
|
||||
* @param {string} url URL.
|
||||
*/
|
||||
export function defaultLoadFunction(tile, url) {
|
||||
const loader = loadFeaturesXhr(url, tile.getFormat(), tile.onLoad.bind(tile), tile.onError.bind(tile));
|
||||
tile.setLoader(loader);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import {clear} from '../../../../../src/ol/obj.js';
|
||||
import Feature from '../../../../../src/ol/Feature.js';
|
||||
import Map from '../../../../../src/ol/Map.js';
|
||||
import TileState from '../../../../../src/ol/TileState.js';
|
||||
import VectorImageTile from '../../../../../src/ol/VectorImageTile.js';
|
||||
import VectorRenderTile from '../../../../../src/ol/VectorRenderTile.js';
|
||||
import VectorTile from '../../../../../src/ol/VectorTile.js';
|
||||
import View from '../../../../../src/ol/View.js';
|
||||
import {getCenter} from '../../../../../src/ol/extent.js';
|
||||
@@ -19,6 +19,7 @@ import Style from '../../../../../src/ol/style/Style.js';
|
||||
import Text from '../../../../../src/ol/style/Text.js';
|
||||
import {createXYZ} from '../../../../../src/ol/tilegrid.js';
|
||||
import VectorTileRenderType from '../../../../../src/ol/layer/VectorTileRenderType.js';
|
||||
import {getUid} from '../../../../../src/ol/util.js';
|
||||
|
||||
|
||||
describe('ol.renderer.canvas.VectorTileLayer', function() {
|
||||
@@ -62,9 +63,9 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
||||
class TileClass extends VectorTile {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.setState(TileState.LOADED);
|
||||
this.setFeatures([feature1, feature2, feature3]);
|
||||
this.setProjection(getProjection('EPSG:4326'));
|
||||
this.setState(TileState.LOADED);
|
||||
tileCallback(this);
|
||||
}
|
||||
}
|
||||
@@ -73,12 +74,14 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
||||
tileClass: TileClass,
|
||||
tileGrid: createXYZ()
|
||||
});
|
||||
source.getSourceTiles = function() {
|
||||
return [new TileClass([0, 0, 0])];
|
||||
};
|
||||
source.getTile = function() {
|
||||
const tile = VectorTileSource.prototype.getTile.apply(source, arguments);
|
||||
tile.hasContext = function() {
|
||||
return true;
|
||||
};
|
||||
tile.sourceTilesLoaded = true;
|
||||
tile.setState(TileState.LOADED);
|
||||
return tile;
|
||||
};
|
||||
@@ -219,13 +222,10 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
||||
});
|
||||
map.addLayer(layer2);
|
||||
|
||||
const spy1 = sinon.spy(VectorTile.prototype, 'getExecutorGroup');
|
||||
const spy2 = sinon.spy(VectorTile.prototype, 'setExecutorGroup');
|
||||
map.renderSync();
|
||||
expect(spy1.callCount).to.be(4);
|
||||
expect(spy2.callCount).to.be(2);
|
||||
spy1.restore();
|
||||
spy2.restore();
|
||||
const tile = source.getTile(0, 0, 0, 1, getProjection('EPSG:3857'));
|
||||
expect(Object.keys(tile.executorGroups)[0]).to.be(getUid(layer));
|
||||
expect(Object.keys(tile.executorGroups)[1]).to.be(getUid(layer2));
|
||||
});
|
||||
|
||||
});
|
||||
@@ -244,15 +244,13 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
||||
sourceTile.getImage = function() {
|
||||
return document.createElement('canvas');
|
||||
};
|
||||
const tile = new VectorImageTile([0, 0, 0], undefined, 1, undefined,
|
||||
undefined, [0, 0, 0], undefined, createXYZ(), createXYZ(), {'0,0,0': sourceTile}, undefined,
|
||||
undefined, undefined, undefined);
|
||||
const tile = new VectorRenderTile([0, 0, 0], 1, [0, 0, 0], createXYZ(),
|
||||
function() {
|
||||
return sourceTile;
|
||||
},
|
||||
function() {});
|
||||
tile.transition_ = 0;
|
||||
tile.wrappedTileCoord = [0, 0, 0];
|
||||
tile.setState(TileState.LOADED);
|
||||
tile.getSourceTile = function() {
|
||||
return sourceTile;
|
||||
};
|
||||
layer.getSource().getTile = function() {
|
||||
return tile;
|
||||
};
|
||||
@@ -287,33 +285,37 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
||||
});
|
||||
|
||||
describe('#forEachFeatureAtCoordinate', function() {
|
||||
let layer, renderer, executorGroup;
|
||||
class TileClass extends VectorImageTile {
|
||||
let layer, renderer, executorGroup, source;
|
||||
class TileClass extends VectorRenderTile {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.extent = [-Infinity, -Infinity, Infinity, Infinity];
|
||||
this.setState(TileState.LOADED);
|
||||
const sourceTile = new VectorTile([0, 0, 0]);
|
||||
sourceTile.setState(TileState.LOADED);
|
||||
sourceTile.setProjection(getProjection('EPSG:3857'));
|
||||
sourceTile.getExecutorGroup = function() {
|
||||
return executorGroup;
|
||||
};
|
||||
const key = sourceTile.tileCoord.toString();
|
||||
this.tileKeys = [key];
|
||||
this.sourceTiles_ = {};
|
||||
this.sourceTiles_[key] = sourceTile;
|
||||
this.wrappedTileCoord = arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
const sourceTile = new VectorTile([0, 0, 0]);
|
||||
sourceTile.setState(TileState.LOADED);
|
||||
sourceTile.setProjection(getProjection('EPSG:3857'));
|
||||
source = new VectorTileSource({
|
||||
tileClass: TileClass,
|
||||
tileGrid: createXYZ()
|
||||
});
|
||||
source.sourceTiles_ = {
|
||||
'0/0/0': sourceTile
|
||||
};
|
||||
source.sourceTilesByTileKey_ = {
|
||||
'0/0/0': [sourceTile]
|
||||
};
|
||||
executorGroup = {};
|
||||
source.getTile = function() {
|
||||
const tile = VectorTileSource.prototype.getTile.apply(source, arguments);
|
||||
tile.executorGroups[getUid(layer)] = [executorGroup];
|
||||
return tile;
|
||||
};
|
||||
layer = new VectorTileLayer({
|
||||
source: new VectorTileSource({
|
||||
tileClass: TileClass,
|
||||
tileGrid: createXYZ()
|
||||
})
|
||||
source: source
|
||||
});
|
||||
renderer = new CanvasVectorTileLayerRenderer(layer);
|
||||
executorGroup.forEachFeatureAtCoordinate = function(coordinate,
|
||||
@@ -336,7 +338,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
||||
rotation: 0
|
||||
}
|
||||
};
|
||||
renderer.renderedTiles = [new TileClass([0, 0, -1], undefined, 1)];
|
||||
renderer.renderedTiles = [source.getTile(0, 0, 0, 1, getProjection('EPSG:3857'))];
|
||||
renderer.forEachFeatureAtCoordinate(
|
||||
coordinate, frameState, 0, spy, undefined);
|
||||
expect(spy.callCount).to.be(1);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import Map from '../../../../src/ol/Map.js';
|
||||
import View from '../../../../src/ol/View.js';
|
||||
import VectorImageTile from '../../../../src/ol/VectorImageTile.js';
|
||||
import VectorRenderTile from '../../../../src/ol/VectorRenderTile.js';
|
||||
import VectorTile from '../../../../src/ol/VectorTile.js';
|
||||
import GeoJSON from '../../../../src/ol/format/GeoJSON.js';
|
||||
import MVT from '../../../../src/ol/format/MVT.js';
|
||||
import VectorTileLayer from '../../../../src/ol/layer/VectorTile.js';
|
||||
import {get as getProjection} from '../../../../src/ol/proj.js';
|
||||
import VectorTileSource from '../../../../src/ol/source/VectorTile.js';
|
||||
import {createXYZ} from '../../../../src/ol/tilegrid.js';
|
||||
import TileGrid from '../../../../src/ol/tilegrid/TileGrid.js';
|
||||
import {listen, unlistenByKey} from '../../../../src/ol/events.js';
|
||||
import TileState from '../../../../src/ol/TileState.js';
|
||||
|
||||
describe('ol.source.VectorTile', function() {
|
||||
|
||||
@@ -38,7 +41,7 @@ describe('ol.source.VectorTile', function() {
|
||||
describe('#getTile()', function() {
|
||||
it('creates a tile with the correct tile class', function() {
|
||||
tile = source.getTile(0, 0, 0, 1, getProjection('EPSG:3857'));
|
||||
expect(tile).to.be.a(VectorImageTile);
|
||||
expect(tile).to.be.a(VectorRenderTile);
|
||||
});
|
||||
it('sets the correct tileCoord on the created tile', function() {
|
||||
expect(tile.getTileCoord()).to.eql([0, 0, 0]);
|
||||
@@ -47,6 +50,24 @@ describe('ol.source.VectorTile', function() {
|
||||
expect(source.getTile(0, 0, 0, 1, getProjection('EPSG:3857')))
|
||||
.to.equal(tile);
|
||||
});
|
||||
it('loads source tiles', function(done) {
|
||||
const source = new VectorTileSource({
|
||||
format: new GeoJSON(),
|
||||
url: 'spec/ol/data/point.json'
|
||||
});
|
||||
const tile = source.getTile(0, 0, 0, 1, source.getProjection());
|
||||
|
||||
tile.load();
|
||||
const key = listen(tile, 'change', function(e) {
|
||||
if (tile.getState() === TileState.LOADED) {
|
||||
const sourceTile = tile.load()[0];
|
||||
expect(sourceTile.getFeatures().length).to.be.greaterThan(0);
|
||||
unlistenByKey(key);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getTileGridForProjection', function() {
|
||||
|
||||
@@ -1,75 +1,49 @@
|
||||
import TileState from '../../../src/ol/TileState.js';
|
||||
import VectorImageTile, {defaultLoadFunction} from '../../../src/ol/VectorImageTile.js';
|
||||
import VectorTile from '../../../src/ol/VectorTile.js';
|
||||
import {listen, listenOnce} from '../../../src/ol/events.js';
|
||||
import {defaultLoadFunction} from '../../../src/ol/source/VectorTile.js';
|
||||
import VectorTileSource from '../../../src/ol/source/VectorTile.js';
|
||||
import {listen, listenOnce, unlistenByKey} from '../../../src/ol/events.js';
|
||||
import GeoJSON from '../../../src/ol/format/GeoJSON.js';
|
||||
import {get as getProjection} from '../../../src/ol/proj.js';
|
||||
import {createXYZ} from '../../../src/ol/tilegrid.js';
|
||||
import TileGrid from '../../../src/ol/tilegrid/TileGrid.js';
|
||||
import {getKey} from '../../../src/ol/tilecoord.js';
|
||||
import EventType from '../../../src/ol/events/EventType.js';
|
||||
|
||||
|
||||
describe('ol.VectorImageTile', function() {
|
||||
describe('ol.VectorRenderTile', function() {
|
||||
|
||||
it('configures loader that sets features on the source tile', function(done) {
|
||||
const format = new GeoJSON();
|
||||
const url = 'spec/ol/data/point.json';
|
||||
const tile = new VectorImageTile([0, 0, 0], 0, url, format,
|
||||
defaultLoadFunction, [0, 0, 0], function() {
|
||||
return url;
|
||||
}, createXYZ(), createXYZ(), {},
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {});
|
||||
|
||||
tile.load();
|
||||
const sourceTile = tile.getTile(tile.tileKeys[0]);
|
||||
const loader = sourceTile.loader_;
|
||||
expect(typeof loader).to.be('function');
|
||||
|
||||
listen(sourceTile, 'change', function(e) {
|
||||
expect(sourceTile.getFeatures().length).to.be.greaterThan(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('sets sourceTilesLoaded when previously failed source tiles are loaded', function(done) {
|
||||
const format = new GeoJSON();
|
||||
const url = 'spec/ol/data/unavailable.json';
|
||||
it('triggers "change" when previously failed source tiles are loaded', function(done) {
|
||||
let sourceTile;
|
||||
const tile = new VectorImageTile([0, 0, 0] /* one world away */, 0, url, format,
|
||||
function(tile, url) {
|
||||
const source = new VectorTileSource({
|
||||
format: new GeoJSON(),
|
||||
url: 'spec/ol/data/unavailable.json',
|
||||
tileLoadFunction: function(tile, url) {
|
||||
sourceTile = tile;
|
||||
defaultLoadFunction(tile, url);
|
||||
}, [0, 0, 0], function() {
|
||||
return url;
|
||||
}, createXYZ(), createXYZ(), {},
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {});
|
||||
}
|
||||
});
|
||||
const tile = source.getTile(0, 0, 0, 1, source.getProjection());
|
||||
|
||||
tile.load();
|
||||
let calls = 0;
|
||||
listen(tile, 'change', function(e) {
|
||||
++calls;
|
||||
if (calls === 1) {
|
||||
expect(tile.sourceTilesLoaded).to.be(false);
|
||||
} else if (calls === 2) {
|
||||
expect(tile.sourceTilesLoaded).to.be(true);
|
||||
}
|
||||
if (calls == 2) {
|
||||
done();
|
||||
} else {
|
||||
expect(tile.getState()).to.be(TileState.ERROR);
|
||||
setTimeout(function() {
|
||||
sourceTile.setState(TileState.LOADED);
|
||||
}, 0);
|
||||
} else if (calls === 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('sets ERROR state when source tiles fail to load', function(done) {
|
||||
const format = new GeoJSON();
|
||||
const url = 'spec/ol/data/unavailable.json';
|
||||
const tile = new VectorImageTile([0, 0, 0], 0, url, format,
|
||||
defaultLoadFunction, [0, 0, 0], function() {
|
||||
return url;
|
||||
}, createXYZ(), createXYZ(), {},
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {});
|
||||
const source = new VectorTileSource({
|
||||
format: new GeoJSON(),
|
||||
url: 'spec/ol/data/unavailable.json'
|
||||
});
|
||||
const tile = source.getTile(0, 0, 0, 1, source.getProjection());
|
||||
|
||||
tile.load();
|
||||
|
||||
@@ -79,81 +53,90 @@ describe('ol.VectorImageTile', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('sets EMPTY state when tile has only empty source tiles', function(done) {
|
||||
const format = new GeoJSON();
|
||||
const url = '';
|
||||
const tile = new VectorImageTile([0, 0, 0], 0, url, format,
|
||||
defaultLoadFunction, [0, 0, 0], function() {},
|
||||
createXYZ(), createXYZ(), {},
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {});
|
||||
it('sets EMPTY state when tile has only empty source tiles', function() {
|
||||
const source = new VectorTileSource({
|
||||
format: new GeoJSON(),
|
||||
url: ''
|
||||
});
|
||||
const tile = source.getTile(0, 0, 0, 1, source.getProjection());
|
||||
|
||||
tile.load();
|
||||
|
||||
listen(tile, 'change', function() {
|
||||
expect(tile.getState()).to.be(TileState.EMPTY);
|
||||
done();
|
||||
});
|
||||
expect(tile.getState()).to.be(TileState.EMPTY);
|
||||
});
|
||||
|
||||
it('only loads tiles within the source tileGrid\'s extent', function() {
|
||||
const format = new GeoJSON();
|
||||
it('only loads tiles within the source tileGrid\'s extent', function(done) {
|
||||
const url = 'spec/ol/data/point.json';
|
||||
const tileGrid = new TileGrid({
|
||||
resolutions: [0.02197265625, 0.010986328125, 0.0054931640625],
|
||||
origin: [-180, 90],
|
||||
extent: [-88, 35, -87, 36]
|
||||
});
|
||||
const sourceTiles = {};
|
||||
const tile = new VectorImageTile([1, 0, 0], 0, url, format,
|
||||
defaultLoadFunction, [1, 0, 0], function(zxy) {
|
||||
const source = new VectorTileSource({
|
||||
projection: 'EPSG:4326',
|
||||
format: new GeoJSON(),
|
||||
tileGrid: new TileGrid({
|
||||
resolutions: [0.02197265625, 0.010986328125, 0.0054931640625],
|
||||
origin: [-180, 90],
|
||||
extent: [-88, 35, -87, 36]
|
||||
}),
|
||||
tileUrlFunction: function(zxy) {
|
||||
return url;
|
||||
}, tileGrid,
|
||||
createXYZ({extent: [-180, -90, 180, 90], tileSize: 512}),
|
||||
sourceTiles, 1, getProjection('EPSG:4326'), VectorTile, function() {});
|
||||
},
|
||||
url: url
|
||||
});
|
||||
const tile = source.getTile(0, 0, 0, 1, source.getProjection());
|
||||
|
||||
tile.load();
|
||||
expect(tile.tileKeys.length).to.be(1);
|
||||
expect(tile.getTile(tile.tileKeys[0]).tileCoord).to.eql([0, 16, 9]);
|
||||
const key = listen(tile, EventType.CHANGE, function() {
|
||||
if (tile.getState() === TileState.LOADED) {
|
||||
unlistenByKey(key);
|
||||
const sourceTiles = tile.load();
|
||||
expect(sourceTiles.length).to.be(1);
|
||||
expect(sourceTiles[0].tileCoord).to.eql([0, 16, 9]);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('#dispose() while loading', function() {
|
||||
const format = new GeoJSON();
|
||||
const url = 'spec/ol/data/point.json';
|
||||
const tile = new VectorImageTile([0, 0, 0] /* one world away */, 0, url, format,
|
||||
defaultLoadFunction, [0, 0, 0], function() {
|
||||
return url;
|
||||
}, createXYZ(), createXYZ({tileSize: 512}), {},
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {});
|
||||
const source = new VectorTileSource({
|
||||
format: new GeoJSON(),
|
||||
url: 'spec/ol/data/point.json',
|
||||
tileGrid: createXYZ()
|
||||
});
|
||||
source.getTileGridForProjection = function() {
|
||||
return createXYZ({tileSize: 512});
|
||||
};
|
||||
const tile = source.getTile(0, 0, 0, 1, source.getProjection());
|
||||
|
||||
tile.load();
|
||||
expect(tile.loadListenerKeys_.length).to.be(4);
|
||||
expect(tile.tileKeys.length).to.be(4);
|
||||
expect(tile.getState()).to.be(TileState.LOADING);
|
||||
tile.dispose();
|
||||
expect(tile.loadListenerKeys_.length).to.be(0);
|
||||
expect(tile.tileKeys.length).to.be(0);
|
||||
expect(tile.sourceTiles_).to.be(null);
|
||||
expect(source.sourceTilesByTileKey_[getKey(tile)]).to.be(undefined);
|
||||
expect(tile.getState()).to.be(TileState.ABORT);
|
||||
});
|
||||
|
||||
it('#dispose() when source tiles are loaded', function(done) {
|
||||
const format = new GeoJSON();
|
||||
const url = 'spec/ol/data/point.json';
|
||||
const tile = new VectorImageTile([0, 0, 0], 0, url, format,
|
||||
defaultLoadFunction, [0, 0, 0], function() {
|
||||
return url;
|
||||
}, createXYZ(), createXYZ({tileSize: 512}), {},
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {});
|
||||
const source = new VectorTileSource({
|
||||
format: new GeoJSON(),
|
||||
url: 'spec/ol/data/point.json',
|
||||
tileGrid: createXYZ()
|
||||
});
|
||||
source.getTileGridForProjection = function() {
|
||||
return createXYZ({tileSize: 512});
|
||||
};
|
||||
const tile = source.getTile(0, 0, 0, 1, source.getProjection());
|
||||
|
||||
tile.load();
|
||||
listenOnce(tile, 'change', function() {
|
||||
expect(tile.getState()).to.be(TileState.LOADING);
|
||||
expect(tile.sourceTilesLoaded).to.be.ok();
|
||||
expect(tile.loadListenerKeys_.length).to.be(0);
|
||||
expect(tile.tileKeys.length).to.be(4);
|
||||
expect(tile.getState()).to.be(TileState.LOADED);
|
||||
expect(tile.loadingSourceTiles).to.be(0);
|
||||
const sourceTiles = tile.load();
|
||||
expect(sourceTiles.length).to.be(4);
|
||||
for (let i = 0, ii = sourceTiles.length; i < ii; ++i) {
|
||||
expect(sourceTiles[i].consumers).to.be(1);
|
||||
}
|
||||
tile.dispose();
|
||||
expect(tile.tileKeys.length).to.be(0);
|
||||
expect(tile.sourceTiles_).to.be(null);
|
||||
expect(tile.getState()).to.be(TileState.ABORT);
|
||||
for (let i = 0, ii = sourceTiles.length; i < ii; ++i) {
|
||||
expect(sourceTiles[i].consumers).to.be(0);
|
||||
expect(sourceTiles[i].getState()).to.be(TileState.ABORT);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Feature from '../../../src/ol/Feature.js';
|
||||
import {defaultLoadFunction} from '../../../src/ol/VectorImageTile.js';
|
||||
import {defaultLoadFunction} from '../../../src/ol/source/VectorTile.js';
|
||||
import VectorTile from '../../../src/ol/VectorTile.js';
|
||||
import {listen} from '../../../src/ol/events.js';
|
||||
import TextFeature from '../../../src/ol/format/TextFeature.js';
|
||||
|
||||
Reference in New Issue
Block a user