Allow interpolation to be configured for data tile sources

This commit is contained in:
Tim Schaub
2021-12-23 11:26:42 -07:00
parent 3edb5d6ddc
commit 8d8632bff7
14 changed files with 152 additions and 13 deletions

View File

@@ -16,6 +16,8 @@ import TileState from './TileState.js';
* @property {function(): Promise<Data>} loader Data loader. * @property {function(): Promise<Data>} loader Data loader.
* @property {number} [transition=250] A duration for tile opacity * @property {number} [transition=250] A duration for tile opacity
* transitions in milliseconds. A duration of 0 disables the opacity transition. * transitions in milliseconds. A duration of 0 disables the opacity transition.
* @property {boolean} [interpolate=false] Use interpolated values when resampling. By default,
* the nearest neighbor is used when resampling.
* @api * @api
*/ */
@@ -26,10 +28,27 @@ class DataTile extends Tile {
constructor(options) { constructor(options) {
const state = TileState.IDLE; const state = TileState.IDLE;
super(options.tileCoord, state, {transition: options.transition}); super(options.tileCoord, state, {
transition: options.transition,
interpolate: options.interpolate,
});
/**
* @type {function(): Promise<Data>}
* @private
*/
this.loader_ = options.loader; this.loader_ = options.loader;
/**
* @type {Data}
* @private
*/
this.data_ = null; this.data_ = null;
/**
* @type {Error}
* @private
*/
this.error_ = null; this.error_ = null;
} }

View File

@@ -63,6 +63,8 @@ import {easeIn} from './easing.js';
* @typedef {Object} Options * @typedef {Object} Options
* @property {number} [transition=250] A duration for tile opacity * @property {number} [transition=250] A duration for tile opacity
* transitions in milliseconds. A duration of 0 disables the opacity transition. * transitions in milliseconds. A duration of 0 disables the opacity transition.
* @property {boolean} [interpolate=false] Use interpolated values when resampling. By default,
* the nearest neighbor is used when resampling.
* @api * @api
*/ */
@@ -123,6 +125,11 @@ class Tile extends EventTarget {
* @type {Object<string, number>} * @type {Object<string, number>}
*/ */
this.transitionStarts_ = {}; this.transitionStarts_ = {};
/**
* @type {boolean}
*/
this.interpolate = !!options.interpolate;
} }
/** /**

View File

@@ -34,6 +34,8 @@ import {toPromise} from '../functions.js';
* @property {boolean} [wrapX=false] Render tiles beyond the antimeridian. * @property {boolean} [wrapX=false] Render tiles beyond the antimeridian.
* @property {number} [transition] Transition time when fading in new tiles (in miliseconds). * @property {number} [transition] Transition time when fading in new tiles (in miliseconds).
* @property {number} [bandCount=4] Number of bands represented in the data. * @property {number} [bandCount=4] Number of bands represented in the data.
* @property {boolean} [interpolate=false] Use interpolated values when resampling. By default,
* the nearest neighbor is used when resampling.
*/ */
/** /**
@@ -71,6 +73,7 @@ class DataTileSource extends TileSource {
tilePixelRatio: options.tilePixelRatio, tilePixelRatio: options.tilePixelRatio,
wrapX: options.wrapX, wrapX: options.wrapX,
transition: options.transition, transition: options.transition,
interpolate: options.interpolate,
}); });
/** /**
@@ -90,6 +93,12 @@ class DataTileSource extends TileSource {
* @type {number} * @type {number}
*/ */
this.bandCount = options.bandCount === undefined ? 4 : options.bandCount; // assume RGBA if undefined this.bandCount = options.bandCount === undefined ? 4 : options.bandCount; // assume RGBA if undefined
/**
* @type {boolean}
* @private
*/
this.interpolate_ = !!options.interpolate;
} }
/** /**

View File

@@ -315,6 +315,8 @@ function getMaxForDataType(array) {
* @property {number} [transition=250] Duration of the opacity transition for rendering. * @property {number} [transition=250] Duration of the opacity transition for rendering.
* To disable the opacity transition, pass `transition: 0`. * To disable the opacity transition, pass `transition: 0`.
* @property {boolean} [wrapX=false] Render tiles beyond the tile grid extent. * @property {boolean} [wrapX=false] Render tiles beyond the tile grid extent.
* @property {boolean} [interpolate=true] Use interpolated values when resampling. By default,
* the linear interpolation is used to resample the data. If false, nearest neighbor is used.
*/ */
/** /**
@@ -333,6 +335,7 @@ class GeoTIFFSource extends DataTile {
projection: null, projection: null,
opaque: options.opaque, opaque: options.opaque,
transition: options.transition, transition: options.transition,
interpolate: options.interpolate !== false,
wrapX: options.wrapX, wrapX: options.wrapX,
}); });

View File

@@ -38,6 +38,8 @@ import {scale as scaleSize, toSize} from '../size.js';
* @property {number} [transition] Transition. * @property {number} [transition] Transition.
* @property {string} [key] Key. * @property {string} [key] Key.
* @property {number|import("../array.js").NearestDirectionFunction} [zDirection=0] ZDirection. * @property {number|import("../array.js").NearestDirectionFunction} [zDirection=0] ZDirection.
* @property {boolean} [interpolate=false] Use interpolated values when resampling. By default,
* the nearest neighbor is used when resampling.
*/ */
/** /**
@@ -122,7 +124,10 @@ class TileSource extends Source {
* @protected * @protected
* @type {import("../Tile.js").Options} * @type {import("../Tile.js").Options}
*/ */
this.tileOptions = {transition: options.transition}; this.tileOptions = {
transition: options.transition,
interpolate: options.interpolate,
};
/** /**
* zDirection hint, read by the renderer. Indicates which resolution should be used * zDirection hint, read by the renderer. Indicates which resolution should be used

View File

@@ -86,6 +86,7 @@ class TileImage extends UrlTile {
urls: options.urls, urls: options.urls,
wrapX: options.wrapX, wrapX: options.wrapX,
transition: options.transition, transition: options.transition,
interpolate: options.imageSmoothing !== false,
key: options.key, key: options.key,
attributionsCollapsible: options.attributionsCollapsible, attributionsCollapsible: options.attributionsCollapsible,
zDirection: options.zDirection, zDirection: options.zDirection,

View File

@@ -26,6 +26,8 @@ import {getUid} from '../util.js';
* @property {number} [transition] Transition. * @property {number} [transition] Transition.
* @property {string} [key] Key. * @property {string} [key] Key.
* @property {number|import("../array.js").NearestDirectionFunction} [zDirection=0] ZDirection. * @property {number|import("../array.js").NearestDirectionFunction} [zDirection=0] ZDirection.
* @property {boolean} [interpolate=false] Use interpolated values when resampling. By default,
* the nearest neighbor is used when resampling.
*/ */
/** /**
@@ -49,6 +51,7 @@ class UrlTile extends TileSource {
tilePixelRatio: options.tilePixelRatio, tilePixelRatio: options.tilePixelRatio,
wrapX: options.wrapX, wrapX: options.wrapX,
transition: options.transition, transition: options.transition,
interpolate: options.interpolate,
key: options.key, key: options.key,
attributionsCollapsible: options.attributionsCollapsible, attributionsCollapsible: options.attributionsCollapsible,
zDirection: options.zDirection, zDirection: options.zDirection,

View File

@@ -11,21 +11,28 @@ import WebGLArrayBuffer from './Buffer.js';
import {ARRAY_BUFFER, STATIC_DRAW} from '../webgl.js'; import {ARRAY_BUFFER, STATIC_DRAW} from '../webgl.js';
import {toSize} from '../size.js'; import {toSize} from '../size.js';
function bindAndConfigure(gl, texture) { /**
* @param {WebGLRenderingContext} gl The WebGL context.
* @param {WebGLTexture} texture The texture.
* @param {boolean} interpolate Interpolate when resampling.
*/
function bindAndConfigure(gl, texture, interpolate) {
const resampleFilter = interpolate ? gl.LINEAR : gl.NEAREST;
gl.bindTexture(gl.TEXTURE_2D, texture); gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, resampleFilter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, resampleFilter);
} }
/** /**
* @param {WebGLRenderingContext} gl The WebGL context. * @param {WebGLRenderingContext} gl The WebGL context.
* @param {WebGLTexture} texture The texture. * @param {WebGLTexture} texture The texture.
* @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image The image. * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image The image.
* @param {boolean} interpolate Interpolate when resampling.
*/ */
function uploadImageTexture(gl, texture, image) { function uploadImageTexture(gl, texture, image, interpolate) {
bindAndConfigure(gl, texture); bindAndConfigure(gl, texture, interpolate);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
} }
@@ -36,10 +43,18 @@ function uploadImageTexture(gl, texture, image) {
* @param {import("../DataTile.js").Data} data The pixel data. * @param {import("../DataTile.js").Data} data The pixel data.
* @param {import("../size.js").Size} size The pixel size. * @param {import("../size.js").Size} size The pixel size.
* @param {number} bandCount The band count. * @param {number} bandCount The band count.
* @param {boolean} interpolate Interpolate when resampling.
*/ */
function uploadDataTexture(helper, texture, data, size, bandCount) { function uploadDataTexture(
helper,
texture,
data,
size,
bandCount,
interpolate
) {
const gl = helper.getGL(); const gl = helper.getGL();
bindAndConfigure(gl, texture); bindAndConfigure(gl, texture, interpolate);
const bytesPerRow = data.byteLength / size[1]; const bytesPerRow = data.byteLength / size[1];
let unpackAlignment = 1; let unpackAlignment = 1;
@@ -174,7 +189,7 @@ class TileTexture extends EventTarget {
const texture = gl.createTexture(); const texture = gl.createTexture();
this.textures.push(texture); this.textures.push(texture);
this.bandCount = 4; this.bandCount = 4;
uploadImageTexture(gl, texture, tile.getImage()); uploadImageTexture(gl, texture, tile.getImage(), tile.interpolate);
return; return;
} }
@@ -191,7 +206,14 @@ class TileTexture extends EventTarget {
if (textureCount === 1) { if (textureCount === 1) {
const texture = gl.createTexture(); const texture = gl.createTexture();
this.textures.push(texture); this.textures.push(texture);
uploadDataTexture(helper, texture, data, this.size, this.bandCount); uploadDataTexture(
helper,
texture,
data,
this.size,
this.bandCount,
tile.interpolate
);
return; return;
} }
@@ -229,7 +251,14 @@ class TileTexture extends EventTarget {
const texture = this.textures[textureIndex]; const texture = this.textures[textureIndex];
const textureData = textureDataArrays[textureIndex]; const textureData = textureDataArrays[textureIndex];
const bandCount = textureData.length / pixelCount; const bandCount = textureData.length / pixelCount;
uploadDataTexture(helper, texture, textureData, this.size, bandCount); uploadDataTexture(
helper,
texture,
textureData,
this.size,
bandCount,
tile.interpolate
);
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 B

View File

@@ -0,0 +1,31 @@
import DataTile from '../../../../src/ol/source/DataTile.js';
import Map from '../../../../src/ol/Map.js';
import TileLayer from '../../../../src/ol/layer/WebGLTile.js';
import View from '../../../../src/ol/View.js';
const size = 256;
const data = new Uint8Array(size * size);
for (let row = 0; row < size; ++row) {
for (let col = 0; col < size; ++col) {
data[row * size + col] = (row + col) % 2 === 0 ? 255 : 0;
}
}
new Map({
target: 'map',
layers: [
new TileLayer({
source: new DataTile({
maxZoom: 0,
loader: () => data,
}),
}),
],
view: new View({
center: [0, 0],
zoom: 4,
}),
});
render();

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,32 @@
import DataTile from '../../../../src/ol/source/DataTile.js';
import Map from '../../../../src/ol/Map.js';
import TileLayer from '../../../../src/ol/layer/WebGLTile.js';
import View from '../../../../src/ol/View.js';
const size = 256;
const data = new Uint8Array(size * size);
for (let row = 0; row < size; ++row) {
for (let col = 0; col < size; ++col) {
data[row * size + col] = (row + col) % 2 === 0 ? 255 : 0;
}
}
new Map({
target: 'map',
layers: [
new TileLayer({
source: new DataTile({
maxZoom: 0,
interpolate: true,
loader: () => data,
}),
}),
],
view: new View({
center: [0, 0],
zoom: 4,
}),
});
render();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 105 KiB