ol
Version:
OpenLayers mapping library
485 lines • 22 kB
JavaScript
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
/**
* @module ol/renderer/canvas/TileLayer
*/
import { getUid } from '../../util.js';
import { fromUserExtent } from '../../proj.js';
import TileRange from '../../TileRange.js';
import TileState from '../../TileState.js';
import { createEmpty, equals, getIntersection, getTopLeft } from '../../extent.js';
import CanvasLayerRenderer from './Layer.js';
import { apply as applyTransform, compose as composeTransform, makeInverse } from '../../transform.js';
import { numberSafeCompareFunction } from '../../array.js';
import { createTransformString } from '../../render/canvas.js';
/**
* @classdesc
* Canvas renderer for tile layers.
* @api
*/
var CanvasTileLayerRenderer = /** @class */ (function (_super) {
__extends(CanvasTileLayerRenderer, _super);
/**
* @param {import("../../layer/Tile.js").default|import("../../layer/VectorTile.js").default} tileLayer Tile layer.
*/
function CanvasTileLayerRenderer(tileLayer) {
var _this = _super.call(this, tileLayer) || this;
/**
* Rendered extent has changed since the previous `renderFrame()` call
* @type {boolean}
*/
_this.extentChanged = true;
/**
* @private
* @type {?import("../../extent.js").Extent}
*/
_this.renderedExtent_ = null;
/**
* @protected
* @type {number}
*/
_this.renderedPixelRatio;
/**
* @protected
* @type {import("../../proj/Projection.js").default}
*/
_this.renderedProjection = null;
/**
* @protected
* @type {number}
*/
_this.renderedRevision;
/**
* @protected
* @type {!Array<import("../../Tile.js").default>}
*/
_this.renderedTiles = [];
/**
* @private
* @type {boolean}
*/
_this.newTiles_ = false;
/**
* @protected
* @type {import("../../extent.js").Extent}
*/
_this.tmpExtent = createEmpty();
/**
* @private
* @type {import("../../TileRange.js").default}
*/
_this.tmpTileRange_ = new TileRange(0, 0, 0, 0);
return _this;
}
/**
* @protected
* @param {import("../../Tile.js").default} tile Tile.
* @return {boolean} Tile is drawable.
*/
CanvasTileLayerRenderer.prototype.isDrawableTile = function (tile) {
var tileLayer = this.getLayer();
var tileState = tile.getState();
var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
return tileState == TileState.LOADED ||
tileState == TileState.EMPTY ||
tileState == TileState.ERROR && !useInterimTilesOnError;
};
/**
* @param {number} z Tile coordinate z.
* @param {number} x Tile coordinate x.
* @param {number} y Tile coordinate y.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @return {!import("../../Tile.js").default} Tile.
*/
CanvasTileLayerRenderer.prototype.getTile = function (z, x, y, frameState) {
var pixelRatio = frameState.pixelRatio;
var projection = frameState.viewState.projection;
var tileLayer = this.getLayer();
var tileSource = tileLayer.getSource();
var tile = tileSource.getTile(z, x, y, pixelRatio, projection);
if (tile.getState() == TileState.ERROR) {
if (!tileLayer.getUseInterimTilesOnError()) {
// When useInterimTilesOnError is false, we consider the error tile as loaded.
tile.setState(TileState.LOADED);
}
else if (tileLayer.getPreload() > 0) {
// Preloaded tiles for lower resolutions might have finished loading.
this.newTiles_ = true;
}
}
if (!this.isDrawableTile(tile)) {
tile = tile.getInterimTile();
}
return tile;
};
/**
* @inheritDoc
*/
CanvasTileLayerRenderer.prototype.loadedTileCallback = function (tiles, zoom, tile) {
if (this.isDrawableTile(tile)) {
return _super.prototype.loadedTileCallback.call(this, tiles, zoom, tile);
}
return false;
};
/**
* @inheritDoc
*/
CanvasTileLayerRenderer.prototype.prepareFrame = function (frameState) {
return !!this.getLayer().getSource();
};
/**
* TODO: File a TypeScript issue about inheritDoc not being followed
* all the way. Without this explicit return type, the VectorTileLayer
* renderFrame function does not pass.
*
* @inheritDoc
* @returns {HTMLElement} The rendered element.
*/
CanvasTileLayerRenderer.prototype.renderFrame = function (frameState, target) {
var layerState = frameState.layerStatesArray[frameState.layerIndex];
var viewState = frameState.viewState;
var projection = viewState.projection;
var viewResolution = viewState.resolution;
var viewCenter = viewState.center;
var rotation = viewState.rotation;
var pixelRatio = frameState.pixelRatio;
var tileLayer = this.getLayer();
var tileSource = tileLayer.getSource();
var sourceRevision = tileSource.getRevision();
var tileGrid = tileSource.getTileGridForProjection(projection);
var z = tileGrid.getZForResolution(viewResolution, tileSource.zDirection);
var tileResolution = tileGrid.getResolution(z);
var extent = frameState.extent;
var layerExtent = layerState.extent && fromUserExtent(layerState.extent, projection);
if (layerExtent) {
extent = getIntersection(extent, fromUserExtent(layerState.extent, projection));
}
var tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio);
// desired dimensions of the canvas in pixels
var width = Math.round(frameState.size[0] * tilePixelRatio);
var height = Math.round(frameState.size[1] * tilePixelRatio);
if (rotation) {
var size = Math.round(Math.sqrt(width * width + height * height));
width = size;
height = size;
}
var dx = tileResolution * width / 2 / tilePixelRatio;
var dy = tileResolution * height / 2 / tilePixelRatio;
var canvasExtent = [
viewCenter[0] - dx,
viewCenter[1] - dy,
viewCenter[0] + dx,
viewCenter[1] + dy
];
var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
/**
* @type {Object<number, Object<string, import("../../Tile.js").default>>}
*/
var tilesToDrawByZ = {};
tilesToDrawByZ[z] = {};
var findLoadedTiles = this.createLoadedTileFinder(tileSource, projection, tilesToDrawByZ);
var tmpExtent = this.tmpExtent;
var tmpTileRange = this.tmpTileRange_;
this.newTiles_ = false;
for (var x = tileRange.minX; x <= tileRange.maxX; ++x) {
for (var y = tileRange.minY; y <= tileRange.maxY; ++y) {
var tile = this.getTile(z, x, y, frameState);
if (this.isDrawableTile(tile)) {
var uid = getUid(this);
if (tile.getState() == TileState.LOADED) {
tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
var inTransition = tile.inTransition(uid);
if (!this.newTiles_ && (inTransition || this.renderedTiles.indexOf(tile) === -1)) {
this.newTiles_ = true;
}
}
if (tile.getAlpha(uid, frameState.time) === 1) {
// don't look for alt tiles if alpha is 1
continue;
}
}
var childTileRange = tileGrid.getTileCoordChildTileRange(tile.tileCoord, tmpTileRange, tmpExtent);
var covered = false;
if (childTileRange) {
covered = findLoadedTiles(z + 1, childTileRange);
}
if (!covered) {
tileGrid.forEachTileCoordParentTileRange(tile.tileCoord, findLoadedTiles, tmpTileRange, tmpExtent);
}
}
}
var canvasScale = tileResolution / viewResolution;
// set forward and inverse pixel transforms
composeTransform(this.pixelTransform, frameState.size[0] / 2, frameState.size[1] / 2, 1 / tilePixelRatio, 1 / tilePixelRatio, rotation, -width / 2, -height / 2);
var canvasTransform = createTransformString(this.pixelTransform);
this.useContainer(target, canvasTransform, layerState.opacity);
var context = this.context;
var canvas = context.canvas;
makeInverse(this.inversePixelTransform, this.pixelTransform);
// set scale transform for calculating tile positions on the canvas
composeTransform(this.tempTransform_, width / 2, height / 2, canvasScale, canvasScale, 0, -width / 2, -height / 2);
if (canvas.width != width || canvas.height != height) {
canvas.width = width;
canvas.height = height;
}
else if (!this.containerReused) {
context.clearRect(0, 0, width, height);
}
if (layerExtent) {
this.clipUnrotated(context, frameState, layerExtent);
}
this.preRender(context, frameState);
this.renderedTiles.length = 0;
/** @type {Array<number>} */
var zs = Object.keys(tilesToDrawByZ).map(Number);
zs.sort(numberSafeCompareFunction);
var clips, clipZs, currentClip;
if (layerState.opacity === 1 && (!this.containerReused || tileSource.getOpaque(frameState.viewState.projection))) {
zs = zs.reverse();
}
else {
clips = [];
clipZs = [];
}
for (var i = zs.length - 1; i >= 0; --i) {
var currentZ = zs[i];
var currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection);
var currentResolution = tileGrid.getResolution(currentZ);
var currentScale = currentResolution / tileResolution;
var dx_1 = currentTilePixelSize[0] * currentScale * canvasScale;
var dy_1 = currentTilePixelSize[1] * currentScale * canvasScale;
var originTileCoord = tileGrid.getTileCoordForCoordAndZ(getTopLeft(canvasExtent), currentZ);
var originTileExtent = tileGrid.getTileCoordExtent(originTileCoord);
var origin_1 = applyTransform(this.tempTransform_, [
tilePixelRatio * (originTileExtent[0] - canvasExtent[0]) / tileResolution,
tilePixelRatio * (canvasExtent[3] - originTileExtent[3]) / tileResolution
]);
var tileGutter = tilePixelRatio * tileSource.getGutterForProjection(projection);
var tilesToDraw = tilesToDrawByZ[currentZ];
for (var tileCoordKey in tilesToDraw) {
var tile = /** @type {import("../../ImageTile.js").default} */ (tilesToDraw[tileCoordKey]);
var tileCoord = tile.tileCoord;
// Calculate integer positions and sizes so that tiles align
var floatX = (origin_1[0] - (originTileCoord[1] - tileCoord[1]) * dx_1);
var nextX = Math.round(floatX + dx_1);
var floatY = (origin_1[1] - (originTileCoord[2] - tileCoord[2]) * dy_1);
var nextY = Math.round(floatY + dy_1);
var x = Math.round(floatX);
var y = Math.round(floatY);
var w = nextX - x;
var h = nextY - y;
var transition = z === currentZ;
var inTransition = transition && tile.getAlpha(getUid(this), frameState.time) !== 1;
if (!inTransition) {
if (clips) {
// Clip mask for regions in this tile that already filled by a higher z tile
context.save();
currentClip = [x, y, x + w, y, x + w, y + h, x, y + h];
for (var i_1 = 0, ii = clips.length; i_1 < ii; ++i_1) {
if (z !== currentZ && currentZ < clipZs[i_1]) {
var clip = clips[i_1];
context.beginPath();
// counter-clockwise (outer ring) for current tile
context.moveTo(currentClip[0], currentClip[1]);
context.lineTo(currentClip[2], currentClip[3]);
context.lineTo(currentClip[4], currentClip[5]);
context.lineTo(currentClip[6], currentClip[7]);
// clockwise (inner ring) for higher z tile
context.moveTo(clip[6], clip[7]);
context.lineTo(clip[4], clip[5]);
context.lineTo(clip[2], clip[3]);
context.lineTo(clip[0], clip[1]);
context.clip();
}
}
clips.push(currentClip);
clipZs.push(currentZ);
}
else {
context.clearRect(x, y, w, h);
}
}
this.drawTileImage(tile, frameState, x, y, w, h, tileGutter, transition, layerState.opacity);
if (clips && !inTransition) {
context.restore();
}
this.renderedTiles.push(tile);
this.updateUsedTiles(frameState.usedTiles, tileSource, tile);
}
}
this.renderedRevision = sourceRevision;
this.renderedResolution = tileResolution;
this.extentChanged = !this.renderedExtent_ || !equals(this.renderedExtent_, canvasExtent);
this.renderedExtent_ = canvasExtent;
this.renderedPixelRatio = pixelRatio;
this.renderedProjection = projection;
this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio, projection, extent, z, tileLayer.getPreload());
this.scheduleExpireCache(frameState, tileSource);
this.postRender(context, frameState);
if (layerState.extent) {
context.restore();
}
if (canvasTransform !== canvas.style.transform) {
canvas.style.transform = canvasTransform;
}
return this.container;
};
/**
* @param {import("../../ImageTile.js").default} tile Tile.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @param {number} x Left of the tile.
* @param {number} y Top of the tile.
* @param {number} w Width of the tile.
* @param {number} h Height of the tile.
* @param {number} gutter Tile gutter.
* @param {boolean} transition Apply an alpha transition.
* @param {number} opacity Opacity.
*/
CanvasTileLayerRenderer.prototype.drawTileImage = function (tile, frameState, x, y, w, h, gutter, transition, opacity) {
var image = this.getTileImage(tile);
if (!image) {
return;
}
var uid = getUid(this);
var tileAlpha = transition ? tile.getAlpha(uid, frameState.time) : 1;
var alpha = opacity * tileAlpha;
var alphaChanged = alpha !== this.context.globalAlpha;
if (alphaChanged) {
this.context.save();
this.context.globalAlpha = alpha;
}
this.context.drawImage(image, gutter, gutter, image.width - 2 * gutter, image.height - 2 * gutter, x, y, w, h);
if (alphaChanged) {
this.context.restore();
}
if (tileAlpha !== 1) {
frameState.animate = true;
}
else if (transition) {
tile.endTransition(uid);
}
};
/**
* @inheritDoc
*/
CanvasTileLayerRenderer.prototype.getImage = function () {
var context = this.context;
return context ? context.canvas : null;
};
/**
* Get the image from a tile.
* @param {import("../../ImageTile.js").default} tile Tile.
* @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image.
* @protected
*/
CanvasTileLayerRenderer.prototype.getTileImage = function (tile) {
return tile.getImage();
};
/**
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @param {import("../../source/Tile.js").default} tileSource Tile source.
* @protected
*/
CanvasTileLayerRenderer.prototype.scheduleExpireCache = function (frameState, tileSource) {
if (tileSource.canExpireCache()) {
/**
* @param {import("../../source/Tile.js").default} tileSource Tile source.
* @param {import("../../PluggableMap.js").default} map Map.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
*/
var postRenderFunction = function (tileSource, map, frameState) {
var tileSourceKey = getUid(tileSource);
if (tileSourceKey in frameState.usedTiles) {
tileSource.expireCache(frameState.viewState.projection, frameState.usedTiles[tileSourceKey]);
}
}.bind(null, tileSource);
frameState.postRenderFunctions.push(
/** @type {import("../../PluggableMap.js").PostRenderFunction} */ (postRenderFunction));
}
};
/**
* @param {!Object<string, !Object<string, boolean>>} usedTiles Used tiles.
* @param {import("../../source/Tile.js").default} tileSource Tile source.
* @param {import('../../Tile.js').default} tile Tile.
* @protected
*/
CanvasTileLayerRenderer.prototype.updateUsedTiles = function (usedTiles, tileSource, tile) {
// FIXME should we use tilesToDrawByZ instead?
var tileSourceKey = getUid(tileSource);
if (!(tileSourceKey in usedTiles)) {
usedTiles[tileSourceKey] = {};
}
usedTiles[tileSourceKey][tile.getKey()] = true;
};
/**
* Manage tile pyramid.
* This function performs a number of functions related to the tiles at the
* current zoom and lower zoom levels:
* - registers idle tiles in frameState.wantedTiles so that they are not
* discarded by the tile queue
* - enqueues missing tiles
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @param {import("../../source/Tile.js").default} tileSource Tile source.
* @param {import("../../tilegrid/TileGrid.js").default} tileGrid Tile grid.
* @param {number} pixelRatio Pixel ratio.
* @param {import("../../proj/Projection.js").default} projection Projection.
* @param {import("../../extent.js").Extent} extent Extent.
* @param {number} currentZ Current Z.
* @param {number} preload Load low resolution tiles up to 'preload' levels.
* @param {function(import("../../Tile.js").default)=} opt_tileCallback Tile callback.
* @protected
*/
CanvasTileLayerRenderer.prototype.manageTilePyramid = function (frameState, tileSource, tileGrid, pixelRatio, projection, extent, currentZ, preload, opt_tileCallback) {
var tileSourceKey = getUid(tileSource);
if (!(tileSourceKey in frameState.wantedTiles)) {
frameState.wantedTiles[tileSourceKey] = {};
}
var wantedTiles = frameState.wantedTiles[tileSourceKey];
var tileQueue = frameState.tileQueue;
var minZoom = tileGrid.getMinZoom();
var tile, tileRange, tileResolution, x, y, z;
for (z = minZoom; z <= currentZ; ++z) {
tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange);
tileResolution = tileGrid.getResolution(z);
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
if (currentZ - z <= preload) {
tile = tileSource.getTile(z, x, y, pixelRatio, projection);
if (tile.getState() == TileState.IDLE) {
wantedTiles[tile.getKey()] = true;
if (!tileQueue.isKeyQueued(tile.getKey())) {
tileQueue.enqueue([tile, tileSourceKey,
tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]);
}
}
if (opt_tileCallback !== undefined) {
opt_tileCallback(tile);
}
}
else {
tileSource.useTile(z, x, y, projection);
}
}
}
}
};
return CanvasTileLayerRenderer;
}(CanvasLayerRenderer));
/**
* @function
* @return {import("../../layer/Tile.js").default|import("../../layer/VectorTile.js").default}
*/
CanvasTileLayerRenderer.prototype.getLayer;
export default CanvasTileLayerRenderer;
//# sourceMappingURL=TileLayer.js.map