Declutter in correct order and for all layers

This commit is contained in:
ahocevar
2019-05-05 12:20:38 +02:00
parent 6cfd0b70ed
commit 12289b8ef9
14 changed files with 105 additions and 96 deletions
+19 -11
View File
@@ -21,6 +21,7 @@ import {
import {createCanvasContext2D} from '../../dom.js';
import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
import Disposable from '../../Disposable.js';
import rbush from 'rbush';
/**
@@ -58,15 +59,10 @@ class Executor extends Disposable {
* @param {number} resolution Resolution.
* @param {number} pixelRatio Pixel ratio.
* @param {boolean} overlaps The replay can have overlapping geometries.
* @param {?} declutterTree Declutter tree.
* @param {SerializableInstructions} instructions The serializable instructions
*/
constructor(resolution, pixelRatio, overlaps, declutterTree, instructions) {
constructor(resolution, pixelRatio, overlaps, instructions) {
super();
/**
* @type {?}
*/
this.declutterTree = declutterTree;
/**
* @protected
@@ -93,6 +89,11 @@ class Executor extends Disposable {
*/
this.alignFill_;
/**
* @type {Array<*>}
*/
this.declutterItems = [];
/**
* @protected
* @type {Array<*>}
@@ -412,8 +413,10 @@ class Executor extends Disposable {
/**
* @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group.
* @param {import("../../Feature.js").FeatureLike} feature Feature.
* @param {?} declutterTree Declutter tree.
* @return {?} Declutter tree.
*/
renderDeclutter_(declutterGroup, feature) {
renderDeclutter(declutterGroup, feature, declutterTree) {
if (declutterGroup && declutterGroup.length > 5) {
const groupCount = declutterGroup[4];
if (groupCount == 1 || groupCount == declutterGroup.length - 5) {
@@ -425,8 +428,11 @@ class Executor extends Disposable {
maxY: /** @type {number} */ (declutterGroup[3]),
value: feature
};
if (!this.declutterTree.collides(box)) {
this.declutterTree.insert(box);
if (!declutterTree) {
declutterTree = rbush(9, undefined);
}
if (!declutterTree.collides(box)) {
declutterTree.insert(box);
for (let j = 5, jj = declutterGroup.length; j < jj; ++j) {
const declutterData = /** @type {Array} */ (declutterGroup[j]);
if (declutterData) {
@@ -443,6 +449,7 @@ class Executor extends Disposable {
createOrUpdateEmpty(declutterGroup);
}
}
return declutterTree;
}
/**
@@ -497,6 +504,7 @@ class Executor extends Disposable {
featureCallback,
opt_hitExtent
) {
this.declutterItems.length = 0;
/** @type {Array<number>} */
let pixelCoordinates;
if (this.pixelCoordinates_ && equals(transform, this.renderedTransform_)) {
@@ -670,7 +678,7 @@ class Executor extends Disposable {
backgroundFill ? /** @type {Array<*>} */ (lastFillInstruction) : null,
backgroundStroke ? /** @type {Array<*>} */ (lastStrokeInstruction) : null);
}
this.renderDeclutter_(declutterGroup, feature);
this.declutterItems.push(this, declutterGroup, feature);
++i;
break;
case CanvasInstruction.DRAW_CHARS:
@@ -739,7 +747,7 @@ class Executor extends Disposable {
}
}
}
this.renderDeclutter_(declutterGroup, feature);
this.declutterItems.push(this, declutterGroup, feature);
++i;
break;
case CanvasInstruction.END_GEOMETRY:
+15 -31
View File
@@ -35,18 +35,12 @@ class ExecutorGroup extends Disposable {
* @param {number} resolution Resolution.
* @param {number} pixelRatio Pixel ratio.
* @param {boolean} overlaps The executor group can have overlapping geometries.
* @param {?} declutterTree Declutter tree for declutter processing in postrender.
* @param {!Object<string, !Object<BuilderType, import("./Builder.js").SerializableInstructions>>} allInstructions
* The serializable instructions.
* @param {number=} opt_renderBuffer Optional rendering buffer.
*/
constructor(maxExtent, resolution, pixelRatio, overlaps, declutterTree, allInstructions, opt_renderBuffer) {
constructor(maxExtent, resolution, pixelRatio, overlaps, allInstructions, opt_renderBuffer) {
super();
/**
* Declutter tree.
* @private
*/
this.declutterTree_ = declutterTree;
/**
* @private
@@ -128,7 +122,7 @@ class ExecutorGroup extends Disposable {
for (const builderType in instructionByZindex) {
const instructions = instructionByZindex[builderType];
executors[builderType] = new Executor(
this.resolution_, this.pixelRatio_, this.overlaps_, this.declutterTree_, instructions);
this.resolution_, this.pixelRatio_, this.overlaps_, instructions);
}
}
}
@@ -172,7 +166,7 @@ class ExecutorGroup extends Disposable {
* @param {number} hitTolerance Hit tolerance in pixels.
* @param {Object<string, boolean>} skippedFeaturesHash Ids of features to skip.
* @param {function(import("../../Feature.js").FeatureLike): T} callback Feature callback.
* @param {Object<string, import("../canvas.js").DeclutterGroup>} declutterReplays Declutter replays.
* @param {Array<import("../../Feature.js").FeatureLike>} declutteredFeatures Decluttered features.
* @return {T|undefined} Callback result.
* @template T
*/
@@ -183,7 +177,7 @@ class ExecutorGroup extends Disposable {
hitTolerance,
skippedFeaturesHash,
callback,
declutterReplays
declutteredFeatures
) {
hitTolerance = Math.round(hitTolerance);
@@ -213,12 +207,6 @@ class ExecutorGroup extends Disposable {
}
const mask = getCircleArray(hitTolerance);
let declutteredFeatures;
if (this.declutterTree_) {
declutteredFeatures = this.declutterTree_.all().map(function(entry) {
return entry.value;
});
}
let builderType;
@@ -261,20 +249,10 @@ class ExecutorGroup extends Disposable {
builderType = ORDER[j];
executor = executors[builderType];
if (executor !== undefined) {
if (declutterReplays &&
(builderType == BuilderType.IMAGE || builderType == BuilderType.TEXT)) {
const declutter = declutterReplays[zIndexKey];
if (!declutter) {
declutterReplays[zIndexKey] = [executor, transform.slice(0)];
} else {
declutter.push(executor, transform.slice(0));
}
} else {
result = executor.executeHitDetection(context, transform, rotation,
skippedFeaturesHash, featureCallback, hitExtent);
if (result) {
return result;
}
result = executor.executeHitDetection(context, transform, rotation,
skippedFeaturesHash, featureCallback, hitExtent);
if (result) {
return result;
}
}
}
@@ -446,14 +424,20 @@ export function getCircleArray(radius) {
* @param {CanvasRenderingContext2D} context Context.
* @param {number} rotation Rotation.
* @param {boolean} snapToPixel Snap point symbols and text to integer pixels.
* @param {Array<Array<*>>} declutterItems Declutter items.
*/
export function replayDeclutter(declutterReplays, context, rotation, snapToPixel) {
export function replayDeclutter(declutterReplays, context, rotation, snapToPixel, declutterItems) {
const zs = Object.keys(declutterReplays).map(Number).sort(numberSafeCompareFunction);
const skippedFeatureUids = {};
for (let z = 0, zz = zs.length; z < zz; ++z) {
const executorData = declutterReplays[zs[z].toString()];
let currentExecutor;
for (let i = 0, ii = executorData.length; i < ii;) {
const executor = executorData[i++];
if (executor !== currentExecutor) {
currentExecutor = executor;
declutterItems.push(executor.declutterItems);
}
const transform = executorData[i++];
executor.execute(context, transform, rotation, skippedFeatureUids, snapToPixel);
}