Organize tests

This commit is contained in:
Tim Schaub
2021-04-27 15:41:14 -07:00
parent 278e355795
commit 490cfabe91
599 changed files with 12374 additions and 1603 deletions
@@ -0,0 +1,540 @@
import BuilderGroup from '../../../../../../src/ol/render/canvas/BuilderGroup.js';
import CanvasBuilder from '../../../../../../src/ol/render/canvas/Builder.js';
import CanvasLineStringBuilder from '../../../../../../src/ol/render/canvas/LineStringBuilder.js';
import CanvasPolygonBuilder from '../../../../../../src/ol/render/canvas/PolygonBuilder.js';
import ExecutorGroup from '../../../../../../src/ol/render/canvas/ExecutorGroup.js';
import Feature from '../../../../../../src/ol/Feature.js';
import Fill from '../../../../../../src/ol/style/Fill.js';
import GeometryCollection from '../../../../../../src/ol/geom/GeometryCollection.js';
import LineString from '../../../../../../src/ol/geom/LineString.js';
import MultiLineString from '../../../../../../src/ol/geom/MultiLineString.js';
import MultiPoint from '../../../../../../src/ol/geom/MultiPoint.js';
import MultiPolygon from '../../../../../../src/ol/geom/MultiPolygon.js';
import Point from '../../../../../../src/ol/geom/Point.js';
import Polygon from '../../../../../../src/ol/geom/Polygon.js';
import Stroke from '../../../../../../src/ol/style/Stroke.js';
import Style from '../../../../../../src/ol/style/Style.js';
import {
create as createTransform,
scale as scaleTransform,
} from '../../../../../../src/ol/transform.js';
import {renderFeature} from '../../../../../../src/ol/renderer/vector.js';
describe('ol.render.canvas.BuilderGroup', function () {
describe('#replay', function () {
let context, builder, fillCount, transform;
let strokeCount, beginPathCount, moveToCount, lineToCount;
let feature0, feature1, feature2, feature3;
let fill0, fill1, style1, style2;
/**
* @param {BuilderGroup} builder The builder to get instructions from.
* @param {number} [pixelRatio] The pixel ratio.
* @param {boolean} [overlaps] Whether there is overlaps.
*/
function execute(builder, pixelRatio, overlaps) {
const executor = new ExecutorGroup(
[-180, -90, 180, 90],
1,
pixelRatio || 1,
!!overlaps,
builder.finish()
);
executor.execute(context, 1, transform, 0, false);
}
beforeEach(function () {
transform = createTransform();
builder = new BuilderGroup(1, [-180, -90, 180, 90], 1, 1, false);
feature0 = new Feature(
new Polygon([
[
[-90, 0],
[-45, 45],
[0, 0],
[1, 1],
[0, -45],
[-90, 0],
],
])
);
feature1 = new Feature(
new Polygon([
[
[-90, -45],
[-90, 0],
[0, 0],
[0, -45],
[-90, -45],
],
])
);
feature2 = new Feature(
new Polygon([
[
[90, 45],
[90, 0],
[0, 0],
[0, 45],
[90, 45],
],
])
);
feature3 = new Feature(
new Polygon([
[
[-90, -45],
[-90, 45],
[90, 45],
[90, -45],
[-90, -45],
],
])
);
fill0 = new Style({
fill: new Fill({color: 'black'}),
});
fill1 = new Style({
fill: new Fill({color: 'red'}),
});
style1 = new Style({
fill: new Fill({color: 'black'}),
stroke: new Stroke({color: 'white', width: 1}),
});
style2 = new Style({
fill: new Fill({color: 'white'}),
stroke: new Stroke({
color: 'black',
width: 1,
lineDash: [3, 6],
lineDashOffset: 2,
}),
});
fillCount = 0;
strokeCount = 0;
beginPathCount = 0;
moveToCount = 0;
lineToCount = 0;
context = {
fill: function () {
fillCount++;
},
stroke: function () {
strokeCount++;
},
beginPath: function () {
beginPathCount++;
},
clip: function () {
// remove beginPath, moveTo and lineTo counts for clipping
beginPathCount--;
moveToCount--;
lineToCount -= 3;
},
moveTo: function () {
moveToCount++;
},
lineTo: function () {
lineToCount++;
},
closePath: function () {},
setLineDash: function () {},
save: function () {},
restore: function () {},
};
});
it('omits lineTo for repeated coordinates', function () {
renderFeature(builder, feature0, fill0, 1);
execute(builder);
expect(lineToCount).to.be(4);
lineToCount = 0;
scaleTransform(transform, 0.25, 0.25);
execute(builder);
expect(lineToCount).to.be(3);
});
it('does not omit moveTo for repeated coordinates', function () {
renderFeature(builder, feature0, fill0, 1);
renderFeature(builder, feature1, fill1, 1);
execute(builder);
expect(moveToCount).to.be(2);
});
it('batches fill and stroke instructions for same style', function () {
renderFeature(builder, feature1, style1, 1);
renderFeature(builder, feature2, style1, 1);
renderFeature(builder, feature3, style1, 1);
execute(builder);
expect(fillCount).to.be(1);
expect(strokeCount).to.be(1);
expect(beginPathCount).to.be(1);
});
it('batches fill and stroke instructions for different styles', function () {
renderFeature(builder, feature1, style1, 1);
renderFeature(builder, feature2, style1, 1);
renderFeature(builder, feature3, style2, 1);
execute(builder);
expect(fillCount).to.be(2);
expect(strokeCount).to.be(2);
expect(beginPathCount).to.be(2);
});
it('batches fill and stroke instructions for changing styles', function () {
renderFeature(builder, feature1, style1, 1);
renderFeature(builder, feature2, style2, 1);
renderFeature(builder, feature3, style1, 1);
execute(builder);
expect(fillCount).to.be(3);
expect(strokeCount).to.be(3);
expect(beginPathCount).to.be(3);
});
it('does not batch when overlaps is set to true', function () {
builder = new BuilderGroup(1, [-180, -90, 180, 90], 1, 1, true);
renderFeature(builder, feature1, style1, 1);
renderFeature(builder, feature2, style1, 1);
renderFeature(builder, feature3, style1, 1);
execute(builder, {}, 1, true);
expect(fillCount).to.be(3);
expect(strokeCount).to.be(3);
expect(beginPathCount).to.be(3);
});
it('applies the pixelRatio to the linedash array and offset', function () {
// replay with a pixelRatio of 2
builder = new BuilderGroup(1, [-180, -90, 180, 90], 1, 2, true);
let lineDash,
lineDashCount = 0,
lineDashOffset,
lineDashOffsetCount = 0;
context.setLineDash = function (lineDash_) {
lineDashCount++;
lineDash = lineDash_.slice();
};
Object.defineProperty(context, 'lineDashOffset', {
set: function (lineDashOffset_) {
lineDashOffsetCount++;
lineDashOffset = lineDashOffset_;
},
});
renderFeature(builder, feature1, style2, 1);
renderFeature(builder, feature2, style2, 1);
execute(builder, {}, 2, true);
expect(lineDashCount).to.be(1);
expect(style2.getStroke().getLineDash()).to.eql([3, 6]);
expect(lineDash).to.eql([6, 12]);
expect(lineDashOffsetCount).to.be(1);
expect(style2.getStroke().getLineDashOffset()).to.be(2);
expect(lineDashOffset).to.be(4);
});
it('calls the renderer function configured for the style', function () {
const calls = [];
const style = new Style({
renderer: function (coords, state) {
calls.push({
coords: coords,
geometry: state.geometry,
feature: state.feature,
context: state.context,
pixelRatio: state.pixelRatio,
rotation: state.rotation,
resolution: state.resolution,
});
},
});
const point = new Feature(new Point([45, 90]));
const multipoint = new Feature(
new MultiPoint([
[45, 90],
[90, 45],
])
);
const linestring = new Feature(
new LineString([
[45, 90],
[45, 45],
[90, 45],
])
);
const multilinestring = new Feature(
new MultiLineString([
linestring.getGeometry().getCoordinates(),
linestring.getGeometry().getCoordinates(),
])
);
const polygon = feature1;
const multipolygon = new Feature(
new MultiPolygon([
polygon.getGeometry().getCoordinates(),
polygon.getGeometry().getCoordinates(),
])
);
const geometrycollection = new Feature(
new GeometryCollection([
point.getGeometry(),
linestring.getGeometry(),
polygon.getGeometry(),
])
);
builder = new BuilderGroup(1, [-180, -90, 180, 90], 1, 1, true);
renderFeature(builder, point, style, 1);
renderFeature(builder, multipoint, style, 1);
renderFeature(builder, linestring, style, 1);
renderFeature(builder, multilinestring, style, 1);
renderFeature(builder, polygon, style, 1);
renderFeature(builder, multipolygon, style, 1);
renderFeature(builder, geometrycollection, style, 1);
scaleTransform(transform, 0.1, 0.1);
execute(builder, 1, true);
expect(calls.length).to.be(9);
expect(calls[0].geometry).to.be(point.getGeometry());
expect(calls[0].feature).to.be(point);
expect(calls[0].context).to.be(context);
expect(calls[0].pixelRatio).to.be(1);
expect(calls[0].rotation).to.be(0);
expect(calls[0].resolution).to.be(1);
expect(calls[0].coords).to.eql([4.5, 9]);
expect(calls[1].feature).to.be(multipoint);
expect(calls[1].coords[0]).to.eql([4.5, 9]);
expect(calls[2].feature).to.be(linestring);
expect(calls[2].coords[0]).to.eql([4.5, 9]);
expect(calls[3].feature).to.be(multilinestring);
expect(calls[3].coords[0][0]).to.eql([4.5, 9]);
expect(calls[4].feature).to.be(polygon);
expect(calls[4].coords[0][0]).to.eql([-9, -4.5]);
expect(calls[5].feature).to.be(multipolygon);
expect(calls[5].coords[0][0][0]).to.eql([-9, -4.5]);
expect(calls[6].feature).to.be(geometrycollection);
expect(calls[6].geometry.getCoordinates()).to.eql([45, 90]);
expect(calls[7].geometry.getCoordinates()[0]).to.eql([45, 90]);
expect(calls[8].geometry.getCoordinates()[0][0]).to.eql([-90, -45]);
});
});
});
describe('ol.render.canvas.Builder', function () {
describe('constructor', function () {
it('creates a new replay batch', function () {
const tolerance = 10;
const extent = [-180, -90, 180, 90];
const replay = new CanvasBuilder(tolerance, extent, 1, 1, true);
expect(replay).to.be.a(CanvasBuilder);
});
});
describe('#appendFlatLineCoordinates()', function () {
let replay;
beforeEach(function () {
replay = new CanvasBuilder(1, [-180, -90, 180, 90], 1, 1, true);
});
it('appends coordinates that are within the max extent', function () {
const flat = [-110, 45, 110, 45, 110, -45, -110, -45];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, false, false);
expect(replay.coordinates).to.eql(flat);
});
it('appends polygon coordinates that are within the max extent', function () {
const flat = [-110, 45, 110, 45, 110, -45, -110, -45, -110, 45];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, true, false);
expect(replay.coordinates).to.eql(flat);
});
it('appends polygon coordinates that are within the max extent (skipping first)', function () {
const flat = [-110, 45, 110, 45, 110, -45, -110, -45, -110, 45];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, true, true);
expect(replay.coordinates).to.eql([
110,
45,
110,
-45,
-110,
-45,
-110,
45,
]);
});
it('works with a single coordinate (inside)', function () {
const flat = [-110, 45];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, false, false);
expect(replay.coordinates).to.eql(flat);
});
it('always appends first point (even if outside)', function () {
// this could be changed, but to make the code simpler for properly
// closing rings, we always add the first point
const flat = [-110, 145];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, false, false);
expect(replay.coordinates).to.eql(flat);
});
it('always appends first polygon vertex (even if outside)', function () {
// this could be changed, but to make the code simpler for properly
// closing rings, we always add the first point
const flat = [-110, 145, -110, 145];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, true, false);
expect(replay.coordinates).to.eql(flat);
});
it('skips first polygon vertex upon request (also when outside)', function () {
const flat = [-110, 145, -110, 145];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, true, true);
expect(replay.coordinates).to.eql([-110, 145]);
});
it('appends points when segments cross (top to bottom)', function () {
// this means we get a few extra points when coordinates are not
// part of a linestring or ring, but only a few extra
const flat = [0, 200, 0, -200];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, false, false);
expect(replay.coordinates).to.eql(flat);
});
it('appends points when segments cross (top to inside)', function () {
const flat = [0, 200, 0, 0];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, false, false);
expect(replay.coordinates).to.eql(flat);
});
it('always appends the first segment (even when outside)', function () {
// this could be changed, but to make the code simpler for properly
// closing rings, we always add the first segment
const flat = [-10, 200, 10, 200];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, false, false);
expect(replay.coordinates).to.eql(flat);
});
it('always appends the first polygon segment (even when outside)', function () {
// this could be changed, but to make the code simpler for properly
// closing rings, we always add the first segment
const flat = [-10, 200, 10, 200, -10, 200];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, true, false);
expect(replay.coordinates).to.eql(flat);
});
it('skips first polygon segment upon request (also when outside)', function () {
const flat = [-10, 200, 10, 200, -10, 200];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, true, true);
expect(replay.coordinates).to.eql([10, 200, -10, 200]);
});
it('eliminates segments outside (and not changing rel)', function () {
const flat = [0, 0, 0, 200, 5, 200, 10, 200];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, false, false);
expect(replay.coordinates).to.eql([0, 0, 0, 200]);
});
it('eliminates polygon segments outside (and not changing rel)', function () {
const flat = [0, 0, 0, 200, 5, 200, 10, 200, 0, 0];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, true, false);
expect(replay.coordinates).to.eql([0, 0, 0, 200, 10, 200, 0, 0]);
});
it('eliminates polygon segments outside (skipping first and not changing rel)', function () {
const flat = [0, 0, 0, 10, 0, 200, 5, 200, 10, 200, 0, 0];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, true, true);
expect(replay.coordinates).to.eql([0, 10, 0, 200, 10, 200, 0, 0]);
});
it('eliminates segments outside (and not changing rel)', function () {
const flat = [0, 0, 0, 200, 10, 200];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, false, false);
expect(replay.coordinates).to.eql([0, 0, 0, 200]);
});
it('includes polygon segments outside (and not changing rel) when on last segment', function () {
const flat = [0, 0, 0, 200, 10, 200, 0, 0];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, true, false);
expect(replay.coordinates).to.eql(flat);
});
it('includes polygon segments outside (skipping first and not changing rel) when on last segment', function () {
const flat = [0, 0, 0, 200, 10, 200, 0, 0];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, true, true);
expect(replay.coordinates).to.eql([0, 200, 10, 200, 0, 0]);
});
it('includes outside segments that change relationship', function () {
const flat = [0, 0, 0, 200, 200, 200, 250, 200];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, false, false);
expect(replay.coordinates).to.eql([0, 0, 0, 200, 200, 200]);
});
it('includes outside polygon segments that change relationship when on last segment', function () {
const flat = [0, 0, 0, 200, 200, 200, 250, 200, 0, 0];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, true, false);
expect(replay.coordinates).to.eql(flat);
});
it('includes outside polygon segments that change relationship when on last segment (when skipping first)', function () {
const flat = [0, 0, 0, 200, 200, 200, 250, 200, 0, 0];
replay.appendFlatLineCoordinates(flat, 0, flat.length, 2, true, true);
expect(replay.coordinates).to.eql([0, 200, 200, 200, 250, 200, 0, 0]);
});
});
});
describe('ol.render.canvas.LineStringBuilder', function () {
describe('#getBufferedMaxExtent()', function () {
it('buffers the max extent to accommodate stroke width', function () {
const tolerance = 1;
const extent = [-180, -90, 180, 90];
const resolution = 10;
const replay = new CanvasLineStringBuilder(tolerance, extent, resolution);
const stroke = new Stroke({
width: 2,
});
replay.setFillStrokeStyle(null, stroke);
const buffered = replay.getBufferedMaxExtent();
expect(buffered).to.eql([-195, -105, 195, 105]);
});
});
});
describe('ol.render.canvas.PolygonBuilder', function () {
let replay;
beforeEach(function () {
const tolerance = 1;
const extent = [-180, -90, 180, 90];
const resolution = 10;
replay = new CanvasPolygonBuilder(tolerance, extent, resolution);
});
describe('#drawFlatCoordinatess_()', function () {
it('returns correct offset', function () {
const coords = [1, 2, 3, 4, 5, 6, 1, 2, 1, 2, 3, 4, 5, 6, 1, 2];
const ends = [7, 14];
const stroke = new Stroke({
width: 5,
});
replay.setFillStrokeStyle(null, stroke);
let offset = replay.drawFlatCoordinatess_(coords, 0, ends, 2);
expect(offset).to.be(14);
replay.setFillStrokeStyle(null, null);
offset = replay.drawFlatCoordinatess_(coords, 0, ends, 2);
expect(offset).to.be(14);
});
});
describe('#getBufferedMaxExtent()', function () {
it('buffers the max extent to accommodate stroke width', function () {
const stroke = new Stroke({
width: 5,
});
replay.setFillStrokeStyle(null, stroke);
const buffered = replay.getBufferedMaxExtent();
expect(buffered).to.eql([-210, -120, 210, 120]);
});
});
});
@@ -0,0 +1,351 @@
import Feature from '../../../../../../src/ol/Feature.js';
import ImageLayer from '../../../../../../src/ol/layer/Image.js';
import Map from '../../../../../../src/ol/Map.js';
import Point from '../../../../../../src/ol/geom/Point.js';
import Projection from '../../../../../../src/ol/proj/Projection.js';
import Static from '../../../../../../src/ol/source/ImageStatic.js';
import VectorImageLayer from '../../../../../../src/ol/layer/VectorImage.js';
import VectorSource from '../../../../../../src/ol/source/Vector.js';
import View from '../../../../../../src/ol/View.js';
import {get as getProj} from '../../../../../../src/ol/proj.js';
describe('ol.renderer.canvas.ImageLayer', function () {
describe('#forEachLayerAtCoordinate', function () {
let map, target, source;
beforeEach(function (done) {
const projection = new Projection({
code: 'custom-image',
units: 'pixels',
extent: [0, 0, 200, 200],
});
target = document.createElement('div');
target.style.width = '100px';
target.style.height = '100px';
document.body.appendChild(target);
source = new Static({
url: 'spec/ol/data/dot.png',
projection: projection,
imageExtent: [0, 0, 20, 20],
});
map = new Map({
pixelRatio: 1,
target: target,
layers: [
new ImageLayer({
source: source,
}),
],
view: new View({
projection: projection,
center: [10, 10],
zoom: 2,
maxZoom: 8,
}),
});
source.on('imageloadend', function () {
done();
});
});
afterEach(function () {
map.setTarget(null);
document.body.removeChild(target);
});
it('properly detects pixels', function () {
map.renderSync();
let has = false;
function hasLayer() {
has = true;
}
map.forEachLayerAtPixel([20, 80], hasLayer);
expect(has).to.be(true);
has = false;
map.forEachLayerAtPixel([10, 90], hasLayer);
expect(has).to.be(false);
});
});
describe('#forEachLayerAtPixel Image CORS', function () {
let map,
target,
imageExtent,
projection,
sourceCross,
source,
imageLayer,
imageLayerCross;
beforeEach(function (done) {
projection = new Projection({
code: 'custom-image',
units: 'pixels',
extent: [0, 0, 200, 200],
});
target = document.createElement('div');
target.style.width = '100px';
target.style.height = '100px';
document.body.appendChild(target);
imageExtent = [0, 0, 20, 20];
source = new Static({
url: `https://openlayers.org/assets/theme/img/logo70.png`,
projection: projection,
imageExtent: imageExtent,
});
imageLayer = new ImageLayer({
source: source,
});
sourceCross = new Static({
url: `https://openlayers.org/assets/theme/img/logo70.png`,
projection: projection,
imageExtent: imageExtent,
crossOrigin: 'anonymous',
});
imageLayerCross = new ImageLayer({
source: sourceCross,
});
map = new Map({
pixelRatio: 1,
target: target,
layers: [imageLayer, imageLayerCross],
view: new View({
projection: projection,
center: [10, 10],
zoom: 1,
maxZoom: 8,
}),
});
let loadedCount = 0;
[source, sourceCross].forEach(function (source) {
source.once('imageloadend', function () {
loadedCount++;
if (loadedCount === 2) {
done();
}
});
});
});
afterEach(function () {
map.setTarget(null);
document.body.removeChild(target);
});
it('should detect pixels even if there is no color because neither crossOrigin or extent is set', function () {
imageLayerCross.setVisible(false);
imageLayer.setVisible(true);
map.renderSync();
let has = false;
function hasLayer() {
has = true;
}
map.forEachLayerAtPixel([50, 50], hasLayer);
expect(has).to.be(true);
has = false;
map.forEachLayerAtPixel([10, 10], hasLayer);
expect(has).to.be(true);
});
it('should not detect pixels outside of the layer extent with crossOrigin set', function () {
imageLayerCross.setVisible(true);
imageLayer.setVisible(false);
map.renderSync();
let has = false;
function hasLayer() {
has = true;
}
map.forEachLayerAtPixel([50, 50], hasLayer);
expect(has).to.be(true);
has = false;
map.forEachLayerAtPixel([10, 10], hasLayer);
expect(has).to.be(false);
});
it('should not detect pixels outside of the layer extent with extent set', function () {
imageLayerCross.setVisible(true);
imageLayerCross.setExtent(imageExtent);
imageLayer.setVisible(false);
map.renderSync();
let has = false;
function hasLayer() {
has = true;
}
map.forEachLayerAtPixel([50, 50], hasLayer);
expect(has).to.be(true);
has = false;
map.forEachLayerAtPixel([10, 10], hasLayer);
expect(has).to.be(false);
});
});
describe('#getDataAtPixel', function () {
let map, target, source, imageLayer;
beforeEach(function (done) {
const projection = new Projection({
code: 'custom-image',
units: 'pixels',
extent: [0, 0, 200, 200],
});
target = document.createElement('div');
target.style.width = '100px';
target.style.height = '100px';
document.body.appendChild(target);
const imageExtent = [0, 0, 20, 20];
source = new Static({
url: 'spec/ol/data/dot.png',
projection: projection,
imageExtent: imageExtent,
});
imageLayer = new ImageLayer({
source: source,
extent: imageExtent,
});
map = new Map({
pixelRatio: 1,
target: target,
layers: [imageLayer],
view: new View({
projection: projection,
center: [10, 10],
zoom: 1,
maxZoom: 8,
}),
});
source.on('imageloadend', function () {
done();
});
});
afterEach(function () {
map.setTarget(null);
document.body.removeChild(target);
});
it('should not detect pixels outside of the layer extent', function () {
map.renderSync();
const pixel = [10, 10];
const frameState = map.frameState_;
const hitTolerance = 0;
const layerRenderer = imageLayer.getRenderer();
const data = layerRenderer.getDataAtPixel(
pixel,
frameState,
hitTolerance
);
expect(data).to.be(null);
});
it('should detect pixels in the layer extent', function () {
map.renderSync();
const pixel = [50, 50];
const frameState = map.frameState_;
const hitTolerance = 0;
const layerRenderer = imageLayer.getRenderer();
const data = layerRenderer.getDataAtPixel(
pixel,
frameState,
hitTolerance
);
expect(data.length > 0).to.be(true);
});
});
describe('Image rendering', function () {
let map, div, layer;
beforeEach(function (done) {
const projection = getProj('EPSG:3857');
layer = new ImageLayer({
source: new Static({
url: 'spec/ol/data/osm-0-0-0.png',
imageExtent: projection.getExtent(),
projection: projection,
}),
});
div = document.createElement('div');
div.style.width = '100px';
div.style.height = '100px';
document.body.appendChild(div);
map = new Map({
target: div,
layers: [layer],
view: new View({
center: [0, 0],
zoom: 2,
}),
});
layer.getSource().on('imageloadend', function () {
done();
});
});
afterEach(function () {
map.setTarget(null);
document.body.removeChild(div);
map.dispose();
});
it('dispatches prerender and postrender events on the image layer', function (done) {
let prerender = 0;
let postrender = 0;
layer.on('prerender', function () {
++prerender;
});
layer.on('postrender', function () {
++postrender;
});
map.on('postrender', function () {
expect(prerender).to.be(1);
expect(postrender).to.be(1);
done();
});
});
});
describe('Vector image rendering', function () {
let map, div, layer;
beforeEach(function () {
layer = new VectorImageLayer({
source: new VectorSource({
features: [new Feature(new Point([0, 0]))],
}),
});
div = document.createElement('div');
div.style.width = '100px';
div.style.height = '100px';
document.body.appendChild(div);
map = new Map({
target: div,
layers: [layer],
view: new View({
center: [0, 0],
zoom: 2,
}),
});
});
afterEach(function () {
map.setTarget(null);
document.body.removeChild(div);
map.dispose();
});
it('dispatches prerender and postrender events on the vector layer', function (done) {
let prerender = 0;
let postrender = 0;
layer.on('prerender', function () {
++prerender;
});
layer.on('postrender', function () {
++postrender;
});
map.once('postrender', function () {
expect(prerender).to.be(1);
expect(postrender).to.be(1);
done();
});
});
});
});
@@ -0,0 +1,91 @@
import Map from '../../../../../../src/ol/Map.js';
import TileLayer from '../../../../../../src/ol/layer/Tile.js';
import TileWMS from '../../../../../../src/ol/source/TileWMS.js';
import View from '../../../../../../src/ol/View.js';
import XYZ from '../../../../../../src/ol/source/XYZ.js';
import {fromLonLat} from '../../../../../../src/ol/proj.js';
describe('ol.renderer.canvas.TileLayer', function () {
describe('#prepareFrame', function () {
let map, target, source, tile;
beforeEach(function (done) {
target = document.createElement('div');
target.style.width = '100px';
target.style.height = '100px';
document.body.appendChild(target);
source = new TileWMS({
url: 'spec/ol/data/osm-0-0-0.png',
params: {LAYERS: 'foo', TIME: '0'},
});
source.once('tileloadend', function (e) {
tile = e.tile;
done();
});
map = new Map({
target: target,
layers: [
new TileLayer({
source: source,
}),
],
view: new View({
zoom: 0,
center: [0, 0],
}),
});
});
afterEach(function () {
map.setTarget(null);
document.body.removeChild(target);
});
it('properly handles interim tiles', function (done) {
const layer = map.getLayers().item(0);
source.once('tileloadend', function (e) {
expect(e.tile.inTransition()).to.be(false);
done();
});
source.updateParams({TIME: '1'});
map.renderSync();
const tiles = layer.getRenderer().renderedTiles;
expect(tiles.length).to.be(1);
expect(tiles[0]).to.equal(tile);
expect(tile.inTransition()).to.be(true);
});
});
describe('#renderFrame', function () {
let map, layer;
beforeEach(function () {
layer = new TileLayer({
source: new XYZ({
cacheSize: 1,
url: 'bogus-url/{z}/{x}/{y}.png',
}),
});
map = new Map({
target: createMapDiv(100, 100),
layers: [layer],
view: new View({
center: fromLonLat([-122.416667, 37.783333]),
zoom: 5,
}),
});
});
afterEach(function () {
disposeMap(map);
});
it("respects the source's zDirection setting", function (done) {
layer.getSource().zDirection = 1;
map.getView().setZoom(5.8); // would lead to z6 tile request with the default zDirection
map.once('rendercomplete', function () {
const tileCache = layer.getSource().tileCache;
const keys = tileCache.getKeys();
expect(keys.some((key) => key.startsWith('6/'))).to.be(false);
done();
});
});
});
});
@@ -0,0 +1,57 @@
import CanvasVectorImageLayerRenderer from '../../../../../../src/ol/renderer/canvas/VectorImageLayer.js';
import VectorImageLayer from '../../../../../../src/ol/layer/VectorImage.js';
import VectorSource from '../../../../../../src/ol/source/Vector.js';
import {create} from '../../../../../../src/ol/transform.js';
import {get as getProjection} from '../../../../../../src/ol/proj.js';
import {scaleFromCenter} from '../../../../../../src/ol/extent.js';
describe('ol/renderer/canvas/VectorImageLayer', function () {
describe('#dispose()', function () {
it('cleans up CanvasVectorRenderer', function () {
const layer = new VectorImageLayer({
source: new VectorSource(),
});
const renderer = new CanvasVectorImageLayerRenderer(layer);
const spy = sinon.spy(renderer.vectorRenderer_, 'dispose');
renderer.dispose();
expect(spy.called).to.be(true);
});
});
describe('#prepareFrame', function () {
it('sets correct extent with imageRatio = 2', function () {
const layer = new VectorImageLayer({
imageRatio: 2,
source: new VectorSource(),
});
const renderer = new CanvasVectorImageLayerRenderer(layer);
const projection = getProjection('EPSG:3857');
const projExtent = projection.getExtent();
const extent = [
projExtent[0] - 10000,
-10000,
projExtent[0] + 10000,
10000,
];
const frameState = {
layerStatesArray: [layer.getLayerState()],
layerIndex: 0,
extent: extent,
viewHints: [],
pixelToCoordinateTransform: create(),
viewState: {
center: [0, 0],
projection: projection,
resolution: 1,
rotation: 0,
},
};
renderer.prepareFrame(frameState);
const expected = renderer.image_.getExtent();
scaleFromCenter(extent, 2);
expect(expected).to.eql(extent);
});
});
});
@@ -0,0 +1,641 @@
import CanvasVectorLayerRenderer from '../../../../../../src/ol/renderer/canvas/VectorLayer.js';
import Circle from '../../../../../../src/ol/geom/Circle.js';
import CircleStyle from '../../../../../../src/ol/style/Circle.js';
import Feature from '../../../../../../src/ol/Feature.js';
import Fill from '../../../../../../src/ol/style/Fill.js';
import Map from '../../../../../../src/ol/Map.js';
import Point from '../../../../../../src/ol/geom/Point.js';
import Stroke from '../../../../../../src/ol/style/Stroke.js';
import Style from '../../../../../../src/ol/style/Style.js';
import Text from '../../../../../../src/ol/style/Text.js';
import VectorLayer from '../../../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../../../src/ol/source/Vector.js';
import View from '../../../../../../src/ol/View.js';
import {bbox as bboxStrategy} from '../../../../../../src/ol/loadingstrategy.js';
import {
buffer as bufferExtent,
getCenter,
getWidth,
} from '../../../../../../src/ol/extent.js';
import {checkedFonts} from '../../../../../../src/ol/render/canvas.js';
import {fromExtent} from '../../../../../../src/ol/geom/Polygon.js';
import {get as getProjection} from '../../../../../../src/ol/proj.js';
describe('ol.renderer.canvas.VectorLayer', function () {
describe('constructor', function () {
const head = document.getElementsByTagName('head')[0];
const font = document.createElement('link');
font.href = 'https://fonts.googleapis.com/css?family=Droid+Sans';
font.rel = 'stylesheet';
let target;
beforeEach(function () {
target = document.createElement('div');
target.style.width = '256px';
target.style.height = '256px';
document.body.appendChild(target);
});
afterEach(function () {
document.body.removeChild(target);
});
it('creates a new instance', function () {
const layer = new VectorLayer({
source: new VectorSource(),
});
const renderer = new CanvasVectorLayerRenderer(layer);
expect(renderer).to.be.a(CanvasVectorLayerRenderer);
});
it('gives precedence to feature styles over layer styles', function () {
const target = document.createElement('div');
target.style.width = '256px';
target.style.height = '256px';
document.body.appendChild(target);
const map = new Map({
view: new View({
center: [0, 0],
zoom: 0,
}),
target: target,
});
const layerStyle = [
new Style({
text: new Text({
text: 'layer',
}),
}),
];
const featureStyle = [
new Style({
text: new Text({
text: 'feature',
}),
}),
];
const feature1 = new Feature(new Point([0, 0]));
const feature2 = new Feature(new Point([0, 0]));
feature2.setStyle(featureStyle);
const layer = new VectorLayer({
source: new VectorSource({
features: [feature1, feature2],
}),
style: layerStyle,
});
map.addLayer(layer);
const spy = sinon.spy(layer.getRenderer(), 'renderFeature');
map.renderSync();
expect(spy.getCall(0).args[2]).to.be(layerStyle);
expect(spy.getCall(1).args[2]).to.be(featureStyle);
document.body.removeChild(target);
});
it('does not re-render for unavailable fonts', function (done) {
checkedFonts.values_ = {};
const map = new Map({
view: new View({
center: [0, 0],
zoom: 0,
}),
target: target,
});
const layerStyle = new Style({
text: new Text({
text: 'layer',
font: '12px "Unavailable Font",sans-serif',
}),
});
const feature = new Feature(new Point([0, 0]));
const layer = new VectorLayer({
source: new VectorSource({
features: [feature],
}),
style: layerStyle,
});
map.addLayer(layer);
const revision = layer.getRevision();
setTimeout(function () {
expect(layer.getRevision()).to.be(revision);
done();
}, 800);
});
it('does not re-render for available fonts', function (done) {
checkedFonts.values_ = {};
const map = new Map({
view: new View({
center: [0, 0],
zoom: 0,
}),
target: target,
});
const layerStyle = new Style({
text: new Text({
text: 'layer',
font: '12px sans-serif',
}),
});
const feature = new Feature(new Point([0, 0]));
const layer = new VectorLayer({
source: new VectorSource({
features: [feature],
}),
style: layerStyle,
});
map.addLayer(layer);
const revision = layer.getRevision();
setTimeout(function () {
expect(layer.getRevision()).to.be(revision);
done();
}, 800);
});
it('re-renders for fonts that become available', function (done) {
checkedFonts.values_ = {};
head.appendChild(font);
const map = new Map({
view: new View({
center: [0, 0],
zoom: 0,
}),
target: target,
});
const layerStyle = new Style({
text: new Text({
text: 'layer',
font: '12px "Droid Sans",sans-serif',
}),
});
const feature = new Feature(new Point([0, 0]));
const layer = new VectorLayer({
source: new VectorSource({
features: [feature],
}),
style: layerStyle,
});
map.addLayer(layer);
const revision = layer.getRevision();
setTimeout(function () {
expect(layer.getRevision()).to.be(revision + 1);
head.removeChild(font);
done();
}, 1600);
});
});
describe('#forEachFeatureAtCoordinate', function () {
/** @type {VectorLayer} */ let layer;
/** @type {CanvasVectorLayerRenderer} */ let renderer;
beforeEach(function () {
layer = new VectorLayer({
source: new VectorSource(),
});
renderer = new CanvasVectorLayerRenderer(layer);
const replayGroup = {};
renderer.replayGroup_ = replayGroup;
replayGroup.forEachFeatureAtCoordinate = function (
coordinate,
resolution,
rotation,
hitTolerance,
callback
) {
const feature = new Feature(new Point([0, 0]));
const distanceSq = 0;
callback(feature, feature.getGeometry(), distanceSq);
callback(feature, feature.getGeometry(), distanceSq);
};
});
it('calls callback once per feature with a layer as 2nd arg', function () {
const spy = sinon.spy();
const coordinate = [0, 0];
const matches = [];
const frameState = {
layerStatesArray: [{}],
viewState: {
center: [0, 0],
resolution: 1,
rotation: 0,
},
};
renderer.forEachFeatureAtCoordinate(
coordinate,
frameState,
0,
spy,
matches
);
expect(spy.callCount).to.be(1);
expect(spy.getCall(0).args[1]).to.be(layer);
expect(matches).to.be.empty();
});
});
describe('#prepareFrame and #compose', function () {
let frameState, projExtent, renderer, worldWidth, buffer, loadExtents;
function loader(extent) {
loadExtents.push(extent);
}
beforeEach(function () {
const layer = new VectorLayer({
source: new VectorSource({
wrapX: true,
loader: loader,
strategy: bboxStrategy,
}),
});
renderer = new CanvasVectorLayerRenderer(layer);
const projection = getProjection('EPSG:3857');
projExtent = projection.getExtent();
worldWidth = getWidth(projExtent);
buffer = layer.getRenderBuffer();
loadExtents = [];
frameState = {
viewHints: [],
viewState: {
projection: projection,
resolution: 1,
rotation: 0,
},
};
});
function setExtent(extent) {
frameState.extent = extent;
frameState.viewState.center = getCenter(extent);
}
it('sets correct extent for small viewport near dateline', function () {
setExtent([projExtent[0] - 10000, -10000, projExtent[0] + 10000, 10000]);
renderer.prepareFrame(frameState);
expect(renderer.replayGroup_.maxExtent_).to.eql(
bufferExtent(
[
projExtent[0] - worldWidth + buffer,
-10000,
projExtent[2] + worldWidth - buffer,
10000,
],
buffer
)
);
expect(loadExtents.length).to.be(2);
expect(loadExtents[0]).to.eql(bufferExtent(frameState.extent, buffer));
const otherExtent = [
projExtent[2] - 10000,
-10000,
projExtent[2] + 10000,
10000,
];
expect(loadExtents[1]).to.eql(bufferExtent(otherExtent, buffer));
});
it('sets correct extent for viewport less than 1 world wide', function () {
setExtent([projExtent[0] - 10000, -10000, projExtent[2] - 10000, 10000]);
renderer.prepareFrame(frameState);
expect(renderer.replayGroup_.maxExtent_).to.eql(
bufferExtent(
[
projExtent[0] - worldWidth + buffer,
-10000,
projExtent[2] + worldWidth - buffer,
10000,
],
buffer
)
);
expect(loadExtents.length).to.be(2);
expect(loadExtents[0]).to.eql(bufferExtent(frameState.extent, buffer));
const otherExtent = [
projExtent[0] - 10000 + worldWidth,
-10000,
projExtent[2] - 10000 + worldWidth,
10000,
];
expect(loadExtents[1]).to.eql(bufferExtent(otherExtent, buffer));
});
it('sets correct extent for viewport more than 1 world wide', function () {
setExtent([
2 * projExtent[0] + 10000,
-10000,
2 * projExtent[2] - 10000,
10000,
]);
renderer.prepareFrame(frameState);
expect(renderer.replayGroup_.maxExtent_).to.eql(
bufferExtent(
[
projExtent[0] - worldWidth + buffer,
-10000,
projExtent[2] + worldWidth - buffer,
10000,
],
buffer
)
);
expect(loadExtents.length).to.be(1);
expect(loadExtents[0]).to.eql(bufferExtent(frameState.extent, buffer));
});
it('sets correct extent for viewport more than 2 worlds wide, one world away', function () {
setExtent([
projExtent[0] - 2 * worldWidth - 10000,
-10000,
projExtent[0] + 2 * worldWidth + 10000,
10000,
]);
renderer.prepareFrame(frameState);
expect(renderer.replayGroup_.maxExtent_).to.eql(
bufferExtent(
[
projExtent[0] - 2 * worldWidth - 10000,
-10000,
projExtent[2] + 2 * worldWidth + 10000,
10000,
],
buffer
)
);
expect(loadExtents.length).to.be(1);
const normalizedExtent = [
projExtent[0] - 2 * worldWidth + worldWidth - 10000,
-10000,
projExtent[0] + 2 * worldWidth + worldWidth + 10000,
10000,
];
expect(loadExtents[0]).to.eql(bufferExtent(normalizedExtent, buffer));
});
it('sets correct extent for small viewport, one world away', function () {
setExtent([-worldWidth - 10000, -10000, -worldWidth + 10000, 10000]);
renderer.prepareFrame(frameState);
expect(renderer.replayGroup_.maxExtent_).to.eql(
bufferExtent(
[
projExtent[0] - worldWidth + buffer,
-10000,
projExtent[2] + worldWidth - buffer,
10000,
],
buffer
)
);
expect(loadExtents.length).to.be(1);
const normalizedExtent = [-10000, -10000, 10000, 10000];
expect(loadExtents[0]).to.eql(bufferExtent(normalizedExtent, buffer));
});
it('sets replayGroupChanged correctly', function () {
setExtent([-10000, -10000, 10000, 10000]);
renderer.prepareFrame(frameState);
expect(renderer.replayGroupChanged).to.be(true);
renderer.prepareFrame(frameState);
expect(renderer.replayGroupChanged).to.be(false);
});
it('dispatches a postrender event when rendering', function (done) {
const layer = renderer.getLayer();
layer.getSource().addFeature(new Feature(new Point([0, 0])));
layer.once('postrender', function () {
expect(true);
done();
});
frameState.layerStatesArray = [layer.getLayerState()];
frameState.layerIndex = 0;
frameState.extent = [-10000, -10000, 10000, 10000];
frameState.size = [100, 100];
frameState.viewState.center = [0, 0];
let rendered = false;
if (renderer.prepareFrame(frameState)) {
rendered = true;
renderer.renderFrame(frameState, null);
}
expect(rendered).to.be(true);
});
});
describe('hit detection', function () {
it('with no fill and transparent fill', function () {
const target = document.createElement('div');
target.style.width = '300px';
target.style.height = '300px';
document.body.appendChild(target);
const styles = {
transparent: new Style({
stroke: new Stroke({
color: 'blue',
width: 3,
}),
fill: new Fill({
color: 'transparent',
}),
image: new CircleStyle({
radius: 30,
stroke: new Stroke({
color: 'blue',
width: 3,
}),
fill: new Fill({
color: 'transparent',
}),
}),
}),
none: new Style({
stroke: new Stroke({
color: 'blue',
width: 3,
}),
image: new CircleStyle({
radius: 30,
stroke: new Stroke({
color: 'blue',
width: 3,
}),
}),
}),
};
const source = new VectorSource({
features: [
new Feature({
geometry: fromExtent([0, 10, 3, 13]),
fillType: 'none',
}),
new Feature({
geometry: fromExtent([1, 11, 4, 14]),
fillType: 'none',
}),
new Feature({
geometry: fromExtent([5, 10, 8, 13]),
fillType: 'transparent',
}),
new Feature({
geometry: fromExtent([6, 11, 9, 14]),
fillType: 'transparent',
}),
new Feature({
geometry: new Circle([1.5, 6.5], 1.5),
fillType: 'none',
}),
new Feature({
geometry: new Circle([2.5, 7.5], 1.5),
fillType: 'none',
}),
new Feature({
geometry: new Circle([6.5, 6.5], 1.5),
fillType: 'transparent',
}),
new Feature({
geometry: new Circle([7.5, 7.5], 1.5),
fillType: 'transparent',
}),
new Feature({
geometry: new Point([1.5, 1.5]),
fillType: 'none',
}),
new Feature({
geometry: new Point([2.5, 2.5]),
fillType: 'none',
}),
new Feature({
geometry: new Point([6.5, 1.5]),
fillType: 'transparent',
}),
new Feature({
geometry: new Point([7.5, 2.5]),
fillType: 'transparent',
}),
],
});
const layer = new VectorLayer({
source: source,
style: function (feature, resolution) {
return styles[feature.get('fillType')];
},
});
const map = new Map({
layers: [layer],
view: new View({
center: [4.5, 7],
resolution: 0.05,
}),
target: target,
});
map.renderSync();
function hitTest(coordinate) {
const features = map.getFeaturesAtPixel(
map.getPixelFromCoordinate(coordinate)
);
const result = {count: 0};
if (features && features.length > 0) {
result.count = features.length;
result.extent = features[0].getGeometry().getExtent();
}
return result;
}
let res;
res = hitTest([0, 12]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(0);
res = hitTest([1, 12]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(1);
res = hitTest([2, 12]);
expect(res.count).to.be(0);
res = hitTest([3, 12]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(0);
res = hitTest([4, 12]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(1);
res = hitTest([5, 12]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(5);
res = hitTest([6, 12]);
expect(res.count).to.be(2);
expect(res.extent[0]).to.be(6);
res = hitTest([7, 12]);
expect(res.count).to.be(2);
expect(res.extent[0]).to.be(6);
res = hitTest([8, 12]);
expect(res.count).to.be(2);
expect(res.extent[0]).to.be(6);
res = hitTest([9, 12]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(6);
res = hitTest([0, 6.5]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(0);
res = hitTest([1, 7.5]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(1);
res = hitTest([2, 7.0]);
expect(res.count).to.be(0);
res = hitTest([3, 6.5]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(0);
res = hitTest([4, 7.5]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(1);
res = hitTest([5, 6.5]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(5);
res = hitTest([6, 7.5]);
expect(res.count).to.be(2);
expect(res.extent[0]).to.be(6);
res = hitTest([7, 7.0]);
expect(res.count).to.be(2);
expect(res.extent[0]).to.be(6);
res = hitTest([8, 6.5]);
expect(res.count).to.be(2);
expect(res.extent[0]).to.be(6);
res = hitTest([9, 7.5]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(6);
res = hitTest([0, 1.5]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(1.5);
res = hitTest([1, 2.5]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(2.5);
res = hitTest([2, 2.0]);
expect(res.count).to.be(0);
res = hitTest([3, 1.5]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(1.5);
res = hitTest([4, 2.5]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(2.5);
res = hitTest([5, 1.5]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(6.5);
res = hitTest([6, 2.5]);
expect(res.count).to.be(2);
expect(res.extent[0]).to.be(7.5);
res = hitTest([7, 2.0]);
expect(res.count).to.be(2);
expect(res.extent[0]).to.be(7.5);
res = hitTest([8, 1.5]);
expect(res.count).to.be(2);
expect(res.extent[0]).to.be(7.5);
res = hitTest([9, 2.5]);
expect(res.count).to.be(1);
expect(res.extent[0]).to.be(7.5);
document.body.removeChild(target);
});
});
});
@@ -0,0 +1,424 @@
import CanvasVectorTileLayerRenderer from '../../../../../../src/ol/renderer/canvas/VectorTileLayer.js';
import Feature from '../../../../../../src/ol/Feature.js';
import MVT from '../../../../../../src/ol/format/MVT.js';
import Map from '../../../../../../src/ol/Map.js';
import Point from '../../../../../../src/ol/geom/Point.js';
import RenderFeature from '../../../../../../src/ol/render/Feature.js';
import Style from '../../../../../../src/ol/style/Style.js';
import Text from '../../../../../../src/ol/style/Text.js';
import TileLayer from '../../../../../../src/ol/layer/Tile.js';
import TileState from '../../../../../../src/ol/TileState.js';
import VectorRenderTile from '../../../../../../src/ol/VectorRenderTile.js';
import VectorTile from '../../../../../../src/ol/VectorTile.js';
import VectorTileLayer from '../../../../../../src/ol/layer/VectorTile.js';
import VectorTileRenderType from '../../../../../../src/ol/layer/VectorTileRenderType.js';
import VectorTileSource from '../../../../../../src/ol/source/VectorTile.js';
import View from '../../../../../../src/ol/View.js';
import XYZ from '../../../../../../src/ol/source/XYZ.js';
import {checkedFonts} from '../../../../../../src/ol/render/canvas.js';
import {create} from '../../../../../../src/ol/transform.js';
import {createXYZ} from '../../../../../../src/ol/tilegrid.js';
import {getCenter} from '../../../../../../src/ol/extent.js';
import {get as getProjection} from '../../../../../../src/ol/proj.js';
import {getUid} from '../../../../../../src/ol/util.js';
describe('ol.renderer.canvas.VectorTileLayer', function () {
describe('constructor', function () {
const head = document.getElementsByTagName('head')[0];
const font = document.createElement('link');
font.href = 'https://fonts.googleapis.com/css?family=Dancing+Script';
font.rel = 'stylesheet';
let map,
layer,
layerStyle,
source,
feature1,
feature2,
feature3,
target,
tileCallback;
beforeEach(function () {
tileCallback = function () {};
target = document.createElement('div');
target.style.width = '256px';
target.style.height = '256px';
document.body.appendChild(target);
map = new Map({
pixelRatio: 1,
view: new View({
center: [0, 0],
zoom: 0,
}),
target: target,
});
layerStyle = [
new Style({
text: new Text({
text: 'layer',
}),
}),
];
const featureStyle = [
new Style({
text: new Text({
text: 'feature',
}),
}),
];
feature1 = new Feature(new Point([1, -1]));
feature2 = new Feature(new Point([0, 0]));
feature3 = new RenderFeature('Point', [1, -1], []);
feature2.setStyle(featureStyle);
class TileClass extends VectorTile {
constructor() {
super(...arguments);
this.setFeatures([feature1, feature2, feature3]);
this.setState(TileState.LOADED);
tileCallback(this);
}
}
source = new VectorTileSource({
format: new MVT(),
tileClass: TileClass,
tileGrid: createXYZ(),
url: '{z}/{x}/{y}.pbf',
});
source.getSourceTiles = function () {
return [new TileClass([0, 0, 0])];
};
source.getTile = function () {
const tile = VectorTileSource.prototype.getTile.apply(
source,
arguments
);
tile.hasContext = function () {
return true;
};
tile.setState(TileState.LOADED);
return tile;
};
layer = new VectorTileLayer({
source: source,
style: layerStyle,
});
map.addLayer(layer);
});
afterEach(function () {
document.body.removeChild(target);
map.dispose();
});
it('creates a new instance', function () {
const renderer = new CanvasVectorTileLayerRenderer(layer);
expect(renderer).to.be.a(CanvasVectorTileLayerRenderer);
expect(renderer.getLayer()).to.be(layer);
});
it('does not render images for pure vector rendering', function () {
const testLayer = new VectorTileLayer({
renderMode: VectorTileRenderType.VECTOR,
source: source,
style: layerStyle,
});
map.removeLayer(layer);
map.addLayer(testLayer);
const spy = sinon.spy(
CanvasVectorTileLayerRenderer.prototype,
'renderTileImage_'
);
map.renderSync();
expect(spy.callCount).to.be(0);
spy.restore();
});
it('renders both replays and images for hybrid rendering', function () {
const spy1 = sinon.spy(
CanvasVectorTileLayerRenderer.prototype,
'getRenderTransform'
);
const spy2 = sinon.spy(
CanvasVectorTileLayerRenderer.prototype,
'renderTileImage_'
);
map.renderSync();
expect(spy1.callCount).to.be(1);
expect(spy2.callCount).to.be(1);
spy1.restore();
spy2.restore();
});
it('renders replays with custom renderers as direct replays', function () {
layer.setStyle(
new Style({
renderer: function () {},
})
);
const spy = sinon.spy(
CanvasVectorTileLayerRenderer.prototype,
'getRenderTransform'
);
map.renderSync();
expect(spy.callCount).to.be(1);
spy.restore();
});
it('gives precedence to feature styles over layer styles', function () {
const spy = sinon.spy(layer.getRenderer(), 'renderFeature');
map.renderSync();
expect(spy.getCall(0).args[2]).to.be(layer.getStyle());
expect(spy.getCall(1).args[2]).to.be(feature2.getStyle());
spy.restore();
});
it('does not re-render for unavailable fonts', function (done) {
map.renderSync();
checkedFonts.values_ = {};
layerStyle[0].getText().setFont('12px "Unavailable font",sans-serif');
layer.changed();
const revision = layer.getRevision();
setTimeout(function () {
expect(layer.getRevision()).to.be(revision);
done();
}, 800);
});
it('does not re-render for available fonts', function (done) {
map.renderSync();
checkedFonts.values_ = {};
layerStyle[0].getText().setFont('12px sans-serif');
layer.changed();
const revision = layer.getRevision();
setTimeout(function () {
expect(layer.getRevision()).to.be(revision);
done();
}, 800);
});
it('re-renders for fonts that become available', function (done) {
map.renderSync();
checkedFonts.values_ = {};
head.appendChild(font);
layerStyle[0].getText().setFont('12px "Dancing Script",sans-serif');
layer.changed();
const revision = layer.getRevision();
setTimeout(function () {
head.removeChild(font);
expect(layer.getRevision()).to.be(revision + 1);
done();
}, 1600);
});
it('works for multiple layers that use the same source', function () {
const layer2 = new VectorTileLayer({
source: source,
style: new Style({
text: new Text({
text: 'layer2',
}),
}),
});
map.addLayer(layer2);
map.renderSync();
const tile = source.getTile(0, 0, 0, 1, getProjection('EPSG:3857'));
expect(Object.keys(tile.executorGroups)[0]).to.be(getUid(layer));
expect(Object.keys(tile.executorGroups)[1]).to.be(getUid(layer2));
});
it('reuses render container and adds and removes overlay context', function (done) {
map.getLayers().insertAt(
0,
new TileLayer({
source: new XYZ({
url: 'rendering/ol/data/tiles/osm/{z}/{x}/{y}.png',
}),
})
);
map.once('rendercomplete', function () {
expect(document.querySelector('.ol-layers').childElementCount).to.be(1);
expect(document.querySelector('.ol-layer').childElementCount).to.be(1);
map.removeLayer(map.getLayers().item(1));
map.renderSync();
expect(document.querySelector('.ol-layer').childElementCount).to.be(1);
done();
});
});
});
describe('#prepareFrame', function () {
it('re-renders when layer changed', function () {
const layer = new VectorTileLayer({
source: new VectorTileSource({
tileGrid: createXYZ(),
transition: 0,
}),
});
const sourceTile = new VectorTile([0, 0, 0], 2);
sourceTile.features_ = [];
sourceTile.getImage = function () {
return document.createElement('canvas');
};
const tile = new VectorRenderTile([0, 0, 0], 1, [0, 0, 0], function () {
return sourceTile;
});
tile.transition_ = 0;
tile.setState(TileState.LOADED);
layer.getSource().getTile = function () {
return tile;
};
const renderer = new CanvasVectorTileLayerRenderer(layer);
renderer.isDrawableTile = function () {
return true;
};
const proj = getProjection('EPSG:3857');
const frameState = {
layerStatesArray: [layer.getLayerState()],
layerIndex: 0,
extent: proj.getExtent(),
pixelRatio: 1,
pixelToCoordinateTransform: create(),
time: Date.now(),
viewHints: [],
viewState: {
center: [0, 0],
resolution: 156543.03392804097,
projection: proj,
},
size: [256, 256],
usedTiles: {},
wantedTiles: {},
};
renderer.renderFrame(frameState);
const replayState = renderer.renderedTiles[0].getReplayState(layer);
const revision = replayState.renderedTileRevision;
renderer.renderFrame(frameState, null);
expect(replayState.renderedTileRevision).to.be(revision);
layer.changed();
renderer.renderFrame(frameState, null);
expect(replayState.renderedTileRevision).to.be(revision + 1);
expect(Object.keys(renderer.tileListenerKeys_).length).to.be(0);
});
});
describe('#forEachFeatureAtCoordinate', function () {
/** @type {VectorTileLayer] */ let layer;
/** @type {CanvasVectorTileLayerRenderer} */ let renderer;
/** @type {VectorTileSource} */ let source;
let executorGroup;
class TileClass extends VectorRenderTile {
constructor() {
super(...arguments);
this.setState(TileState.LOADED);
this.wrappedTileCoord = arguments[0];
}
}
beforeEach(function () {
const sourceTile = new VectorTile([0, 0, 0]);
sourceTile.setState(TileState.LOADED);
source = new VectorTileSource({
tileClass: TileClass,
tileGrid: createXYZ(),
});
source.sourceTileCache.set('0/0/0.mvt', sourceTile);
executorGroup = {};
executorGroup.forEachFeatureAtCoordinate = function (
coordinate,
resolution,
rotation,
hitTolerance,
callback
) {
const feature = new Feature(new Point([0, 0]));
const distanceSq = 0;
callback(feature, feature.getGeometry(), distanceSq);
callback(feature, feature.getGeometry(), distanceSq);
};
source.getTile = function () {
const tile = VectorTileSource.prototype.getTile.apply(
source,
arguments
);
tile.sourceTiles = [sourceTile];
tile.executorGroups[getUid(layer)] = [executorGroup];
return tile;
};
layer = new VectorTileLayer({
source: source,
});
renderer = new CanvasVectorTileLayerRenderer(layer);
});
it('calls callback once per feature with a layer as 2nd arg', function () {
const spy = sinon.spy();
const coordinate = [0, 0];
const matches = [];
const frameState = {
layerStatesArray: [{}],
viewState: {
projection: getProjection('EPSG:3857'),
resolution: 1,
rotation: 0,
},
};
renderer.renderedTiles = [
source.getTile(0, 0, 0, 1, getProjection('EPSG:3857')),
];
renderer.forEachFeatureAtCoordinate(
coordinate,
frameState,
0,
spy,
matches
);
expect(spy.callCount).to.be(1);
expect(spy.getCall(0).args[1]).to.be(layer);
expect(matches).to.be.empty();
});
it('does not give false positives when overzoomed', function (done) {
const target = document.createElement('div');
target.style.width = '100px';
target.style.height = '100px';
document.body.appendChild(target);
const extent = [
1824704.739223726,
6141868.096770482,
1827150.7241288517,
6144314.081675608,
];
const source = new VectorTileSource({
format: new MVT(),
url: 'spec/ol/data/14-8938-5680.vector.pbf',
minZoom: 14,
maxZoom: 14,
});
const map = new Map({
target: target,
layers: [
new VectorTileLayer({
extent: extent,
source: source,
}),
],
view: new View({
center: getCenter(extent),
zoom: 19,
}),
});
source.on('tileloadend', function () {
setTimeout(function () {
const features = map.getFeaturesAtPixel([96, 96]);
document.body.removeChild(target);
map.dispose();
expect(features).to.be.an(Array);
expect(features).to.be.empty();
done();
}, 200);
});
});
});
});
+145
View File
@@ -0,0 +1,145 @@
import ImageWrapper from '../../../../../src/ol/Image.js';
import Layer from '../../../../../src/ol/layer/Layer.js';
import LayerRenderer from '../../../../../src/ol/renderer/Layer.js';
import Map from '../../../../../src/ol/Map.js';
import TileLayer from '../../../../../src/ol/layer/Tile.js';
import View from '../../../../../src/ol/View.js';
import XYZ from '../../../../../src/ol/source/XYZ.js';
import {fromKey} from '../../../../../src/ol/tilecoord.js';
describe('ol.renderer.Layer', function () {
let renderer;
const eventType = 'change';
beforeEach(function () {
const layer = new Layer({});
renderer = new LayerRenderer(layer);
});
describe('#loadImage', function () {
let image;
let imageLoadFunction;
beforeEach(function () {
const extent = [];
const resolution = 1;
const pixelRatio = 1;
const src = '';
const crossOrigin = '';
imageLoadFunction = sinon.spy();
image = new ImageWrapper(
extent,
resolution,
pixelRatio,
src,
crossOrigin,
imageLoadFunction
);
});
describe('load IDLE image', function () {
it('returns false', function () {
const loaded = renderer.loadImage(image);
expect(loaded).to.be(false);
});
it('registers a listener', function () {
renderer.loadImage(image);
const listeners = image.listeners_[eventType];
expect(listeners).to.have.length(1);
});
});
describe('load LOADED image', function () {
it('returns true', function () {
image.state = 2; // LOADED
const loaded = renderer.loadImage(image);
expect(loaded).to.be(true);
});
it('does not register a listener', function () {
image.state = 2; // LOADED
const loaded = renderer.loadImage(image);
expect(loaded).to.be(true);
});
});
describe('load LOADING image', function () {
beforeEach(function () {
renderer.loadImage(image);
expect(image.getState()).to.be(1); // LOADING
});
it('returns false', function () {
const loaded = renderer.loadImage(image);
expect(loaded).to.be(false);
});
it('does not register a new listener', function () {
renderer.loadImage(image);
const listeners = image.listeners_[eventType];
expect(listeners).to.have.length(1);
});
});
});
describe('manageTilePyramid behavior', function () {
let target, map, view, source;
beforeEach(function (done) {
target = document.createElement('div');
Object.assign(target.style, {
position: 'absolute',
left: '-1000px',
top: '-1000px',
width: '360px',
height: '180px',
});
document.body.appendChild(target);
view = new View({
center: [0, 0],
multiWorld: true,
zoom: 0,
});
source = new XYZ({
url: '#{x}/{y}/{z}',
});
map = new Map({
target: target,
view: view,
layers: [
new TileLayer({
source: source,
}),
],
});
map.once('postrender', function () {
done();
});
});
afterEach(function () {
map.dispose();
document.body.removeChild(target);
});
it('accesses tiles from current zoom level last', function (done) {
// expect most recent tile in the cache to be from zoom level 0
const key = source.tileCache.peekFirstKey();
const tileCoord = fromKey(key);
expect(tileCoord[0]).to.be(0);
map.once('moveend', function () {
// expect most recent tile in the cache to be from zoom level 4
const key = source.tileCache.peekFirstKey();
const tileCoord = fromKey(key);
expect(tileCoord[0]).to.be(4);
done();
});
view.setZoom(4);
});
});
});
+205
View File
@@ -0,0 +1,205 @@
import Disposable from '../../../../../src/ol/Disposable.js';
import Feature from '../../../../../src/ol/Feature.js';
import Map from '../../../../../src/ol/Map.js';
import MapRenderer from '../../../../../src/ol/renderer/Map.js';
import VectorLayer from '../../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../../src/ol/source/Vector.js';
import View from '../../../../../src/ol/View.js';
import {Circle, Fill, Stroke, Style} from '../../../../../src/ol/style.js';
import {
GeometryCollection,
LineString,
MultiPoint,
Point,
} from '../../../../../src/ol/geom.js';
import {Projection} from '../../../../../src/ol/proj.js';
import {fromExtent} from '../../../../../src/ol/geom/Polygon.js';
describe('ol.renderer.Map', function () {
describe('constructor', function () {
it('createst an instance', function () {
const map = new Map({});
const renderer = new MapRenderer(null, map);
expect(renderer).to.be.a(MapRenderer);
expect(renderer).to.be.a(Disposable);
renderer.dispose();
map.dispose();
});
});
describe('#forEachFeatureAtPixel', function () {
let map;
beforeEach(function () {
const target = document.createElement('div');
target.style.width = '100px';
target.style.height = '100px';
document.body.appendChild(target);
map = new Map({
target: target,
view: new View({
center: [0, 0],
zoom: 2,
}),
});
});
afterEach(function () {
document.body.removeChild(map.getTargetElement());
map.setTarget(null);
});
it('calls callback with feature, layer and geometry', function () {
let hit;
const point = new Point([0, 0]);
const polygon = fromExtent([0, -1e6, 1e6, 1e6]);
const geometryCollection = new Feature(
new GeometryCollection([polygon, point])
);
const multiPoint = new MultiPoint([
[-1e6, -1e6],
[-1e6, 1e6],
]);
const multiGeometry = new Feature(multiPoint);
const layer = new VectorLayer({
source: new VectorSource({
features: [geometryCollection, multiGeometry],
}),
});
map.addLayer(layer);
map.renderSync();
hit = map.forEachFeatureAtPixel([50, 50], (feature, layer, geometry) => ({
feature,
layer,
geometry,
}));
expect(hit.feature).to.be(geometryCollection);
expect(hit.layer).to.be(layer);
expect(hit.geometry).to.be(point);
hit = map.forEachFeatureAtPixel([75, 50], (feature, layer, geometry) => ({
feature,
layer,
geometry,
}));
expect(hit.feature).to.be(geometryCollection);
expect(hit.geometry).to.be(polygon);
hit = map.forEachFeatureAtPixel([25, 25], (feature, layer, geometry) => ({
feature,
layer,
geometry,
}));
expect(hit.feature).to.be(multiGeometry);
expect(hit.geometry).to.be(multiPoint);
});
it('hits lines even if they are dashed', function () {
const geometry = new LineString([
[-1e6, 0],
[1e6, 0],
]);
const feature = new Feature(geometry);
const layer = new VectorLayer({
source: new VectorSource({
features: [feature],
}),
style: new Style({
stroke: new Stroke({
color: 'black',
width: 8,
lineDash: [10, 20],
}),
}),
});
map.addLayer(layer);
map.renderSync();
const hit = map.forEachFeatureAtPixel(
[50, 50],
(feature, layer, geometry) => ({
feature,
layer,
geometry,
})
);
expect(hit).to.be.ok();
expect(hit.feature).to.be(feature);
expect(hit.layer).to.be(layer);
expect(hit.geometry).to.be(geometry);
});
});
describe('#forEachFeatureAtCoordinate', function () {
let map, source, style;
beforeEach(function () {
const target = document.createElement('div');
target.style.width = '100px';
target.style.height = '100px';
document.body.appendChild(target);
const projection = new Projection({
code: 'EPSG:21781',
units: 'm',
});
source = new VectorSource({
projection: projection,
features: [new Feature(new Point([660000, 190000]))],
});
style = new Style({
image: new Circle({
radius: 6,
fill: new Fill({
color: 'fuchsia',
}),
}),
});
map = new Map({
target: target,
layers: [
new VectorLayer({
source: source,
renderBuffer: 12,
style: style,
}),
],
view: new View({
projection: projection,
center: [660000, 190000],
zoom: 9,
}),
});
});
afterEach(function () {
const target = map.getTargetElement();
map.setTarget(null);
document.body.removeChild(target);
});
it('works with custom projection', function () {
map.renderSync();
const features = map.getFeaturesAtPixel([50, 50]);
expect(features.length).to.be(1);
});
it('works with negative image scale', function () {
style.getImage().setScale([-1, -1]);
map.renderSync();
const features = map.getFeaturesAtPixel([50, 50]);
expect(features.length).to.be(1);
});
it('only draws features that intersect the hit detection viewport', function () {
const resolution = map.getView().getResolution();
source.addFeature(
new Feature(new Point([660000 + resolution * 6, 190000]))
);
source.addFeature(
new Feature(new Point([660000 - resolution * 12, 190000]))
);
map.renderSync();
const spy = sinon.spy(CanvasRenderingContext2D.prototype, 'drawImage');
const features = map.getFeaturesAtPixel([50, 44]);
expect(features.length).to.be(1);
expect(spy.callCount).to.be(2);
spy.restore();
});
});
});
@@ -0,0 +1,209 @@
import CanvasBuilderGroup from '../../../../../src/ol/render/canvas/BuilderGroup.js';
import Feature from '../../../../../src/ol/Feature.js';
import Fill from '../../../../../src/ol/style/Fill.js';
import Icon from '../../../../../src/ol/style/Icon.js';
import LineString from '../../../../../src/ol/geom/LineString.js';
import MultiLineString from '../../../../../src/ol/geom/MultiLineString.js';
import MultiPoint from '../../../../../src/ol/geom/MultiPoint.js';
import MultiPolygon from '../../../../../src/ol/geom/MultiPolygon.js';
import Point from '../../../../../src/ol/geom/Point.js';
import Polygon from '../../../../../src/ol/geom/Polygon.js';
import Stroke from '../../../../../src/ol/style/Stroke.js';
import Style from '../../../../../src/ol/style/Style.js';
import {VOID} from '../../../../../src/ol/functions.js';
import {renderFeature} from '../../../../../src/ol/renderer/vector.js';
describe('ol.renderer.vector', function () {
describe('#renderFeature', function () {
let builderGroup;
let feature, iconStyle, style, squaredTolerance, listener;
let iconStyleLoadSpy;
beforeEach(function () {
builderGroup = new CanvasBuilderGroup(1);
feature = new Feature();
iconStyle = new Icon({
src: 'http://example.com/icon.png',
});
style = new Style({
image: iconStyle,
fill: new Fill({}),
stroke: new Stroke({}),
});
squaredTolerance = 1;
listener = function () {};
iconStyleLoadSpy = sinon.stub(iconStyle, 'load').callsFake(function () {
iconStyle.iconImage_.imageState_ = 1; // LOADING
});
});
afterEach(function () {
iconStyleLoadSpy.restore();
});
describe('call multiple times', function () {
it('does not set multiple listeners', function () {
let listeners;
// call #1
renderFeature(builderGroup, feature, style, squaredTolerance, listener);
expect(iconStyleLoadSpy.calledOnce).to.be.ok();
listeners = iconStyle.iconImage_.listeners_['change'];
expect(listeners.length).to.eql(1);
// call #2
renderFeature(builderGroup, feature, style, squaredTolerance, listener);
expect(iconStyleLoadSpy.calledOnce).to.be.ok();
listeners = iconStyle.iconImage_.listeners_['change'];
expect(listeners.length).to.eql(1);
});
});
describe('call renderFeature with a loading icon', function () {
it('does not render the point', function () {
feature.setGeometry(new Point([0, 0]));
const imageReplay = builderGroup.getBuilder(style.getZIndex(), 'Image');
const setImageStyleSpy = sinon.spy(imageReplay, 'setImageStyle');
const drawPointSpy = sinon
.stub(imageReplay, 'drawPoint')
.callsFake(VOID);
renderFeature(builderGroup, feature, style, squaredTolerance, listener);
expect(setImageStyleSpy.called).to.be(false);
setImageStyleSpy.restore();
drawPointSpy.restore();
});
it('does not render the multipoint', function () {
feature.setGeometry(
new MultiPoint([
[0, 0],
[1, 1],
])
);
const imageReplay = builderGroup.getBuilder(style.getZIndex(), 'Image');
const setImageStyleSpy = sinon.spy(imageReplay, 'setImageStyle');
const drawMultiPointSpy = sinon
.stub(imageReplay, 'drawMultiPoint')
.callsFake(VOID);
renderFeature(builderGroup, feature, style, squaredTolerance, listener);
expect(setImageStyleSpy.called).to.be(false);
setImageStyleSpy.restore();
drawMultiPointSpy.restore();
});
it('does render the linestring', function () {
feature.setGeometry(
new LineString([
[0, 0],
[1, 1],
])
);
const lineStringReplay = builderGroup.getBuilder(
style.getZIndex(),
'LineString'
);
const setFillStrokeStyleSpy = sinon.spy(
lineStringReplay,
'setFillStrokeStyle'
);
const drawLineStringSpy = sinon
.stub(lineStringReplay, 'drawLineString')
.callsFake(VOID);
renderFeature(builderGroup, feature, style, squaredTolerance, listener);
expect(setFillStrokeStyleSpy.called).to.be(true);
expect(drawLineStringSpy.called).to.be(true);
setFillStrokeStyleSpy.restore();
drawLineStringSpy.restore();
});
it('does render the multilinestring', function () {
feature.setGeometry(
new MultiLineString([
[
[0, 0],
[1, 1],
],
])
);
const lineStringReplay = builderGroup.getBuilder(
style.getZIndex(),
'LineString'
);
const setFillStrokeStyleSpy = sinon.spy(
lineStringReplay,
'setFillStrokeStyle'
);
const drawMultiLineStringSpy = sinon
.stub(lineStringReplay, 'drawMultiLineString')
.callsFake(VOID);
renderFeature(builderGroup, feature, style, squaredTolerance, listener);
expect(setFillStrokeStyleSpy.called).to.be(true);
expect(drawMultiLineStringSpy.called).to.be(true);
setFillStrokeStyleSpy.restore();
drawMultiLineStringSpy.restore();
});
it('does render the polygon', function () {
feature.setGeometry(
new Polygon([
[
[0, 0],
[1, 1],
[1, 0],
[0, 0],
],
])
);
const polygonReplay = builderGroup.getBuilder(
style.getZIndex(),
'Polygon'
);
const setFillStrokeStyleSpy = sinon.spy(
polygonReplay,
'setFillStrokeStyle'
);
const drawPolygonSpy = sinon
.stub(polygonReplay, 'drawPolygon')
.callsFake(VOID);
renderFeature(builderGroup, feature, style, squaredTolerance, listener);
expect(setFillStrokeStyleSpy.called).to.be(true);
expect(drawPolygonSpy.called).to.be(true);
setFillStrokeStyleSpy.restore();
drawPolygonSpy.restore();
});
it('does render the multipolygon', function () {
feature.setGeometry(
new MultiPolygon([
[
[
[0, 0],
[1, 1],
[1, 0],
[0, 0],
],
],
])
);
const polygonReplay = builderGroup.getBuilder(
style.getZIndex(),
'Polygon'
);
const setFillStrokeStyleSpy = sinon.spy(
polygonReplay,
'setFillStrokeStyle'
);
const drawMultiPolygonSpy = sinon
.stub(polygonReplay, 'drawMultiPolygon')
.callsFake(VOID);
renderFeature(builderGroup, feature, style, squaredTolerance, listener);
expect(setFillStrokeStyleSpy.called).to.be(true);
expect(drawMultiPolygonSpy.called).to.be(true);
setFillStrokeStyleSpy.restore();
drawMultiPolygonSpy.restore();
});
});
});
});
@@ -0,0 +1,229 @@
import Layer from '../../../../../../src/ol/layer/Layer.js';
import WebGLLayerRenderer, {
colorDecodeId,
colorEncodeId,
getBlankImageData,
writePointFeatureToBuffers,
} from '../../../../../../src/ol/renderer/webgl/Layer.js';
describe('ol.renderer.webgl.Layer', function () {
describe('constructor', function () {
let target;
beforeEach(function () {
target = document.createElement('div');
target.style.width = '256px';
target.style.height = '256px';
document.body.appendChild(target);
});
afterEach(function () {
document.body.removeChild(target);
});
it('creates a new instance', function () {
const layer = new Layer({});
const renderer = new WebGLLayerRenderer(layer);
expect(renderer).to.be.a(WebGLLayerRenderer);
});
});
describe('writePointFeatureToBuffers', function () {
let vertexBuffer, indexBuffer, instructions;
beforeEach(function () {
vertexBuffer = new Float32Array(100);
indexBuffer = new Uint32Array(100);
instructions = new Float32Array(100);
instructions.set([0, 0, 0, 0, 10, 11]);
});
it('writes correctly to the buffers (without custom attributes)', function () {
const stride = 3;
const positions = writePointFeatureToBuffers(
instructions,
4,
vertexBuffer,
indexBuffer,
0
);
expect(vertexBuffer[0]).to.eql(10);
expect(vertexBuffer[1]).to.eql(11);
expect(vertexBuffer[2]).to.eql(0);
expect(vertexBuffer[stride + 0]).to.eql(10);
expect(vertexBuffer[stride + 1]).to.eql(11);
expect(vertexBuffer[stride + 2]).to.eql(1);
expect(vertexBuffer[stride * 2 + 0]).to.eql(10);
expect(vertexBuffer[stride * 2 + 1]).to.eql(11);
expect(vertexBuffer[stride * 2 + 2]).to.eql(2);
expect(vertexBuffer[stride * 3 + 0]).to.eql(10);
expect(vertexBuffer[stride * 3 + 1]).to.eql(11);
expect(vertexBuffer[stride * 3 + 2]).to.eql(3);
expect(indexBuffer[0]).to.eql(0);
expect(indexBuffer[1]).to.eql(1);
expect(indexBuffer[2]).to.eql(3);
expect(indexBuffer[3]).to.eql(1);
expect(indexBuffer[4]).to.eql(2);
expect(indexBuffer[5]).to.eql(3);
expect(positions.indexPosition).to.eql(6);
expect(positions.vertexPosition).to.eql(stride * 4);
});
it('writes correctly to the buffers (with 2 custom attributes)', function () {
instructions.set([0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13]);
const stride = 5;
const positions = writePointFeatureToBuffers(
instructions,
8,
vertexBuffer,
indexBuffer,
2
);
expect(vertexBuffer[0]).to.eql(10);
expect(vertexBuffer[1]).to.eql(11);
expect(vertexBuffer[2]).to.eql(0);
expect(vertexBuffer[3]).to.eql(12);
expect(vertexBuffer[4]).to.eql(13);
expect(vertexBuffer[stride + 0]).to.eql(10);
expect(vertexBuffer[stride + 1]).to.eql(11);
expect(vertexBuffer[stride + 2]).to.eql(1);
expect(vertexBuffer[stride + 3]).to.eql(12);
expect(vertexBuffer[stride + 4]).to.eql(13);
expect(vertexBuffer[stride * 2 + 0]).to.eql(10);
expect(vertexBuffer[stride * 2 + 1]).to.eql(11);
expect(vertexBuffer[stride * 2 + 2]).to.eql(2);
expect(vertexBuffer[stride * 2 + 3]).to.eql(12);
expect(vertexBuffer[stride * 2 + 4]).to.eql(13);
expect(vertexBuffer[stride * 3 + 0]).to.eql(10);
expect(vertexBuffer[stride * 3 + 1]).to.eql(11);
expect(vertexBuffer[stride * 3 + 2]).to.eql(3);
expect(vertexBuffer[stride * 3 + 3]).to.eql(12);
expect(vertexBuffer[stride * 3 + 4]).to.eql(13);
expect(indexBuffer[0]).to.eql(0);
expect(indexBuffer[1]).to.eql(1);
expect(indexBuffer[2]).to.eql(3);
expect(indexBuffer[3]).to.eql(1);
expect(indexBuffer[4]).to.eql(2);
expect(indexBuffer[5]).to.eql(3);
expect(positions.indexPosition).to.eql(6);
expect(positions.vertexPosition).to.eql(stride * 4);
});
it('correctly chains buffer writes', function () {
instructions.set([10, 11, 20, 21, 30, 31]);
const stride = 3;
let positions = writePointFeatureToBuffers(
instructions,
0,
vertexBuffer,
indexBuffer,
0
);
positions = writePointFeatureToBuffers(
instructions,
2,
vertexBuffer,
indexBuffer,
0,
positions
);
positions = writePointFeatureToBuffers(
instructions,
4,
vertexBuffer,
indexBuffer,
0,
positions
);
expect(vertexBuffer[0]).to.eql(10);
expect(vertexBuffer[1]).to.eql(11);
expect(vertexBuffer[2]).to.eql(0);
expect(vertexBuffer[stride * 4 + 0]).to.eql(20);
expect(vertexBuffer[stride * 4 + 1]).to.eql(21);
expect(vertexBuffer[stride * 4 + 2]).to.eql(0);
expect(vertexBuffer[stride * 8 + 0]).to.eql(30);
expect(vertexBuffer[stride * 8 + 1]).to.eql(31);
expect(vertexBuffer[stride * 8 + 2]).to.eql(0);
expect(indexBuffer[6 + 0]).to.eql(4);
expect(indexBuffer[6 + 1]).to.eql(5);
expect(indexBuffer[6 + 2]).to.eql(7);
expect(indexBuffer[6 + 3]).to.eql(5);
expect(indexBuffer[6 + 4]).to.eql(6);
expect(indexBuffer[6 + 5]).to.eql(7);
expect(indexBuffer[6 * 2 + 0]).to.eql(8);
expect(indexBuffer[6 * 2 + 1]).to.eql(9);
expect(indexBuffer[6 * 2 + 2]).to.eql(11);
expect(indexBuffer[6 * 2 + 3]).to.eql(9);
expect(indexBuffer[6 * 2 + 4]).to.eql(10);
expect(indexBuffer[6 * 2 + 5]).to.eql(11);
expect(positions.indexPosition).to.eql(6 * 3);
expect(positions.vertexPosition).to.eql(stride * 4 * 3);
});
});
describe('getBlankImageData', function () {
it('creates a 1x1 white texture', function () {
const texture = getBlankImageData();
expect(texture.height).to.eql(1);
expect(texture.width).to.eql(1);
expect(texture.data[0]).to.eql(255);
expect(texture.data[1]).to.eql(255);
expect(texture.data[2]).to.eql(255);
expect(texture.data[3]).to.eql(255);
});
});
describe('colorEncodeId and colorDecodeId', function () {
it('correctly encodes and decodes ids', function () {
expect(colorDecodeId(colorEncodeId(0))).to.eql(0);
expect(colorDecodeId(colorEncodeId(1))).to.eql(1);
expect(colorDecodeId(colorEncodeId(123))).to.eql(123);
expect(colorDecodeId(colorEncodeId(12345))).to.eql(12345);
expect(colorDecodeId(colorEncodeId(123456))).to.eql(123456);
expect(colorDecodeId(colorEncodeId(91612))).to.eql(91612);
expect(colorDecodeId(colorEncodeId(1234567890))).to.eql(1234567890);
});
it('correctly reuses array', function () {
const arr = [];
expect(colorEncodeId(123, arr)).to.be(arr);
});
it('is compatible with Uint8Array storage', function () {
const encoded = colorEncodeId(91612);
const typed = Uint8Array.of(
encoded[0] * 255,
encoded[1] * 255,
encoded[2] * 255,
encoded[3] * 255
);
const arr = [
typed[0] / 255,
typed[1] / 255,
typed[2] / 255,
typed[3] / 255,
];
const decoded = colorDecodeId(arr);
expect(decoded).to.eql(91612);
});
});
});
@@ -0,0 +1,679 @@
import Feature from '../../../../../../src/ol/Feature.js';
import GeoJSON from '../../../../../../src/ol/format/GeoJSON.js';
import Point from '../../../../../../src/ol/geom/Point.js';
import VectorLayer from '../../../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../../../src/ol/source/Vector.js';
import ViewHint from '../../../../../../src/ol/ViewHint.js';
import WebGLPointsLayer from '../../../../../../src/ol/layer/WebGLPoints.js';
import WebGLPointsLayerRenderer from '../../../../../../src/ol/renderer/webgl/PointsLayer.js';
import {WebGLWorkerMessageType} from '../../../../../../src/ol/renderer/webgl/Layer.js';
import {
compose as composeTransform,
create as createTransform,
} from '../../../../../../src/ol/transform.js';
import {get as getProjection} from '../../../../../../src/ol/proj.js';
import {getUid} from '../../../../../../src/ol/util.js';
const baseFrameState = {
viewHints: [],
viewState: {
projection: getProjection('EPSG:3857'),
resolution: 1,
rotation: 0,
center: [0, 0],
},
layerStatesArray: [{}],
layerIndex: 0,
pixelRatio: 1,
};
const simpleVertexShader = `
precision mediump float;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
attribute vec2 a_position;
attribute float a_index;
void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix;
float offsetX = a_index == 0.0 || a_index == 3.0 ? -2.0 : 2.0;
float offsetY = a_index == 0.0 || a_index == 1.0 ? -2.0 : 2.0;
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
}`;
const simpleFragmentShader = `
precision mediump float;
void main(void) {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}`;
// these shaders support hit detection
// they have a built-in size value of 4
const hitVertexShader = `
precision mediump float;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
attribute vec2 a_position;
attribute float a_index;
attribute vec4 a_hitColor;
varying vec4 v_hitColor;
void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix;
float offsetX = a_index == 0.0 || a_index == 3.0 ? -2.0 : 2.0;
float offsetY = a_index == 0.0 || a_index == 1.0 ? -2.0 : 2.0;
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
v_hitColor = a_hitColor;
}`;
const hitFragmentShader = `
precision mediump float;
varying vec4 v_hitColor;
void main(void) {
gl_FragColor = v_hitColor;
}`;
describe('ol.renderer.webgl.PointsLayer', function () {
describe('constructor', function () {
let target;
beforeEach(function () {
target = document.createElement('div');
target.style.width = '256px';
target.style.height = '256px';
document.body.appendChild(target);
});
afterEach(function () {
document.body.removeChild(target);
});
it('creates a new instance', function () {
const layer = new VectorLayer({
source: new VectorSource(),
});
const renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader,
});
expect(renderer).to.be.a(WebGLPointsLayerRenderer);
});
});
describe('#prepareFrame', function () {
let layer, renderer, frameState;
beforeEach(function () {
layer = new VectorLayer({
source: new VectorSource(),
renderBuffer: 10,
});
renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader,
hitVertexShader: hitVertexShader,
hitFragmentShader: hitFragmentShader,
});
frameState = Object.assign(
{
size: [2, 2],
extent: [-100, -100, 100, 100],
},
baseFrameState
);
});
it('calls WebGlHelper#prepareDraw', function () {
const spy = sinon.spy(renderer.helper, 'prepareDraw');
renderer.prepareFrame(frameState);
expect(spy.called).to.be(true);
});
it('fills up a buffer with 2 triangles per point', function (done) {
layer.getSource().addFeature(
new Feature({
geometry: new Point([10, 20]),
})
);
layer.getSource().addFeature(
new Feature({
geometry: new Point([30, 40]),
})
);
renderer.prepareFrame(frameState);
const attributePerVertex = 3;
renderer.worker_.addEventListener('message', function (event) {
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
return;
}
expect(renderer.verticesBuffer_.getArray().length).to.eql(
2 * 4 * attributePerVertex
);
expect(renderer.indicesBuffer_.getArray().length).to.eql(2 * 6);
expect(renderer.verticesBuffer_.getArray()[0]).to.eql(10);
expect(renderer.verticesBuffer_.getArray()[1]).to.eql(20);
expect(
renderer.verticesBuffer_.getArray()[4 * attributePerVertex + 0]
).to.eql(30);
expect(
renderer.verticesBuffer_.getArray()[4 * attributePerVertex + 1]
).to.eql(40);
done();
});
});
it('fills up the hit render buffer with 2 triangles per point', function (done) {
layer.getSource().addFeature(
new Feature({
geometry: new Point([10, 20]),
})
);
layer.getSource().addFeature(
new Feature({
geometry: new Point([30, 40]),
})
);
renderer.prepareFrame(frameState);
const attributePerVertex = 8;
renderer.worker_.addEventListener('message', function (event) {
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
return;
}
if (!renderer.hitVerticesBuffer_.getArray()) {
return;
}
expect(renderer.hitVerticesBuffer_.getArray().length).to.eql(
2 * 4 * attributePerVertex
);
expect(renderer.indicesBuffer_.getArray().length).to.eql(2 * 6);
expect(renderer.hitVerticesBuffer_.getArray()[0]).to.eql(10);
expect(renderer.hitVerticesBuffer_.getArray()[1]).to.eql(20);
expect(
renderer.hitVerticesBuffer_.getArray()[4 * attributePerVertex + 0]
).to.eql(30);
expect(
renderer.hitVerticesBuffer_.getArray()[4 * attributePerVertex + 1]
).to.eql(40);
done();
});
});
it('clears the buffers when the features are gone', function (done) {
const source = layer.getSource();
source.addFeature(
new Feature({
geometry: new Point([10, 20]),
})
);
source.removeFeature(source.getFeatures()[0]);
source.addFeature(
new Feature({
geometry: new Point([10, 20]),
})
);
renderer.prepareFrame(frameState);
renderer.worker_.addEventListener('message', function (event) {
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
return;
}
const attributePerVertex = 3;
expect(renderer.verticesBuffer_.getArray().length).to.eql(
4 * attributePerVertex
);
expect(renderer.indicesBuffer_.getArray().length).to.eql(6);
done();
});
});
it('rebuilds the buffers only when not interacting or animating', function () {
const spy = sinon.spy(renderer, 'rebuildBuffers_');
frameState.viewHints[ViewHint.INTERACTING] = 1;
frameState.viewHints[ViewHint.ANIMATING] = 0;
renderer.prepareFrame(frameState);
expect(spy.called).to.be(false);
frameState.viewHints[ViewHint.INTERACTING] = 0;
frameState.viewHints[ViewHint.ANIMATING] = 1;
renderer.prepareFrame(frameState);
expect(spy.called).to.be(false);
frameState.viewHints[ViewHint.INTERACTING] = 0;
frameState.viewHints[ViewHint.ANIMATING] = 0;
renderer.prepareFrame(frameState);
expect(spy.called).to.be(true);
});
it('rebuilds the buffers only when the frame extent changed', function () {
const spy = sinon.spy(renderer, 'rebuildBuffers_');
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(1);
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(1);
frameState.extent = [10, 20, 30, 40];
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(2);
});
it('triggers source loading when the extent changes', function () {
const spy = sinon.spy(layer.getSource(), 'loadFeatures');
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(1);
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(1);
frameState.extent = [10, 20, 30, 40];
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(2);
expect(spy.getCall(1).args[0]).to.eql([0, 10, 40, 50]); // renderBuffer is 10
});
it('triggers source loading when the source revision changes', function () {
const spy = sinon.spy(layer.getSource(), 'loadFeatures');
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(1);
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(1);
layer.getSource().changed();
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(2);
});
});
describe('#forEachFeatureAtCoordinate', function () {
let layer, renderer, feature, feature2;
beforeEach(function () {
feature = new Feature({geometry: new Point([0, 0]), id: 1});
feature2 = new Feature({geometry: new Point([14, 14]), id: 2});
layer = new VectorLayer({
source: new VectorSource({
features: [feature, feature2],
}),
});
renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader,
hitVertexShader: hitVertexShader,
hitFragmentShader: hitFragmentShader,
});
});
it('correctly hit detects a feature', function (done) {
const transform = composeTransform(
createTransform(),
20,
20,
1,
-1,
0,
0,
0
);
const frameState = Object.assign(
{
extent: [-20, -20, 20, 20],
size: [40, 40],
coordinateToPixelTransform: transform,
},
baseFrameState
);
let found;
const cb = function (feature) {
found = feature;
};
renderer.prepareFrame(frameState);
renderer.worker_.addEventListener('message', function () {
if (!renderer.hitRenderInstructions_) {
return;
}
renderer.prepareFrame(frameState);
renderer.renderFrame(frameState);
function checkHit(x, y, expected) {
found = null;
renderer.forEachFeatureAtCoordinate([x, y], frameState, 0, cb, null);
expect(found).to.be(expected);
}
checkHit(0, 0, feature);
checkHit(1, -1, feature);
checkHit(-2, 2, feature);
checkHit(2, 0, null);
checkHit(1, -3, null);
checkHit(14, 14, feature2);
checkHit(15, 13, feature2);
checkHit(12, 16, feature2);
checkHit(16, 14, null);
checkHit(13, 11, null);
done();
});
});
it('correctly hit detects with pixelratio != 1', function (done) {
const transform = composeTransform(
createTransform(),
20,
20,
1,
-1,
0,
0,
0
);
const frameState = Object.assign(
{
pixelRatio: 3,
extent: [-20, -20, 20, 20],
size: [40, 40],
coordinateToPixelTransform: transform,
},
baseFrameState
);
let found;
const cb = function (feature) {
found = feature;
};
renderer.prepareFrame(frameState);
renderer.worker_.addEventListener('message', function () {
if (!renderer.hitRenderInstructions_) {
return;
}
renderer.prepareFrame(frameState);
renderer.renderFrame(frameState);
function checkHit(x, y, expected) {
found = null;
renderer.forEachFeatureAtCoordinate([x, y], frameState, 0, cb, null);
expect(found).to.be(expected);
}
checkHit(0, 0, feature);
checkHit(1, -1, feature);
checkHit(-2, 2, feature);
checkHit(2, 0, null);
checkHit(1, -3, null);
checkHit(14, 14, feature2);
checkHit(15, 13, feature2);
checkHit(12, 16, feature2);
checkHit(16, 14, null);
checkHit(13, 11, null);
done();
});
});
});
describe('#disposeInternal', function () {
it('terminates the worker and calls dispose on the helper', function () {
const layer = new VectorLayer({
source: new VectorSource(),
});
const renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader,
});
const spyHelper = sinon.spy(renderer.helper, 'disposeInternal');
const spyWorker = sinon.spy(renderer.worker_, 'terminate');
renderer.disposeInternal();
expect(spyHelper.called).to.be(true);
expect(spyWorker.called).to.be(true);
});
});
describe('featureCache_', function () {
let source, layer, features;
function getCache(feature, renderer) {
return renderer.featureCache_[getUid(feature)];
}
beforeEach(function () {
source = new VectorSource();
layer = new VectorLayer({
source,
});
features = [
new Feature({
id: 'A',
test: 'abcd',
geometry: new Point([0, 1]),
}),
new Feature({
id: 'D',
test: 'efgh',
geometry: new Point([2, 3]),
}),
new Feature({
id: 'C',
test: 'ijkl',
geometry: new Point([4, 5]),
}),
];
});
it('contains no features initially', function () {
const renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader,
});
expect(renderer.featureCount_).to.be(0);
});
it('contains the features initially present in the source', function () {
source.addFeatures(features);
const renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader,
});
expect(renderer.featureCount_).to.be(3);
expect(getCache(features[0], renderer).feature).to.be(features[0]);
expect(getCache(features[0], renderer).geometry).to.be(
features[0].getGeometry()
);
expect(getCache(features[0], renderer).properties['test']).to.be(
features[0].get('test')
);
expect(getCache(features[1], renderer).feature).to.be(features[1]);
expect(getCache(features[1], renderer).geometry).to.be(
features[1].getGeometry()
);
expect(getCache(features[1], renderer).properties['test']).to.be(
features[1].get('test')
);
expect(getCache(features[2], renderer).feature).to.be(features[2]);
expect(getCache(features[2], renderer).geometry).to.be(
features[2].getGeometry()
);
expect(getCache(features[2], renderer).properties['test']).to.be(
features[2].get('test')
);
});
it('contains the features added to the source', function () {
const renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader,
});
source.addFeature(features[0]);
expect(renderer.featureCount_).to.be(1);
source.addFeature(features[1]);
expect(renderer.featureCount_).to.be(2);
expect(getCache(features[0], renderer).feature).to.be(features[0]);
expect(getCache(features[0], renderer).geometry).to.be(
features[0].getGeometry()
);
expect(getCache(features[0], renderer).properties['test']).to.be(
features[0].get('test')
);
expect(getCache(features[1], renderer).feature).to.be(features[1]);
expect(getCache(features[1], renderer).geometry).to.be(
features[1].getGeometry()
);
expect(getCache(features[1], renderer).properties['test']).to.be(
features[1].get('test')
);
});
it('does not contain the features removed to the source', function () {
const renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader,
});
source.addFeatures(features);
expect(renderer.featureCount_).to.be(3);
source.removeFeature(features[1]);
expect(renderer.featureCount_).to.be(2);
expect(getCache(features[0], renderer).feature).to.be(features[0]);
expect(getCache(features[0], renderer).geometry).to.be(
features[0].getGeometry()
);
expect(getCache(features[0], renderer).properties['test']).to.be(
features[0].get('test')
);
expect(getCache(features[2], renderer).feature).to.be(features[2]);
expect(getCache(features[2], renderer).geometry).to.be(
features[2].getGeometry()
);
expect(getCache(features[2], renderer).properties['test']).to.be(
features[2].get('test')
);
});
it('contains up to date properties and geometry', function () {
const renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader,
});
source.addFeatures(features);
features[0].set('test', 'updated');
features[0].set('added', true);
features[0].getGeometry().setCoordinates([10, 20]);
expect(renderer.featureCount_).to.be(3);
expect(getCache(features[0], renderer).feature).to.be(features[0]);
expect(getCache(features[0], renderer).geometry.getCoordinates()).to.eql([
10,
20,
]);
expect(getCache(features[0], renderer).properties['test']).to.be(
features[0].get('test')
);
expect(getCache(features[0], renderer).properties['added']).to.be(
features[0].get('added')
);
});
});
describe('fires events', () => {
let layer, source, renderer, frameState;
beforeEach(function () {
source = new VectorSource({
features: new GeoJSON().readFeatures({
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'Point',
'coordinates': [13, 52],
},
},
],
}),
});
layer = new WebGLPointsLayer({
source,
style: {
symbol: {
symbolType: 'square',
},
},
});
renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader,
});
frameState = {
viewHints: [],
viewState: {
projection: getProjection('EPSG:4326'),
resolution: 0.010986328125,
rotation: 0,
center: [15, 52],
zoom: 7,
},
extent: [
11.1932373046875,
46.429931640625,
18.8067626953125,
57.570068359375,
],
size: [693, 1014],
layerIndex: 0,
layerStatesArray: [
{
layer: layer,
opacity: 1,
visible: true,
zIndex: 0,
},
],
};
});
it('fires prerender and postrender events', function (done) {
let prerenderNotified = false;
let postrenderNotified = false;
layer.once('prerender', (evt) => {
prerenderNotified = true;
});
layer.once('postrender', (evt) => {
postrenderNotified = true;
expect(prerenderNotified).to.be(true);
expect(postrenderNotified).to.be(true);
done();
});
renderer.prepareFrame(frameState);
renderer.renderFrame(frameState);
});
});
});