Skip to content

Commit

Permalink
Fix decluttering opacity, zIndex and circles
Browse files Browse the repository at this point in the history
  • Loading branch information
ahocevar committed Mar 5, 2024
1 parent c1f0569 commit 67cff23
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 28 deletions.
5 changes: 3 additions & 2 deletions src/ol/render/canvas/ExecutorGroup.js
Expand Up @@ -426,8 +426,9 @@ class ExecutorGroup {

renderDeferred() {
const deferredZIndexContexts = this.deferredZIndexContexts_;
for (const key in deferredZIndexContexts) {
deferredZIndexContexts[key].forEach((zIndexContext) => {
const zs = Object.keys(deferredZIndexContexts).sort(ascending);
for (let i = 0, ii = zs.length; i < ii; ++i) {
deferredZIndexContexts[zs[i]].forEach((zIndexContext) => {
zIndexContext.draw(this.renderedContext_); // FIXME Pass clip to replay for temporarily enabling clip
zIndexContext.clear();
});
Expand Down
53 changes: 29 additions & 24 deletions src/ol/renderer/canvas/VectorLayer.js
Expand Up @@ -153,7 +153,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
* @private
* @type {CanvasRenderingContext2D}
*/
this.compositionContext_ = null;
this.targetContext_ = null;

/**
* @private
Expand Down Expand Up @@ -183,7 +183,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const snapToPixel = !(
viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]
);
const context = this.compositionContext_;
const context = this.context;
const width = Math.round(frameState.size[0] * pixelRatio);
const height = Math.round(frameState.size[1] * pixelRatio);

Expand Down Expand Up @@ -223,28 +223,33 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
} while (++world < endWorld);
}

setupCompositionContext_() {
/**
* @private
*/
setContext_() {
if (this.opacity_ !== 1) {
const compositionContext = createCanvasContext2D(
this.targetContext_ = this.context;
this.context = createCanvasContext2D(
this.context.canvas.width,
this.context.canvas.height,
canvasPool,
);
this.compositionContext_ = compositionContext;
} else {
this.compositionContext_ = this.context;
}
}

releaseCompositionContext_() {
/**
* @private
*/
resetContext_() {
if (this.opacity_ !== 1) {
const alpha = this.context.globalAlpha;
this.context.globalAlpha = this.opacity_;
this.context.drawImage(this.compositionContext_.canvas, 0, 0);
this.context.globalAlpha = alpha;
releaseCanvas(this.compositionContext_);
canvasPool.push(this.compositionContext_.canvas);
this.compositionContext_ = null;
const alpha = this.targetContext_.globalAlpha;
this.targetContext_.globalAlpha = this.opacity_;
this.targetContext_.drawImage(this.context.canvas, 0, 0);
this.targetContext_.globalAlpha = alpha;
releaseCanvas(this.context);
canvasPool.push(this.context.canvas);
this.context = this.targetContext_;
this.targetContext_ = null;
}
}

Expand All @@ -257,9 +262,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
if (!declutter) {
return;
}
this.setupCompositionContext_(); //FIXME Check if this works, or if we need to defer something.
this.renderWorlds(this.replayGroup_, frameState, true);
this.releaseCompositionContext_();
}

/**
Expand All @@ -268,6 +271,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
*/
renderDeferredInternal(frameState) {
this.replayGroup_.renderDeferred();
this.resetContext_();
}

/**
Expand All @@ -279,6 +283,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
renderFrame(frameState, target) {
const pixelRatio = frameState.pixelRatio;
const layerState = frameState.layerStatesArray[frameState.layerIndex];
this.opacity_ = layerState.opacity;

// set forward and inverse pixel transforms
makeScale(this.pixelTransform, 1 / pixelRatio, 1 / pixelRatio);
Expand All @@ -287,6 +292,8 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const canvasTransform = transformToString(this.pixelTransform);

this.useContainer(target, canvasTransform, this.getBackground(frameState));
this.setContext_();

const context = this.context;
const canvas = context.canvas;

Expand Down Expand Up @@ -319,17 +326,14 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const viewState = frameState.viewState;
const projection = viewState.projection;

this.opacity_ = layerState.opacity;
this.setupCompositionContext_();

// clipped rendering if layer extent is set
let clipped = false;
if (render && layerState.extent && this.clipping) {
const layerExtent = fromUserExtent(layerState.extent, projection);
render = intersectsExtent(layerExtent, frameState.extent);
clipped = render && !containsExtent(layerExtent, frameState.extent);
if (clipped) {
this.clipUnrotated(this.compositionContext_, frameState, layerExtent);
this.clipUnrotated(context, frameState, layerExtent);
}
}

Expand All @@ -342,17 +346,18 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
}

if (clipped) {
this.compositionContext_.restore();
context.restore();
}

this.releaseCompositionContext_();

this.postRender(context, frameState);

if (this.renderedRotation_ !== viewState.rotation) {
this.renderedRotation_ = viewState.rotation;
this.hitDetectionImageData_ = null;
}
if (!frameState.declutter) {
this.resetContext_();
}
return this.container;
}

Expand Down
5 changes: 3 additions & 2 deletions src/ol/renderer/vector.js
Expand Up @@ -70,14 +70,15 @@ export function getTolerance(resolution, pixelRatio) {
* @param {import("../geom/Circle.js").default} geometry Geometry.
* @param {import("../style/Style.js").default} style Style.
* @param {import("../Feature.js").default} feature Feature.
* @param {number} [index] Render order index.
*/
function renderCircleGeometry(builderGroup, geometry, style, feature) {
function renderCircleGeometry(builderGroup, geometry, style, feature, index) {
const fillStyle = style.getFill();
const strokeStyle = style.getStroke();
if (fillStyle || strokeStyle) {
const circleReplay = builderGroup.getBuilder(style.getZIndex(), 'Circle');
circleReplay.setFillStrokeStyle(fillStyle, strokeStyle);
circleReplay.drawCircle(geometry, feature);
circleReplay.drawCircle(geometry, feature, index);
}
const textStyle = style.getText();
if (textStyle && textStyle.getText()) {
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
131 changes: 131 additions & 0 deletions test/rendering/cases/layer-vector-declutter/main.js
@@ -0,0 +1,131 @@
import Circle from '../../../../src/ol/geom/Circle.js';
import CircleStyle from '../../../../src/ol/style/Circle.js';
import Feature from '../../../../src/ol/Feature.js';
import LineString from '../../../../src/ol/geom/LineString.js';
import Map from '../../../../src/ol/Map.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 VectorLayer from '../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../src/ol/source/Vector.js';
import View from '../../../../src/ol/View.js';
import {Fill} from '../../../../src/ol/style.js';

const center = [1825927.7316762917, 6143091.089223046];

const source1 = new VectorSource();
const source2 = new VectorSource();
const vectorLayer1 = new VectorLayer({
declutter: true,
source: source1,
style: new Style({
stroke: new Stroke({
color: '#3399CC',
width: 1.25,
}),
}),
});
const vectorLayer2 = new VectorLayer({
declutter: true,
source: source2,
opacity: 0.6,
style: new Style({
image: new CircleStyle({
radius: 20,
fill: new Fill({color: 'orange'}),
}),
}),
});

function addCircle(r, source) {
source.addFeature(new Feature(new Circle(center, r)));
}

function addPolygon(r, source) {
source.addFeature(
new Feature(
new Polygon([
[
[center[0] - r, center[1] - r],
[center[0] + r, center[1] - r],
[center[0] + r, center[1] + r],
[center[0] - r, center[1] + r],
[center[0] - r, center[1] - r],
],
]),
),
);
}

const smallLine = new Feature(
new LineString([
[center[0], center[1] - 1],
[center[0], center[1] + 1],
]),
);
smallLine.setStyle(
new Style({
zIndex: -99,
stroke: new Stroke({width: 75, color: 'red'}),
}),
);
smallLine.getGeometry().translate(-1000, 1000);
source1.addFeature(smallLine);
addPolygon(100, source1);
addCircle(200, source1);
addPolygon(250, source1);
addCircle(500, source1);
addPolygon(600, source1);
addPolygon(720, source1);

const smallLine2 = new Feature(
new LineString([
[center[0], center[1] - 1000],
[center[0], center[1] + 1000],
]),
);
smallLine2.setStyle([
new Style({
stroke: new Stroke({width: 35, color: 'blue'}),
}),
new Style({
stroke: new Stroke({width: 15, color: 'green'}),
}),
]);
smallLine2.getGeometry().translate(1000, 1000);
source1.addFeature(smallLine2);

const smallLine3 = new Feature(
new LineString([
[center[0], center[1] - 1],
[center[0], center[1] + 1],
]),
);
smallLine3.setStyle([
new Style({
stroke: new Stroke({width: 75, color: 'red'}),
}),
new Style({
stroke: new Stroke({width: 45, color: 'white'}),
}),
]);
smallLine3.getGeometry().translate(-1000, -1000);

addPolygon(400, source2);
addCircle(400, source2);
source2.addFeature(smallLine3);
source2.addFeature(new Feature(new Point(center)));

const map = new Map({
layers: [vectorLayer1, vectorLayer2],
target: 'map',
view: new View({
center: center,
zoom: 13,
}),
});

map.getView().setRotation(Math.PI + Math.PI / 4);

render();

0 comments on commit 67cff23

Please sign in to comment.