Organize tests
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user