Merge pull request #9466 from ahocevar/declutter-global

Declutter in correct order and for all layers
This commit is contained in:
Andreas Hocevar
2019-05-07 14:33:24 +02:00
committed by GitHub
21 changed files with 294 additions and 175 deletions
+1
View File
@@ -99,6 +99,7 @@ class CompositeMapRenderer extends MapRenderer {
this.children_.push(element);
}
}
super.renderFrame(frameState);
replaceChildren(this.element_, this.children_);
+2 -1
View File
@@ -89,10 +89,11 @@ class LayerRenderer extends Observable {
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
* @param {number} hitTolerance Hit tolerance in pixels.
* @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.
* @template T
*/
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback) {}
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures) {}
/**
* @abstract
+14 -3
View File
@@ -10,6 +10,7 @@ import {TRUE} from '../functions.js';
import {visibleAtResolution} from '../layer/Layer.js';
import {shared as iconImageCache} from '../style/IconImageCache.js';
import {compose as composeTransform, makeInverse} from '../transform.js';
import {renderDeclutterItems} from '../render.js';
/**
* @abstract
@@ -28,6 +29,11 @@ class MapRenderer extends Disposable {
*/
this.map_ = map;
/**
* @private
*/
this.declutterTree_ = null;
/**
* @private
* @type {!Object<string, import("./Layer.js").default>}
@@ -133,6 +139,12 @@ class MapRenderer extends Disposable {
const layerStates = frameState.layerStatesArray;
const numLayers = layerStates.length;
let declutteredFeatures;
if (this.declutterTree_) {
declutteredFeatures = this.declutterTree_.all().map(function(entry) {
return entry.value;
});
}
let i;
for (i = numLayers - 1; i >= 0; --i) {
const layerState = layerStates[i];
@@ -144,7 +156,7 @@ class MapRenderer extends Disposable {
const callback = forEachFeatureAtCoordinate.bind(null, layerState.managed);
result = layerRenderer.forEachFeatureAtCoordinate(
source.getWrapX() ? translatedCoordinate : coordinate,
frameState, hitTolerance, callback);
frameState, hitTolerance, callback, declutteredFeatures);
}
if (result) {
return result;
@@ -252,11 +264,10 @@ class MapRenderer extends Disposable {
/**
* Render.
* @abstract
* @param {?import("../PluggableMap.js").FrameState} frameState Frame state.
*/
renderFrame(frameState) {
abstract();
this.declutterTree_ = renderDeclutterItems(frameState, this.declutterTree_);
}
/**
+6 -3
View File
@@ -11,6 +11,7 @@ import CanvasVectorLayerRenderer from './VectorLayer.js';
import {listen} from '../../events.js';
import EventType from '../../events/EventType.js';
import ImageState from '../../ImageState.js';
import {renderDeclutterItems} from '../../render.js';
/**
* @classdesc
@@ -72,6 +73,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
let skippedFeatures = this.skippedFeatures_;
const context = vectorRenderer.context;
const imageFrameState = /** @type {import("../../PluggableMap.js").FrameState} */ (assign({}, frameState, {
declutterItems: [],
size: [
getWidth(renderedExtent) / viewResolution,
getHeight(renderedExtent) / viewResolution
@@ -86,6 +88,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
(vectorRenderer.replayGroupChanged ||
!equals(skippedFeatures, newSkippedFeatures))) {
vectorRenderer.renderFrame(imageFrameState, layerState);
renderDeclutterItems(imageFrameState, null);
skippedFeatures = newSkippedFeatures;
callback();
}
@@ -123,11 +126,11 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
/**
* @inheritDoc
*/
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback) {
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures) {
if (this.vectorRenderer_) {
return this.vectorRenderer_.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback);
return this.vectorRenderer_.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures);
} else {
return super.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback);
return super.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures);
}
}
}
+15 -20
View File
@@ -5,11 +5,10 @@ import {getUid} from '../../util.js';
import ViewHint from '../../ViewHint.js';
import {listen, unlisten} from '../../events.js';
import EventType from '../../events/EventType.js';
import rbush from 'rbush';
import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js';
import {labelCache} from '../../render/canvas.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 {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
import {toString as transformToString, makeScale, makeInverse} from '../../transform.js';
@@ -28,12 +27,6 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
super(vectorLayer);
/**
* Declutter tree.
* @private
*/
this.declutterTree_ = vectorLayer.getDeclutter() ? rbush(9, undefined) : null;
/**
* @private
* @type {boolean}
@@ -138,17 +131,14 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
this.clip(context, frameState, clipExtent);
}
if (this.declutterTree_) {
this.declutterTree_.clear();
}
const viewHints = frameState.viewHints;
const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
const transform = this.getRenderTransform(frameState, width, height, 0);
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)) {
let startX = extent[0];
@@ -159,7 +149,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
--world;
offsetX = worldWidth * world;
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;
}
world = 0;
@@ -168,10 +158,15 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
++world;
offsetX = worldWidth * world;
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;
}
}
if (declutterReplays) {
const viewHints = frameState.viewHints;
const hifi = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
replayDeclutter(declutterReplays, context, rotation, hifi, frameState.declutterItems);
}
if (clipped) {
context.restore();
@@ -190,7 +185,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
/**
* @inheritDoc
*/
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, thisArg) {
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures) {
if (!this.replayGroup_) {
return undefined;
} else {
@@ -208,9 +203,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const key = getUid(feature);
if (!(key in features)) {
features[key] = true;
return callback.call(thisArg, feature, layer);
return callback(feature, layer);
}
}, null);
}, declutteredFeatures);
return result;
}
}
@@ -299,7 +294,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const replayGroup = new CanvasBuilderGroup(
getRenderTolerance(resolution, pixelRatio), extent, resolution,
pixelRatio, !!this.declutterTree_);
pixelRatio, vectorLayer.getDeclutter());
vectorSource.loadFeatures(extent, resolution, projection);
@@ -339,7 +334,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const replayGroupInstructions = replayGroup.finish();
const executorGroup = new ExecutorGroup(extent, resolution,
pixelRatio, vectorSource.getOverlaps(), this.declutterTree_,
pixelRatio, vectorSource.getOverlaps(),
replayGroupInstructions, vectorLayer.getRenderBuffer());
this.renderedResolution_ = resolution;
+9 -21
View File
@@ -7,7 +7,6 @@ import TileState from '../../TileState.js';
import ViewHint from '../../ViewHint.js';
import {listen, unlisten, unlistenByKey} from '../../events.js';
import EventType from '../../events/EventType.js';
import rbush from 'rbush';
import {buffer, containsCoordinate, equals, getIntersection, getTopLeft, intersects} from '../../extent.js';
import VectorTileRenderType from '../../layer/VectorTileRenderType.js';
import ReplayType from '../../render/canvas/BuilderType.js';
@@ -103,12 +102,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
*/
this.inverseOverlayPixelTransform_ = createTransform();
/**
* Declutter tree.
* @private
*/
this.declutterTree_ = layer.getDeclutter() ? rbush(9, undefined) : null;
/**
* @private
* @type {boolean}
@@ -274,7 +267,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
buffer(sharedExtent, layer.getRenderBuffer() * resolution, this.tmpExtent);
builderState.dirty = false;
const builderGroup = new CanvasBuilderGroup(0, sharedExtent, resolution,
pixelRatio, !!this.declutterTree_);
pixelRatio, layer.getDeclutter());
const squaredTolerance = getSquaredRenderTolerance(resolution, pixelRatio);
/**
@@ -310,7 +303,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
null :
sharedExtent;
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);
}
builderState.renderedRevision = revision;
@@ -322,11 +315,12 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
/**
* @inheritDoc
*/
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, thisArg) {
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures) {
const resolution = frameState.viewState.resolution;
const rotation = frameState.viewState.rotation;
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 tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
/** @type {!Object<string, boolean>} */
@@ -338,7 +332,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
let i, ii;
for (i = 0, ii = renderedTiles.length; i < ii; ++i) {
const tile = renderedTiles[i];
if (!this.declutterTree_) {
if (!declutter) {
// 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.
const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
@@ -361,9 +355,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
}
if (!(key in features)) {
features[key] = true;
return callback.call(thisArg, feature, layer);
return callback(feature, layer);
}
}, null);
}, declutteredFeatures);
}
}
return found;
@@ -464,9 +458,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
context.clearRect(0, 0, width, height);
}
if (declutterReplays) {
this.declutterTree_.clear();
}
const tiles = this.renderedTiles;
const tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
const clips = [];
@@ -522,7 +513,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
}
}
if (declutterReplays) {
replayDeclutter(declutterReplays, context, rotation, hifi);
replayDeclutter(declutterReplays, context, rotation, hifi, frameState.declutterItems);
}
const opacity = layerState.opacity;
@@ -552,9 +543,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
frameState.animate = true;
delete this.renderTileImageQueue_[uid];
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 tileGrid = layer.getSource().getTileGridForProjection(viewState.projection);
const tileResolution = tileGrid.getResolution(tile.tileCoord[0]);