Add more tests

This commit is contained in:
Andreas Hocevar
2021-08-17 00:49:12 +02:00
parent 7acd5338c9
commit 814d70b1cc
14 changed files with 508 additions and 5 deletions

View File

@@ -27,7 +27,8 @@
<a href="module-ol_layer_Tile-TileLayer.html">ol/layer/Tile</a><br> <a href="module-ol_layer_Tile-TileLayer.html">ol/layer/Tile</a><br>
<a href="module-ol_layer_Image-ImageLayer.html">ol/layer/Image</a><br> <a href="module-ol_layer_Image-ImageLayer.html">ol/layer/Image</a><br>
<a href="module-ol_layer_Vector-VectorLayer.html">ol/layer/Vector</a><br> <a href="module-ol_layer_Vector-VectorLayer.html">ol/layer/Vector</a><br>
<a href="module-ol_layer_VectorTile-VectorTileLayer.html">ol/layer/VectorTile</a> <a href="module-ol_layer_VectorTile-VectorTileLayer.html">ol/layer/VectorTile</a><br>
<a href="module-ol_layer_WebGLTile-WebGLTileLayer.html">ol/layer/WebGLTile</a>
</div> </div>
</div> </div>
</div> </div>
@@ -58,7 +59,7 @@
<div class="card h-100 bg-light"> <div class="card h-100 bg-light">
<div class="card-body"> <div class="card-body">
<h4 class="card-title">Sources and formats</h4> <h4 class="card-title">Sources and formats</h4>
<a href="module-ol_source_Tile-TileSource.html">Tile sources</a> for <a href="module-ol_layer_Tile-TileLayer.html">ol/layer/Tile</a> <a href="module-ol_source_Tile-TileSource.html">Tile sources</a> for <a href="module-ol_layer_Tile-TileLayer.html">ol/layer/Tile</a> or <a href="module-ol_layer_WebGLTile-WebGLTileLayer.html">ol/layer/WebGLTile</a>
<br><a href="module-ol_source_Image-ImageSource.html">Image sources</a> for <a href="module-ol_layer_Image-ImageLayer.html">ol/layer/Image</a> <br><a href="module-ol_source_Image-ImageSource.html">Image sources</a> for <a href="module-ol_layer_Image-ImageLayer.html">ol/layer/Image</a>
<br><a href="module-ol_source_Vector-VectorSource.html">Vector sources</a> for <a href="module-ol_layer_Vector-VectorLayer.html">ol/layer/Vector</a> <br><a href="module-ol_source_Vector-VectorSource.html">Vector sources</a> for <a href="module-ol_layer_Vector-VectorLayer.html">ol/layer/Vector</a>
<br><a href="module-ol_source_VectorTile-VectorTile.html">Vector tile sources</a> for <a href="module-ol_layer_VectorTile-VectorTileLayer.html">ol/layer/VectorTile</a> <br><a href="module-ol_source_VectorTile-VectorTile.html">Vector tile sources</a> for <a href="module-ol_layer_VectorTile-VectorTileLayer.html">ol/layer/VectorTile</a>
@@ -71,7 +72,7 @@
<div class="card h-100 bg-light"> <div class="card h-100 bg-light">
<div class="card-body"> <div class="card-body">
<h4 class="card-title">Projections</h4> <h4 class="card-title">Projections</h4>
<p>All coordinates and extents need to be provided in view projection (default: EPSG:3857). To transform, use <a href="module-ol_proj.html#.transform">ol/proj#transform()</a> and <a href="module-ol_proj.html#.transformExtent">ol/proj#transformExtent()</a>.</p> <p>All coordinates and extents need to be provided in view projection (default: EPSG:3857). To transform coordinates from and to geographic, use <a href="module-ol_proj.html#.fromLonLat">ol/proj#fromLonLat()</a> and <a href="module-ol_proj.html#.toLonLat">ol/proj#toLonLat()</a>. For extents and other projections, use <a href="module-ol_proj.html#.transformExtent">ol/proj#transformExtent()</a> and <a href="module-ol_proj.html#.transform">ol/proj#transform()</a>.<p>
<a href="module-ol_proj.html">ol/proj</a> <a href="module-ol_proj.html">ol/proj</a>
</div> </div>
</div> </div>

View File

@@ -74,7 +74,7 @@ import {assign} from '../obj.js';
/** /**
* @param {Style} style The layer style. * @param {Style} style The layer style.
* @param {number} bandCount The number of bands. * @param {number} [bandCount] The number of bands.
* @return {ParsedStyle} Shaders and uniforms generated from the style. * @return {ParsedStyle} Shaders and uniforms generated from the style.
*/ */
function parseStyle(style, bandCount) { function parseStyle(style, bandCount) {
@@ -291,6 +291,7 @@ class WebGLTileLayer extends BaseTileLayer {
vertexShader: parsedStyle.vertexShader, vertexShader: parsedStyle.vertexShader,
fragmentShader: parsedStyle.fragmentShader, fragmentShader: parsedStyle.fragmentShader,
uniforms: parsedStyle.uniforms, uniforms: parsedStyle.uniforms,
className: this.getClassName(),
}); });
} }

View File

@@ -100,7 +100,7 @@ class DataTileSource extends TileSource {
* @param {number} y Tile coordinate y. * @param {number} y Tile coordinate y.
* @param {number} pixelRatio Pixel ratio. * @param {number} pixelRatio Pixel ratio.
* @param {import("../proj/Projection.js").default} projection Projection. * @param {import("../proj/Projection.js").default} projection Projection.
* @return {!import("../Tile.js").default} Tile. * @return {!DataTile} Tile.
*/ */
getTile(z, x, y, pixelRatio, projection) { getTile(z, x, y, pixelRatio, projection) {
const tileCoordKey = getKeyZXY(z, x, y); const tileCoordKey = getKeyZXY(z, x, y);

View File

@@ -71,6 +71,13 @@ module.exports = function (karma) {
webpack: { webpack: {
devtool: 'inline-source-map', devtool: 'inline-source-map',
mode: 'development', mode: 'development',
resolve: {
fallback: {
fs: false,
http: false,
https: false,
},
},
module: { module: {
rules: [ rules: [
{ {

View File

@@ -0,0 +1,51 @@
import DataTile from '../../../../src/ol/DataTile.js';
import TileState from '../../../../src/ol/TileState.js';
describe('ol.DataTile', function () {
/** @type {Promise<import('../../../../src/ol/DataTile.js').Data} */
let loader;
beforeEach(function () {
loader = function () {
return new Promise((resolve) => {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const context = canvas.getContext('2d');
context.fillStyle = 'red';
context.fillRect(0, 0, 256, 256);
resolve(context.getImageData(0, 0, 256, 256).data);
});
};
});
describe('constructor', function () {
it('sets options', function () {
const tileCoord = [0, 0, 0];
const tile = new DataTile({
tileCoord: tileCoord,
loader: loader,
transition: 200,
});
expect(tile.tileCoord).to.equal(tileCoord);
expect(tile.transition_).to.be(200);
expect(tile.loader_).to.equal(loader);
});
});
describe('#load()', function () {
it('handles loading states correctly', function (done) {
const tileCoord = [0, 0, 0];
const tile = new DataTile({
tileCoord: tileCoord,
loader: loader,
});
expect(tile.getState()).to.be(TileState.IDLE);
tile.load();
expect(tile.getState()).to.be(TileState.LOADING);
setTimeout(() => {
expect(tile.getState()).to.be(TileState.LOADED);
done();
}, 16);
});
});
});

View File

@@ -0,0 +1,139 @@
import DataTileSource from '../../../../../src/ol/source/DataTile.js';
import Map from '../../../../../src/ol/Map.js';
import View from '../../../../../src/ol/View.js';
import WebGLHelper from '../../../../../src/ol/webgl/Helper.js';
import WebGLTileLayer from '../../../../../src/ol/layer/WebGLTile.js';
import {createCanvasContext2D} from '../../../../../src/ol/dom.js';
describe('ol.layer.Tile', function () {
/** @type {WebGLTileLayer} */
let layer;
/** @type {Map} */
let map, target;
beforeEach(function (done) {
layer = new WebGLTileLayer({
className: 'testlayer',
source: new DataTileSource({
loader(z, x, y) {
return new Promise((resolve) => {
resolve(new ImageData(256, 256));
});
},
}),
style: {
variables: {
r: 0,
g: 255,
b: 0,
},
color: ['color', ['var', 'r'], ['var', 'g'], ['var', 'b']],
},
});
target = document.createElement('div');
target.style.width = '100px';
target.style.height = '100px';
document.body.appendChild(target);
map = new Map({
target: target,
layers: [layer],
view: new View({
center: [0, 0],
zoom: 2,
}),
});
map.once('rendercomplete', () => done());
});
afterEach(function () {
map.setTarget(null);
document.body.removeChild(target);
});
it('creates fragment and vertex shaders', function () {
const compileShaderSpy = sinon.spy(WebGLHelper.prototype, 'compileShader');
layer.createRenderer();
compileShaderSpy.restore();
expect(compileShaderSpy.callCount).to.be(2);
expect(compileShaderSpy.getCall(0).args[0].replace(/[ \n]+/g, ' ')).to.be(
`
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
varying vec2 v_textureCoord;
uniform float u_transitionAlpha;
uniform float u_texturePixelWidth;
uniform float u_texturePixelHeight;
uniform float u_resolution;
uniform float u_zoom;
uniform float u_var_r;
uniform float u_var_g;
uniform float u_var_b;
uniform sampler2D u_tileTexture0;
void main() {
vec4 color0 = texture2D(u_tileTexture0, v_textureCoord);
vec4 color = color0;
color = vec4(u_var_r / 255.0, u_var_g / 255.0, u_var_b / 255.0, 1.0);
if (color.a == 0.0) {
discard;
}
gl_FragColor = color;
gl_FragColor.rgb *= gl_FragColor.a;
gl_FragColor *= u_transitionAlpha;
}`.replace(/[ \n]+/g, ' ')
);
expect(compileShaderSpy.getCall(1).args[0].replace(/[ \n]+/g, ' ')).to.be(
`
attribute vec2 a_textureCoord;
uniform mat4 u_tileTransform;
uniform float u_depth;
varying vec2 v_textureCoord;
void main() {
v_textureCoord = a_textureCoord;
gl_Position = u_tileTransform * vec4(a_textureCoord, u_depth, 1.0);
}
`.replace(/[ \n]+/g, ' ')
);
});
it('updates style variables', function (done) {
layer.updateStyleVariables({
r: 255,
g: 0,
b: 255,
});
expect(layer.styleVariables_['r']).to.be(255);
const targetContext = createCanvasContext2D(100, 100);
layer.on('postrender', () => {
targetContext.clearRect(0, 0, 100, 100);
targetContext.drawImage(target.querySelector('.testlayer'), 0, 0);
});
map.once('rendercomplete', () => {
expect(Array.from(targetContext.getImageData(0, 0, 1, 1).data)).to.eql([
255, 0, 255, 255,
]);
done();
});
});
it('throws on incorrect style configs', function () {
function incorrectStyle() {
layer.style_ = {
variables: {
'red': 25,
'green': 200,
},
exposure: 0,
contrast: 0,
saturation: 0,
color: ['color', ['var', 'red'], ['var', 'green'], ['var', 'blue']],
};
layer.createRenderer();
}
expect(incorrectStyle).to.throwException(); // missing 'blue' in styleVariables
});
});

View File

@@ -0,0 +1,95 @@
import TileQueue from '../../../../../../src/ol/TileQueue.js';
import TileState from '../../../../../../src/ol/TileState.js';
import WebGLTileLayer from '../../../../../../src/ol/layer/WebGLTile.js';
import {DataTile} from '../../../../../../src/ol/source.js';
import {VOID} from '../../../../../../src/ol/functions.js';
import {create} from '../../../../../../src/ol/transform.js';
import {createCanvasContext2D} from '../../../../../../src/ol/dom.js';
import {get} from '../../../../../../src/ol/proj.js';
describe('ol.renderer.webgl.TileLayer', function () {
/** @type {import("../../../../../../src/ol/renderer/webgl/TileLayer.js").default} */
let renderer;
/** @type {WebGLTileLayer} */
let tileLayer;
/** @type {import('../../../../../../src/ol/PluggableMap.js').FrameState} */
let frameState;
beforeEach(function () {
const size = 256;
const context = createCanvasContext2D(size, size);
tileLayer = new WebGLTileLayer({
source: new DataTile({
loader: function (z, x, y) {
context.clearRect(0, 0, size, size);
context.fillStyle = 'rgba(100, 100, 100, 0.5)';
context.fillRect(0, 0, size, size);
const data = context.getImageData(0, 0, size, size).data;
return Promise.resolve(data);
},
}),
});
renderer = tileLayer.createRenderer();
const proj = get('EPSG:3857');
frameState = {
layerStatesArray: [tileLayer.getLayerState()],
layerIndex: 0,
extent: proj.getExtent(),
pixelRatio: 1,
pixelToCoordinateTransform: create(),
postRenderFunctions: [],
time: Date.now(),
viewHints: [],
viewState: {
center: [0, 0],
resolution: 156543.03392804097,
projection: proj,
},
size: [256, 256],
usedTiles: {},
wantedTiles: {},
tileQueue: new TileQueue(VOID, VOID),
};
});
it('#prepareFrame()', function () {
const source = tileLayer.getSource();
tileLayer.setSource(null);
expect(renderer.prepareFrame(frameState)).to.be(false);
tileLayer.setSource(source);
expect(renderer.prepareFrame(frameState)).to.be(true);
const tileGrid = source.getTileGrid();
tileLayer.setExtent(tileGrid.getTileCoordExtent([2, 0, 0]));
frameState.resolution = tileGrid.getResolution(2);
frameState.extent = tileGrid.getTileCoordExtent([2, 2, 2]);
frameState.layerStatesArray = [tileLayer.getLayerState()];
expect(renderer.prepareFrame(frameState)).to.be(false);
});
it('#renderFrame()', function () {
const rendered = renderer.renderFrame(frameState);
expect(rendered).to.be.a(HTMLCanvasElement);
expect(frameState.tileQueue.getCount()).to.be(1);
expect(Object.keys(frameState.wantedTiles).length).to.be(1);
expect(frameState.postRenderFunctions.length).to.be(0); // no tile expired
expect(renderer.tileTextureCache_.count_).to.be(1);
});
it('#isDrawableTile()', function (done) {
const tile = tileLayer.getSource().getTile(0, 0, 0);
expect(renderer.isDrawableTile(tile)).to.be(false);
tileLayer.getSource().on('tileloadend', () => {
expect(renderer.isDrawableTile(tile)).to.be(true);
done();
});
tile.load();
const errorTile = tileLayer.getSource().getTile(1, 0, 1);
errorTile.setState(TileState.ERROR);
tileLayer.setUseInterimTilesOnError(false);
expect(renderer.isDrawableTile(errorTile)).to.be(true);
tileLayer.setUseInterimTilesOnError(true);
expect(renderer.isDrawableTile(errorTile)).to.be(false);
});
});

View File

@@ -0,0 +1,42 @@
import DataTile from '../../../../../src/ol/DataTile.js';
import DataTileSource from '../../../../../src/ol/source/DataTile.js';
import TileState from '../../../../../src/ol/TileState.js';
describe('ol.source.DataTile', function () {
/** @type {DataTileSource} */
let source;
beforeEach(function () {
const loader = function (z, x, y) {
return new Promise((resolve) => {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const context = canvas.getContext('2d');
// encode tile coordinate in rgb
context.fillStyle = `rgb(${z}, ${x % 255}, ${y % 255})`;
context.fillRect(0, 0, 256, 256);
resolve(context.getImageData(0, 0, 256, 256).data);
});
};
source = new DataTileSource({
loader: loader,
});
});
describe('#getTile()', function () {
it('gets tiles and fires a tileloadend event', function (done) {
const tile = source.getTile(3, 2, 1);
expect(tile).to.be.a(DataTile);
expect(tile.state).to.be(TileState.IDLE);
source.on('tileloadend', () => {
expect(tile.state).to.be(TileState.LOADED);
// decode tile coordinate from rgb
expect(Array.from(tile.getData().slice(0, 3))).to.eql([3, 2, 1]);
done();
});
tile.load();
});
});
});

View File

@@ -0,0 +1,45 @@
import GeoTIFFSource from '../../../../../src/ol/source/GeoTIFF.js';
import State from '../../../../../src/ol/source/State.js';
import TileState from '../../../../../src/ol/TileState.js';
describe('ol.source.GeoTIFF', function () {
/** @type {GeoTIFFSource} */
let source;
beforeEach(function () {
source = new GeoTIFFSource({
sources: [
{
url: 'spec/ol/source/images/0-0-0.tif',
},
],
});
});
it('manages load states', function (done) {
expect(source.getState()).to.be(State.LOADING);
source.on('change', () => {
expect(source.getState()).to.be(State.READY);
done();
});
});
it('configures itself from source metadata', function (done) {
source.on('change', () => {
expect(source.bandCount).to.be(3);
expect(source.getTileGrid().getResolutions().length).to.be(1);
expect(source.projection.getCode()).to.be('EPSG:4326');
done();
});
});
it('loads tiles', function (done) {
source.on('change', () => {
const tile = source.getTile(0, 0, 0);
source.on('tileloadend', () => {
expect(tile.getState()).to.be(TileState.LOADED);
done();
});
tile.load();
});
});
});

Binary file not shown.

View File

@@ -241,6 +241,12 @@ describe('ol.style.expressions', function () {
['-', ['get', 'attr3'], ['get', 'attr2']], ['-', ['get', 'attr3'], ['get', 'attr2']],
]) ])
).to.eql('abs((a_attr3 - a_attr2))'); ).to.eql('abs((a_attr3 - a_attr2))');
expect(expressionToGlsl(context, ['sin', 1])).to.eql('sin(1.0)');
expect(expressionToGlsl(context, ['cos', 1])).to.eql('cos(1.0)');
expect(expressionToGlsl(context, ['atan', 1])).to.eql('atan(1.0)');
expect(expressionToGlsl(context, ['atan', 1, 0.5])).to.eql(
'atan(1.0, 0.5)'
);
expect(expressionToGlsl(context, ['>', 10, ['get', 'attr4']])).to.eql( expect(expressionToGlsl(context, ['>', 10, ['get', 'attr4']])).to.eql(
'(10.0 > a_attr4)' '(10.0 > a_attr4)'
); );
@@ -283,6 +289,10 @@ describe('ol.style.expressions', function () {
expect( expect(
expressionToGlsl(context, ['color', ['get', 'attr4'], 1, 2, 0.5]) expressionToGlsl(context, ['color', ['get', 'attr4'], 1, 2, 0.5])
).to.eql('vec4(a_attr4 / 255.0, 1.0 / 255.0, 2.0 / 255.0, 0.5)'); ).to.eql('vec4(a_attr4 / 255.0, 1.0 / 255.0, 2.0 / 255.0, 0.5)');
expect(expressionToGlsl(context, ['band', 1])).to.eql('color0[0]');
expect(expressionToGlsl(context, ['band', 1, -1, 2])).to.eql(
'texture2D(u_tileTexture0, v_textureCoord + vec2(-1.0 / u_texturePixelWidth, 2.0 / u_texturePixelHeight))[0]'
);
}); });
it('throws if the value does not match the type', function () { it('throws if the value does not match the type', function () {

View File

@@ -0,0 +1,86 @@
import DataTile from '../../../../../src/ol/DataTile.js';
import DataTileSource from '../../../../../src/ol/source/DataTile.js';
import ImageTile from '../../../../../src/ol/ImageTile.js';
import TileState from '../../../../../src/ol/TileState.js';
import TileTexture from '../../../../../src/ol/webgl/TileTexture.js';
import WebGLArrayBuffer from '../../../../../src/ol/webgl/Buffer.js';
import WebGLTileLayer from '../../../../../src/ol/layer/WebGLTile.js';
import {createCanvasContext2D} from '../../../../../src/ol/dom.js';
describe('ol.webgl.TileTexture', function () {
/** @type {TileTexture} */
let tileTexture;
beforeEach(function () {
const layer = new WebGLTileLayer({
source: new DataTileSource({
loader(z, x, y) {
return new Promise((resolve) => {
const context = createCanvasContext2D(256, 256);
context.fillStyle = `rgb(${z}, ${x % 255}, ${y % 255})`;
context.fillRect(0, 0, 256, 256);
resolve(context.getImageData(0, 0, 256, 256).data);
});
},
}),
});
const renderer =
/** @type {import("../../../../../src/ol/renderer/webgl/TileLayer.js").default} */ (
layer.createRenderer()
);
tileTexture = new TileTexture(
layer.getSource().getTile(3, 2, 1),
layer.getSource().getTileGrid(),
renderer.helper
);
});
it('constructor', function () {
expect(tileTexture.tile.tileCoord).to.eql([3, 2, 1]);
expect(tileTexture.coords).to.be.a(WebGLArrayBuffer);
});
it('handles data tiles', function (done) {
const dataTile = tileTexture.tile;
expect(tileTexture.loaded).to.be(false);
expect(dataTile.getState()).to.be(TileState.IDLE);
tileTexture.addEventListener('change', () => {
if (dataTile.getState() === TileState.LOADED) {
expect(tileTexture.loaded).to.be(true);
done();
}
});
dataTile.load();
});
it('handles image tiles', function () {
const imageTile = new ImageTile([0, 0, 0], TileState.LOADED);
tileTexture.setTile(imageTile);
expect(tileTexture.loaded).to.be(true);
});
it('registers and unregisters change listener', function () {
const tile = tileTexture.tile;
expect(tile.getListeners('change').length).to.be(2);
tileTexture.dispose();
expect(tile.getListeners('change').length).to.be(1);
});
it('updates metadata and unregisters change listener when setting a different tile', function (done) {
const tile = tileTexture.tile;
expect(tile.getListeners('change').length).to.be(2);
const differentTile = new DataTile({
tileCoord: [1, 0, 1],
loader(z, x, y) {
return Promise.resolve(new Uint8Array(256 * 256 * 3));
},
});
tileTexture.setTile(differentTile);
expect(tile.getListeners('change').length).to.be(1);
tileTexture.addEventListener('change', () => {
expect(tileTexture.bandCount).to.be(3);
done();
});
differentTile.load();
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -0,0 +1,26 @@
import Map from '../../../../src/ol/Map.js';
import TileLayer from '../../../../src/ol/layer/WebGLTile.js';
import View from '../../../../src/ol/View.js';
import XYZ from '../../../../src/ol/source/XYZ.js';
import {fromLonLat} from '../../../../src/ol/proj.js';
const center = fromLonLat([8.6, 50.1]);
new Map({
layers: [
new TileLayer({
source: new XYZ({
url: '/data/tiles/satellite/{z}/{x}/{y}.jpg',
transition: 0,
crossOrigin: 'anonymous',
}),
}),
],
target: 'map',
view: new View({
center: center,
zoom: 3,
}),
});
render();