Tile layer rendering with the composite renderer

This commit is contained in:
Tim Schaub
2018-11-14 15:45:03 +01:00
parent 433ab97d1c
commit 5ba8795355
4 changed files with 313 additions and 88 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -2,33 +2,34 @@ import Map from '../../../src/ol/Map.js';
import View from '../../../src/ol/View.js'; import View from '../../../src/ol/View.js';
import {Vector as VectorLayer, Tile as TileLayer} from '../../../src/ol/layer.js'; import {Vector as VectorLayer, Tile as TileLayer} from '../../../src/ol/layer.js';
import {Vector as VectorSource, XYZ} from '../../../src/ol/source.js'; import {Vector as VectorSource, XYZ} from '../../../src/ol/source.js';
import Point from '../../../src/ol/geom/Point.js'; import GeoJSON from '../../../src/ol/format/GeoJSON.js';
import Feature from '../../../src/ol/Feature.js'; import {Style, Stroke} from '../../../src/ol/style.js';
import {fromLonLat} from '../../../src/ol/proj.js';
const center = fromLonLat([-111, 45.7]);
new Map({ new Map({
layers: [ layers: [
new TileLayer({ new TileLayer({
source: new XYZ({ source: new XYZ({
url: '/data/tiles/satellite/{z}/{x}/{y}.jpg' url: '/data/tiles/satellite/{z}/{x}/{y}.jpg',
maxZoom: 3
}) })
}), }),
new VectorLayer({ new VectorLayer({
style: new Style({
stroke: new Stroke({
color: 'rgba(255,255,255,0.5)',
width: 0.75
})
}),
source: new VectorSource({ source: new VectorSource({
features: [ url: '/data/countries.json',
new Feature( format: new GeoJSON()
new Point(center)
)
]
}) })
}) })
], ],
target: 'map', target: 'map',
view: new View({ view: new View({
center: center, center: [0, 0],
zoom: 3 zoom: 2
}) })
}); });

File diff suppressed because one or more lines are too long

View File

@@ -4,9 +4,9 @@
import {getUid} from '../../util.js'; import {getUid} from '../../util.js';
import TileRange from '../../TileRange.js'; import TileRange from '../../TileRange.js';
import TileState from '../../TileState.js'; import TileState from '../../TileState.js';
import ViewHint from '../../ViewHint.js'; import {createEmpty, equals, getIntersection, isEmpty} from '../../extent.js';
import {containsExtent, createEmpty, equals, getIntersection, isEmpty} from '../../extent.js'; import {createCanvasContext2D} from '../../dom.js';
import IntermediateCanvasRenderer from './IntermediateCanvas.js'; import CanvasLayerRenderer from './Layer.js';
import {create as createTransform, compose as composeTransform} from '../../transform.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js';
/** /**
@@ -14,15 +14,28 @@ import {create as createTransform, compose as composeTransform} from '../../tran
* Canvas renderer for tile layers. * Canvas renderer for tile layers.
* @api * @api
*/ */
class CanvasTileLayerRenderer extends IntermediateCanvasRenderer { class CanvasTileLayerRenderer extends CanvasLayerRenderer {
/** /**
* @param {import("../../layer/Tile.js").default|import("../../layer/VectorTile.js").default} tileLayer Tile layer. * @param {import("../../layer/Tile.js").default|import("../../layer/VectorTile.js").default} tileLayer Tile layer.
* @param {boolean=} opt_noContext Skip the context creation.
*/ */
constructor(tileLayer, opt_noContext) { constructor(tileLayer) {
super(tileLayer);
super(tileLayer, opt_noContext); /**
* @protected
* @type {CanvasRenderingContext2D}
*/
this.context = createCanvasContext2D();
const canvas = this.context.canvas;
canvas.style.position = 'absolute';
/**
* @protected
* @type {import("../../transform.js").Transform}
*/
this.coordinateToCanvasPixelTransform = createTransform();
/** /**
* @private * @private
@@ -125,13 +138,13 @@ class CanvasTileLayerRenderer extends IntermediateCanvasRenderer {
* @inheritDoc * @inheritDoc
*/ */
prepareFrame(frameState, layerState) { prepareFrame(frameState, layerState) {
const pixelRatio = frameState.pixelRatio;
const size = frameState.size; const size = frameState.size;
const viewState = frameState.viewState; const viewState = frameState.viewState;
const projection = viewState.projection; const projection = viewState.projection;
const viewResolution = viewState.resolution; const viewResolution = viewState.resolution;
const viewCenter = viewState.center; const viewCenter = viewState.center;
const rotation = viewState.rotation;
const pixelRatio = frameState.pixelRatio;
const tileLayer = /** @type {import("../../layer/Tile.js").default} */ (this.getLayer()); const tileLayer = /** @type {import("../../layer/Tile.js").default} */ (this.getLayer());
const tileSource = /** @type {import("../../source/Tile.js").default} */ (tileLayer.getSource()); const tileSource = /** @type {import("../../source/Tile.js").default} */ (tileLayer.getSource());
@@ -139,7 +152,6 @@ class CanvasTileLayerRenderer extends IntermediateCanvasRenderer {
const tileGrid = tileSource.getTileGridForProjection(projection); const tileGrid = tileSource.getTileGridForProjection(projection);
const z = tileGrid.getZForResolution(viewResolution, this.zDirection); const z = tileGrid.getZForResolution(viewResolution, this.zDirection);
const tileResolution = tileGrid.getResolution(z); const tileResolution = tileGrid.getResolution(z);
let oversampling = Math.round(viewResolution / tileResolution) || 1;
let extent = frameState.extent; let extent = frameState.extent;
if (layerState.extent !== undefined) { if (layerState.extent !== undefined) {
@@ -149,34 +161,49 @@ class CanvasTileLayerRenderer extends IntermediateCanvasRenderer {
// Return false to prevent the rendering of the layer. // Return false to prevent the rendering of the layer.
return false; return false;
} }
// TODO: clip by layer extent
const tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
const imageExtent = tileGrid.getTileRangeExtent(z, tileRange);
const tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio); const tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio);
// desired dimensions of the canvas in pixels
let width = Math.round(frameState.size[0] * tilePixelRatio);
let height = Math.round(frameState.size[1] * tilePixelRatio);
if (tileResolution < viewResolution) {
const scale = tileResolution / tileGrid.getResolution(z + 1);
width *= scale;
height *= scale;
}
if (rotation) {
const size = Math.round(Math.sqrt(width * width + height * height));
width = height = size;
}
const dx = tileResolution * width / 2;
const dy = tileResolution * height / 2;
const canvasExtent = [
viewCenter[0] - dx,
viewCenter[1] - dy,
viewCenter[0] + dx,
viewCenter[1] + dy
];
const tileRange = tileGrid.getTileRangeForExtentAndZ(canvasExtent, z);
/** /**
* @type {Object<number, Object<string, import("../../Tile.js").default>>} * @type {Object<number, Object<string, import("../../Tile.js").default>>}
*/ */
const tilesToDrawByZ = {}; const tilesToDrawByZ = {};
tilesToDrawByZ[z] = {}; tilesToDrawByZ[z] = {};
const findLoadedTiles = this.createLoadedTileFinder( const findLoadedTiles = this.createLoadedTileFinder(tileSource, projection, tilesToDrawByZ);
tileSource, projection, tilesToDrawByZ);
const hints = frameState.viewHints;
const animatingOrInteracting = hints[ViewHint.ANIMATING] || hints[ViewHint.INTERACTING];
const tmpExtent = this.tmpExtent; const tmpExtent = this.tmpExtent;
const tmpTileRange = this.tmpTileRange_; const tmpTileRange = this.tmpTileRange_;
this.newTiles_ = false; this.newTiles_ = false;
let tile, x, y; for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
for (x = tileRange.minX; x <= tileRange.maxX; ++x) { for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
for (y = tileRange.minY; y <= tileRange.maxY; ++y) { const tile = this.getTile(z, x, y, pixelRatio, projection);
if (Date.now() - frameState.time > 16 && animatingOrInteracting) {
continue;
}
tile = this.getTile(z, x, y, pixelRatio, projection);
if (this.isDrawableTile_(tile)) { if (this.isDrawableTile_(tile)) {
const uid = getUid(this); const uid = getUid(this);
if (tile.getState() == TileState.LOADED) { if (tile.getState() == TileState.LOADED) {
@@ -192,45 +219,28 @@ class CanvasTileLayerRenderer extends IntermediateCanvasRenderer {
} }
} }
const childTileRange = tileGrid.getTileCoordChildTileRange( const childTileRange = tileGrid.getTileCoordChildTileRange(tile.tileCoord, tmpTileRange, tmpExtent);
tile.tileCoord, tmpTileRange, tmpExtent);
let covered = false; let covered = false;
if (childTileRange) { if (childTileRange) {
covered = findLoadedTiles(z + 1, childTileRange); covered = findLoadedTiles(z + 1, childTileRange);
} }
if (!covered) { if (!covered) {
tileGrid.forEachTileCoordParentTileRange( tileGrid.forEachTileCoordParentTileRange(tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
} }
} }
} }
const renderedResolution = tileResolution * pixelRatio / tilePixelRatio * oversampling; if (this.newTiles_ || !this.renderedExtent_ || !equals(canvasExtent, this.renderedExtent_)) {
if (!(this.renderedResolution && Date.now() - frameState.time > 16 && animatingOrInteracting) && (
this.newTiles_ ||
!(this.renderedExtent_ && containsExtent(this.renderedExtent_, extent)) ||
this.renderedRevision != sourceRevision ||
oversampling != this.oversampling_ ||
!animatingOrInteracting && renderedResolution != this.renderedResolution
)) {
const context = this.context; const context = this.context;
if (context) { const canvas = context.canvas;
const tilePixelSize = tileSource.getTilePixelSize(z, pixelRatio, projection); if (canvas.width != width || canvas.height != height) {
const width = Math.round(tileRange.getWidth() * tilePixelSize[0] / oversampling); canvas.width = width;
const height = Math.round(tileRange.getHeight() * tilePixelSize[1] / oversampling); canvas.height = height;
const canvas = context.canvas; } else {
if (canvas.width != width || canvas.height != height) { context.clearRect(0, 0, width, height);
this.oversampling_ = oversampling;
canvas.width = width;
canvas.height = height;
} else {
if (this.renderedExtent_ && !equals(imageExtent, this.renderedExtent_)) {
context.clearRect(0, 0, width, height);
}
oversampling = this.oversampling_;
}
} }
this.renderedTiles.length = 0; this.renderedTiles.length = 0;
@@ -245,39 +255,43 @@ class CanvasTileLayerRenderer extends IntermediateCanvasRenderer {
return a > b ? 1 : a < b ? -1 : 0; return a > b ? 1 : a < b ? -1 : 0;
} }
}); });
let currentResolution, currentScale, currentTilePixelSize, currentZ, i, ii;
let tileExtent, tileGutter, tilesToDraw, w, h; for (let i = 0, ii = zs.length; i < ii; ++i) {
for (i = 0, ii = zs.length; i < ii; ++i) { const currentZ = zs[i];
currentZ = zs[i]; const currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection);
currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection); const currentResolution = tileGrid.getResolution(currentZ);
currentResolution = tileGrid.getResolution(currentZ); const currentScale = currentResolution / tileResolution;
currentScale = currentResolution / tileResolution; const tileGutter = tilePixelRatio * tileSource.getGutterForProjection(projection);
tileGutter = tilePixelRatio * tileSource.getGutterForProjection(projection); const tilesToDraw = tilesToDrawByZ[currentZ];
tilesToDraw = tilesToDrawByZ[currentZ];
for (const tileCoordKey in tilesToDraw) { for (const tileCoordKey in tilesToDraw) {
tile = tilesToDraw[tileCoordKey]; const tile = tilesToDraw[tileCoordKey];
tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent); const tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent);
x = (tileExtent[0] - imageExtent[0]) / tileResolution * tilePixelRatio / oversampling; const x = (tileExtent[0] - canvasExtent[0]) / tileResolution;
y = (imageExtent[3] - tileExtent[3]) / tileResolution * tilePixelRatio / oversampling; const y = (canvasExtent[3] - tileExtent[3]) / tileResolution;
w = currentTilePixelSize[0] * currentScale / oversampling; const w = currentTilePixelSize[0] * currentScale;
h = currentTilePixelSize[1] * currentScale / oversampling; const h = currentTilePixelSize[1] * currentScale;
this.drawTileImage(tile, frameState, layerState, x, y, w, h, tileGutter, z === currentZ); this.drawTileImage(tile, frameState, layerState, x, y, w, h, tileGutter, z === currentZ);
this.renderedTiles.push(tile); this.renderedTiles.push(tile);
} }
} }
this.renderedRevision = sourceRevision; this.renderedRevision = sourceRevision;
this.renderedResolution = tileResolution * pixelRatio / tilePixelRatio * oversampling; this.renderedResolution = tileResolution;
this.renderedExtent_ = imageExtent; this.renderedExtent_ = canvasExtent;
} }
const scale = this.renderedResolution / viewResolution; const scale = this.renderedResolution / frameState.viewState.resolution;
const transform = composeTransform(this.imageTransform_, const halfWidth = width / 2;
pixelRatio * size[0] / 2, pixelRatio * size[1] / 2, const halfHeight = height / 2;
const transform = composeTransform(
this.imageTransform_,
halfWidth, halfHeight,
scale, scale, scale, scale,
0, rotation,
(this.renderedExtent_[0] - viewCenter[0]) / this.renderedResolution * pixelRatio, -halfWidth, -halfHeight
(viewCenter[1] - this.renderedExtent_[3]) / this.renderedResolution * pixelRatio); );
composeTransform(this.coordinateToCanvasPixelTransform, composeTransform(this.coordinateToCanvasPixelTransform,
pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5],
pixelRatio / viewResolution, -pixelRatio / viewResolution, pixelRatio / viewResolution, -pixelRatio / viewResolution,
@@ -293,6 +307,35 @@ class CanvasTileLayerRenderer extends IntermediateCanvasRenderer {
return this.renderedTiles.length > 0; return this.renderedTiles.length > 0;
} }
/**
* @inheritDoc
*/
renderFrame(frameState, layerState) {
const context = this.context;
this.preRender(context, frameState);
// consider moving work from prepareFrame to here
this.postRender(context, frameState, layerState);
const canvas = context.canvas;
const opacity = layerState.opacity;
if (opacity !== canvas.style.opacity) {
canvas.style.opacity = opacity;
}
const rotation = frameState.viewState.rotation;
const scale = this.renderedResolution / frameState.viewState.resolution;
const transform = 'rotate(' + rotation + 'rad) scale(' + scale + ')';
if (transform !== canvas.style.transform) {
canvas.style.transform = transform;
}
return canvas;
}
/** /**
* @param {import("../../Tile.js").default} tile Tile. * @param {import("../../Tile.js").default} tile Tile.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state.