Merge pull request #11148 from ahocevar/vectortile-label-flicker

Vectortile label flicker
This commit is contained in:
Andreas Hocevar
2020-06-13 08:28:22 +02:00
committed by GitHub
4 changed files with 150 additions and 113 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -7,7 +7,6 @@ import ImageBuilder from './ImageBuilder.js';
import LineStringBuilder from './LineStringBuilder.js';
import PolygonBuilder from './PolygonBuilder.js';
import TextBuilder from './TextBuilder.js';
import {createEmpty} from '../../extent.js';
/**
* @type {Object<import("./BuilderType").default, typeof Builder>}
@@ -78,15 +77,15 @@ class BuilderGroup {
* @return {import("../canvas").DeclutterGroups} The resulting instruction groups.
*/
addDeclutter(group) {
/** @type {Array<*>} */
let declutter = null;
if (this.declutter_) {
if (group) {
declutter = this.declutterGroups_;
/** @type {number} */ (declutter[0][4])++;
/** @type {number} */ (declutter[0][0])++;
} else {
declutter = [createEmpty()];
declutter = [[1]];
this.declutterGroups_ = declutter;
declutter[0].push(1);
}
}
return declutter;

View File

@@ -14,8 +14,8 @@ import {
import {
createEmpty,
createOrUpdate,
createOrUpdateEmpty,
extend,
getHeight,
getWidth,
intersects,
} from '../../extent.js';
import {
@@ -69,8 +69,9 @@ class Executor {
* @param {number} pixelRatio Pixel ratio.
* @param {boolean} overlaps The replay can have overlapping geometries.
* @param {SerializableInstructions} instructions The serializable instructions
* @param {import("../../size.js").Size} renderBuffer Render buffer (width/height) in pixels.
*/
constructor(resolution, pixelRatio, overlaps, instructions) {
constructor(resolution, pixelRatio, overlaps, instructions, renderBuffer) {
/**
* @protected
* @type {boolean}
@@ -119,6 +120,12 @@ class Executor {
*/
this.coordinateCache_ = {};
/**
* @private
* @type {import("../../size.js").Size}
*/
this.renderBuffer_ = renderBuffer;
/**
* @private
* @type {!import("../../transform.js").Transform}
@@ -316,6 +323,7 @@ class Executor {
* @param {Array<number>} padding Padding.
* @param {Array<*>} fillInstruction Fill instruction.
* @param {Array<*>} strokeInstruction Stroke instruction.
* @return {boolean} The image or label was rendered.
*/
replayImageOrLabel_(
context,
@@ -397,15 +405,26 @@ class Executor {
} else {
createOrUpdate(boxX, boxY, boxX + boxW, boxY + boxH, tmpExtent);
}
this.renderBuffer_[0] = Math.max(
this.renderBuffer_[0],
getWidth(tmpExtent)
);
this.renderBuffer_[1] = Math.max(
this.renderBuffer_[1],
getHeight(tmpExtent)
);
const canvas = context.canvas;
const strokePadding = strokeInstruction
? (strokeInstruction[2] * scale) / 2
: 0;
const renderBuffer = this.renderBuffer_;
const intersects =
tmpExtent[0] - strokePadding <= canvas.width / contextScale &&
tmpExtent[2] + strokePadding >= 0 &&
tmpExtent[1] - strokePadding <= canvas.height / contextScale &&
tmpExtent[3] + strokePadding >= 0;
tmpExtent[0] - strokePadding <=
(canvas.width + renderBuffer[0]) / contextScale &&
tmpExtent[2] + strokePadding >= -renderBuffer[0] / contextScale &&
tmpExtent[1] - strokePadding <=
(canvas.height + renderBuffer[1]) / contextScale &&
tmpExtent[3] + strokePadding >= -renderBuffer[1] / contextScale;
if (snapToPixel) {
x = Math.round(x);
@@ -413,10 +432,9 @@ class Executor {
}
if (declutterGroup) {
if (!intersects && declutterGroup[4] == 1) {
return;
if (!intersects && declutterGroup[0] == 1) {
return false;
}
extend(declutterGroup, tmpExtent);
const declutterArgs = intersects
? [
context,
@@ -430,6 +448,7 @@ class Executor {
x,
y,
scale,
tmpExtent.slice(),
]
: null;
if (declutterArgs) {
@@ -472,6 +491,7 @@ class Executor {
scale
);
}
return true;
}
/**
@@ -517,51 +537,57 @@ class Executor {
* @return {?} Declutter tree.
*/
renderDeclutter(declutterGroup, feature, opacity, declutterTree) {
if (declutterGroup && declutterGroup.length > 5) {
const groupCount = declutterGroup[4];
if (groupCount == 1 || groupCount == declutterGroup.length - 5) {
/** @type {import("../../structs/RBush.js").Entry} */
const box = {
minX: /** @type {number} */ (declutterGroup[0]),
minY: /** @type {number} */ (declutterGroup[1]),
maxX: /** @type {number} */ (declutterGroup[2]),
maxY: /** @type {number} */ (declutterGroup[3]),
value: feature,
};
if (!declutterTree) {
declutterTree = new RBush(9);
}
if (!declutterTree.collides(box)) {
declutterTree.insert(box);
for (let j = 5, jj = declutterGroup.length; j < jj; ++j) {
const declutterData = /** @type {Array} */ (declutterGroup[j]);
const context = declutterData[0];
const currentAlpha = context.globalAlpha;
if (currentAlpha !== opacity) {
context.globalAlpha = opacity;
}
if (declutterData.length > 11) {
this.replayTextBackground_(
declutterData[0],
declutterData[13],
declutterData[14],
declutterData[15],
declutterData[16],
declutterData[11],
declutterData[12],
true
);
}
drawImageOrLabel.apply(undefined, declutterData);
if (currentAlpha !== opacity) {
context.globalAlpha = currentAlpha;
}
}
}
declutterGroup.length = 5;
createOrUpdateEmpty(declutterGroup);
/** @type {Array<import("../../structs/RBush.js").Entry>} */
const boxes = [];
for (let i = 1, ii = declutterGroup.length; i < ii; ++i) {
const declutterData = declutterGroup[i];
const box = declutterData[11];
boxes.push({
minX: box[0],
minY: box[1],
maxX: box[2],
maxY: box[3],
value: feature,
});
}
if (!declutterTree) {
declutterTree = new RBush(9);
}
let collides = false;
for (let i = 0, ii = boxes.length; i < ii; ++i) {
if (declutterTree.collides(boxes[i])) {
collides = true;
break;
}
}
if (!collides) {
declutterTree.load(boxes);
for (let j = 1, jj = declutterGroup.length; j < jj; ++j) {
const declutterData = /** @type {Array} */ (declutterGroup[j]);
const context = declutterData[0];
const currentAlpha = context.globalAlpha;
if (currentAlpha !== opacity) {
context.globalAlpha = opacity;
}
if (declutterData.length > 12) {
this.replayTextBackground_(
declutterData[0],
declutterData[14],
declutterData[15],
declutterData[16],
declutterData[17],
declutterData[12],
declutterData[13],
true
);
}
drawImageOrLabel.apply(undefined, declutterData);
if (currentAlpha !== opacity) {
context.globalAlpha = currentAlpha;
}
}
}
declutterGroup.length = 1;
return declutterTree;
}
@@ -828,14 +854,12 @@ class Executor {
}
if (declutterGroups) {
const index = Math.floor(declutterGroupIndex);
if (declutterGroups.length < index + 1) {
declutterGroup = createEmpty();
declutterGroup.push(declutterGroups[0][4]);
declutterGroups.push(declutterGroup);
}
declutterGroup = declutterGroups[index];
declutterGroup =
declutterGroups.length < index + 1
? [declutterGroups[0][0]]
: declutterGroups[index];
}
this.replayImageOrLabel_(
const rendered = this.replayImageOrLabel_(
context,
contextScale,
pixelCoordinates[d],
@@ -860,11 +884,18 @@ class Executor {
? /** @type {Array<*>} */ (lastStrokeInstruction)
: null
);
if (
rendered &&
declutterGroup &&
declutterGroups[declutterGroups.length - 1] !== declutterGroup
) {
declutterGroups.push(declutterGroup);
}
if (declutterGroup) {
if (declutterGroupIndex === Math.floor(declutterGroupIndex)) {
if (declutterGroup.length - 1 === declutterGroup[0]) {
this.declutterItems.push(this, declutterGroup, feature);
}
declutterGroupIndex += 1 / declutterGroup[4];
declutterGroupIndex += 1 / declutterGroup[0];
}
}
++i;
@@ -917,6 +948,7 @@ class Executor {
cachedWidths
);
if (parts) {
let rendered = false;
let c, cc, chars, label, part;
if (strokeKey) {
for (c = 0, cc = parts.length; c < cc; ++c) {
@@ -928,27 +960,28 @@ class Executor {
baseline * label.height +
(0.5 - baseline) * 2 * strokeWidth -
offsetY;
this.replayImageOrLabel_(
context,
contextScale,
/** @type {number} */ (part[0]),
/** @type {number} */ (part[1]),
label,
anchorX,
anchorY,
declutterGroup,
label.height,
1,
0,
0,
/** @type {number} */ (part[3]),
pixelRatioScale,
false,
label.width,
defaultPadding,
null,
null
);
rendered =
this.replayImageOrLabel_(
context,
contextScale,
/** @type {number} */ (part[0]),
/** @type {number} */ (part[1]),
label,
anchorX,
anchorY,
declutterGroup,
label.height,
1,
0,
0,
/** @type {number} */ (part[3]),
pixelRatioScale,
false,
label.width,
defaultPadding,
null,
null
) || rendered;
}
}
if (fillKey) {
@@ -958,32 +991,35 @@ class Executor {
label = this.createLabel(chars, textKey, fillKey, '');
anchorX = /** @type {number} */ (part[2]);
anchorY = baseline * label.height - offsetY;
this.replayImageOrLabel_(
context,
contextScale,
/** @type {number} */ (part[0]),
/** @type {number} */ (part[1]),
label,
anchorX,
anchorY,
declutterGroup,
label.height,
1,
0,
0,
/** @type {number} */ (part[3]),
pixelRatioScale,
false,
label.width,
defaultPadding,
null,
null
);
rendered =
this.replayImageOrLabel_(
context,
contextScale,
/** @type {number} */ (part[0]),
/** @type {number} */ (part[1]),
label,
anchorX,
anchorY,
declutterGroup,
label.height,
1,
0,
0,
/** @type {number} */ (part[3]),
pixelRatioScale,
false,
label.width,
defaultPadding,
null,
null
) || rendered;
}
}
if (rendered) {
this.declutterItems.push(this, declutterGroup, feature);
}
}
}
this.declutterItems.push(this, declutterGroup, feature);
++i;
break;
case CanvasInstruction.END_GEOMETRY:

View File

@@ -126,13 +126,15 @@ class ExecutorGroup {
this.executorsByZIndex_[zIndex] = executors;
}
const instructionByZindex = allInstructions[zIndex];
const renderBuffer = [this.renderBuffer_ || 0, this.renderBuffer_ || 0];
for (const builderType in instructionByZindex) {
const instructions = instructionByZindex[builderType];
executors[builderType] = new Executor(
this.resolution_,
this.pixelRatio_,
this.overlaps_,
instructions
instructions,
renderBuffer
);
}
}
@@ -476,15 +478,15 @@ export function replayDeclutter(
let currentExecutor;
for (let i = 0, ii = executorData.length; i < ii; ) {
const executor = executorData[i++];
if (executor !== currentExecutor) {
const transform = executorData[i++];
executor.execute(context, 1, transform, rotation, snapToPixel);
if (executor !== currentExecutor && executor.declutterItems.length > 0) {
currentExecutor = executor;
declutterItems.push({
items: executor.declutterItems,
opacity: opacity,
});
}
const transform = executorData[i++];
executor.execute(context, 1, transform, rotation, snapToPixel);
}
}
}