Declutter in correct order and for all layers
This commit is contained in:
@@ -98,6 +98,14 @@ If you were previously using `VectorTile` layers with `renderMode: 'vector'`, yo
|
|||||||
|
|
||||||
If you were previously using `Vector` layers with `renderMode: 'image'`, you have to remove this configuration option. Instead, use the new `ol/layer/VectorImage` layer with your `ol/source/Vector`.
|
If you were previously using `Vector` layers with `renderMode: 'image'`, you have to remove this configuration option. Instead, use the new `ol/layer/VectorImage` layer with your `ol/source/Vector`.
|
||||||
|
|
||||||
|
##### New declutter behavior
|
||||||
|
|
||||||
|
If a map has more than one layer with `declutter` set to true, decluttering now considers all layers instead of decluttering each layer separately. The higher the z-index of the layer, the higher the priority of decluttered items.
|
||||||
|
|
||||||
|
Within a layer, the declutter order has changed. Previously, styles with a lower `zIndex` were prioritized over those with a higher `zIndex`. Now the opposite order is used.
|
||||||
|
|
||||||
|
On vector layers, even if decluttered images or texts have a lower z-Index than polygons or lines, they will now be rendered on top of the polygons or lines. For vector tile layers, this was the case already in previous releases.
|
||||||
|
|
||||||
##### New `prerender` and `postrender` layer events replace old `precompose`, `render` and `postcompose` events
|
##### New `prerender` and `postrender` layer events replace old `precompose`, `render` and `postcompose` events
|
||||||
|
|
||||||
If you were previously registering for `precompose` and `postcompose` events, you should now register for `prerender` and `postrender` events on layers. Instead of the previous `render` event, you should now listen for `postrender`. Layers are no longer composed to a single Canvas element. Instead, they are added to the map viewport as individual elements.
|
If you were previously registering for `precompose` and `postcompose` events, you should now register for `prerender` and `postrender` events on layers. Instead of the previous `render` event, you should now listen for `postrender`. Layers are no longer composed to a single Canvas element. Instead, they are added to the map viewport as individual elements.
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import {create as createTransform, apply as applyTransform} from './transform.js
|
|||||||
* @property {boolean} animate
|
* @property {boolean} animate
|
||||||
* @property {import("./transform.js").Transform} coordinateToPixelTransform
|
* @property {import("./transform.js").Transform} coordinateToPixelTransform
|
||||||
* @property {null|import("./extent.js").Extent} extent
|
* @property {null|import("./extent.js").Extent} extent
|
||||||
|
* @property {Array<*>} declutterItems
|
||||||
* @property {import("./coordinate.js").Coordinate} focus
|
* @property {import("./coordinate.js").Coordinate} focus
|
||||||
* @property {number} index
|
* @property {number} index
|
||||||
* @property {Array<import("./layer/Layer.js").State>} layerStatesArray
|
* @property {Array<import("./layer/Layer.js").State>} layerStatesArray
|
||||||
@@ -1175,6 +1176,7 @@ class PluggableMap extends BaseObject {
|
|||||||
frameState = /** @type {FrameState} */ ({
|
frameState = /** @type {FrameState} */ ({
|
||||||
animate: false,
|
animate: false,
|
||||||
coordinateToPixelTransform: this.coordinateToPixelTransform_,
|
coordinateToPixelTransform: this.coordinateToPixelTransform_,
|
||||||
|
declutterItems: previousFrameState ? previousFrameState.declutterItems : [],
|
||||||
extent: extent,
|
extent: extent,
|
||||||
focus: this.focus_ ? this.focus_ : viewState.center,
|
focus: this.focus_ ? this.focus_ : viewState.center,
|
||||||
index: this.frameIndex_++,
|
index: this.frameIndex_++,
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ import {createDefaultStyle, toFunction as toStyleFunction} from '../style/Style.
|
|||||||
* temporary layers. The standard way to add a layer to a map and have it managed by the map is to
|
* temporary layers. The standard way to add a layer to a map and have it managed by the map is to
|
||||||
* use {@link module:ol/Map#addLayer}.
|
* use {@link module:ol/Map#addLayer}.
|
||||||
* @property {boolean} [declutter=false] Declutter images and text. Decluttering is applied to all
|
* @property {boolean} [declutter=false] Declutter images and text. Decluttering is applied to all
|
||||||
* image and text styles, and the priority is defined by the z-index of the style. Lower z-index
|
* image and text styles of all layers that have set this to `true`. The priority is defined by the z-index
|
||||||
* means higher priority.
|
* of the layer, the `zIndex` of the style and the render order of features. Higher z-index means higher
|
||||||
|
* priority. Within the same z-index, a feature rendered before another has higher priority.
|
||||||
* @property {import("../style/Style.js").StyleLike} [style] Layer style. See
|
* @property {import("../style/Style.js").StyleLike} [style] Layer style. See
|
||||||
* {@link module:ol/style} for default style which will be used if this is not defined.
|
* {@link module:ol/style} for default style which will be used if this is not defined.
|
||||||
* @property {boolean} [updateWhileAnimating=false] When set to `true`, feature batches will
|
* @property {boolean} [updateWhileAnimating=false] When set to `true`, feature batches will
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ import {assign} from '../obj.js';
|
|||||||
* temporary layers. The standard way to add a layer to a map and have it managed by the map is to
|
* temporary layers. The standard way to add a layer to a map and have it managed by the map is to
|
||||||
* use {@link module:ol/Map#addLayer}.
|
* use {@link module:ol/Map#addLayer}.
|
||||||
* @property {boolean} [declutter=false] Declutter images and text. Decluttering is applied to all
|
* @property {boolean} [declutter=false] Declutter images and text. Decluttering is applied to all
|
||||||
* image and text styles, and the priority is defined by the z-index of the style. Lower z-index
|
* image and text styles of all layers that have set this to `true`. The priority is defined by the z-index
|
||||||
* means higher priority.
|
* of the layer, the `zIndex` of the style and the render order of features. Higher z-index means higher
|
||||||
|
* priority. Within the same z-index, a feature rendered before another has higher priority.
|
||||||
* @property {import("../style/Style.js").StyleLike} [style] Layer style. See
|
* @property {import("../style/Style.js").StyleLike} [style] Layer style. See
|
||||||
* {@link module:ol/style} for default style which will be used if this is not defined.
|
* {@link module:ol/style} for default style which will be used if this is not defined.
|
||||||
* @property {boolean} [updateWhileAnimating=false] When set to `true`, feature batches will be
|
* @property {boolean} [updateWhileAnimating=false] When set to `true`, feature batches will be
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
import {createCanvasContext2D} from '../../dom.js';
|
import {createCanvasContext2D} from '../../dom.js';
|
||||||
import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
|
import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
|
||||||
import Disposable from '../../Disposable.js';
|
import Disposable from '../../Disposable.js';
|
||||||
|
import rbush from 'rbush';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,15 +59,10 @@ class Executor extends Disposable {
|
|||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {boolean} overlaps The replay can have overlapping geometries.
|
* @param {boolean} overlaps The replay can have overlapping geometries.
|
||||||
* @param {?} declutterTree Declutter tree.
|
|
||||||
* @param {SerializableInstructions} instructions The serializable instructions
|
* @param {SerializableInstructions} instructions The serializable instructions
|
||||||
*/
|
*/
|
||||||
constructor(resolution, pixelRatio, overlaps, declutterTree, instructions) {
|
constructor(resolution, pixelRatio, overlaps, instructions) {
|
||||||
super();
|
super();
|
||||||
/**
|
|
||||||
* @type {?}
|
|
||||||
*/
|
|
||||||
this.declutterTree = declutterTree;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
@@ -93,6 +89,11 @@ class Executor extends Disposable {
|
|||||||
*/
|
*/
|
||||||
this.alignFill_;
|
this.alignFill_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<*>}
|
||||||
|
*/
|
||||||
|
this.declutterItems = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
* @type {Array<*>}
|
* @type {Array<*>}
|
||||||
@@ -412,8 +413,10 @@ class Executor extends Disposable {
|
|||||||
/**
|
/**
|
||||||
* @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group.
|
* @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group.
|
||||||
* @param {import("../../Feature.js").FeatureLike} feature Feature.
|
* @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) {
|
if (declutterGroup && declutterGroup.length > 5) {
|
||||||
const groupCount = declutterGroup[4];
|
const groupCount = declutterGroup[4];
|
||||||
if (groupCount == 1 || groupCount == declutterGroup.length - 5) {
|
if (groupCount == 1 || groupCount == declutterGroup.length - 5) {
|
||||||
@@ -425,8 +428,11 @@ class Executor extends Disposable {
|
|||||||
maxY: /** @type {number} */ (declutterGroup[3]),
|
maxY: /** @type {number} */ (declutterGroup[3]),
|
||||||
value: feature
|
value: feature
|
||||||
};
|
};
|
||||||
if (!this.declutterTree.collides(box)) {
|
if (!declutterTree) {
|
||||||
this.declutterTree.insert(box);
|
declutterTree = rbush(9, undefined);
|
||||||
|
}
|
||||||
|
if (!declutterTree.collides(box)) {
|
||||||
|
declutterTree.insert(box);
|
||||||
for (let j = 5, jj = declutterGroup.length; j < jj; ++j) {
|
for (let j = 5, jj = declutterGroup.length; j < jj; ++j) {
|
||||||
const declutterData = /** @type {Array} */ (declutterGroup[j]);
|
const declutterData = /** @type {Array} */ (declutterGroup[j]);
|
||||||
if (declutterData) {
|
if (declutterData) {
|
||||||
@@ -443,6 +449,7 @@ class Executor extends Disposable {
|
|||||||
createOrUpdateEmpty(declutterGroup);
|
createOrUpdateEmpty(declutterGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return declutterTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -497,6 +504,7 @@ class Executor extends Disposable {
|
|||||||
featureCallback,
|
featureCallback,
|
||||||
opt_hitExtent
|
opt_hitExtent
|
||||||
) {
|
) {
|
||||||
|
this.declutterItems.length = 0;
|
||||||
/** @type {Array<number>} */
|
/** @type {Array<number>} */
|
||||||
let pixelCoordinates;
|
let pixelCoordinates;
|
||||||
if (this.pixelCoordinates_ && equals(transform, this.renderedTransform_)) {
|
if (this.pixelCoordinates_ && equals(transform, this.renderedTransform_)) {
|
||||||
@@ -670,7 +678,7 @@ class Executor extends Disposable {
|
|||||||
backgroundFill ? /** @type {Array<*>} */ (lastFillInstruction) : null,
|
backgroundFill ? /** @type {Array<*>} */ (lastFillInstruction) : null,
|
||||||
backgroundStroke ? /** @type {Array<*>} */ (lastStrokeInstruction) : null);
|
backgroundStroke ? /** @type {Array<*>} */ (lastStrokeInstruction) : null);
|
||||||
}
|
}
|
||||||
this.renderDeclutter_(declutterGroup, feature);
|
this.declutterItems.push(this, declutterGroup, feature);
|
||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
case CanvasInstruction.DRAW_CHARS:
|
case CanvasInstruction.DRAW_CHARS:
|
||||||
@@ -739,7 +747,7 @@ class Executor extends Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.renderDeclutter_(declutterGroup, feature);
|
this.declutterItems.push(this, declutterGroup, feature);
|
||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
case CanvasInstruction.END_GEOMETRY:
|
case CanvasInstruction.END_GEOMETRY:
|
||||||
|
|||||||
@@ -35,18 +35,12 @@ class ExecutorGroup extends Disposable {
|
|||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {boolean} overlaps The executor group can have overlapping geometries.
|
* @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
|
* @param {!Object<string, !Object<BuilderType, import("./Builder.js").SerializableInstructions>>} allInstructions
|
||||||
* The serializable instructions.
|
* The serializable instructions.
|
||||||
* @param {number=} opt_renderBuffer Optional rendering buffer.
|
* @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();
|
super();
|
||||||
/**
|
|
||||||
* Declutter tree.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this.declutterTree_ = declutterTree;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -128,7 +122,7 @@ class ExecutorGroup extends Disposable {
|
|||||||
for (const builderType in instructionByZindex) {
|
for (const builderType in instructionByZindex) {
|
||||||
const instructions = instructionByZindex[builderType];
|
const instructions = instructionByZindex[builderType];
|
||||||
executors[builderType] = new Executor(
|
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 {number} hitTolerance Hit tolerance in pixels.
|
||||||
* @param {Object<string, boolean>} skippedFeaturesHash Ids of features to skip.
|
* @param {Object<string, boolean>} skippedFeaturesHash Ids of features to skip.
|
||||||
* @param {function(import("../../Feature.js").FeatureLike): T} callback Feature callback.
|
* @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.
|
* @return {T|undefined} Callback result.
|
||||||
* @template T
|
* @template T
|
||||||
*/
|
*/
|
||||||
@@ -183,7 +177,7 @@ class ExecutorGroup extends Disposable {
|
|||||||
hitTolerance,
|
hitTolerance,
|
||||||
skippedFeaturesHash,
|
skippedFeaturesHash,
|
||||||
callback,
|
callback,
|
||||||
declutterReplays
|
declutteredFeatures
|
||||||
) {
|
) {
|
||||||
|
|
||||||
hitTolerance = Math.round(hitTolerance);
|
hitTolerance = Math.round(hitTolerance);
|
||||||
@@ -213,12 +207,6 @@ class ExecutorGroup extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mask = getCircleArray(hitTolerance);
|
const mask = getCircleArray(hitTolerance);
|
||||||
let declutteredFeatures;
|
|
||||||
if (this.declutterTree_) {
|
|
||||||
declutteredFeatures = this.declutterTree_.all().map(function(entry) {
|
|
||||||
return entry.value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let builderType;
|
let builderType;
|
||||||
|
|
||||||
@@ -261,15 +249,6 @@ class ExecutorGroup extends Disposable {
|
|||||||
builderType = ORDER[j];
|
builderType = ORDER[j];
|
||||||
executor = executors[builderType];
|
executor = executors[builderType];
|
||||||
if (executor !== undefined) {
|
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,
|
result = executor.executeHitDetection(context, transform, rotation,
|
||||||
skippedFeaturesHash, featureCallback, hitExtent);
|
skippedFeaturesHash, featureCallback, hitExtent);
|
||||||
if (result) {
|
if (result) {
|
||||||
@@ -278,7 +257,6 @@ class ExecutorGroup extends Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,14 +424,20 @@ export function getCircleArray(radius) {
|
|||||||
* @param {CanvasRenderingContext2D} context Context.
|
* @param {CanvasRenderingContext2D} context Context.
|
||||||
* @param {number} rotation Rotation.
|
* @param {number} rotation Rotation.
|
||||||
* @param {boolean} snapToPixel Snap point symbols and text to integer pixels.
|
* @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 zs = Object.keys(declutterReplays).map(Number).sort(numberSafeCompareFunction);
|
||||||
const skippedFeatureUids = {};
|
const skippedFeatureUids = {};
|
||||||
for (let z = 0, zz = zs.length; z < zz; ++z) {
|
for (let z = 0, zz = zs.length; z < zz; ++z) {
|
||||||
const executorData = declutterReplays[zs[z].toString()];
|
const executorData = declutterReplays[zs[z].toString()];
|
||||||
|
let currentExecutor;
|
||||||
for (let i = 0, ii = executorData.length; i < ii;) {
|
for (let i = 0, ii = executorData.length; i < ii;) {
|
||||||
const executor = executorData[i++];
|
const executor = executorData[i++];
|
||||||
|
if (executor !== currentExecutor) {
|
||||||
|
currentExecutor = executor;
|
||||||
|
declutterItems.push(executor.declutterItems);
|
||||||
|
}
|
||||||
const transform = executorData[i++];
|
const transform = executorData[i++];
|
||||||
executor.execute(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
executor.execute(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ class CompositeMapRenderer extends MapRenderer {
|
|||||||
this.children_.push(element);
|
this.children_.push(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
super.renderFrame(frameState);
|
||||||
|
|
||||||
replaceChildren(this.element_, this.children_);
|
replaceChildren(this.element_, this.children_);
|
||||||
|
|
||||||
|
|||||||
@@ -89,10 +89,11 @@ class LayerRenderer extends Observable {
|
|||||||
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
|
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
* @param {number} hitTolerance Hit tolerance in pixels.
|
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||||
* @param {function(import("../Feature.js").FeatureLike, import("../layer/Layer.js").default): T} callback Feature callback.
|
* @param {function(import("../Feature.js").FeatureLike, import("../layer/Layer.js").default): T} callback Feature callback.
|
||||||
|
* @param {Array<import("../Feature.js").FeatureLike>} declutteredFeatures Decluttered features.
|
||||||
* @return {T|void} Callback result.
|
* @return {T|void} Callback result.
|
||||||
* @template T
|
* @template T
|
||||||
*/
|
*/
|
||||||
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback) {}
|
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract
|
* @abstract
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ class MapRenderer extends Disposable {
|
|||||||
*/
|
*/
|
||||||
this.map_ = map;
|
this.map_ = map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.declutterTree_ = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {!Object<string, import("./Layer.js").default>}
|
* @type {!Object<string, import("./Layer.js").default>}
|
||||||
@@ -133,6 +138,12 @@ class MapRenderer extends Disposable {
|
|||||||
|
|
||||||
const layerStates = frameState.layerStatesArray;
|
const layerStates = frameState.layerStatesArray;
|
||||||
const numLayers = layerStates.length;
|
const numLayers = layerStates.length;
|
||||||
|
let declutteredFeatures;
|
||||||
|
if (this.declutterTree_) {
|
||||||
|
declutteredFeatures = this.declutterTree_.all().map(function(entry) {
|
||||||
|
return entry.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
let i;
|
let i;
|
||||||
for (i = numLayers - 1; i >= 0; --i) {
|
for (i = numLayers - 1; i >= 0; --i) {
|
||||||
const layerState = layerStates[i];
|
const layerState = layerStates[i];
|
||||||
@@ -144,7 +155,7 @@ class MapRenderer extends Disposable {
|
|||||||
const callback = forEachFeatureAtCoordinate.bind(null, layerState.managed);
|
const callback = forEachFeatureAtCoordinate.bind(null, layerState.managed);
|
||||||
result = layerRenderer.forEachFeatureAtCoordinate(
|
result = layerRenderer.forEachFeatureAtCoordinate(
|
||||||
source.getWrapX() ? translatedCoordinate : coordinate,
|
source.getWrapX() ? translatedCoordinate : coordinate,
|
||||||
frameState, hitTolerance, callback);
|
frameState, hitTolerance, callback, declutteredFeatures);
|
||||||
}
|
}
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
@@ -252,11 +263,20 @@ class MapRenderer extends Disposable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Render.
|
* Render.
|
||||||
* @abstract
|
|
||||||
* @param {?import("../PluggableMap.js").FrameState} frameState Frame state.
|
* @param {?import("../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
*/
|
*/
|
||||||
renderFrame(frameState) {
|
renderFrame(frameState) {
|
||||||
abstract();
|
if (this.declutterTree_) {
|
||||||
|
this.declutterTree_.clear();
|
||||||
|
}
|
||||||
|
const items = frameState.declutterItems;
|
||||||
|
for (let z = items.length - 1; z >= 0; --z) {
|
||||||
|
const zIndexItems = items[z];
|
||||||
|
for (let i = 0, ii = zIndexItems.length; i < ii; i += 3) {
|
||||||
|
this.declutterTree_ = zIndexItems[i].renderDeclutter(zIndexItems[i + 1], zIndexItems[i + 2], this.declutterTree_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -123,11 +123,11 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback) {
|
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures) {
|
||||||
if (this.vectorRenderer_) {
|
if (this.vectorRenderer_) {
|
||||||
return this.vectorRenderer_.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback);
|
return this.vectorRenderer_.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures);
|
||||||
} else {
|
} else {
|
||||||
return super.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback);
|
return super.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ import {getUid} from '../../util.js';
|
|||||||
import ViewHint from '../../ViewHint.js';
|
import ViewHint from '../../ViewHint.js';
|
||||||
import {listen, unlisten} from '../../events.js';
|
import {listen, unlisten} from '../../events.js';
|
||||||
import EventType from '../../events/EventType.js';
|
import EventType from '../../events/EventType.js';
|
||||||
import rbush from 'rbush';
|
|
||||||
import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js';
|
import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js';
|
||||||
import {labelCache} from '../../render/canvas.js';
|
import {labelCache} from '../../render/canvas.js';
|
||||||
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
|
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
|
||||||
import ExecutorGroup from '../../render/canvas/ExecutorGroup.js';
|
import ExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
||||||
import CanvasLayerRenderer from './Layer.js';
|
import CanvasLayerRenderer from './Layer.js';
|
||||||
import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
|
import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
|
||||||
import {toString as transformToString, makeScale, makeInverse} from '../../transform.js';
|
import {toString as transformToString, makeScale, makeInverse} from '../../transform.js';
|
||||||
@@ -28,12 +27,6 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
super(vectorLayer);
|
super(vectorLayer);
|
||||||
|
|
||||||
/**
|
|
||||||
* Declutter tree.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this.declutterTree_ = vectorLayer.getDeclutter() ? rbush(9, undefined) : null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -138,17 +131,14 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
this.clip(context, frameState, clipExtent);
|
this.clip(context, frameState, clipExtent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.declutterTree_) {
|
|
||||||
this.declutterTree_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const viewHints = frameState.viewHints;
|
const viewHints = frameState.viewHints;
|
||||||
const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
||||||
|
|
||||||
const transform = this.getRenderTransform(frameState, width, height, 0);
|
const transform = this.getRenderTransform(frameState, width, height, 0);
|
||||||
const skippedFeatureUids = layerState.managed ? frameState.skippedFeatureUids : {};
|
const skippedFeatureUids = layerState.managed ? frameState.skippedFeatureUids : {};
|
||||||
replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
const declutterReplays = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer()).getDeclutter() ? {} : null;
|
||||||
|
replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel, undefined, declutterReplays);
|
||||||
|
|
||||||
if (vectorSource.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) {
|
if (vectorSource.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) {
|
||||||
let startX = extent[0];
|
let startX = extent[0];
|
||||||
@@ -159,7 +149,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
--world;
|
--world;
|
||||||
offsetX = worldWidth * world;
|
offsetX = worldWidth * world;
|
||||||
const transform = this.getRenderTransform(frameState, width, height, offsetX);
|
const transform = this.getRenderTransform(frameState, width, height, offsetX);
|
||||||
replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel, undefined, declutterReplays);
|
||||||
startX += worldWidth;
|
startX += worldWidth;
|
||||||
}
|
}
|
||||||
world = 0;
|
world = 0;
|
||||||
@@ -168,10 +158,15 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
++world;
|
++world;
|
||||||
offsetX = worldWidth * world;
|
offsetX = worldWidth * world;
|
||||||
const transform = this.getRenderTransform(frameState, width, height, offsetX);
|
const transform = this.getRenderTransform(frameState, width, height, offsetX);
|
||||||
replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel);
|
replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel, undefined, declutterReplays);
|
||||||
startX -= worldWidth;
|
startX -= worldWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (declutterReplays) {
|
||||||
|
const viewHints = frameState.viewHints;
|
||||||
|
const hifi = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
||||||
|
replayDeclutter(declutterReplays, context, rotation, hifi, frameState.declutterItems);
|
||||||
|
}
|
||||||
|
|
||||||
if (clipped) {
|
if (clipped) {
|
||||||
context.restore();
|
context.restore();
|
||||||
@@ -190,7 +185,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, thisArg) {
|
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures) {
|
||||||
if (!this.replayGroup_) {
|
if (!this.replayGroup_) {
|
||||||
return undefined;
|
return undefined;
|
||||||
} else {
|
} else {
|
||||||
@@ -208,9 +203,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const key = getUid(feature);
|
const key = getUid(feature);
|
||||||
if (!(key in features)) {
|
if (!(key in features)) {
|
||||||
features[key] = true;
|
features[key] = true;
|
||||||
return callback.call(thisArg, feature, layer);
|
return callback(feature, layer);
|
||||||
}
|
}
|
||||||
}, null);
|
}, declutteredFeatures);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,7 +294,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
const replayGroup = new CanvasBuilderGroup(
|
const replayGroup = new CanvasBuilderGroup(
|
||||||
getRenderTolerance(resolution, pixelRatio), extent, resolution,
|
getRenderTolerance(resolution, pixelRatio), extent, resolution,
|
||||||
pixelRatio, !!this.declutterTree_);
|
pixelRatio, vectorLayer.getDeclutter());
|
||||||
|
|
||||||
vectorSource.loadFeatures(extent, resolution, projection);
|
vectorSource.loadFeatures(extent, resolution, projection);
|
||||||
|
|
||||||
@@ -339,7 +334,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
const replayGroupInstructions = replayGroup.finish();
|
const replayGroupInstructions = replayGroup.finish();
|
||||||
const executorGroup = new ExecutorGroup(extent, resolution,
|
const executorGroup = new ExecutorGroup(extent, resolution,
|
||||||
pixelRatio, vectorSource.getOverlaps(), this.declutterTree_,
|
pixelRatio, vectorSource.getOverlaps(),
|
||||||
replayGroupInstructions, vectorLayer.getRenderBuffer());
|
replayGroupInstructions, vectorLayer.getRenderBuffer());
|
||||||
|
|
||||||
this.renderedResolution_ = resolution;
|
this.renderedResolution_ = resolution;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import TileState from '../../TileState.js';
|
|||||||
import ViewHint from '../../ViewHint.js';
|
import ViewHint from '../../ViewHint.js';
|
||||||
import {listen, unlisten, unlistenByKey} from '../../events.js';
|
import {listen, unlisten, unlistenByKey} from '../../events.js';
|
||||||
import EventType from '../../events/EventType.js';
|
import EventType from '../../events/EventType.js';
|
||||||
import rbush from 'rbush';
|
|
||||||
import {buffer, containsCoordinate, equals, getIntersection, getTopLeft, intersects} from '../../extent.js';
|
import {buffer, containsCoordinate, equals, getIntersection, getTopLeft, intersects} from '../../extent.js';
|
||||||
import VectorTileRenderType from '../../layer/VectorTileRenderType.js';
|
import VectorTileRenderType from '../../layer/VectorTileRenderType.js';
|
||||||
import ReplayType from '../../render/canvas/BuilderType.js';
|
import ReplayType from '../../render/canvas/BuilderType.js';
|
||||||
@@ -103,12 +102,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
*/
|
*/
|
||||||
this.inverseOverlayPixelTransform_ = createTransform();
|
this.inverseOverlayPixelTransform_ = createTransform();
|
||||||
|
|
||||||
/**
|
|
||||||
* Declutter tree.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this.declutterTree_ = layer.getDeclutter() ? rbush(9, undefined) : null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -274,7 +267,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
buffer(sharedExtent, layer.getRenderBuffer() * resolution, this.tmpExtent);
|
buffer(sharedExtent, layer.getRenderBuffer() * resolution, this.tmpExtent);
|
||||||
builderState.dirty = false;
|
builderState.dirty = false;
|
||||||
const builderGroup = new CanvasBuilderGroup(0, sharedExtent, resolution,
|
const builderGroup = new CanvasBuilderGroup(0, sharedExtent, resolution,
|
||||||
pixelRatio, !!this.declutterTree_);
|
pixelRatio, layer.getDeclutter());
|
||||||
const squaredTolerance = getSquaredRenderTolerance(resolution, pixelRatio);
|
const squaredTolerance = getSquaredRenderTolerance(resolution, pixelRatio);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -310,7 +303,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
null :
|
null :
|
||||||
sharedExtent;
|
sharedExtent;
|
||||||
const renderingReplayGroup = new CanvasExecutorGroup(replayExtent, resolution,
|
const renderingReplayGroup = new CanvasExecutorGroup(replayExtent, resolution,
|
||||||
pixelRatio, source.getOverlaps(), this.declutterTree_, executorGroupInstructions, layer.getRenderBuffer());
|
pixelRatio, source.getOverlaps(), executorGroupInstructions, layer.getRenderBuffer());
|
||||||
tile.executorGroups[layerUid].push(renderingReplayGroup);
|
tile.executorGroups[layerUid].push(renderingReplayGroup);
|
||||||
}
|
}
|
||||||
builderState.renderedRevision = revision;
|
builderState.renderedRevision = revision;
|
||||||
@@ -322,11 +315,12 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, thisArg) {
|
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures) {
|
||||||
const resolution = frameState.viewState.resolution;
|
const resolution = frameState.viewState.resolution;
|
||||||
const rotation = frameState.viewState.rotation;
|
const rotation = frameState.viewState.rotation;
|
||||||
hitTolerance = hitTolerance == undefined ? 0 : hitTolerance;
|
hitTolerance = hitTolerance == undefined ? 0 : hitTolerance;
|
||||||
const layer = this.getLayer();
|
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
||||||
|
const declutter = layer.getDeclutter();
|
||||||
const source = layer.getSource();
|
const source = layer.getSource();
|
||||||
const tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
|
const tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
|
||||||
/** @type {!Object<string, boolean>} */
|
/** @type {!Object<string, boolean>} */
|
||||||
@@ -338,7 +332,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
let i, ii;
|
let i, ii;
|
||||||
for (i = 0, ii = renderedTiles.length; i < ii; ++i) {
|
for (i = 0, ii = renderedTiles.length; i < ii; ++i) {
|
||||||
const tile = renderedTiles[i];
|
const tile = renderedTiles[i];
|
||||||
if (!this.declutterTree_) {
|
if (!declutter) {
|
||||||
// When not decluttering, we only need to consider the tile that contains the given
|
// When not decluttering, we only need to consider the tile that contains the given
|
||||||
// coordinate, because each feature will be rendered for each tile that contains it.
|
// coordinate, because each feature will be rendered for each tile that contains it.
|
||||||
const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
|
const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
|
||||||
@@ -361,9 +355,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
}
|
}
|
||||||
if (!(key in features)) {
|
if (!(key in features)) {
|
||||||
features[key] = true;
|
features[key] = true;
|
||||||
return callback.call(thisArg, feature, layer);
|
return callback(feature, layer);
|
||||||
}
|
}
|
||||||
}, null);
|
}, declutteredFeatures);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
@@ -464,9 +458,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
context.clearRect(0, 0, width, height);
|
context.clearRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (declutterReplays) {
|
|
||||||
this.declutterTree_.clear();
|
|
||||||
}
|
|
||||||
const tiles = this.renderedTiles;
|
const tiles = this.renderedTiles;
|
||||||
const tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
|
const tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
|
||||||
const clips = [];
|
const clips = [];
|
||||||
@@ -522,7 +513,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (declutterReplays) {
|
if (declutterReplays) {
|
||||||
replayDeclutter(declutterReplays, context, rotation, hifi);
|
replayDeclutter(declutterReplays, context, rotation, hifi, frameState.declutterItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
const opacity = layerState.opacity;
|
const opacity = layerState.opacity;
|
||||||
@@ -552,9 +543,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
frameState.animate = true;
|
frameState.animate = true;
|
||||||
delete this.renderTileImageQueue_[uid];
|
delete this.renderTileImageQueue_[uid];
|
||||||
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
||||||
if (this.declutterTree_ && layer.getRenderMode() === VectorTileRenderType.IMAGE) {
|
|
||||||
this.declutterTree_.clear();
|
|
||||||
}
|
|
||||||
const viewState = frameState.viewState;
|
const viewState = frameState.viewState;
|
||||||
const tileGrid = layer.getSource().getTileGridForProjection(viewState.projection);
|
const tileGrid = layer.getSource().getTileGridForProjection(viewState.projection);
|
||||||
const tileResolution = tileGrid.getResolution(tile.tileCoord[0]);
|
const tileResolution = tileGrid.getResolution(tile.tileCoord[0]);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function createContext() {
|
|||||||
function executeInstructions(builder, expectedDrawTextImageCalls, expectedBuilderImageCalls) {
|
function executeInstructions(builder, expectedDrawTextImageCalls, expectedBuilderImageCalls) {
|
||||||
const transform = createTransform();
|
const transform = createTransform();
|
||||||
const context = createContext();
|
const context = createContext();
|
||||||
const executor = new Executor(0.02, 1, false, null, builder.finish());
|
const executor = new Executor(0.02, 1, false, builder.finish());
|
||||||
sinon.spy(executor, 'drawTextImageWithPointPlacement_');
|
sinon.spy(executor, 'drawTextImageWithPointPlacement_');
|
||||||
const replayImageStub = sinon.stub(executor, 'replayImage_');
|
const replayImageStub = sinon.stub(executor, 'replayImage_');
|
||||||
executor.execute(context, transform);
|
executor.execute(context, transform);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ describe('ol.render.canvas.BuilderGroup', function() {
|
|||||||
*/
|
*/
|
||||||
function execute(builder, skippedUids, pixelRatio, overlaps) {
|
function execute(builder, skippedUids, pixelRatio, overlaps) {
|
||||||
const executor = new ExecutorGroup([-180, -90, 180, 90], 1,
|
const executor = new ExecutorGroup([-180, -90, 180, 90], 1,
|
||||||
pixelRatio || 1, !!overlaps, null, builder.finish());
|
pixelRatio || 1, !!overlaps, builder.finish());
|
||||||
executor.execute(context, transform, 0, skippedUids || {});
|
executor.execute(context, transform, 0, skippedUids || {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user