Merge pull request #9584 from ahocevar/reuse-render-target
Reuse render target
This commit is contained in:
@@ -9,12 +9,6 @@ import Point from '../../../src/ol/geom/Point.js';
|
|||||||
|
|
||||||
const map = new Map({
|
const map = new Map({
|
||||||
layers: [
|
layers: [
|
||||||
new TileLayer({
|
|
||||||
source: new XYZ({
|
|
||||||
url: '/data/tiles/satellite/{z}/{x}/{y}.jpg',
|
|
||||||
maxZoom: 3
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
new VectorLayer({
|
new VectorLayer({
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
style: new Style({
|
style: new Style({
|
||||||
@@ -27,6 +21,12 @@ const map = new Map({
|
|||||||
url: '/data/countries.json',
|
url: '/data/countries.json',
|
||||||
format: new GeoJSON()
|
format: new GeoJSON()
|
||||||
})
|
})
|
||||||
|
}),
|
||||||
|
new TileLayer({
|
||||||
|
source: new XYZ({
|
||||||
|
url: '/data/tiles/satellite/{z}/{x}/{y}.jpg',
|
||||||
|
maxZoom: 3
|
||||||
|
})
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
target: 'map',
|
target: 'map',
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import {create as createTransform, apply as applyTransform} from './transform.js
|
|||||||
* @property {boolean} animate
|
* @property {boolean} animate
|
||||||
* @property {import("./transform.js").Transform} coordinateToPixelTransform
|
* @property {import("./transform.js").Transform} coordinateToPixelTransform
|
||||||
* @property {null|import("./extent.js").Extent} extent
|
* @property {null|import("./extent.js").Extent} extent
|
||||||
* @property {Array<*>} declutterItems
|
* @property {Array<DeclutterItems>} declutterItems
|
||||||
* @property {import("./coordinate.js").Coordinate} focus
|
* @property {import("./coordinate.js").Coordinate} focus
|
||||||
* @property {number} index
|
* @property {number} index
|
||||||
* @property {Array<import("./layer/Layer.js").State>} layerStatesArray
|
* @property {Array<import("./layer/Layer.js").State>} layerStatesArray
|
||||||
@@ -54,6 +54,13 @@ import {create as createTransform, apply as applyTransform} from './transform.js
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} DeclutterItems
|
||||||
|
* @property {Array<*>} items Declutter items of an executor.
|
||||||
|
* @property {number} opacity Layer opacity.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {function(PluggableMap, ?FrameState): any} PostRenderFunction
|
* @typedef {function(PluggableMap, ?FrameState): any} PostRenderFunction
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -189,13 +189,15 @@ class Layer extends BaseLayer {
|
|||||||
* In charge to manage the rendering of the layer. One layer type is
|
* In charge to manage the rendering of the layer. One layer type is
|
||||||
* bounded with one layer renderer.
|
* bounded with one layer renderer.
|
||||||
* @param {?import("../PluggableMap.js").FrameState} frameState Frame state.
|
* @param {?import("../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
|
* @param {HTMLElement} target Target which the renderer may (but need not) use
|
||||||
|
* for rendering its content.
|
||||||
* @return {HTMLElement} The rendered element.
|
* @return {HTMLElement} The rendered element.
|
||||||
*/
|
*/
|
||||||
render(frameState) {
|
render(frameState, target) {
|
||||||
const layerRenderer = this.getRenderer();
|
const layerRenderer = this.getRenderer();
|
||||||
const layerState = this.getLayerState();
|
const layerState = this.getLayerState();
|
||||||
if (layerRenderer.prepareFrame(frameState, layerState)) {
|
if (layerRenderer.prepareFrame(frameState, layerState)) {
|
||||||
return layerRenderer.renderFrame(frameState, layerState);
|
return layerRenderer.renderFrame(frameState, layerState, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -122,9 +122,10 @@ export function renderDeclutterItems(frameState, declutterTree) {
|
|||||||
}
|
}
|
||||||
const items = frameState.declutterItems;
|
const items = frameState.declutterItems;
|
||||||
for (let z = items.length - 1; z >= 0; --z) {
|
for (let z = items.length - 1; z >= 0; --z) {
|
||||||
const zIndexItems = items[z];
|
const item = items[z];
|
||||||
|
const zIndexItems = item.items;
|
||||||
for (let i = 0, ii = zIndexItems.length; i < ii; i += 3) {
|
for (let i = 0, ii = zIndexItems.length; i < ii; i += 3) {
|
||||||
declutterTree = zIndexItems[i].renderDeclutter(zIndexItems[i + 1], zIndexItems[i + 2], declutterTree);
|
declutterTree = zIndexItems[i].renderDeclutter(zIndexItems[i + 1], zIndexItems[i + 2], item.opacity, declutterTree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items.length = 0;
|
items.length = 0;
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ export function drawImage(context,
|
|||||||
|
|
||||||
context.drawImage(image, originX, originY, w, h, x, y, w * scale, h * scale);
|
context.drawImage(image, originX, originY, w, h, x, y, w * scale, h * scale);
|
||||||
|
|
||||||
if (alpha) {
|
if (opacity != 1) {
|
||||||
context.globalAlpha = alpha;
|
context.globalAlpha = alpha;
|
||||||
}
|
}
|
||||||
if (transform) {
|
if (transform) {
|
||||||
|
|||||||
@@ -362,10 +362,12 @@ class Executor extends Disposable {
|
|||||||
const declutterArgs = intersects ?
|
const declutterArgs = intersects ?
|
||||||
[context, transform ? transform.slice(0) : null, opacity, image, originX, originY, w, h, x, y, scale] :
|
[context, transform ? transform.slice(0) : null, opacity, image, originX, originY, w, h, x, y, scale] :
|
||||||
null;
|
null;
|
||||||
if (declutterArgs && fillStroke) {
|
if (declutterArgs) {
|
||||||
declutterArgs.push(fillInstruction, strokeInstruction, p1, p2, p3, p4);
|
if (fillStroke) {
|
||||||
|
declutterArgs.push(fillInstruction, strokeInstruction, p1, p2, p3, p4);
|
||||||
|
}
|
||||||
|
declutterGroup.push(declutterArgs);
|
||||||
}
|
}
|
||||||
declutterGroup.push(declutterArgs);
|
|
||||||
} else if (intersects) {
|
} else if (intersects) {
|
||||||
if (fillStroke) {
|
if (fillStroke) {
|
||||||
this.replayTextBackground_(context, p1, p2, p3, p4,
|
this.replayTextBackground_(context, p1, p2, p3, p4,
|
||||||
@@ -414,10 +416,11 @@ class Executor extends Disposable {
|
|||||||
/**
|
/**
|
||||||
* @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group.
|
* @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group.
|
||||||
* @param {import("../../Feature.js").FeatureLike} feature Feature.
|
* @param {import("../../Feature.js").FeatureLike} feature Feature.
|
||||||
|
* @param {number} opacity Layer opacity.
|
||||||
* @param {?} declutterTree Declutter tree.
|
* @param {?} declutterTree Declutter tree.
|
||||||
* @return {?} Declutter tree.
|
* @return {?} Declutter tree.
|
||||||
*/
|
*/
|
||||||
renderDeclutter(declutterGroup, feature, declutterTree) {
|
renderDeclutter(declutterGroup, feature, opacity, declutterTree) {
|
||||||
if (declutterGroup && declutterGroup.length > 5) {
|
if (declutterGroup && declutterGroup.length > 5) {
|
||||||
const groupCount = declutterGroup[4];
|
const groupCount = declutterGroup[4];
|
||||||
if (groupCount == 1 || groupCount == declutterGroup.length - 5) {
|
if (groupCount == 1 || groupCount == declutterGroup.length - 5) {
|
||||||
@@ -436,13 +439,19 @@ class Executor extends Disposable {
|
|||||||
declutterTree.insert(box);
|
declutterTree.insert(box);
|
||||||
for (let j = 5, jj = declutterGroup.length; j < jj; ++j) {
|
for (let j = 5, jj = declutterGroup.length; j < jj; ++j) {
|
||||||
const declutterData = /** @type {Array} */ (declutterGroup[j]);
|
const declutterData = /** @type {Array} */ (declutterGroup[j]);
|
||||||
if (declutterData) {
|
const context = declutterData[0];
|
||||||
if (declutterData.length > 11) {
|
const currentAlpha = context.globalAlpha;
|
||||||
this.replayTextBackground_(declutterData[0],
|
if (currentAlpha !== opacity) {
|
||||||
declutterData[13], declutterData[14], declutterData[15], declutterData[16],
|
context.globalAlpha = opacity;
|
||||||
declutterData[11], declutterData[12]);
|
}
|
||||||
}
|
if (declutterData.length > 11) {
|
||||||
drawImage.apply(undefined, declutterData);
|
this.replayTextBackground_(declutterData[0],
|
||||||
|
declutterData[13], declutterData[14], declutterData[15], declutterData[16],
|
||||||
|
declutterData[11], declutterData[12]);
|
||||||
|
}
|
||||||
|
drawImage.apply(undefined, declutterData);
|
||||||
|
if (currentAlpha !== opacity) {
|
||||||
|
context.globalAlpha = currentAlpha;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -430,10 +430,11 @@ export function getCircleArray(radius) {
|
|||||||
* @param {!Object<string, Array<*>>} declutterReplays Declutter replays.
|
* @param {!Object<string, Array<*>>} declutterReplays Declutter replays.
|
||||||
* @param {CanvasRenderingContext2D} context Context.
|
* @param {CanvasRenderingContext2D} context Context.
|
||||||
* @param {number} rotation Rotation.
|
* @param {number} rotation Rotation.
|
||||||
|
* @param {number} opacity Opacity.
|
||||||
* @param {boolean} snapToPixel Snap point symbols and text to integer pixels.
|
* @param {boolean} snapToPixel Snap point symbols and text to integer pixels.
|
||||||
* @param {Array<Array<*>>} declutterItems Declutter items.
|
* @param {Array<import("../../PluggableMap.js").DeclutterItems>} declutterItems Declutter items.
|
||||||
*/
|
*/
|
||||||
export function replayDeclutter(declutterReplays, context, rotation, snapToPixel, declutterItems) {
|
export function replayDeclutter(declutterReplays, context, rotation, opacity, snapToPixel, declutterItems) {
|
||||||
const zs = Object.keys(declutterReplays).map(Number).sort(numberSafeCompareFunction);
|
const zs = Object.keys(declutterReplays).map(Number).sort(numberSafeCompareFunction);
|
||||||
const skippedFeatureUids = {};
|
const skippedFeatureUids = {};
|
||||||
for (let z = 0, zz = zs.length; z < zz; ++z) {
|
for (let z = 0, zz = zs.length; z < zz; ++z) {
|
||||||
@@ -443,7 +444,10 @@ export function replayDeclutter(declutterReplays, context, rotation, snapToPixel
|
|||||||
const executor = executorData[i++];
|
const executor = executorData[i++];
|
||||||
if (executor !== currentExecutor) {
|
if (executor !== currentExecutor) {
|
||||||
currentExecutor = executor;
|
currentExecutor = executor;
|
||||||
declutterItems.push(executor.declutterItems);
|
declutterItems.push({
|
||||||
|
items: executor.declutterItems,
|
||||||
|
opacity: opacity
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const transform = executorData[i++];
|
const transform = executorData[i++];
|
||||||
executor.execute(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
executor.execute(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
||||||
|
|||||||
@@ -81,10 +81,13 @@ class CompositeMapRenderer extends MapRenderer {
|
|||||||
this.calculateMatrices2D(frameState);
|
this.calculateMatrices2D(frameState);
|
||||||
this.dispatchRenderEvent(RenderEventType.PRECOMPOSE, frameState);
|
this.dispatchRenderEvent(RenderEventType.PRECOMPOSE, frameState);
|
||||||
|
|
||||||
const layerStatesArray = frameState.layerStatesArray;
|
const layerStatesArray = frameState.layerStatesArray.sort(function(a, b) {
|
||||||
|
return a.zIndex - b.zIndex;
|
||||||
|
});
|
||||||
const viewResolution = frameState.viewState.resolution;
|
const viewResolution = frameState.viewState.resolution;
|
||||||
|
|
||||||
this.children_.length = 0;
|
this.children_.length = 0;
|
||||||
|
let previousElement = null;
|
||||||
for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) {
|
for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) {
|
||||||
const layerState = layerStatesArray[i];
|
const layerState = layerStatesArray[i];
|
||||||
if (!visibleAtResolution(layerState, viewResolution) ||
|
if (!visibleAtResolution(layerState, viewResolution) ||
|
||||||
@@ -93,13 +96,10 @@ class CompositeMapRenderer extends MapRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const layer = layerState.layer;
|
const layer = layerState.layer;
|
||||||
const element = layer.render(frameState);
|
const element = layer.render(frameState, previousElement);
|
||||||
if (element) {
|
if (element !== previousElement) {
|
||||||
const zIndex = layerState.zIndex;
|
|
||||||
if (zIndex !== element.style.zIndex) {
|
|
||||||
element.style.zIndex = zIndex === Infinity ? Number.MAX_SAFE_INTEGER : zIndex;
|
|
||||||
}
|
|
||||||
this.children_.push(element);
|
this.children_.push(element);
|
||||||
|
previousElement = element;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.renderFrame(frameState);
|
super.renderFrame(frameState);
|
||||||
|
|||||||
@@ -41,9 +41,10 @@ class LayerRenderer extends Observable {
|
|||||||
* @abstract
|
* @abstract
|
||||||
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
|
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
* @param {import("../layer/Layer.js").State} layerState Layer state.
|
* @param {import("../layer/Layer.js").State} layerState Layer state.
|
||||||
|
* @param {HTMLElement} target Target that may be used to render content to.
|
||||||
* @return {HTMLElement} The rendered element.
|
* @return {HTMLElement} The rendered element.
|
||||||
*/
|
*/
|
||||||
renderFrame(frameState, layerState) {
|
renderFrame(frameState, layerState, target) {
|
||||||
return abstract();
|
return abstract();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
renderFrame(frameState, layerState) {
|
renderFrame(frameState, layerState, target) {
|
||||||
const image = this.image_;
|
const image = this.image_;
|
||||||
const imageExtent = image.getExtent();
|
const imageExtent = image.getExtent();
|
||||||
const imageResolution = image.getResolution();
|
const imageResolution = image.getResolution();
|
||||||
@@ -101,13 +101,15 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
|
|||||||
);
|
);
|
||||||
makeInverse(this.inversePixelTransform_, this.pixelTransform_);
|
makeInverse(this.inversePixelTransform_, this.pixelTransform_);
|
||||||
|
|
||||||
|
this.useContainer(target, this.pixelTransform_, layerState.opacity);
|
||||||
|
|
||||||
const context = this.context;
|
const context = this.context;
|
||||||
const canvas = context.canvas;
|
const canvas = context.canvas;
|
||||||
|
|
||||||
if (canvas.width != width || canvas.height != height) {
|
if (canvas.width != width || canvas.height != height) {
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
} else {
|
} else if (!this.containerReused) {
|
||||||
context.clearRect(0, 0, width, height);
|
context.clearRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,8 +140,17 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
this.preRender(context, frameState);
|
this.preRender(context, frameState);
|
||||||
if (dw >= 0.5 && dh >= 0.5) {
|
if (dw >= 0.5 && dh >= 0.5) {
|
||||||
|
const opacity = layerState.opacity;
|
||||||
|
let previousAlpha;
|
||||||
|
if (opacity !== 1) {
|
||||||
|
previousAlpha = this.context.globalAlpha;
|
||||||
|
this.context.globalAlpha = opacity;
|
||||||
|
}
|
||||||
this.context.drawImage(img, 0, 0, +img.width, +img.height,
|
this.context.drawImage(img, 0, 0, +img.width, +img.height,
|
||||||
Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh));
|
Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh));
|
||||||
|
if (opacity !== 1) {
|
||||||
|
this.context.globalAlpha = previousAlpha;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.postRender(context, frameState);
|
this.postRender(context, frameState);
|
||||||
|
|
||||||
@@ -147,17 +158,12 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
|
|||||||
context.restore();
|
context.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
const opacity = layerState.opacity;
|
|
||||||
if (opacity !== parseFloat(canvas.style.opacity)) {
|
|
||||||
canvas.style.opacity = opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
const canvasTransform = transformToString(this.pixelTransform_);
|
const canvasTransform = transformToString(this.pixelTransform_);
|
||||||
if (canvasTransform !== canvas.style.transform) {
|
if (canvasTransform !== canvas.style.transform) {
|
||||||
canvas.style.transform = canvasTransform;
|
canvas.style.transform = canvasTransform;
|
||||||
}
|
}
|
||||||
|
|
||||||
return canvas;
|
return this.container;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import RenderEvent from '../../render/Event.js';
|
|||||||
import RenderEventType from '../../render/EventType.js';
|
import RenderEventType from '../../render/EventType.js';
|
||||||
import {rotateAtOffset} from '../../render/canvas.js';
|
import {rotateAtOffset} from '../../render/canvas.js';
|
||||||
import LayerRenderer from '../Layer.js';
|
import LayerRenderer from '../Layer.js';
|
||||||
import {create as createTransform, apply as applyTransform, compose as composeTransform} from '../../transform.js';
|
import {create as createTransform, apply as applyTransform, compose as composeTransform, toString as transformToString} from '../../transform.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract
|
* @abstract
|
||||||
@@ -21,6 +21,12 @@ class CanvasLayerRenderer extends LayerRenderer {
|
|||||||
|
|
||||||
super(layer);
|
super(layer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {HTMLElement}
|
||||||
|
*/
|
||||||
|
this.container = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -55,20 +61,57 @@ class CanvasLayerRenderer extends LayerRenderer {
|
|||||||
* @protected
|
* @protected
|
||||||
* @type {CanvasRenderingContext2D}
|
* @type {CanvasRenderingContext2D}
|
||||||
*/
|
*/
|
||||||
this.context = createCanvasContext2D();
|
this.context = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.containerReused = false;
|
||||||
|
|
||||||
const canvas = this.context.canvas;
|
|
||||||
canvas.style.position = 'absolute';
|
|
||||||
canvas.style.transformOrigin = 'top left';
|
|
||||||
canvas.className = this.getLayer().getClassName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* Get a rendering container from an existing target, if compatible.
|
||||||
|
* @param {HTMLElement} target Potential render target.
|
||||||
|
* @param {import("../../transform").Transform} transform Transform.
|
||||||
|
* @param {number} opacity Opacity.
|
||||||
*/
|
*/
|
||||||
disposeInternal() {
|
useContainer(target, transform, opacity) {
|
||||||
this.context.canvas.width = this.context.canvas.height = 0;
|
const layerClassName = this.getLayer().getClassName();
|
||||||
super.disposeInternal();
|
let container, context;
|
||||||
|
if (target && target.style.opacity === '' && target.className === layerClassName) {
|
||||||
|
const canvas = target.firstElementChild;
|
||||||
|
if (canvas instanceof HTMLCanvasElement) {
|
||||||
|
context = canvas.getContext('2d');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (context && context.canvas.style.transform === transformToString(transform)) {
|
||||||
|
// Container of the previous layer renderer can be used.
|
||||||
|
this.container = target;
|
||||||
|
this.context = context;
|
||||||
|
this.containerReused = true;
|
||||||
|
} else if (this.containerReused) {
|
||||||
|
// Previously reused container cannot be used any more.
|
||||||
|
this.container = null;
|
||||||
|
this.context = null;
|
||||||
|
this.containerReused = false;
|
||||||
|
}
|
||||||
|
if (!this.container) {
|
||||||
|
container = document.createElement('div');
|
||||||
|
container.className = layerClassName;
|
||||||
|
let style = container.style;
|
||||||
|
style.position = 'absolute';
|
||||||
|
style.width = '100%';
|
||||||
|
style.height = '100%';
|
||||||
|
context = createCanvasContext2D();
|
||||||
|
const canvas = context.canvas;
|
||||||
|
container.appendChild(canvas);
|
||||||
|
style = canvas.style;
|
||||||
|
style.position = 'absolute';
|
||||||
|
style.transformOrigin = 'top left';
|
||||||
|
this.container = container;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -137,8 +137,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
* @returns {HTMLElement} The rendered element.
|
* @returns {HTMLElement} The rendered element.
|
||||||
*/
|
*/
|
||||||
renderFrame(frameState, layerState) {
|
renderFrame(frameState, layerState, target) {
|
||||||
const context = this.context;
|
|
||||||
const viewState = frameState.viewState;
|
const viewState = frameState.viewState;
|
||||||
const projection = viewState.projection;
|
const projection = viewState.projection;
|
||||||
const viewResolution = viewState.resolution;
|
const viewResolution = viewState.resolution;
|
||||||
@@ -224,7 +223,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const canvas = context.canvas;
|
|
||||||
const canvasScale = tileResolution / viewResolution;
|
const canvasScale = tileResolution / viewResolution;
|
||||||
|
|
||||||
// set forward and inverse pixel transforms
|
// set forward and inverse pixel transforms
|
||||||
@@ -234,6 +232,11 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
rotation,
|
rotation,
|
||||||
-width / 2, -height / 2
|
-width / 2, -height / 2
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.useContainer(target, this.pixelTransform_, layerState.opacity);
|
||||||
|
const context = this.context;
|
||||||
|
const canvas = context.canvas;
|
||||||
|
|
||||||
makeInverse(this.inversePixelTransform_, this.pixelTransform_);
|
makeInverse(this.inversePixelTransform_, this.pixelTransform_);
|
||||||
|
|
||||||
// set scale transform for calculating tile positions on the canvas
|
// set scale transform for calculating tile positions on the canvas
|
||||||
@@ -247,7 +250,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
if (canvas.width != width || canvas.height != height) {
|
if (canvas.width != width || canvas.height != height) {
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
} else {
|
} else if (!this.containerReused) {
|
||||||
context.clearRect(0, 0, width, height);
|
context.clearRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +262,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
this.renderedTiles.length = 0;
|
this.renderedTiles.length = 0;
|
||||||
/** @type {Array<number>} */
|
/** @type {Array<number>} */
|
||||||
const zs = Object.keys(tilesToDrawByZ).map(Number);
|
let zs = Object.keys(tilesToDrawByZ).map(Number);
|
||||||
zs.sort(function(a, b) {
|
zs.sort(function(a, b) {
|
||||||
if (a === z) {
|
if (a === z) {
|
||||||
return 1;
|
return 1;
|
||||||
@@ -270,7 +273,14 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let i = 0, ii = zs.length; i < ii; ++i) {
|
let clips, clipZs, currentClip;
|
||||||
|
if (layerState.opacity === 1 && (!this.containerReused || tileSource.getOpaque(frameState.viewState.projection))) {
|
||||||
|
zs = zs.reverse();
|
||||||
|
} else {
|
||||||
|
clips = [];
|
||||||
|
clipZs = [];
|
||||||
|
}
|
||||||
|
for (let i = zs.length - 1; i >= 0; --i) {
|
||||||
const currentZ = zs[i];
|
const currentZ = zs[i];
|
||||||
const currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection);
|
const currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection);
|
||||||
const currentResolution = tileGrid.getResolution(currentZ);
|
const currentResolution = tileGrid.getResolution(currentZ);
|
||||||
@@ -298,8 +308,36 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const y = Math.round(floatY);
|
const y = Math.round(floatY);
|
||||||
const w = nextX - x;
|
const w = nextX - x;
|
||||||
const h = nextY - y;
|
const h = nextY - y;
|
||||||
|
const transition = z === currentZ;
|
||||||
|
|
||||||
this.drawTileImage(tile, frameState, x, y, w, h, tileGutter, z === currentZ);
|
if (clips && (!transition || tile.getAlpha(getUid(this), frameState.time) === 1)) {
|
||||||
|
// 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 (let i = 0, ii = clips.length; i < ii; ++i) {
|
||||||
|
if (z !== currentZ && currentZ < clipZs[i]) {
|
||||||
|
const clip = clips[i];
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
this.drawTileImage(tile, frameState, x, y, w, h, tileGutter, transition, layerState.opacity);
|
||||||
|
if (clips) {
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
this.renderedTiles.push(tile);
|
this.renderedTiles.push(tile);
|
||||||
this.updateUsedTiles(frameState.usedTiles, tileSource, tile);
|
this.updateUsedTiles(frameState.usedTiles, tileSource, tile);
|
||||||
}
|
}
|
||||||
@@ -322,17 +360,12 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
context.restore();
|
context.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
const opacity = layerState.opacity;
|
|
||||||
if (opacity !== parseFloat(canvas.style.opacity)) {
|
|
||||||
canvas.style.opacity = opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
const canvasTransform = transformToString(this.pixelTransform_);
|
const canvasTransform = transformToString(this.pixelTransform_);
|
||||||
if (canvasTransform !== canvas.style.transform) {
|
if (canvasTransform !== canvas.style.transform) {
|
||||||
canvas.style.transform = canvasTransform;
|
canvas.style.transform = canvasTransform;
|
||||||
}
|
}
|
||||||
|
|
||||||
return canvas;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -344,19 +377,15 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
* @param {number} h Height of the tile.
|
* @param {number} h Height of the tile.
|
||||||
* @param {number} gutter Tile gutter.
|
* @param {number} gutter Tile gutter.
|
||||||
* @param {boolean} transition Apply an alpha transition.
|
* @param {boolean} transition Apply an alpha transition.
|
||||||
|
* @param {number} opacity Opacity.
|
||||||
*/
|
*/
|
||||||
drawTileImage(tile, frameState, x, y, w, h, gutter, transition) {
|
drawTileImage(tile, frameState, x, y, w, h, gutter, transition, opacity) {
|
||||||
const image = this.getTileImage(tile);
|
const image = this.getTileImage(tile);
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const uid = getUid(this);
|
const uid = getUid(this);
|
||||||
const alpha = transition ? tile.getAlpha(uid, frameState.time) : 1;
|
const alpha = opacity * (transition ? tile.getAlpha(uid, frameState.time) : 1);
|
||||||
const tileLayer = /** @type {import("../../layer/Tile.js").default} */ (this.getLayer());
|
|
||||||
const tileSource = tileLayer.getSource();
|
|
||||||
if (alpha === 1 && !tileSource.getOpaque(frameState.viewState.projection)) {
|
|
||||||
this.context.clearRect(x, y, w, h);
|
|
||||||
}
|
|
||||||
const alphaChanged = alpha !== this.context.globalAlpha;
|
const alphaChanged = alpha !== this.context.globalAlpha;
|
||||||
if (alphaChanged) {
|
if (alphaChanged) {
|
||||||
this.context.save();
|
this.context.save();
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
|||||||
|
|
||||||
if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) {
|
if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) {
|
||||||
let skippedFeatures = this.skippedFeatures_;
|
let skippedFeatures = this.skippedFeatures_;
|
||||||
|
vectorRenderer.useContainer(null, null, 1);
|
||||||
const context = vectorRenderer.context;
|
const context = vectorRenderer.context;
|
||||||
const imageFrameState = /** @type {import("../../PluggableMap.js").FrameState} */ (assign({}, frameState, {
|
const imageFrameState = /** @type {import("../../PluggableMap.js").FrameState} */ (assign({}, frameState, {
|
||||||
declutterItems: [],
|
declutterItems: [],
|
||||||
@@ -94,7 +95,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
|||||||
if (vectorRenderer.prepareFrame(imageFrameState, layerState) &&
|
if (vectorRenderer.prepareFrame(imageFrameState, layerState) &&
|
||||||
(vectorRenderer.replayGroupChanged ||
|
(vectorRenderer.replayGroupChanged ||
|
||||||
!equals(skippedFeatures, newSkippedFeatures))) {
|
!equals(skippedFeatures, newSkippedFeatures))) {
|
||||||
vectorRenderer.renderFrame(imageFrameState, layerState);
|
vectorRenderer.renderFrame(imageFrameState, layerState, null);
|
||||||
renderDeclutterItems(imageFrameState, null);
|
renderDeclutterItems(imageFrameState, null);
|
||||||
skippedFeatures = newSkippedFeatures;
|
skippedFeatures = newSkippedFeatures;
|
||||||
callback();
|
callback();
|
||||||
|
|||||||
@@ -70,17 +70,17 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
renderFrame(frameState, layerState) {
|
useContainer(target, transform, opacity) {
|
||||||
const context = this.context;
|
if (opacity < 1) {
|
||||||
const canvas = context.canvas;
|
target = null;
|
||||||
|
|
||||||
const replayGroup = this.replayGroup_;
|
|
||||||
if (!replayGroup || replayGroup.isEmpty()) {
|
|
||||||
if (canvas.width > 0) {
|
|
||||||
canvas.width = 0;
|
|
||||||
}
|
|
||||||
return canvas;
|
|
||||||
}
|
}
|
||||||
|
super.useContainer(target, transform, opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
renderFrame(frameState, layerState, target) {
|
||||||
|
|
||||||
const pixelRatio = frameState.pixelRatio;
|
const pixelRatio = frameState.pixelRatio;
|
||||||
|
|
||||||
@@ -88,6 +88,18 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
makeScale(this.pixelTransform_, 1 / pixelRatio, 1 / pixelRatio);
|
makeScale(this.pixelTransform_, 1 / pixelRatio, 1 / pixelRatio);
|
||||||
makeInverse(this.inversePixelTransform_, this.pixelTransform_);
|
makeInverse(this.inversePixelTransform_, this.pixelTransform_);
|
||||||
|
|
||||||
|
this.useContainer(target, this.pixelTransform_, layerState.opacity);
|
||||||
|
const context = this.context;
|
||||||
|
const canvas = context.canvas;
|
||||||
|
|
||||||
|
const replayGroup = this.replayGroup_;
|
||||||
|
if (!replayGroup || replayGroup.isEmpty()) {
|
||||||
|
if (!this.containerReused && canvas.width > 0) {
|
||||||
|
canvas.width = 0;
|
||||||
|
}
|
||||||
|
return this.container;
|
||||||
|
}
|
||||||
|
|
||||||
// resize and clear
|
// resize and clear
|
||||||
const width = Math.round(frameState.size[0] * pixelRatio);
|
const width = Math.round(frameState.size[0] * pixelRatio);
|
||||||
const height = Math.round(frameState.size[1] * pixelRatio);
|
const height = Math.round(frameState.size[1] * pixelRatio);
|
||||||
@@ -98,7 +110,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
if (canvas.style.transform !== canvasTransform) {
|
if (canvas.style.transform !== canvasTransform) {
|
||||||
canvas.style.transform = canvasTransform;
|
canvas.style.transform = canvasTransform;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (!this.containerReused) {
|
||||||
context.clearRect(0, 0, width, height);
|
context.clearRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +164,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
if (declutterReplays) {
|
if (declutterReplays) {
|
||||||
const viewHints = frameState.viewHints;
|
const viewHints = frameState.viewHints;
|
||||||
const hifi = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
const hifi = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
||||||
replayDeclutter(declutterReplays, context, rotation, hifi, frameState.declutterItems);
|
replayDeclutter(declutterReplays, context, rotation, 1, hifi, frameState.declutterItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clipped) {
|
if (clipped) {
|
||||||
@@ -162,11 +174,12 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
this.postRender(context, frameState);
|
this.postRender(context, frameState);
|
||||||
|
|
||||||
const opacity = layerState.opacity;
|
const opacity = layerState.opacity;
|
||||||
if (opacity !== parseFloat(canvas.style.opacity)) {
|
const container = this.container;
|
||||||
canvas.style.opacity = opacity;
|
if (opacity !== parseFloat(container.style.opacity)) {
|
||||||
|
container.style.opacity = opacity === 1 ? '' : opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
return canvas;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import {
|
|||||||
makeInverse
|
makeInverse
|
||||||
} from '../../transform.js';
|
} from '../../transform.js';
|
||||||
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
||||||
import {clear, isEmpty} from '../../obj.js';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,32 +58,11 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
constructor(layer) {
|
constructor(layer) {
|
||||||
super(layer);
|
super(layer);
|
||||||
|
|
||||||
const baseCanvas = this.context.canvas;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {CanvasRenderingContext2D}
|
* @type {CanvasRenderingContext2D}
|
||||||
*/
|
*/
|
||||||
this.overlayContext_ = createCanvasContext2D();
|
this.overlayContext_ = null;
|
||||||
|
|
||||||
const overlayCanvas = this.overlayContext_.canvas;
|
|
||||||
overlayCanvas.style.position = 'absolute';
|
|
||||||
overlayCanvas.style.transformOrigin = 'top left';
|
|
||||||
|
|
||||||
const container = document.createElement('div');
|
|
||||||
const style = container.style;
|
|
||||||
style.position = 'absolute';
|
|
||||||
style.width = '100%';
|
|
||||||
style.height = '100%';
|
|
||||||
|
|
||||||
container.appendChild(baseCanvas);
|
|
||||||
container.appendChild(overlayCanvas);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {HTMLElement}
|
|
||||||
*/
|
|
||||||
this.container_ = container;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The transform for rendered pixels to viewport CSS pixels for the overlay canvas.
|
* The transform for rendered pixels to viewport CSS pixels for the overlay canvas.
|
||||||
@@ -136,17 +114,43 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
disposeInternal() {
|
useContainer(target, transform, opacity) {
|
||||||
this.overlayContext_.canvas.width = this.overlayContext_.canvas.height = 0;
|
let overlayContext;
|
||||||
super.disposeInternal();
|
if (target && target.childElementCount === 2) {
|
||||||
|
overlayContext = target.lastElementChild.getContext('2d');
|
||||||
|
if (!overlayContext) {
|
||||||
|
target = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const containerReused = this.containerReused;
|
||||||
|
super.useContainer(target, transform, opacity);
|
||||||
|
if (containerReused && !this.containerReused && !overlayContext) {
|
||||||
|
this.overlayContext_ = null;
|
||||||
|
}
|
||||||
|
if (this.containerReused && overlayContext) {
|
||||||
|
this.overlayContext_ = overlayContext;
|
||||||
|
}
|
||||||
|
if (!this.overlayContext_) {
|
||||||
|
const overlayContext = createCanvasContext2D();
|
||||||
|
const style = overlayContext.canvas.style;
|
||||||
|
style.position = 'absolute';
|
||||||
|
style.transformOrigin = 'top left';
|
||||||
|
this.overlayContext_ = overlayContext;
|
||||||
|
}
|
||||||
|
if (this.container.childElementCount === 1) {
|
||||||
|
this.container.appendChild(this.overlayContext_.canvas);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../../VectorRenderTile.js").default} tile Tile.
|
* @param {import("../../VectorRenderTile.js").default} tile Tile.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {import("../../proj/Projection").default} projection Projection.
|
* @param {import("../../proj/Projection").default} projection Projection.
|
||||||
|
* @param {boolean} queue Queue tile for rendering.
|
||||||
|
* @return {boolean} Tile needs to be rendered.
|
||||||
*/
|
*/
|
||||||
prepareTile(tile, pixelRatio, projection) {
|
prepareTile(tile, pixelRatio, projection, queue) {
|
||||||
|
let render = false;
|
||||||
const tileUid = getUid(tile);
|
const tileUid = getUid(tile);
|
||||||
const state = tile.getState();
|
const state = tile.getState();
|
||||||
if (((state === TileState.LOADED && tile.hifi) ||
|
if (((state === TileState.LOADED && tile.hifi) ||
|
||||||
@@ -158,9 +162,13 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
if (state === TileState.LOADED || state === TileState.ERROR) {
|
if (state === TileState.LOADED || state === TileState.ERROR) {
|
||||||
this.updateExecutorGroup_(tile, pixelRatio, projection);
|
this.updateExecutorGroup_(tile, pixelRatio, projection);
|
||||||
if (this.tileImageNeedsRender_(tile, pixelRatio, projection)) {
|
if (this.tileImageNeedsRender_(tile, pixelRatio, projection)) {
|
||||||
this.renderTileImageQueue_[tileUid] = tile;
|
render = true;
|
||||||
|
if (queue) {
|
||||||
|
this.renderTileImageQueue_[tileUid] = tile;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return render;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -176,7 +184,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
tile.wantedResolution = resolution;
|
tile.wantedResolution = resolution;
|
||||||
const tileUid = getUid(tile);
|
const tileUid = getUid(tile);
|
||||||
if (!(tileUid in this.tileListenerKeys_)) {
|
if (!(tileUid in this.tileListenerKeys_)) {
|
||||||
const listenerKey = listen(tile, EventType.CHANGE, this.prepareTile.bind(this, tile, pixelRatio, projection));
|
const listenerKey = listen(tile, EventType.CHANGE, this.prepareTile.bind(this, tile, pixelRatio, projection, true));
|
||||||
this.tileListenerKeys_[tileUid] = listenerKey;
|
this.tileListenerKeys_[tileUid] = listenerKey;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -185,7 +193,10 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
if (hifi || !tile.wantedResolution) {
|
if (hifi || !tile.wantedResolution) {
|
||||||
tile.wantedResolution = resolution;
|
tile.wantedResolution = resolution;
|
||||||
}
|
}
|
||||||
this.prepareTile(tile, pixelRatio, projection);
|
const render = this.prepareTile(tile, pixelRatio, projection, false);
|
||||||
|
if (render) {
|
||||||
|
this.renderTileImage_(tile, frameState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return tile;
|
return tile;
|
||||||
}
|
}
|
||||||
@@ -379,26 +390,30 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
renderFrame(frameState, layerState) {
|
renderFrame(frameState, layerState, target) {
|
||||||
super.renderFrame(frameState, layerState);
|
|
||||||
|
|
||||||
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
|
||||||
const viewHints = frameState.viewHints;
|
const viewHints = frameState.viewHints;
|
||||||
const hifi = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
const hifi = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
||||||
|
this.renderQueuedTileImages_(hifi, frameState);
|
||||||
|
|
||||||
|
super.renderFrame(frameState, layerState, target);
|
||||||
|
|
||||||
|
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
||||||
const renderMode = layer.getRenderMode();
|
const renderMode = layer.getRenderMode();
|
||||||
if (renderMode === VectorTileRenderType.IMAGE) {
|
if (renderMode === VectorTileRenderType.IMAGE) {
|
||||||
this.renderTileImages_(hifi, frameState);
|
return this.container;
|
||||||
return this.container_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEmpty(this.renderTileImageQueue_) && !this.extentChanged) {
|
const source = layer.getSource();
|
||||||
this.renderTileImages_(hifi, frameState);
|
// Unqueue tiles from the image queue when we don't need any more
|
||||||
return this.container_;
|
const usedTiles = frameState.usedTiles[getUid(source)];
|
||||||
|
for (const tileUid in this.renderTileImageQueue_) {
|
||||||
|
if (!(tileUid in usedTiles)) {
|
||||||
|
delete this.renderTileImageQueue_[tileUid];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = this.overlayContext_;
|
const context = this.overlayContext_;
|
||||||
const declutterReplays = layer.getDeclutter() ? {} : null;
|
const declutterReplays = layer.getDeclutter() ? {} : null;
|
||||||
const source = layer.getSource();
|
|
||||||
const replayTypes = VECTOR_REPLAYS[renderMode];
|
const replayTypes = VECTOR_REPLAYS[renderMode];
|
||||||
const pixelRatio = frameState.pixelRatio;
|
const pixelRatio = frameState.pixelRatio;
|
||||||
const rotation = frameState.viewState.rotation;
|
const rotation = frameState.viewState.rotation;
|
||||||
@@ -419,13 +434,14 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
if (canvas.style.transform !== canvasTransform) {
|
if (canvas.style.transform !== canvasTransform) {
|
||||||
canvas.style.transform = canvasTransform;
|
canvas.style.transform = canvasTransform;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (!this.containerReused) {
|
||||||
context.clearRect(0, 0, width, height);
|
context.clearRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tiles = this.renderedTiles;
|
const tiles = this.renderedTiles;
|
||||||
const tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
|
const tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
|
||||||
const clips = [];
|
const clips = [];
|
||||||
|
const clipZs = [];
|
||||||
for (let i = tiles.length - 1; i >= 0; --i) {
|
for (let i = tiles.length - 1; i >= 0; --i) {
|
||||||
const tile = /** @type {import("../../VectorRenderTile.js").default} */ (tiles[i]);
|
const tile = /** @type {import("../../VectorRenderTile.js").default} */ (tiles[i]);
|
||||||
if (tile.getState() == TileState.ABORT) {
|
if (tile.getState() == TileState.ABORT) {
|
||||||
@@ -436,6 +452,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tileExtent[0];
|
const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tileExtent[0];
|
||||||
const transform = this.getRenderTransform(frameState, width, height, worldOffset);
|
const transform = this.getRenderTransform(frameState, width, height, worldOffset);
|
||||||
const executorGroups = tile.executorGroups[getUid(layer)];
|
const executorGroups = tile.executorGroups[getUid(layer)];
|
||||||
|
let clipped = false;
|
||||||
for (let t = 0, tt = executorGroups.length; t < tt; ++t) {
|
for (let t = 0, tt = executorGroups.length; t < tt; ++t) {
|
||||||
const executorGroup = executorGroups[t];
|
const executorGroup = executorGroups[t];
|
||||||
if (!executorGroup.hasExecutors(replayTypes)) {
|
if (!executorGroup.hasExecutors(replayTypes)) {
|
||||||
@@ -443,9 +460,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const currentZ = tile.tileCoord[0];
|
const currentZ = tile.tileCoord[0];
|
||||||
let zs, currentClip;
|
let currentClip;
|
||||||
if (!declutterReplays) {
|
if (!declutterReplays && !clipped) {
|
||||||
zs = [];
|
|
||||||
currentClip = executorGroup.getClipCoords(transform);
|
currentClip = executorGroup.getClipCoords(transform);
|
||||||
context.save();
|
context.save();
|
||||||
|
|
||||||
@@ -453,7 +469,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
// already filled by a higher resolution tile
|
// already filled by a higher resolution tile
|
||||||
for (let j = 0, jj = clips.length; j < jj; ++j) {
|
for (let j = 0, jj = clips.length; j < jj; ++j) {
|
||||||
const clip = clips[j];
|
const clip = clips[j];
|
||||||
if (currentZ < zs[j]) {
|
if (currentZ < clipZs[j]) {
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
// counter-clockwise (outer ring) for current tile
|
// counter-clockwise (outer ring) for current tile
|
||||||
context.moveTo(currentClip[0], currentClip[1]);
|
context.moveTo(currentClip[0], currentClip[1]);
|
||||||
@@ -470,51 +486,37 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
executorGroup.execute(context, transform, rotation, {}, hifi, replayTypes, declutterReplays);
|
executorGroup.execute(context, transform, rotation, {}, hifi, replayTypes, declutterReplays);
|
||||||
if (!declutterReplays) {
|
if (!declutterReplays && !clipped) {
|
||||||
context.restore();
|
context.restore();
|
||||||
clips.push(currentClip);
|
clips.push(currentClip);
|
||||||
zs.push(currentZ);
|
clipZs.push(currentZ);
|
||||||
|
clipped = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (declutterReplays) {
|
if (declutterReplays) {
|
||||||
replayDeclutter(declutterReplays, context, rotation, hifi, frameState.declutterItems);
|
replayDeclutter(declutterReplays, context, rotation, layerState.opacity, hifi, frameState.declutterItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
const opacity = layerState.opacity;
|
return this.container;
|
||||||
if (opacity !== parseFloat(canvas.style.opacity)) {
|
|
||||||
canvas.style.opacity = opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} hifi We have time to render a high fidelity map image.
|
* @param {boolean} hifi We have time to render a high fidelity map image.
|
||||||
* @param {import('../../PluggableMap.js').FrameState} frameState Frame state.
|
* @param {import('../../PluggableMap.js').FrameState} frameState Frame state.
|
||||||
*/
|
*/
|
||||||
renderTileImages_(hifi, frameState) {
|
renderQueuedTileImages_(hifi, frameState) {
|
||||||
// When we don't have time to render hifi, only render tiles until we have used up
|
// When we don't have time to render hifi, only render tiles until we have used up
|
||||||
// half of the frame budget of 16 ms
|
// half of the frame budget of 16 ms
|
||||||
for (const uid in this.renderTileImageQueue_) {
|
for (const uid in this.renderTileImageQueue_) {
|
||||||
if (!hifi && Date.now() - frameState.time > 8) {
|
if (!hifi && Date.now() - frameState.time > 8) {
|
||||||
|
frameState.animate = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const tile = this.renderTileImageQueue_[uid];
|
const tile = this.renderTileImageQueue_[uid];
|
||||||
frameState.animate = true;
|
|
||||||
delete this.renderTileImageQueue_[uid];
|
delete this.renderTileImageQueue_[uid];
|
||||||
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
this.renderTileImage_(tile, frameState);
|
||||||
const viewState = frameState.viewState;
|
|
||||||
const tileGrid = layer.getSource().getTileGridForProjection(viewState.projection);
|
|
||||||
const tileResolution = tileGrid.getResolution(tile.tileCoord[0]);
|
|
||||||
const renderPixelRatio = frameState.pixelRatio / tile.wantedResolution * tileResolution;
|
|
||||||
this.renderTileImage_(tile, frameState.pixelRatio, renderPixelRatio, viewState.projection);
|
|
||||||
}
|
}
|
||||||
clear(this.renderTileImageQueue_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -561,24 +563,29 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../../VectorRenderTile.js").default} tile Tile.
|
* @param {import("../../VectorRenderTile.js").default} tile Tile.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {import("../../PluggableMap").FrameState} frameState Frame state.
|
||||||
* @param {number} renderPixelRatio Render pixel ratio.
|
|
||||||
* @param {import("../../proj/Projection.js").default} projection Projection.
|
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
renderTileImage_(tile, pixelRatio, renderPixelRatio, projection) {
|
renderTileImage_(tile, frameState) {
|
||||||
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
||||||
const replayState = tile.getReplayState(layer);
|
const replayState = tile.getReplayState(layer);
|
||||||
const revision = layer.getRevision();
|
const revision = layer.getRevision();
|
||||||
const executorGroups = tile.executorGroups[getUid(layer)];
|
const executorGroups = tile.executorGroups[getUid(layer)];
|
||||||
replayState.renderedTileRevision = revision;
|
replayState.renderedTileRevision = revision;
|
||||||
replayState.renderedTileZ = tile.sourceZ;
|
replayState.renderedTileZ = tile.sourceZ;
|
||||||
|
|
||||||
const tileCoord = tile.wrappedTileCoord;
|
const tileCoord = tile.wrappedTileCoord;
|
||||||
const z = tileCoord[0];
|
const z = tileCoord[0];
|
||||||
const source = layer.getSource();
|
const source = layer.getSource();
|
||||||
|
let pixelRatio = frameState.pixelRatio;
|
||||||
|
const viewState = frameState.viewState;
|
||||||
|
const projection = viewState.projection;
|
||||||
const tileGrid = source.getTileGridForProjection(projection);
|
const tileGrid = source.getTileGridForProjection(projection);
|
||||||
|
const tileResolution = tileGrid.getResolution(tile.tileCoord[0]);
|
||||||
|
const renderPixelRatio = frameState.pixelRatio / tile.wantedResolution * tileResolution;
|
||||||
const resolution = tileGrid.getResolution(z);
|
const resolution = tileGrid.getResolution(z);
|
||||||
const context = tile.getContext(layer);
|
const context = tile.getContext(layer);
|
||||||
|
|
||||||
// Increase tile size when overzooming for low pixel ratio, to avoid blurry tiles
|
// Increase tile size when overzooming for low pixel ratio, to avoid blurry tiles
|
||||||
pixelRatio = Math.max(pixelRatio, renderPixelRatio / pixelRatio);
|
pixelRatio = Math.max(pixelRatio, renderPixelRatio / pixelRatio);
|
||||||
const size = source.getTilePixelSize(z, pixelRatio, projection);
|
const size = source.getTilePixelSize(z, pixelRatio, projection);
|
||||||
|
|||||||
@@ -444,7 +444,11 @@ function getImageData(layer, frameState, layerState) {
|
|||||||
}
|
}
|
||||||
const width = frameState.size[0];
|
const width = frameState.size[0];
|
||||||
const height = frameState.size[1];
|
const height = frameState.size[1];
|
||||||
const element = renderer.renderFrame(frameState, layerState);
|
const container = renderer.renderFrame(frameState, layerState, null);
|
||||||
|
let element;
|
||||||
|
if (container) {
|
||||||
|
element = container.firstElementChild;
|
||||||
|
}
|
||||||
if (!(element instanceof HTMLCanvasElement)) {
|
if (!(element instanceof HTMLCanvasElement)) {
|
||||||
throw new Error('Unsupported rendered element: ' + element);
|
throw new Error('Unsupported rendered element: ' + element);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ describe('ol.renderer.canvas.VectorLayer', function() {
|
|||||||
let rendered = false;
|
let rendered = false;
|
||||||
if (renderer.prepareFrame(frameState, {})) {
|
if (renderer.prepareFrame(frameState, {})) {
|
||||||
rendered = true;
|
rendered = true;
|
||||||
renderer.renderFrame(frameState, layer.getLayerState());
|
renderer.renderFrame(frameState, layer.getLayerState(), null);
|
||||||
}
|
}
|
||||||
expect(rendered).to.be(true);
|
expect(rendered).to.be(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -250,10 +250,10 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
|||||||
renderer.renderFrame(frameState, {});
|
renderer.renderFrame(frameState, {});
|
||||||
const replayState = renderer.renderedTiles[0].getReplayState(layer);
|
const replayState = renderer.renderedTiles[0].getReplayState(layer);
|
||||||
const revision = replayState.renderedTileRevision;
|
const revision = replayState.renderedTileRevision;
|
||||||
renderer.renderFrame(frameState, {});
|
renderer.renderFrame(frameState, {}, null);
|
||||||
expect(replayState.renderedTileRevision).to.be(revision);
|
expect(replayState.renderedTileRevision).to.be(revision);
|
||||||
layer.changed();
|
layer.changed();
|
||||||
renderer.renderFrame(frameState, {});
|
renderer.renderFrame(frameState, {}, null);
|
||||||
expect(replayState.renderedTileRevision).to.be(revision + 1);
|
expect(replayState.renderedTileRevision).to.be(revision + 1);
|
||||||
expect(Object.keys(renderer.tileListenerKeys_).length).to.be(0);
|
expect(Object.keys(renderer.tileListenerKeys_).length).to.be(0);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user