Merge pull request #12442 from MoonE/fix-layer-extent

Fix layer render extent when it is not at all in view
This commit is contained in:
MoonE
2021-07-11 20:04:45 +02:00
committed by GitHub
5 changed files with 162 additions and 48 deletions

View File

@@ -6,7 +6,7 @@ import ViewHint from '../../ViewHint.js';
import {ENABLE_RASTER_REPROJECTION} from '../../reproj/common.js';
import {assign} from '../../obj.js';
import {compose as composeTransform, makeInverse} from '../../transform.js';
import {containsExtent, intersects} from '../../extent.js';
import {containsExtent, intersects as intersectsExtent} from '../../extent.js';
import {fromUserExtent} from '../../proj.js';
import {getIntersection, isEmpty} from '../../extent.js';
import {toString as toTransformString} from '../../transform.js';
@@ -148,14 +148,14 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
// clipped rendering if layer extent is set
let clipped = false;
let render = true;
if (layerState.extent) {
const layerExtent = fromUserExtent(
layerState.extent,
viewState.projection
);
clipped =
!containsExtent(layerExtent, frameState.extent) &&
intersects(layerExtent, frameState.extent);
render = intersectsExtent(layerExtent, frameState.extent);
clipped = render && !containsExtent(layerExtent, frameState.extent);
if (clipped) {
this.clipUnrotated(context, frameState, layerExtent);
}
@@ -176,21 +176,21 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
this.renderedResolution = (imageResolution * pixelRatio) / imagePixelRatio;
const dx = transform[4];
const dy = transform[5];
const dw = img.width * transform[0];
const dh = img.height * transform[3];
assign(context, this.getLayer().getSource().getContextOptions());
this.preRender(context, frameState);
if (dw >= 0.5 && dh >= 0.5) {
if (render && dw >= 0.5 && dh >= 0.5) {
const dx = transform[4];
const dy = transform[5];
const opacity = layerState.opacity;
let previousAlpha;
if (opacity !== 1) {
previousAlpha = this.context.globalAlpha;
this.context.globalAlpha = opacity;
previousAlpha = context.globalAlpha;
context.globalAlpha = opacity;
}
this.context.drawImage(
context.drawImage(
img,
0,
0,
@@ -202,7 +202,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
Math.round(dh)
);
if (opacity !== 1) {
this.context.globalAlpha = previousAlpha;
context.globalAlpha = previousAlpha;
}
}
this.postRender(context, frameState);

View File

@@ -17,7 +17,6 @@ import {
getTopRight,
} from '../../extent.js';
import {createCanvasContext2D} from '../../dom.js';
import {rotateAtOffset} from '../../render/canvas.js';
/**
* @abstract
@@ -130,38 +129,6 @@ class CanvasLayerRenderer extends LayerRenderer {
}
}
/**
* @param {CanvasRenderingContext2D} context Context.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @param {import("../../extent.js").Extent} extent Clip extent.
* @protected
*/
clip(context, frameState, extent) {
const pixelRatio = frameState.pixelRatio;
const halfWidth = (frameState.size[0] * pixelRatio) / 2;
const halfHeight = (frameState.size[1] * pixelRatio) / 2;
const rotation = frameState.viewState.rotation;
const topLeft = getTopLeft(extent);
const topRight = getTopRight(extent);
const bottomRight = getBottomRight(extent);
const bottomLeft = getBottomLeft(extent);
applyTransform(frameState.coordinateToPixelTransform, topLeft);
applyTransform(frameState.coordinateToPixelTransform, topRight);
applyTransform(frameState.coordinateToPixelTransform, bottomRight);
applyTransform(frameState.coordinateToPixelTransform, bottomLeft);
context.save();
rotateAtOffset(context, -rotation, halfWidth, halfHeight);
context.beginPath();
context.moveTo(topLeft[0] * pixelRatio, topLeft[1] * pixelRatio);
context.lineTo(topRight[0] * pixelRatio, topRight[1] * pixelRatio);
context.lineTo(bottomRight[0] * pixelRatio, bottomRight[1] * pixelRatio);
context.lineTo(bottomLeft[0] * pixelRatio, bottomLeft[1] * pixelRatio);
context.clip();
rotateAtOffset(context, rotation, halfWidth, halfHeight);
}
/**
* @param {CanvasRenderingContext2D} context Context.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.

View File

@@ -280,17 +280,19 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
// clipped rendering if layer extent is set
let clipped = false;
let render = true;
if (layerState.extent && this.clipping) {
const layerExtent = fromUserExtent(layerState.extent, projection);
clipped =
!containsExtent(layerExtent, frameState.extent) &&
intersectsExtent(layerExtent, frameState.extent);
render = intersectsExtent(layerExtent, frameState.extent);
clipped = render && !containsExtent(layerExtent, frameState.extent);
if (clipped) {
this.clipUnrotated(context, frameState, layerExtent);
}
}
this.renderWorlds(replayGroup, frameState);
if (render) {
this.renderWorlds(replayGroup, frameState);
}
if (clipped) {
context.restore();

View File

@@ -1,3 +1,4 @@
import CanvasImageLayerRenderer from '../../../../../../src/ol/renderer/canvas/ImageLayer.js';
import Feature from '../../../../../../src/ol/Feature.js';
import ImageLayer from '../../../../../../src/ol/layer/Image.js';
import Map from '../../../../../../src/ol/Map.js';
@@ -348,4 +349,90 @@ describe('ol.renderer.canvas.ImageLayer', function () {
});
});
});
describe('renderFrame', function () {
const projection = new Projection({
code: 'custom-image',
units: 'pixels',
extent: [0, 0, 256, 256],
});
let renderer, layer;
function createLayerFrameState(extent) {
layer = new ImageLayer({
source: new Static({
url: 'spec/ol/data/osm-0-0-0.png',
imageExtent: projection.getExtent(),
projection: projection,
}),
extent: extent,
});
layer.getSource().image_.load();
renderer = layer.getRenderer();
renderer.renderWorlds = sinon.spy();
renderer.clipUnrotated = sinon.spy();
renderer.useContainer = function () {
CanvasImageLayerRenderer.prototype.useContainer.apply(this, arguments);
this.context = sinon.spy(this.context);
};
return {
pixelRatio: 1,
time: 1000000000000,
viewState: {
center: [0, 0],
projection: projection,
resolution: 1,
rotation: 0,
},
animate: false,
coordinateToPixelTransform: [1, 0, 0, 1, 0, 0],
extent: [0, 0, 100, 100],
index: 0,
layerStatesArray: [layer.getLayerState()],
layerIndex: 0,
pixelToCoordinateTransform: [1, 0, 0, 1, 0, 0],
size: [100, 100],
viewHints: [],
};
}
it('does not render if layer extent does not intersect view extent', function (done) {
const frameState = createLayerFrameState([200, 200, 300, 300]);
layer.getSource().on('imageloadend', function () {
try {
expect(renderer.prepareFrame(frameState)).to.be(false);
done();
} catch (e) {
done(e);
}
});
});
it('renders if layer extent partially intersects view extent', function (done) {
const frameState = createLayerFrameState([50, 50, 150, 150]);
layer.getSource().on('imageloadend', function () {
if (renderer.prepareFrame(frameState)) {
renderer.renderFrame(frameState, null);
}
try {
expect(renderer.clipUnrotated.callCount).to.be(1);
expect(renderer.context.drawImage.callCount).to.be(1);
done();
} catch (e) {
done(e);
}
});
});
it('renders without clipping when layer extent covers view', function (done) {
const frameState = createLayerFrameState([0, 0, 100, 100]);
layer.getSource().on('imageloadend', function () {
if (renderer.prepareFrame(frameState)) {
renderer.renderFrame(frameState, null);
}
try {
expect(renderer.clipUnrotated.callCount).to.be(0);
expect(renderer.context.drawImage.callCount).to.be(1);
done();
} catch (e) {
done(e);
}
});
});
});
});

View File

@@ -699,4 +699,62 @@ describe('ol.renderer.canvas.VectorLayer', function () {
}
});
});
describe('renderFrame', function () {
const projection = getProjection('EPSG:3857');
let renderer;
function createLayerFrameState(extent) {
const layer = new VectorLayer({
source: new VectorSource({
features: [new Feature(new Point(getCenter(extent)))],
}),
extent: extent,
});
renderer = layer.getRenderer();
renderer.renderWorlds = sinon.spy();
renderer.clipUnrotated = sinon.spy();
return {
pixelRatio: 1,
time: 1000000000000,
viewState: {
center: [0, 0],
projection: projection,
resolution: 1,
rotation: 0,
},
animate: false,
coordinateToPixelTransform: [1, 0, 0, 1, 0, 0],
extent: [-50, -50, 50, 50],
index: 0,
layerStatesArray: [layer.getLayerState()],
layerIndex: 0,
pixelToCoordinateTransform: [1, 0, 0, 1, 0, 0],
size: [100, 100],
viewHints: [],
};
}
it('does not render if layer extent does not intersect view extent', function () {
const frameState = createLayerFrameState([100, 100, 200, 200]);
if (renderer.prepareFrame(frameState)) {
renderer.renderFrame(frameState, null);
}
expect(renderer.renderWorlds.callCount).to.be(0);
expect(renderer.clipUnrotated.callCount).to.be(0);
});
it('renders if layer extent partially intersects view extent', function () {
const frameState = createLayerFrameState([0, 0, 100, 100]);
if (renderer.prepareFrame(frameState)) {
renderer.renderFrame(frameState, null);
}
expect(renderer.renderWorlds.callCount).to.be(1);
expect(renderer.clipUnrotated.callCount).to.be(1);
});
it('renders withoutt clipping when layer extent covers view', function () {
const frameState = createLayerFrameState([-200, -200, 200, 200]);
if (renderer.prepareFrame(frameState)) {
renderer.renderFrame(frameState, null);
}
expect(renderer.renderWorlds.callCount).to.be(1);
expect(renderer.clipUnrotated.callCount).to.be(0);
});
});
});