package.renderer.webgl.TileLayer.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ol Show documentation
Show all versions of ol Show documentation
OpenLayers mapping library
The newest version!
/**
* @module ol/renderer/webgl/TileLayer
*/
import ReprojDataTile from '../../reproj/DataTile.js';
import ReprojTile from '../../reproj/Tile.js';
import TileState from '../../TileState.js';
import TileTexture from '../../webgl/TileTexture.js';
import WebGLArrayBuffer from '../../webgl/Buffer.js';
import WebGLBaseTileLayerRenderer, {
Uniforms as BaseUniforms,
getCacheKey,
} from './TileLayerBase.js';
import {AttributeType} from '../../webgl/Helper.js';
import {ELEMENT_ARRAY_BUFFER, STATIC_DRAW} from '../../webgl.js';
import {apply as applyTransform} from '../../transform.js';
import {
boundingExtent,
containsCoordinate,
getIntersection,
} from '../../extent.js';
import {fromUserExtent} from '../../proj.js';
import {fromTransform as mat4FromTransform} from '../../vec/mat4.js';
import {toSize} from '../../size.js';
export const Uniforms = {
...BaseUniforms,
TILE_TEXTURE_ARRAY: 'u_tileTextures',
TEXTURE_PIXEL_WIDTH: 'u_texturePixelWidth',
TEXTURE_PIXEL_HEIGHT: 'u_texturePixelHeight',
TEXTURE_RESOLUTION: 'u_textureResolution', // map units per texture pixel
TEXTURE_ORIGIN_X: 'u_textureOriginX', // map x coordinate of left edge of texture
TEXTURE_ORIGIN_Y: 'u_textureOriginY', // map y coordinate of top edge of texture
};
export const Attributes = {
TEXTURE_COORD: 'a_textureCoord',
};
/**
* @type {Array}
*/
const attributeDescriptions = [
{
name: Attributes.TEXTURE_COORD,
size: 2,
type: AttributeType.FLOAT,
},
];
/**
* @typedef {Object} Options
* @property {string} vertexShader Vertex shader source.
* @property {string} fragmentShader Fragment shader source.
* @property {Object} [uniforms] Additional uniforms
* made available to shaders.
* @property {Array} [paletteTextures] Palette textures.
* @property {number} [cacheSize=512] The texture cache size.
* @property {Array} [postProcesses] Post-processes definitions.
*/
/**
* @typedef {import("../../webgl/TileTexture.js").TileType} TileTextureType
*/
/**
* @typedef {import("../../webgl/TileTexture.js").default} TileTextureRepresentation
*/
/**
* @classdesc
* WebGL renderer for tile layers.
* @template {import("../../layer/WebGLTile.js").default|import("../../layer/Flow.js").default} LayerType
* @extends {WebGLBaseTileLayerRenderer}
* @api
*/
class WebGLTileLayerRenderer extends WebGLBaseTileLayerRenderer {
/**
* @param {LayerType} tileLayer Tile layer.
* @param {Options} options Options.
*/
constructor(tileLayer, options) {
super(tileLayer, options);
/**
* @type {WebGLProgram}
* @private
*/
this.program_;
/**
* @private
*/
this.vertexShader_ = options.vertexShader;
/**
* @private
*/
this.fragmentShader_ = options.fragmentShader;
/**
* Tiles are rendered as a quad with the following structure:
*
* [P3]---------[P2]
* |` |
* | ` B |
* | ` |
* | ` |
* | A ` |
* | ` |
* [P0]---------[P1]
*
* Triangle A: P0, P1, P3
* Triangle B: P1, P2, P3
*
* @private
*/
this.indices_ = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, STATIC_DRAW);
this.indices_.fromArray([0, 1, 3, 1, 2, 3]);
/**
* @type {Array}
* @private
*/
this.paletteTextures_ = options.paletteTextures || [];
}
/**
* @param {Options} options Options.
* @override
*/
reset(options) {
super.reset(options);
if (this.helper) {
const gl = this.helper.getGL();
for (const paletteTexture of this.paletteTextures_) {
paletteTexture.delete(gl);
}
}
this.vertexShader_ = options.vertexShader;
this.fragmentShader_ = options.fragmentShader;
this.paletteTextures_ = options.paletteTextures || [];
if (this.helper) {
this.program_ = this.helper.getProgram(
this.fragmentShader_,
this.vertexShader_,
);
const gl = this.helper.getGL();
for (const paletteTexture of this.paletteTextures_) {
// upload the texture data
paletteTexture.getTexture(gl);
}
}
}
/**
* @override
*/
afterHelperCreated() {
super.afterHelperCreated();
const gl = this.helper.getGL();
for (const paletteTexture of this.paletteTextures_) {
// upload the texture data
paletteTexture.getTexture(gl);
}
this.program_ = this.helper.getProgram(
this.fragmentShader_,
this.vertexShader_,
);
this.helper.flushBufferData(this.indices_);
}
/**
* @override
*/
removeHelper() {
if (this.helper) {
const gl = this.helper.getGL();
for (const paletteTexture of this.paletteTextures_) {
paletteTexture.delete(gl);
}
}
super.removeHelper();
}
/**
* @override
*/
createTileRepresentation(options) {
return new TileTexture(options);
}
/**
* @override
*/
beforeTilesRender(frameState, tilesWithAlpha) {
super.beforeTilesRender(frameState, tilesWithAlpha);
this.helper.useProgram(this.program_, frameState);
}
/**
* @override
*/
renderTile(
tileTexture,
tileTransform,
frameState,
renderExtent,
tileResolution,
tileSize,
tileOrigin,
tileExtent,
depth,
gutter,
alpha,
) {
const gl = this.helper.getGL();
this.helper.bindBuffer(tileTexture.coords);
this.helper.bindBuffer(this.indices_);
this.helper.enableAttributes(attributeDescriptions);
let textureSlot = 0;
while (textureSlot < tileTexture.textures.length) {
const uniformName = `${Uniforms.TILE_TEXTURE_ARRAY}[${textureSlot}]`;
this.helper.bindTexture(
tileTexture.textures[textureSlot],
textureSlot,
uniformName,
);
++textureSlot;
}
for (
let paletteIndex = 0;
paletteIndex < this.paletteTextures_.length;
++paletteIndex
) {
const paletteTexture = this.paletteTextures_[paletteIndex];
const texture = paletteTexture.getTexture(gl);
this.helper.bindTexture(texture, textureSlot, paletteTexture.name);
++textureSlot;
}
const viewState = frameState.viewState;
const tileWidthWithGutter = tileSize[0] + 2 * gutter;
const tileHeightWithGutter = tileSize[1] + 2 * gutter;
const tile = tileTexture.tile;
const tileCoord = tile.tileCoord;
const tileCenterI = tileCoord[1];
const tileCenterJ = tileCoord[2];
this.helper.setUniformMatrixValue(
Uniforms.TILE_TRANSFORM,
mat4FromTransform(this.tempMat4, tileTransform),
);
this.helper.setUniformFloatValue(Uniforms.TRANSITION_ALPHA, alpha);
this.helper.setUniformFloatValue(Uniforms.DEPTH, depth);
let gutterExtent = renderExtent;
if (gutter > 0) {
gutterExtent = tileExtent;
getIntersection(gutterExtent, renderExtent, gutterExtent);
}
this.helper.setUniformFloatVec4(Uniforms.RENDER_EXTENT, gutterExtent);
this.helper.setUniformFloatValue(Uniforms.RESOLUTION, viewState.resolution);
this.helper.setUniformFloatValue(Uniforms.ZOOM, viewState.zoom);
this.helper.setUniformFloatValue(
Uniforms.TEXTURE_PIXEL_WIDTH,
tileWidthWithGutter,
);
this.helper.setUniformFloatValue(
Uniforms.TEXTURE_PIXEL_HEIGHT,
tileHeightWithGutter,
);
this.helper.setUniformFloatValue(
Uniforms.TEXTURE_RESOLUTION,
tileResolution,
);
this.helper.setUniformFloatValue(
Uniforms.TEXTURE_ORIGIN_X,
tileOrigin[0] +
tileCenterI * tileSize[0] * tileResolution -
gutter * tileResolution,
);
this.helper.setUniformFloatValue(
Uniforms.TEXTURE_ORIGIN_Y,
tileOrigin[1] -
tileCenterJ * tileSize[1] * tileResolution +
gutter * tileResolution,
);
this.helper.drawElements(0, this.indices_.getSize());
}
/**
* @param {import("../../pixel.js").Pixel} pixel Pixel.
* @return {Uint8ClampedArray|Uint8Array|Float32Array|DataView} Data at the pixel location.
* @override
*/
getData(pixel) {
const gl = this.helper.getGL();
if (!gl) {
return null;
}
const frameState = this.frameState;
if (!frameState) {
return null;
}
const layer = this.getLayer();
const coordinate = applyTransform(
frameState.pixelToCoordinateTransform,
pixel.slice(),
);
const viewState = frameState.viewState;
const layerExtent = layer.getExtent();
if (layerExtent) {
if (
!containsCoordinate(
fromUserExtent(layerExtent, viewState.projection),
coordinate,
)
) {
return null;
}
}
// determine last source suitable for rendering at coordinate
const sources = layer.getSources(
boundingExtent([coordinate]),
viewState.resolution,
);
let i, source, tileGrid;
for (i = sources.length - 1; i >= 0; --i) {
source = sources[i];
if (source.getState() === 'ready') {
tileGrid = source.getTileGridForProjection(viewState.projection);
if (source.getWrapX()) {
break;
}
const gridExtent = tileGrid.getExtent();
if (!gridExtent || containsCoordinate(gridExtent, coordinate)) {
break;
}
}
}
if (i < 0) {
return null;
}
const tileTextureCache = this.tileRepresentationCache;
for (
let z = tileGrid.getZForResolution(viewState.resolution);
z >= tileGrid.getMinZoom();
--z
) {
const tileCoord = tileGrid.getTileCoordForCoordAndZ(coordinate, z);
const cacheKey = getCacheKey(source, tileCoord);
if (!tileTextureCache.containsKey(cacheKey)) {
continue;
}
const tileTexture = tileTextureCache.get(cacheKey);
const tile = tileTexture.tile;
if (
(tile instanceof ReprojTile || tile instanceof ReprojDataTile) &&
tile.getState() === TileState.EMPTY
) {
return null;
}
if (!tileTexture.loaded) {
continue;
}
const tileOrigin = tileGrid.getOrigin(z);
const tileSize = toSize(tileGrid.getTileSize(z));
const tileResolution = tileGrid.getResolution(z);
const col =
(coordinate[0] - tileOrigin[0]) / tileResolution -
tileCoord[1] * tileSize[0];
const row =
(tileOrigin[1] - coordinate[1]) / tileResolution -
tileCoord[2] * tileSize[1];
return tileTexture.getPixelData(col, row);
}
return null;
}
/**
* Clean up.
* @override
*/
disposeInternal() {
const helper = this.helper;
if (helper) {
const gl = helper.getGL();
for (const paletteTexture of this.paletteTextures_) {
paletteTexture.delete(gl);
}
this.paletteTextures_.length = 0;
gl.deleteProgram(this.program_);
delete this.program_;
helper.deleteBuffer(this.indices_);
}
super.disposeInternal();
delete this.indices_;
}
}
export default WebGLTileLayerRenderer;