New decluttering implementation
This commit is contained in:
@@ -144,6 +144,7 @@ worker.addEventListener('message', (event) => {
|
||||
renderer.renderFrame(frameState, canvas);
|
||||
}
|
||||
});
|
||||
layers.forEach((layer) => layer.renderDeclutter(frameState));
|
||||
if (tileQueue.getTilesLoading() < maxTotalLoading) {
|
||||
tileQueue.reprioritize();
|
||||
tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads);
|
||||
|
||||
@@ -50,6 +50,7 @@ import {removeNode} from './dom.js';
|
||||
* @property {import("./View.js").State} viewState The state of the current view.
|
||||
* @property {boolean} animate
|
||||
* @property {import("./transform.js").Transform} coordinateToPixelTransform
|
||||
* @property {import("rbush").default} declutterTree
|
||||
* @property {null|import("./extent.js").Extent} extent
|
||||
* @property {number} index
|
||||
* @property {Array<import("./layer/Layer.js").State>} layerStatesArray
|
||||
@@ -1372,6 +1373,7 @@ class PluggableMap extends BaseObject {
|
||||
frameState = {
|
||||
animate: false,
|
||||
coordinateToPixelTransform: this.coordinateToPixelTransform_,
|
||||
declutterTree: null,
|
||||
extent: getForViewAndSize(
|
||||
viewState.center,
|
||||
viewState.resolution,
|
||||
|
||||
@@ -45,6 +45,12 @@ class VectorRenderTile extends Tile {
|
||||
*/
|
||||
this.executorGroups = {};
|
||||
|
||||
/**
|
||||
* Executor groups for decluttering, by layer uid. Entries are read/written by the renderer.
|
||||
* @type {Object<string, Array<import("./render/canvas/ExecutorGroup.js").default>>}
|
||||
*/
|
||||
this.declutterExecutorGroups = {};
|
||||
|
||||
/**
|
||||
* Number of loading source tiles. Read/written by the source.
|
||||
* @type {number}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* @module ol/layer/BaseVector
|
||||
*/
|
||||
import Layer from './Layer.js';
|
||||
import RBush from 'rbush';
|
||||
import {assign} from '../obj.js';
|
||||
import {
|
||||
createDefaultStyle,
|
||||
@@ -214,6 +215,17 @@ class BaseVectorLayer extends Layer {
|
||||
return this.updateWhileInteracting_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render declutter items for this layer
|
||||
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
|
||||
*/
|
||||
renderDeclutter(frameState) {
|
||||
if (!frameState.declutterTree) {
|
||||
frameState.declutterTree = new RBush(9);
|
||||
}
|
||||
/** @type {*} */ (this.getRenderer()).renderDeclutter(frameState);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../render.js").OrderFunction|null|undefined} renderOrder
|
||||
* Render order.
|
||||
|
||||
@@ -309,6 +309,8 @@ class Heatmap extends VectorLayer {
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
renderDeclutter() {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -100,13 +100,15 @@ class VectorContext {
|
||||
|
||||
/**
|
||||
* @param {import("../style/Image.js").default} imageStyle Image style.
|
||||
* @param {Object=} opt_sharedData Shared data for combined decluttering with a text style.
|
||||
*/
|
||||
setImageStyle(imageStyle) {}
|
||||
setImageStyle(imageStyle, opt_sharedData) {}
|
||||
|
||||
/**
|
||||
* @param {import("../style/Text.js").default} textStyle Text style.
|
||||
* @param {Object=} opt_sharedData Shared data for combined decluttering with an image style.
|
||||
*/
|
||||
setTextStyle(textStyle) {}
|
||||
setTextStyle(textStyle, opt_sharedData) {}
|
||||
}
|
||||
|
||||
export default VectorContext;
|
||||
|
||||
@@ -67,6 +67,16 @@ import {toString} from '../transform.js';
|
||||
* @property {Array<number>} [padding]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SerializableInstructions
|
||||
* @property {Array<*>} instructions The rendering instructions.
|
||||
* @property {Array<*>} hitDetectionInstructions The rendering hit detection instructions.
|
||||
* @property {Array<number>} coordinates The array of all coordinates.
|
||||
* @property {!Object<string, TextState>} [textStates] The text states (decluttering).
|
||||
* @property {!Object<string, FillState>} [fillStates] The fill states (decluttering).
|
||||
* @property {!Object<string, StrokeState>} [strokeStates] The stroke states (decluttering).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {string}
|
||||
@@ -276,9 +286,8 @@ export const measureTextHeight = (function () {
|
||||
* @type {HTMLDivElement}
|
||||
*/
|
||||
let div;
|
||||
const heights = textHeights;
|
||||
return function (fontSpec) {
|
||||
let height = heights[fontSpec];
|
||||
let height = textHeights[fontSpec];
|
||||
if (height == undefined) {
|
||||
if (WORKER_OFFSCREEN_CANVAS) {
|
||||
const font = getFontParameters(fontSpec);
|
||||
@@ -286,7 +295,7 @@ export const measureTextHeight = (function () {
|
||||
const lineHeight = isNaN(Number(font.lineHeight))
|
||||
? 1.2
|
||||
: Number(font.lineHeight);
|
||||
textHeights[fontSpec] =
|
||||
height =
|
||||
lineHeight *
|
||||
(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent);
|
||||
} else {
|
||||
@@ -301,9 +310,9 @@ export const measureTextHeight = (function () {
|
||||
div.style.font = fontSpec;
|
||||
document.body.appendChild(div);
|
||||
height = div.offsetHeight;
|
||||
heights[fontSpec] = height;
|
||||
document.body.removeChild(div);
|
||||
}
|
||||
textHeights[fontSpec] = height;
|
||||
}
|
||||
return height;
|
||||
};
|
||||
|
||||
@@ -29,16 +29,6 @@ import {
|
||||
inflateMultiCoordinatesArray,
|
||||
} from '../../geom/flat/inflate.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} SerializableInstructions
|
||||
* @property {Array<*>} instructions The rendering instructions.
|
||||
* @property {Array<*>} hitDetectionInstructions The rendering hit detection instructions.
|
||||
* @property {Array<number>} coordinates The array of all coordinates.
|
||||
* @property {!Object<string, import("../canvas.js").TextState>} [textStates] The text states (decluttering).
|
||||
* @property {!Object<string, import("../canvas.js").FillState>} [fillStates] The fill states (decluttering).
|
||||
* @property {!Object<string, import("../canvas.js").StrokeState>} [strokeStates] The stroke states (decluttering).
|
||||
*/
|
||||
|
||||
class CanvasBuilder extends VectorContext {
|
||||
/**
|
||||
* @param {number} tolerance Tolerance.
|
||||
@@ -383,7 +373,7 @@ class CanvasBuilder extends VectorContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {SerializableInstructions} the serializable instructions.
|
||||
* @return {import("../canvas.js").SerializableInstructions} the serializable instructions.
|
||||
*/
|
||||
finish() {
|
||||
return {
|
||||
|
||||
@@ -28,13 +28,25 @@ import {lineStringLength} from '../../geom/flat/length.js';
|
||||
import {transform2D} from '../../geom/flat/transform.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} SerializableInstructions
|
||||
* @property {Array<*>} instructions The rendering instructions.
|
||||
* @property {Array<*>} hitDetectionInstructions The rendering hit detection instructions.
|
||||
* @property {Array<number>} coordinates The array of all coordinates.
|
||||
* @property {!Object<string, import("../canvas.js").TextState>} textStates The text states (decluttering).
|
||||
* @property {!Object<string, import("../canvas.js").FillState>} fillStates The fill states (decluttering).
|
||||
* @property {!Object<string, import("../canvas.js").StrokeState>} strokeStates The stroke states (decluttering).
|
||||
* @typedef {Object} BBox
|
||||
* @property {number} minX
|
||||
* @property {number} minY
|
||||
* @property {number} maxX
|
||||
* @property {number} maxY
|
||||
* @property {*} value
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ImageOrLabelDimensions
|
||||
* @property {number} drawImageX
|
||||
* @property {number} drawImageY
|
||||
* @property {number} drawImageW
|
||||
* @property {number} drawImageH
|
||||
* @property {number} originX
|
||||
* @property {number} originY
|
||||
* @property {Array<number>} scale
|
||||
* @property {BBox} declutterBox
|
||||
* @property {import("../../transform.js").Transform} canvasTransform
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -42,11 +54,6 @@ import {transform2D} from '../../geom/flat/transform.js';
|
||||
*/
|
||||
const tmpExtent = createEmpty();
|
||||
|
||||
/**
|
||||
* @type {!import("../../transform.js").Transform}
|
||||
*/
|
||||
const tmpTransform = createTransform();
|
||||
|
||||
/** @type {import("../../coordinate.js").Coordinate} */
|
||||
const p1 = [];
|
||||
/** @type {import("../../coordinate.js").Coordinate} */
|
||||
@@ -56,12 +63,21 @@ const p3 = [];
|
||||
/** @type {import("../../coordinate.js").Coordinate} */
|
||||
const p4 = [];
|
||||
|
||||
/**
|
||||
* @param {Array<*>} replayImageOrLabelArgs Arguments to replayImageOrLabel
|
||||
* @return {BBox} Declutter bbox.
|
||||
*/
|
||||
function getDeclutterBox(replayImageOrLabelArgs) {
|
||||
return /** @type {ImageOrLabelDimensions} */ (replayImageOrLabelArgs[3])
|
||||
.declutterBox;
|
||||
}
|
||||
|
||||
class Executor {
|
||||
/**
|
||||
* @param {number} resolution Resolution.
|
||||
* @param {number} pixelRatio Pixel ratio.
|
||||
* @param {boolean} overlaps The replay can have overlapping geometries.
|
||||
* @param {SerializableInstructions} instructions The serializable instructions
|
||||
* @param {import("../canvas.js").SerializableInstructions} instructions The serializable instructions
|
||||
* @param {import("../../size.js").Size} renderBuffer Render buffer (width/height) in pixels.
|
||||
*/
|
||||
constructor(resolution, pixelRatio, overlaps, instructions, renderBuffer) {
|
||||
@@ -293,60 +309,49 @@ class Executor {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {CanvasRenderingContext2D} context Context.
|
||||
* @param {number} contextScale Scale of the context.
|
||||
* @param {number} x X.
|
||||
* @param {number} y Y.
|
||||
* @param {import("../canvas.js").Label|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} imageOrLabel Image.
|
||||
* @param {number} sheetWidth Width of the sprite sheet.
|
||||
* @param {number} sheetHeight Height of the sprite sheet.
|
||||
* @param {number} centerX X.
|
||||
* @param {number} centerY Y.
|
||||
* @param {number} width Width.
|
||||
* @param {number} height Height.
|
||||
* @param {number} anchorX Anchor X.
|
||||
* @param {number} anchorY Anchor Y.
|
||||
* @param {number} height Height.
|
||||
* @param {number} opacity Opacity.
|
||||
* @param {number} originX Origin X.
|
||||
* @param {number} originY Origin Y.
|
||||
* @param {number} rotation Rotation.
|
||||
* @param {import("../../size.js").Size} scale Scale.
|
||||
* @param {boolean} snapToPixel Snap to pixel.
|
||||
* @param {number} width Width.
|
||||
* @param {Array<number>} padding Padding.
|
||||
* @param {Array<*>} fillInstruction Fill instruction.
|
||||
* @param {Array<*>} strokeInstruction Stroke instruction.
|
||||
* @return {boolean} The image or label was rendered.
|
||||
* @param {boolean} fillStroke Background fill or stroke.
|
||||
* @param {import("../../Feature.js").FeatureLike} feature Feature.
|
||||
* @return {ImageOrLabelDimensions} Dimensions for positioning and decluttering the image or label.
|
||||
*/
|
||||
replayImageOrLabel_(
|
||||
context,
|
||||
contextScale,
|
||||
x,
|
||||
y,
|
||||
imageOrLabel,
|
||||
calculateImageOrLabelDimensions_(
|
||||
sheetWidth,
|
||||
sheetHeight,
|
||||
centerX,
|
||||
centerY,
|
||||
width,
|
||||
height,
|
||||
anchorX,
|
||||
anchorY,
|
||||
height,
|
||||
opacity,
|
||||
originX,
|
||||
originY,
|
||||
rotation,
|
||||
scale,
|
||||
snapToPixel,
|
||||
width,
|
||||
padding,
|
||||
fillInstruction,
|
||||
strokeInstruction
|
||||
fillStroke,
|
||||
feature
|
||||
) {
|
||||
const fillStroke = fillInstruction || strokeInstruction;
|
||||
anchorX *= scale[0];
|
||||
anchorY *= scale[1];
|
||||
x -= anchorX;
|
||||
y -= anchorY;
|
||||
let x = centerX - anchorX;
|
||||
let y = centerY - anchorY;
|
||||
|
||||
const w =
|
||||
width + originX > imageOrLabel.width
|
||||
? imageOrLabel.width - originX
|
||||
: width;
|
||||
const h =
|
||||
height + originY > imageOrLabel.height
|
||||
? imageOrLabel.height - originY
|
||||
: height;
|
||||
const w = width + originX > sheetWidth ? sheetWidth - originX : width;
|
||||
const h = height + originY > sheetHeight ? sheetHeight - originY : height;
|
||||
const boxW = padding[3] + w * scale[0] + padding[1];
|
||||
const boxH = padding[0] + h * scale[1] + padding[2];
|
||||
const boxX = x - padding[3];
|
||||
@@ -363,12 +368,10 @@ class Executor {
|
||||
p4[1] = p3[1];
|
||||
}
|
||||
|
||||
let transform = null;
|
||||
let transform;
|
||||
if (rotation !== 0) {
|
||||
const centerX = x + anchorX;
|
||||
const centerY = y + anchorY;
|
||||
transform = composeTransform(
|
||||
tmpTransform,
|
||||
createTransform(),
|
||||
centerX,
|
||||
centerY,
|
||||
1,
|
||||
@@ -378,10 +381,10 @@ class Executor {
|
||||
-centerY
|
||||
);
|
||||
|
||||
applyTransform(tmpTransform, p1);
|
||||
applyTransform(tmpTransform, p2);
|
||||
applyTransform(tmpTransform, p3);
|
||||
applyTransform(tmpTransform, p4);
|
||||
applyTransform(transform, p1);
|
||||
applyTransform(transform, p2);
|
||||
applyTransform(transform, p3);
|
||||
applyTransform(transform, p4);
|
||||
createOrUpdate(
|
||||
Math.min(p1[0], p2[0], p3[0], p4[0]),
|
||||
Math.min(p1[1], p2[1], p3[1], p4[1]),
|
||||
@@ -398,24 +401,61 @@ class Executor {
|
||||
tmpExtent
|
||||
);
|
||||
}
|
||||
const renderBufferX = 0; // increase this.renderBuffer_ for decluttering
|
||||
const renderBufferY = 0; // increase this.renderBuffer_ for decluttering
|
||||
const canvas = context.canvas;
|
||||
const strokePadding = strokeInstruction
|
||||
? (strokeInstruction[2] * scale[0]) / 2
|
||||
: 0;
|
||||
const intersects =
|
||||
tmpExtent[0] - strokePadding <=
|
||||
(canvas.width + renderBufferX) / contextScale &&
|
||||
tmpExtent[2] + strokePadding >= -renderBufferX / contextScale &&
|
||||
tmpExtent[1] - strokePadding <=
|
||||
(canvas.height + renderBufferY) / contextScale &&
|
||||
tmpExtent[3] + strokePadding >= -renderBufferY / contextScale;
|
||||
|
||||
if (snapToPixel) {
|
||||
x = Math.round(x);
|
||||
y = Math.round(y);
|
||||
}
|
||||
return {
|
||||
drawImageX: x,
|
||||
drawImageY: y,
|
||||
drawImageW: w,
|
||||
drawImageH: h,
|
||||
originX: originX,
|
||||
originY: originY,
|
||||
declutterBox: {
|
||||
minX: tmpExtent[0],
|
||||
minY: tmpExtent[1],
|
||||
maxX: tmpExtent[2],
|
||||
maxY: tmpExtent[3],
|
||||
value: feature,
|
||||
},
|
||||
canvasTransform: transform,
|
||||
scale: scale,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {CanvasRenderingContext2D} context Context.
|
||||
* @param {number} contextScale Scale of the context.
|
||||
* @param {import("../canvas.js").Label|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} imageOrLabel Image.
|
||||
* @param {ImageOrLabelDimensions} dimensions Dimensions.
|
||||
* @param {number} opacity Opacity.
|
||||
* @param {Array<*>} fillInstruction Fill instruction.
|
||||
* @param {Array<*>} strokeInstruction Stroke instruction.
|
||||
* @return {boolean} The image or label was rendered.
|
||||
*/
|
||||
replayImageOrLabel_(
|
||||
context,
|
||||
contextScale,
|
||||
imageOrLabel,
|
||||
dimensions,
|
||||
opacity,
|
||||
fillInstruction,
|
||||
strokeInstruction
|
||||
) {
|
||||
const fillStroke = !!(fillInstruction || strokeInstruction);
|
||||
|
||||
const box = dimensions.declutterBox;
|
||||
const canvas = context.canvas;
|
||||
const strokePadding = strokeInstruction
|
||||
? (strokeInstruction[2] * dimensions.scale[0]) / 2
|
||||
: 0;
|
||||
const intersects =
|
||||
box.minX - strokePadding <= canvas.width / contextScale &&
|
||||
box.maxX + strokePadding >= 0 &&
|
||||
box.minY - strokePadding <= canvas.height / contextScale &&
|
||||
box.maxY + strokePadding >= 0;
|
||||
|
||||
if (intersects) {
|
||||
if (fillStroke) {
|
||||
@@ -431,16 +471,16 @@ class Executor {
|
||||
}
|
||||
drawImageOrLabel(
|
||||
context,
|
||||
transform,
|
||||
dimensions.canvasTransform,
|
||||
opacity,
|
||||
imageOrLabel,
|
||||
originX,
|
||||
originY,
|
||||
w,
|
||||
h,
|
||||
x,
|
||||
y,
|
||||
scale
|
||||
dimensions.originX,
|
||||
dimensions.originY,
|
||||
dimensions.drawImageW,
|
||||
dimensions.drawImageH,
|
||||
dimensions.drawImageX,
|
||||
dimensions.drawImageY,
|
||||
dimensions.scale
|
||||
);
|
||||
}
|
||||
return true;
|
||||
@@ -470,7 +510,9 @@ class Executor {
|
||||
* @param {Array<*>} instruction Instruction.
|
||||
*/
|
||||
setStrokeStyle_(context, instruction) {
|
||||
context.strokeStyle = /** @type {import("../../colorlike.js").ColorLike} */ (instruction[1]);
|
||||
context[
|
||||
'strokeStyle'
|
||||
] = /** @type {import("../../colorlike.js").ColorLike} */ (instruction[1]);
|
||||
context.lineWidth = /** @type {number} */ (instruction[2]);
|
||||
context.lineCap = /** @type {CanvasLineCap} */ (instruction[3]);
|
||||
context.lineJoin = /** @type {CanvasLineJoin} */ (instruction[4]);
|
||||
@@ -525,6 +567,7 @@ class Executor {
|
||||
* @param {function(import("../../Feature.js").FeatureLike): T|undefined} featureCallback Feature callback.
|
||||
* @param {import("../../extent.js").Extent=} opt_hitExtent Only check features that intersect this
|
||||
* extent.
|
||||
* @param {import("rbush").default=} opt_declutterTree Declutter tree.
|
||||
* @return {T|undefined} Callback result.
|
||||
* @template T
|
||||
*/
|
||||
@@ -535,7 +578,8 @@ class Executor {
|
||||
instructions,
|
||||
snapToPixel,
|
||||
featureCallback,
|
||||
opt_hitExtent
|
||||
opt_hitExtent,
|
||||
opt_declutterTree
|
||||
) {
|
||||
/** @type {Array<number>} */
|
||||
let pixelCoordinates;
|
||||
@@ -559,8 +603,17 @@ class Executor {
|
||||
const ii = instructions.length; // end of instructions
|
||||
let d = 0; // data index
|
||||
let dd; // end of per-instruction data
|
||||
let anchorX, anchorY, prevX, prevY, roundX, roundY, image, text, textKey;
|
||||
let strokeKey, fillKey;
|
||||
let anchorX,
|
||||
anchorY,
|
||||
prevX,
|
||||
prevY,
|
||||
roundX,
|
||||
roundY,
|
||||
image,
|
||||
text,
|
||||
textKey,
|
||||
strokeKey,
|
||||
fillKey;
|
||||
let pendingFill = 0;
|
||||
let pendingStroke = 0;
|
||||
let lastFillInstruction = null;
|
||||
@@ -671,13 +724,14 @@ class Executor {
|
||||
let rotation = /** @type {number} */ (instruction[11]);
|
||||
const scale = /** @type {import("../../size.js").Size} */ (instruction[12]);
|
||||
let width = /** @type {number} */ (instruction[13]);
|
||||
const sharedData = instruction[14];
|
||||
|
||||
if (!image && instruction.length >= 18) {
|
||||
if (!image && instruction.length >= 19) {
|
||||
// create label images
|
||||
text = /** @type {string} */ (instruction[17]);
|
||||
textKey = /** @type {string} */ (instruction[18]);
|
||||
strokeKey = /** @type {string} */ (instruction[19]);
|
||||
fillKey = /** @type {string} */ (instruction[20]);
|
||||
text = /** @type {string} */ (instruction[18]);
|
||||
textKey = /** @type {string} */ (instruction[19]);
|
||||
strokeKey = /** @type {string} */ (instruction[20]);
|
||||
fillKey = /** @type {string} */ (instruction[21]);
|
||||
const labelWithAnchor = this.drawLabelWithPointPlacement_(
|
||||
text,
|
||||
textKey,
|
||||
@@ -686,10 +740,10 @@ class Executor {
|
||||
);
|
||||
image = labelWithAnchor.label;
|
||||
instruction[3] = image;
|
||||
const textOffsetX = /** @type {number} */ (instruction[21]);
|
||||
const textOffsetX = /** @type {number} */ (instruction[22]);
|
||||
anchorX = (labelWithAnchor.anchorX - textOffsetX) * this.pixelRatio;
|
||||
instruction[4] = anchorX;
|
||||
const textOffsetY = /** @type {number} */ (instruction[22]);
|
||||
const textOffsetY = /** @type {number} */ (instruction[23]);
|
||||
anchorY = (labelWithAnchor.anchorY - textOffsetY) * this.pixelRatio;
|
||||
instruction[5] = anchorY;
|
||||
height = image.height;
|
||||
@@ -699,15 +753,15 @@ class Executor {
|
||||
}
|
||||
|
||||
let geometryWidths;
|
||||
if (instruction.length > 23) {
|
||||
geometryWidths = /** @type {number} */ (instruction[23]);
|
||||
if (instruction.length > 24) {
|
||||
geometryWidths = /** @type {number} */ (instruction[24]);
|
||||
}
|
||||
|
||||
let padding, backgroundFill, backgroundStroke;
|
||||
if (instruction.length > 15) {
|
||||
padding = /** @type {Array<number>} */ (instruction[14]);
|
||||
backgroundFill = /** @type {boolean} */ (instruction[15]);
|
||||
backgroundStroke = /** @type {boolean} */ (instruction[16]);
|
||||
if (instruction.length > 16) {
|
||||
padding = /** @type {Array<number>} */ (instruction[15]);
|
||||
backgroundFill = /** @type {boolean} */ (instruction[16]);
|
||||
backgroundStroke = /** @type {boolean} */ (instruction[17]);
|
||||
} else {
|
||||
padding = defaultPadding;
|
||||
backgroundFill = false;
|
||||
@@ -729,30 +783,67 @@ class Executor {
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
this.replayImageOrLabel_(
|
||||
context,
|
||||
contextScale,
|
||||
const dimensions = this.calculateImageOrLabelDimensions_(
|
||||
image.width,
|
||||
image.height,
|
||||
pixelCoordinates[d],
|
||||
pixelCoordinates[d + 1],
|
||||
image,
|
||||
width,
|
||||
height,
|
||||
anchorX,
|
||||
anchorY,
|
||||
height,
|
||||
opacity,
|
||||
originX,
|
||||
originY,
|
||||
rotation,
|
||||
scale,
|
||||
snapToPixel,
|
||||
width,
|
||||
padding,
|
||||
backgroundFill || backgroundStroke,
|
||||
feature
|
||||
);
|
||||
const args = [
|
||||
context,
|
||||
contextScale,
|
||||
image,
|
||||
dimensions,
|
||||
opacity,
|
||||
backgroundFill
|
||||
? /** @type {Array<*>} */ (lastFillInstruction)
|
||||
: null,
|
||||
backgroundStroke
|
||||
? /** @type {Array<*>} */ (lastStrokeInstruction)
|
||||
: null
|
||||
);
|
||||
: null,
|
||||
];
|
||||
let imageArgs;
|
||||
let imageDeclutterBox;
|
||||
if (opt_declutterTree && sharedData) {
|
||||
if (!sharedData[d]) {
|
||||
sharedData[d] = args;
|
||||
continue;
|
||||
}
|
||||
imageArgs = sharedData[d];
|
||||
delete sharedData[d];
|
||||
imageDeclutterBox = getDeclutterBox(imageArgs);
|
||||
if (opt_declutterTree.collides(imageDeclutterBox)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (
|
||||
opt_declutterTree &&
|
||||
opt_declutterTree.collides(dimensions.declutterBox)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (imageArgs) {
|
||||
if (opt_declutterTree) {
|
||||
opt_declutterTree.insert(imageDeclutterBox);
|
||||
}
|
||||
this.replayImageOrLabel_.apply(this, imageArgs);
|
||||
}
|
||||
if (opt_declutterTree) {
|
||||
opt_declutterTree.insert(dimensions.declutterBox);
|
||||
}
|
||||
this.replayImageOrLabel_.apply(this, args);
|
||||
}
|
||||
++i;
|
||||
break;
|
||||
@@ -810,8 +901,8 @@ class Executor {
|
||||
cachedWidths,
|
||||
viewRotationFromTransform ? 0 : this.viewRotation_
|
||||
);
|
||||
if (parts) {
|
||||
let rendered = false;
|
||||
drawChars: if (parts) {
|
||||
const replayImageOrLabelArgs = [];
|
||||
let c, cc, chars, label, part;
|
||||
if (strokeKey) {
|
||||
for (c = 0, cc = parts.length; c < cc; ++c) {
|
||||
@@ -824,27 +915,39 @@ class Executor {
|
||||
((0.5 - baseline) * 2 * strokeWidth * textScale[1]) /
|
||||
textScale[0] -
|
||||
offsetY;
|
||||
rendered =
|
||||
this.replayImageOrLabel_(
|
||||
context,
|
||||
contextScale,
|
||||
/** @type {number} */ (part[0]),
|
||||
/** @type {number} */ (part[1]),
|
||||
label,
|
||||
const dimensions = this.calculateImageOrLabelDimensions_(
|
||||
label.width,
|
||||
label.height,
|
||||
part[0],
|
||||
part[1],
|
||||
label.width,
|
||||
label.height,
|
||||
anchorX,
|
||||
anchorY,
|
||||
label.height,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
/** @type {number} */ (part[3]),
|
||||
part[3],
|
||||
pixelRatioScale,
|
||||
false,
|
||||
label.width,
|
||||
defaultPadding,
|
||||
false,
|
||||
feature
|
||||
);
|
||||
if (
|
||||
opt_declutterTree &&
|
||||
opt_declutterTree.collides(dimensions.declutterBox)
|
||||
) {
|
||||
break drawChars;
|
||||
}
|
||||
replayImageOrLabelArgs.push([
|
||||
context,
|
||||
contextScale,
|
||||
label,
|
||||
dimensions,
|
||||
1,
|
||||
null,
|
||||
null
|
||||
) || rendered;
|
||||
null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (fillKey) {
|
||||
@@ -854,28 +957,48 @@ class Executor {
|
||||
label = this.createLabel(chars, textKey, fillKey, '');
|
||||
anchorX = /** @type {number} */ (part[2]);
|
||||
anchorY = baseline * label.height - offsetY;
|
||||
rendered =
|
||||
this.replayImageOrLabel_(
|
||||
context,
|
||||
contextScale,
|
||||
/** @type {number} */ (part[0]),
|
||||
/** @type {number} */ (part[1]),
|
||||
label,
|
||||
const dimensions = this.calculateImageOrLabelDimensions_(
|
||||
label.width,
|
||||
label.height,
|
||||
part[0],
|
||||
part[1],
|
||||
label.width,
|
||||
label.height,
|
||||
anchorX,
|
||||
anchorY,
|
||||
label.height,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
/** @type {number} */ (part[3]),
|
||||
part[3],
|
||||
pixelRatioScale,
|
||||
false,
|
||||
label.width,
|
||||
defaultPadding,
|
||||
null,
|
||||
null
|
||||
) || rendered;
|
||||
false,
|
||||
feature
|
||||
);
|
||||
if (
|
||||
opt_declutterTree &&
|
||||
opt_declutterTree.collides(dimensions.declutterBox)
|
||||
) {
|
||||
break drawChars;
|
||||
}
|
||||
replayImageOrLabelArgs.push([
|
||||
context,
|
||||
contextScale,
|
||||
label,
|
||||
dimensions,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (opt_declutterTree) {
|
||||
opt_declutterTree.load(
|
||||
replayImageOrLabelArgs.map(getDeclutterBox)
|
||||
);
|
||||
}
|
||||
for (let i = 0, ii = replayImageOrLabelArgs.length; i < ii; ++i) {
|
||||
this.replayImageOrLabel_.apply(this, replayImageOrLabelArgs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -977,8 +1100,16 @@ class Executor {
|
||||
* @param {import("../../transform.js").Transform} transform Transform.
|
||||
* @param {number} viewRotation View rotation.
|
||||
* @param {boolean} snapToPixel Snap point symbols and text to integer pixels.
|
||||
* @param {import("rbush").default=} opt_declutterTree Declutter tree.
|
||||
*/
|
||||
execute(context, contextScale, transform, viewRotation, snapToPixel) {
|
||||
execute(
|
||||
context,
|
||||
contextScale,
|
||||
transform,
|
||||
viewRotation,
|
||||
snapToPixel,
|
||||
opt_declutterTree
|
||||
) {
|
||||
this.viewRotation_ = viewRotation;
|
||||
this.execute_(
|
||||
context,
|
||||
@@ -987,7 +1118,8 @@ class Executor {
|
||||
this.instructions,
|
||||
snapToPixel,
|
||||
undefined,
|
||||
undefined
|
||||
undefined,
|
||||
opt_declutterTree
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class ExecutorGroup {
|
||||
* @param {number} resolution Resolution.
|
||||
* @param {number} pixelRatio Pixel ratio.
|
||||
* @param {boolean} overlaps The executor group can have overlapping geometries.
|
||||
* @param {!Object<string, !Object<import("./BuilderType.js").default, import("./Builder.js").SerializableInstructions>>} allInstructions
|
||||
* @param {!Object<string, !Object<import("./BuilderType.js").default, import("../canvas.js").SerializableInstructions>>} allInstructions
|
||||
* The serializable instructions.
|
||||
* @param {number=} opt_renderBuffer Optional rendering buffer.
|
||||
*/
|
||||
@@ -116,7 +116,7 @@ class ExecutorGroup {
|
||||
/**
|
||||
* Create executors and populate them using the provided instructions.
|
||||
* @private
|
||||
* @param {!Object<string, !Object<import("./BuilderType.js").default, import("./Builder.js").SerializableInstructions>>} allInstructions The serializable instructions
|
||||
* @param {!Object<string, !Object<import("./BuilderType.js").default, import("../canvas.js").SerializableInstructions>>} allInstructions The serializable instructions
|
||||
*/
|
||||
createExecutors_(allInstructions) {
|
||||
for (const zIndex in allInstructions) {
|
||||
@@ -162,6 +162,7 @@ class ExecutorGroup {
|
||||
* @param {number} rotation Rotation.
|
||||
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||
* @param {function(import("../../Feature.js").FeatureLike): T} callback Feature callback.
|
||||
* @param {Array<import("../../Feature.js").FeatureLike>} declutteredFeatures Decluttered features.
|
||||
* @return {T|undefined} Callback result.
|
||||
* @template T
|
||||
*/
|
||||
@@ -170,7 +171,8 @@ class ExecutorGroup {
|
||||
resolution,
|
||||
rotation,
|
||||
hitTolerance,
|
||||
callback
|
||||
callback,
|
||||
declutteredFeatures
|
||||
) {
|
||||
hitTolerance = Math.round(hitTolerance);
|
||||
const contextSize = hitTolerance * 2 + 1;
|
||||
@@ -232,7 +234,17 @@ class ExecutorGroup {
|
||||
for (let j = 0; j < contextSize; j++) {
|
||||
if (mask[i][j]) {
|
||||
if (imageData[(j * contextSize + i) * 4 + 3] > 0) {
|
||||
const result = callback(feature);
|
||||
let result;
|
||||
if (
|
||||
!(
|
||||
declutteredFeatures &&
|
||||
(builderType == BuilderType.IMAGE ||
|
||||
builderType == BuilderType.TEXT)
|
||||
) ||
|
||||
declutteredFeatures.indexOf(feature) !== -1
|
||||
) {
|
||||
result = callback(feature);
|
||||
}
|
||||
if (result) {
|
||||
return result;
|
||||
} else {
|
||||
@@ -306,6 +318,7 @@ class ExecutorGroup {
|
||||
* @param {boolean} snapToPixel Snap point symbols and test to integer pixel.
|
||||
* @param {Array<import("./BuilderType.js").default>=} opt_builderTypes Ordered replay types to replay.
|
||||
* Default is {@link module:ol/render/replay~ORDER}
|
||||
* @param {import("rbush").default=} opt_declutterTree Declutter tree.
|
||||
*/
|
||||
execute(
|
||||
context,
|
||||
@@ -313,7 +326,8 @@ class ExecutorGroup {
|
||||
transform,
|
||||
viewRotation,
|
||||
snapToPixel,
|
||||
opt_builderTypes
|
||||
opt_builderTypes,
|
||||
opt_declutterTree
|
||||
) {
|
||||
/** @type {Array<number>} */
|
||||
const zs = Object.keys(this.executorsByZIndex_).map(Number);
|
||||
@@ -328,6 +342,9 @@ class ExecutorGroup {
|
||||
|
||||
const builderTypes = opt_builderTypes ? opt_builderTypes : ORDER;
|
||||
let i, ii, j, jj, replays, replay;
|
||||
if (opt_declutterTree) {
|
||||
zs.reverse();
|
||||
}
|
||||
for (i = 0, ii = zs.length; i < ii; ++i) {
|
||||
const zIndexKey = zs[i].toString();
|
||||
replays = this.executorsByZIndex_[zIndexKey];
|
||||
@@ -340,7 +357,8 @@ class ExecutorGroup {
|
||||
contextScale,
|
||||
transform,
|
||||
viewRotation,
|
||||
snapToPixel
|
||||
snapToPixel,
|
||||
opt_declutterTree
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,13 @@ class CanvasImageBuilder extends CanvasBuilder {
|
||||
* @type {number|undefined}
|
||||
*/
|
||||
this.width_ = undefined;
|
||||
|
||||
/**
|
||||
* Data shared with a text builder for combined decluttering.
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
this.sharedData_ = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,6 +132,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
||||
(this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_,
|
||||
],
|
||||
Math.ceil(this.width_ * this.imagePixelRatio_),
|
||||
this.sharedData_,
|
||||
]);
|
||||
this.hitDetectionInstructions.push([
|
||||
CanvasInstruction.DRAW_IMAGE,
|
||||
@@ -142,6 +150,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
||||
this.rotation_,
|
||||
this.scale_,
|
||||
this.width_,
|
||||
this.sharedData_,
|
||||
]);
|
||||
this.endGeometry(feature);
|
||||
}
|
||||
@@ -178,6 +187,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
||||
(this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_,
|
||||
],
|
||||
Math.ceil(this.width_ * this.imagePixelRatio_),
|
||||
this.sharedData_,
|
||||
]);
|
||||
this.hitDetectionInstructions.push([
|
||||
CanvasInstruction.DRAW_IMAGE,
|
||||
@@ -195,12 +205,13 @@ class CanvasImageBuilder extends CanvasBuilder {
|
||||
this.rotation_,
|
||||
this.scale_,
|
||||
this.width_,
|
||||
this.sharedData_,
|
||||
]);
|
||||
this.endGeometry(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {import("./Builder.js").SerializableInstructions} the serializable instructions.
|
||||
* @return {import("../canvas.js").SerializableInstructions} the serializable instructions.
|
||||
*/
|
||||
finish() {
|
||||
this.reverseHitDetectionInstructions();
|
||||
@@ -223,8 +234,9 @@ class CanvasImageBuilder extends CanvasBuilder {
|
||||
|
||||
/**
|
||||
* @param {import("../../style/Image.js").default} imageStyle Image style.
|
||||
* @param {Object=} opt_sharedData Shared data.
|
||||
*/
|
||||
setImageStyle(imageStyle) {
|
||||
setImageStyle(imageStyle, opt_sharedData) {
|
||||
const anchor = imageStyle.getAnchor();
|
||||
const size = imageStyle.getSize();
|
||||
const hitDetectionImage = imageStyle.getHitDetectionImage();
|
||||
@@ -243,6 +255,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
||||
this.rotation_ = imageStyle.getRotation();
|
||||
this.scale_ = imageStyle.getScaleArray();
|
||||
this.width_ = size[0];
|
||||
this.sharedData_ = opt_sharedData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ class CanvasLineStringBuilder extends CanvasBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {import("./Builder.js").SerializableInstructions} the serializable instructions.
|
||||
* @return {import("../canvas.js").SerializableInstructions} the serializable instructions.
|
||||
*/
|
||||
finish() {
|
||||
const state = this.state;
|
||||
|
||||
@@ -220,7 +220,7 @@ class CanvasPolygonBuilder extends CanvasBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {import("./Builder.js").SerializableInstructions} the serializable instructions.
|
||||
* @return {import("../canvas.js").SerializableInstructions} the serializable instructions.
|
||||
*/
|
||||
finish() {
|
||||
this.reverseHitDetectionInstructions();
|
||||
|
||||
@@ -138,10 +138,17 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
* @type {string}
|
||||
*/
|
||||
this.strokeKey_ = '';
|
||||
|
||||
/**
|
||||
* Data shared with an image builder for combined decluttering.
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
this.sharedData_ = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {import("./Builder.js").SerializableInstructions} the serializable instructions.
|
||||
* @return {import("../canvas.js").SerializableInstructions} the serializable instructions.
|
||||
*/
|
||||
finish() {
|
||||
const instructions = super.finish();
|
||||
@@ -328,6 +335,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
this.textRotation_,
|
||||
[1, 1],
|
||||
NaN,
|
||||
this.sharedData_,
|
||||
padding == defaultPadding
|
||||
? defaultPadding
|
||||
: padding.map(function (p) {
|
||||
@@ -359,6 +367,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
this.textRotation_,
|
||||
[scale, scale],
|
||||
NaN,
|
||||
this.sharedData_,
|
||||
padding,
|
||||
!!textState.backgroundFill,
|
||||
!!textState.backgroundStroke,
|
||||
@@ -475,8 +484,9 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
|
||||
/**
|
||||
* @param {import("../../style/Text.js").default} textStyle Text style.
|
||||
* @param {Object=} opt_sharedData Shared data.
|
||||
*/
|
||||
setTextStyle(textStyle) {
|
||||
setTextStyle(textStyle, opt_sharedData) {
|
||||
let textState, fillState, strokeState;
|
||||
if (!textStyle) {
|
||||
this.text_ = '';
|
||||
@@ -576,6 +586,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
||||
: '|' + getUid(fillState.fillStyle)
|
||||
: '';
|
||||
}
|
||||
this.sharedData_ = opt_sharedData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,10 @@ class CompositeMapRenderer extends MapRenderer {
|
||||
const viewState = frameState.viewState;
|
||||
|
||||
this.children_.length = 0;
|
||||
/**
|
||||
* @type {Array<import("../layer/BaseVector.js").default>}
|
||||
*/
|
||||
const declutterLayers = [];
|
||||
let previousElement = null;
|
||||
for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) {
|
||||
const layerState = layerStatesArray[i];
|
||||
@@ -123,8 +127,13 @@ class CompositeMapRenderer extends MapRenderer {
|
||||
this.children_.push(element);
|
||||
previousElement = element;
|
||||
}
|
||||
if ('getDeclutter' in layer) {
|
||||
declutterLayers.push(layer);
|
||||
}
|
||||
}
|
||||
for (let i = declutterLayers.length - 1; i >= 0; --i) {
|
||||
declutterLayers[i].renderDeclutter(frameState);
|
||||
}
|
||||
super.renderFrame(frameState);
|
||||
|
||||
replaceChildren(this.element_, this.children_);
|
||||
|
||||
|
||||
@@ -25,6 +25,11 @@ class LayerRenderer extends Observable {
|
||||
* @type {LayerType}
|
||||
*/
|
||||
this.layer_ = layer;
|
||||
|
||||
/**
|
||||
* @type {import("../render/canvas/ExecutorGroup").default}
|
||||
*/
|
||||
this.declutterExecutorGroup = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -211,9 +211,12 @@ class MapRenderer extends Disposable {
|
||||
|
||||
/**
|
||||
* Render.
|
||||
* @abstract
|
||||
* @param {?import("../PluggableMap.js").FrameState} frameState Frame state.
|
||||
*/
|
||||
renderFrame(frameState) {}
|
||||
renderFrame(frameState) {
|
||||
abstract();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
|
||||
|
||||
@@ -6,6 +6,7 @@ import CanvasVectorLayerRenderer from './VectorLayer.js';
|
||||
import EventType from '../../events/EventType.js';
|
||||
import ImageCanvas from '../../ImageCanvas.js';
|
||||
import ImageState from '../../ImageState.js';
|
||||
import RBush from 'rbush';
|
||||
import ViewHint from '../../ViewHint.js';
|
||||
import {apply, compose, create} from '../../transform.js';
|
||||
import {assign} from '../../obj.js';
|
||||
@@ -114,6 +115,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
||||
{},
|
||||
frameState,
|
||||
{
|
||||
declutterTree: new RBush(9),
|
||||
extent: renderedExtent,
|
||||
size: [width, height],
|
||||
viewState: /** @type {import("../../View.js").State} */ (assign(
|
||||
@@ -137,6 +139,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
||||
) {
|
||||
vectorRenderer.clipping = false;
|
||||
vectorRenderer.renderFrame(imageFrameState, null);
|
||||
vectorRenderer.renderDeclutter(imageFrameState);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
@@ -183,6 +186,10 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
||||
*/
|
||||
postRender() {}
|
||||
|
||||
/**
|
||||
*/
|
||||
renderDeclutter() {}
|
||||
|
||||
/**
|
||||
* @param {import("../../coordinate.js").Coordinate} coordinate Coordinate.
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
|
||||
@@ -128,6 +128,11 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
*/
|
||||
this.replayGroupChanged = true;
|
||||
|
||||
/**
|
||||
* @type {import("../../render/canvas/ExecutorGroup").default}
|
||||
*/
|
||||
this.declutterExecutorGroup = null;
|
||||
|
||||
/**
|
||||
* Clipping to be performed by `renderFrame()`
|
||||
* @type {boolean}
|
||||
@@ -148,6 +153,73 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
super.useContainer(target, transform, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ExecutorGroup} executorGroup Executor group.
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
* @param {import("rbush").default=} opt_declutterTree Declutter tree.
|
||||
*/
|
||||
renderWorlds(executorGroup, frameState, opt_declutterTree) {
|
||||
const extent = frameState.extent;
|
||||
const viewState = frameState.viewState;
|
||||
const center = viewState.center;
|
||||
const resolution = viewState.resolution;
|
||||
const projection = viewState.projection;
|
||||
const rotation = viewState.rotation;
|
||||
const projectionExtent = projection.getExtent();
|
||||
const vectorSource = this.getLayer().getSource();
|
||||
const pixelRatio = frameState.pixelRatio;
|
||||
const viewHints = frameState.viewHints;
|
||||
const snapToPixel = !(
|
||||
viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]
|
||||
);
|
||||
const context = this.context;
|
||||
const width = Math.round(frameState.size[0] * pixelRatio);
|
||||
const height = Math.round(frameState.size[1] * pixelRatio);
|
||||
|
||||
const multiWorld = vectorSource.getWrapX() && projection.canWrapX();
|
||||
const worldWidth = multiWorld ? getWidth(projectionExtent) : null;
|
||||
const endWorld = multiWorld
|
||||
? Math.ceil((extent[2] - projectionExtent[2]) / worldWidth) + 1
|
||||
: 1;
|
||||
let world = multiWorld
|
||||
? Math.floor((extent[0] - projectionExtent[0]) / worldWidth)
|
||||
: 0;
|
||||
do {
|
||||
const transform = this.getRenderTransform(
|
||||
center,
|
||||
resolution,
|
||||
rotation,
|
||||
pixelRatio,
|
||||
width,
|
||||
height,
|
||||
world * worldWidth
|
||||
);
|
||||
executorGroup.execute(
|
||||
context,
|
||||
1,
|
||||
transform,
|
||||
rotation,
|
||||
snapToPixel,
|
||||
undefined,
|
||||
opt_declutterTree
|
||||
);
|
||||
} while (++world < endWorld);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render declutter items for this layer
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
*/
|
||||
renderDeclutter(frameState) {
|
||||
if (this.declutterExecutorGroup) {
|
||||
this.renderWorlds(
|
||||
this.declutterExecutorGroup,
|
||||
frameState,
|
||||
frameState.declutterTree
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the layer.
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
@@ -169,7 +241,11 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
const canvas = context.canvas;
|
||||
|
||||
const replayGroup = this.replayGroup_;
|
||||
if (!replayGroup || replayGroup.isEmpty()) {
|
||||
const declutterExecutorGroup = this.declutterExecutorGroup;
|
||||
if (
|
||||
(!replayGroup || replayGroup.isEmpty()) &&
|
||||
(!declutterExecutorGroup || declutterExecutorGroup.isEmpty())
|
||||
) {
|
||||
if (!this.containerReused && canvas.width > 0) {
|
||||
canvas.width = 0;
|
||||
}
|
||||
@@ -191,14 +267,8 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
|
||||
this.preRender(context, frameState);
|
||||
|
||||
const extent = frameState.extent;
|
||||
const viewState = frameState.viewState;
|
||||
const center = viewState.center;
|
||||
const resolution = viewState.resolution;
|
||||
const projection = viewState.projection;
|
||||
const rotation = viewState.rotation;
|
||||
const projectionExtent = projection.getExtent();
|
||||
const vectorSource = this.getLayer().getSource();
|
||||
|
||||
// clipped rendering if layer extent is set
|
||||
let clipped = false;
|
||||
@@ -212,38 +282,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
const viewHints = frameState.viewHints;
|
||||
const snapToPixel = !(
|
||||
viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]
|
||||
);
|
||||
|
||||
const multiWorld = vectorSource.getWrapX() && projection.canWrapX();
|
||||
const worldWidth = multiWorld ? getWidth(projectionExtent) : null;
|
||||
const endWorld = multiWorld
|
||||
? Math.ceil((extent[2] - projectionExtent[2]) / worldWidth) + 1
|
||||
: 1;
|
||||
let world = multiWorld
|
||||
? Math.floor((extent[0] - projectionExtent[0]) / worldWidth)
|
||||
: 0;
|
||||
do {
|
||||
const transform = this.getRenderTransform(
|
||||
center,
|
||||
resolution,
|
||||
rotation,
|
||||
pixelRatio,
|
||||
width,
|
||||
height,
|
||||
world * worldWidth
|
||||
);
|
||||
replayGroup.execute(
|
||||
context,
|
||||
1,
|
||||
transform,
|
||||
rotation,
|
||||
snapToPixel,
|
||||
undefined
|
||||
);
|
||||
} while (++world < endWorld);
|
||||
this.renderWorlds(replayGroup, frameState);
|
||||
|
||||
if (clipped) {
|
||||
context.restore();
|
||||
@@ -378,26 +417,41 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
const resolution = frameState.viewState.resolution;
|
||||
const rotation = frameState.viewState.rotation;
|
||||
const layer = this.getLayer();
|
||||
|
||||
/** @type {!Object<string, boolean>} */
|
||||
const features = {};
|
||||
|
||||
const result = this.replayGroup_.forEachFeatureAtCoordinate(
|
||||
coordinate,
|
||||
resolution,
|
||||
rotation,
|
||||
hitTolerance,
|
||||
/**
|
||||
* @param {import("../../Feature.js").FeatureLike} feature Feature.
|
||||
* @return {?} Callback result.
|
||||
*/
|
||||
function (feature) {
|
||||
const featureCallback = function (feature) {
|
||||
const key = getUid(feature);
|
||||
if (!(key in features)) {
|
||||
features[key] = true;
|
||||
return callback(feature, layer);
|
||||
}
|
||||
};
|
||||
|
||||
let result;
|
||||
const executorGroups = [this.replayGroup_];
|
||||
if (this.declutterExecutorGroup) {
|
||||
executorGroups.push(this.declutterExecutorGroup);
|
||||
}
|
||||
executorGroups.forEach((executorGroup) => {
|
||||
result =
|
||||
result ||
|
||||
executorGroup.forEachFeatureAtCoordinate(
|
||||
coordinate,
|
||||
resolution,
|
||||
rotation,
|
||||
hitTolerance,
|
||||
featureCallback,
|
||||
executorGroup === this.declutterExecutorGroup
|
||||
? frameState.declutterTree.all().map((item) => item.value)
|
||||
: null
|
||||
);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -531,6 +585,16 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
pixelRatio
|
||||
);
|
||||
|
||||
let declutterBuilderGroup;
|
||||
if (this.getLayer().getDeclutter()) {
|
||||
declutterBuilderGroup = new CanvasBuilderGroup(
|
||||
getRenderTolerance(resolution, pixelRatio),
|
||||
extent,
|
||||
resolution,
|
||||
pixelRatio
|
||||
);
|
||||
}
|
||||
|
||||
const userProjection = getUserProjection();
|
||||
let userTransform;
|
||||
if (userProjection) {
|
||||
@@ -568,7 +632,8 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
squaredTolerance,
|
||||
styles,
|
||||
replayGroup,
|
||||
userTransform
|
||||
userTransform,
|
||||
declutterBuilderGroup
|
||||
);
|
||||
this.dirty_ = this.dirty_ || dirty;
|
||||
}
|
||||
@@ -595,6 +660,17 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
vectorLayer.getRenderBuffer()
|
||||
);
|
||||
|
||||
if (declutterBuilderGroup) {
|
||||
this.declutterExecutorGroup = new ExecutorGroup(
|
||||
extent,
|
||||
resolution,
|
||||
pixelRatio,
|
||||
vectorSource.getOverlaps(),
|
||||
declutterBuilderGroup.finish(),
|
||||
vectorLayer.getRenderBuffer()
|
||||
);
|
||||
}
|
||||
|
||||
this.renderedResolution_ = resolution;
|
||||
this.renderedRevision_ = vectorLayerRevision;
|
||||
this.renderedRenderOrder_ = vectorLayerRenderOrder;
|
||||
@@ -614,6 +690,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
* @param {import("../../style/Style.js").default|Array<import("../../style/Style.js").default>} styles The style or array of styles.
|
||||
* @param {import("../../render/canvas/BuilderGroup.js").default} builderGroup Builder group.
|
||||
* @param {import("../../proj.js").TransformFunction=} opt_transform Transform from user to view projection.
|
||||
* @param {import("../../render/canvas/BuilderGroup.js").default=} opt_declutterBuilderGroup Builder for decluttering.
|
||||
* @return {boolean} `true` if an image is loading.
|
||||
*/
|
||||
renderFeature(
|
||||
@@ -621,7 +698,8 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
squaredTolerance,
|
||||
styles,
|
||||
builderGroup,
|
||||
opt_transform
|
||||
opt_transform,
|
||||
opt_declutterBuilderGroup
|
||||
) {
|
||||
if (!styles) {
|
||||
return false;
|
||||
@@ -636,7 +714,8 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
styles[i],
|
||||
squaredTolerance,
|
||||
this.boundHandleStyleImageChange_,
|
||||
opt_transform
|
||||
opt_transform,
|
||||
opt_declutterBuilderGroup
|
||||
) || loading;
|
||||
}
|
||||
} else {
|
||||
@@ -646,7 +725,8 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
styles,
|
||||
squaredTolerance,
|
||||
this.boundHandleStyleImageChange_,
|
||||
opt_transform
|
||||
opt_transform,
|
||||
opt_declutterBuilderGroup
|
||||
);
|
||||
}
|
||||
return loading;
|
||||
|
||||
@@ -260,6 +260,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
}
|
||||
|
||||
const source = layer.getSource();
|
||||
const declutter = layer.getDeclutter();
|
||||
const sourceTileGrid = source.getTileGrid();
|
||||
const tileGrid = source.getTileGridForProjection(projection);
|
||||
const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
|
||||
@@ -268,6 +269,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
const layerUid = getUid(layer);
|
||||
delete tile.hitDetectionImageData[layerUid];
|
||||
tile.executorGroups[layerUid] = [];
|
||||
if (declutter) {
|
||||
tile.declutterExecutorGroups[layerUid] = [];
|
||||
}
|
||||
for (let t = 0, tt = sourceTiles.length; t < tt; ++t) {
|
||||
const sourceTile = sourceTiles[t];
|
||||
if (sourceTile.getState() != TileState.LOADED) {
|
||||
@@ -292,6 +296,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
resolution,
|
||||
pixelRatio
|
||||
);
|
||||
const declutterBuilderGroup = declutter
|
||||
? new CanvasBuilderGroup(0, sharedExtent, resolution, pixelRatio)
|
||||
: undefined;
|
||||
const squaredTolerance = getSquaredRenderTolerance(
|
||||
resolution,
|
||||
pixelRatio
|
||||
@@ -313,7 +320,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
feature,
|
||||
squaredTolerance,
|
||||
styles,
|
||||
builderGroup
|
||||
builderGroup,
|
||||
declutterBuilderGroup
|
||||
);
|
||||
this.dirty_ = this.dirty_ || dirty;
|
||||
builderState.dirty = builderState.dirty || dirty;
|
||||
@@ -337,7 +345,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
// no need to clip when the render tile is covered by a single source tile
|
||||
const replayExtent =
|
||||
layer.getRenderMode() !== VectorTileRenderType.VECTOR &&
|
||||
layer.getDeclutter() &&
|
||||
declutter &&
|
||||
sourceTiles.length === 1
|
||||
? null
|
||||
: sharedExtent;
|
||||
@@ -350,6 +358,17 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
layer.getRenderBuffer()
|
||||
);
|
||||
tile.executorGroups[layerUid].push(renderingReplayGroup);
|
||||
if (declutterBuilderGroup) {
|
||||
const declutterExecutorGroup = new CanvasExecutorGroup(
|
||||
replayExtent,
|
||||
resolution,
|
||||
pixelRatio,
|
||||
source.getOverlaps(),
|
||||
declutterBuilderGroup.finish(),
|
||||
layer.getRenderBuffer()
|
||||
);
|
||||
tile.declutterExecutorGroups[layerUid].push(declutterExecutorGroup);
|
||||
}
|
||||
}
|
||||
builderState.renderedRevision = revision;
|
||||
builderState.renderedZ = tile.sourceZ;
|
||||
@@ -395,7 +414,13 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const executorGroups = tile.executorGroups[getUid(layer)];
|
||||
const layerUid = getUid(layer);
|
||||
const executorGroups = [tile.executorGroups[layerUid]];
|
||||
const declutterExecutorGroups = tile.declutterExecutorGroups[layerUid];
|
||||
if (declutterExecutorGroups) {
|
||||
executorGroups.push(declutterExecutorGroups);
|
||||
}
|
||||
executorGroups.forEach((executorGroups) => {
|
||||
for (let t = 0, tt = executorGroups.length; t < tt; ++t) {
|
||||
const executorGroup = executorGroups[t];
|
||||
found =
|
||||
@@ -420,9 +445,13 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
return callback(feature, layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
executorGroups === declutterExecutorGroups
|
||||
? frameState.declutterTree.all().map((item) => item.value)
|
||||
: null
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
return found;
|
||||
}
|
||||
@@ -539,6 +568,70 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
this.renderIfReadyAndVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render declutter items for this layer
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
*/
|
||||
renderDeclutter(frameState) {
|
||||
const viewHints = frameState.viewHints;
|
||||
const hifi = !(
|
||||
viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]
|
||||
);
|
||||
const tiles = /** @type {Array<import("../../VectorRenderTile.js").default>} */ (this
|
||||
.renderedTiles);
|
||||
for (let i = 0, ii = tiles.length; i < ii; ++i) {
|
||||
const tile = tiles[i];
|
||||
const declutterExecutorGroups =
|
||||
tile.declutterExecutorGroups[getUid(this.getLayer())];
|
||||
if (declutterExecutorGroups) {
|
||||
for (let j = declutterExecutorGroups.length - 1; j >= 0; --j) {
|
||||
declutterExecutorGroups[j].execute(
|
||||
this.context,
|
||||
1,
|
||||
this.getTileRenderTransform(tile, frameState),
|
||||
frameState.viewState.rotation,
|
||||
hifi,
|
||||
undefined,
|
||||
frameState.declutterTree
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTileRenderTransform(tile, frameState) {
|
||||
const pixelRatio = frameState.pixelRatio;
|
||||
const viewState = frameState.viewState;
|
||||
const center = viewState.center;
|
||||
const resolution = viewState.resolution;
|
||||
const rotation = viewState.rotation;
|
||||
const size = frameState.size;
|
||||
const width = Math.round(size[0] * pixelRatio);
|
||||
const height = Math.round(size[1] * pixelRatio);
|
||||
|
||||
const source = this.getLayer().getSource();
|
||||
const tileGrid = source.getTileGridForProjection(
|
||||
frameState.viewState.projection
|
||||
);
|
||||
const tileCoord = tile.tileCoord;
|
||||
const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
|
||||
const worldOffset =
|
||||
tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tileExtent[0];
|
||||
const transform = multiply(
|
||||
scale(this.inversePixelTransform.slice(), 1 / pixelRatio, 1 / pixelRatio),
|
||||
this.getRenderTransform(
|
||||
center,
|
||||
resolution,
|
||||
rotation,
|
||||
pixelRatio,
|
||||
width,
|
||||
height,
|
||||
worldOffset
|
||||
)
|
||||
);
|
||||
return transform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the layer.
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
@@ -573,47 +666,17 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
|
||||
const context = this.context;
|
||||
const replayTypes = VECTOR_REPLAYS[renderMode];
|
||||
const pixelRatio = frameState.pixelRatio;
|
||||
const viewState = frameState.viewState;
|
||||
const center = viewState.center;
|
||||
const resolution = viewState.resolution;
|
||||
const rotation = viewState.rotation;
|
||||
const size = frameState.size;
|
||||
|
||||
const width = Math.round(size[0] * pixelRatio);
|
||||
const height = Math.round(size[1] * pixelRatio);
|
||||
|
||||
const tiles = this.renderedTiles;
|
||||
const tileGrid = source.getTileGridForProjection(
|
||||
frameState.viewState.projection
|
||||
);
|
||||
const clips = [];
|
||||
const clipZs = [];
|
||||
for (let i = tiles.length - 1; i >= 0; --i) {
|
||||
const tile = /** @type {import("../../VectorRenderTile.js").default} */ (tiles[
|
||||
i
|
||||
]);
|
||||
const tileCoord = tile.tileCoord;
|
||||
const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
|
||||
const worldOffset =
|
||||
tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] -
|
||||
tileExtent[0];
|
||||
const transform = multiply(
|
||||
scale(
|
||||
this.inversePixelTransform.slice(),
|
||||
1 / pixelRatio,
|
||||
1 / pixelRatio
|
||||
),
|
||||
this.getRenderTransform(
|
||||
center,
|
||||
resolution,
|
||||
rotation,
|
||||
pixelRatio,
|
||||
width,
|
||||
height,
|
||||
worldOffset
|
||||
)
|
||||
);
|
||||
const transform = this.getTileRenderTransform(tile, frameState);
|
||||
const executorGroups = tile.executorGroups[getUid(layer)];
|
||||
let clipped = false;
|
||||
for (let t = 0, tt = executorGroups.length; t < tt; ++t) {
|
||||
@@ -692,10 +755,17 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
* @param {import("../../Feature.js").FeatureLike} feature Feature.
|
||||
* @param {number} squaredTolerance Squared tolerance.
|
||||
* @param {import("../../style/Style.js").default|Array<import("../../style/Style.js").default>} styles The style or array of styles.
|
||||
* @param {import("../../render/canvas/BuilderGroup.js").default} executorGroup Replay group.
|
||||
* @param {import("../../render/canvas/BuilderGroup.js").default} builderGroup Replay group.
|
||||
* @param {import("../../render/canvas/BuilderGroup.js").default=} opt_declutterBuilderGroup Builder group for decluttering.
|
||||
* @return {boolean} `true` if an image is loading.
|
||||
*/
|
||||
renderFeature(feature, squaredTolerance, styles, executorGroup) {
|
||||
renderFeature(
|
||||
feature,
|
||||
squaredTolerance,
|
||||
styles,
|
||||
builderGroup,
|
||||
opt_declutterBuilderGroup
|
||||
) {
|
||||
if (!styles) {
|
||||
return false;
|
||||
}
|
||||
@@ -704,20 +774,24 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
for (let i = 0, ii = styles.length; i < ii; ++i) {
|
||||
loading =
|
||||
renderFeature(
|
||||
executorGroup,
|
||||
builderGroup,
|
||||
feature,
|
||||
styles[i],
|
||||
squaredTolerance,
|
||||
this.boundHandleStyleImageChange_
|
||||
this.boundHandleStyleImageChange_,
|
||||
undefined,
|
||||
opt_declutterBuilderGroup
|
||||
) || loading;
|
||||
}
|
||||
} else {
|
||||
loading = renderFeature(
|
||||
executorGroup,
|
||||
builderGroup,
|
||||
feature,
|
||||
styles,
|
||||
squaredTolerance,
|
||||
this.boundHandleStyleImageChange_
|
||||
this.boundHandleStyleImageChange_,
|
||||
undefined,
|
||||
opt_declutterBuilderGroup
|
||||
);
|
||||
}
|
||||
return loading;
|
||||
|
||||
@@ -62,8 +62,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 {import("../render/canvas/BuilderGroup.js").default=} opt_declutterBuilderGroup Builder for decluttering.
|
||||
*/
|
||||
function renderCircleGeometry(builderGroup, geometry, style, feature) {
|
||||
function renderCircleGeometry(
|
||||
builderGroup,
|
||||
geometry,
|
||||
style,
|
||||
feature,
|
||||
opt_declutterBuilderGroup
|
||||
) {
|
||||
const fillStyle = style.getFill();
|
||||
const strokeStyle = style.getStroke();
|
||||
if (fillStyle || strokeStyle) {
|
||||
@@ -76,7 +83,7 @@ function renderCircleGeometry(builderGroup, geometry, style, feature) {
|
||||
}
|
||||
const textStyle = style.getText();
|
||||
if (textStyle) {
|
||||
const textReplay = builderGroup.getBuilder(
|
||||
const textReplay = (opt_declutterBuilderGroup || builderGroup).getBuilder(
|
||||
style.getZIndex(),
|
||||
BuilderType.TEXT
|
||||
);
|
||||
@@ -92,8 +99,8 @@ function renderCircleGeometry(builderGroup, geometry, style, feature) {
|
||||
* @param {number} squaredTolerance Squared tolerance.
|
||||
* @param {function(import("../events/Event.js").default): void} listener Listener function.
|
||||
* @param {import("../proj.js").TransformFunction} [opt_transform] Transform from user to view projection.
|
||||
* @param {import("../render/canvas/BuilderGroup.js").default=} opt_declutterBuilderGroup Builder for decluttering.
|
||||
* @return {boolean} `true` if style is loading.
|
||||
* @template T
|
||||
*/
|
||||
export function renderFeature(
|
||||
replayGroup,
|
||||
@@ -101,7 +108,8 @@ export function renderFeature(
|
||||
style,
|
||||
squaredTolerance,
|
||||
listener,
|
||||
opt_transform
|
||||
opt_transform,
|
||||
opt_declutterBuilderGroup
|
||||
) {
|
||||
let loading = false;
|
||||
const imageStyle = style.getImage();
|
||||
@@ -123,7 +131,8 @@ export function renderFeature(
|
||||
feature,
|
||||
style,
|
||||
squaredTolerance,
|
||||
opt_transform
|
||||
opt_transform,
|
||||
opt_declutterBuilderGroup
|
||||
);
|
||||
|
||||
return loading;
|
||||
@@ -135,13 +144,15 @@ export function renderFeature(
|
||||
* @param {import("../style/Style.js").default} style Style.
|
||||
* @param {number} squaredTolerance Squared tolerance.
|
||||
* @param {import("../proj.js").TransformFunction} [opt_transform] Optional transform function.
|
||||
* @param {import("../render/canvas/BuilderGroup.js").default=} opt_declutterBuilderGroup Builder for decluttering.
|
||||
*/
|
||||
function renderFeatureInternal(
|
||||
replayGroup,
|
||||
feature,
|
||||
style,
|
||||
squaredTolerance,
|
||||
opt_transform
|
||||
opt_transform,
|
||||
opt_declutterBuilderGroup
|
||||
) {
|
||||
const geometry = style.getGeometryFunction()(feature);
|
||||
if (!geometry) {
|
||||
@@ -156,7 +167,13 @@ function renderFeatureInternal(
|
||||
renderGeometry(replayGroup, simplifiedGeometry, style, feature);
|
||||
} else {
|
||||
const geometryRenderer = GEOMETRY_RENDERERS[simplifiedGeometry.getType()];
|
||||
geometryRenderer(replayGroup, simplifiedGeometry, style, feature);
|
||||
geometryRenderer(
|
||||
replayGroup,
|
||||
simplifiedGeometry,
|
||||
style,
|
||||
feature,
|
||||
opt_declutterBuilderGroup
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,18 +204,26 @@ function renderGeometry(replayGroup, geometry, style, feature) {
|
||||
* @param {import("../geom/GeometryCollection.js").default} geometry Geometry.
|
||||
* @param {import("../style/Style.js").default} style Style.
|
||||
* @param {import("../Feature.js").default} feature Feature.
|
||||
* @param {import("../render/canvas/BuilderGroup.js").default=} opt_declutterBuilderGroup Builder for decluttering.
|
||||
*/
|
||||
function renderGeometryCollectionGeometry(
|
||||
replayGroup,
|
||||
geometry,
|
||||
style,
|
||||
feature
|
||||
feature,
|
||||
opt_declutterBuilderGroup
|
||||
) {
|
||||
const geometries = geometry.getGeometriesArray();
|
||||
let i, ii;
|
||||
for (i = 0, ii = geometries.length; i < ii; ++i) {
|
||||
const geometryRenderer = GEOMETRY_RENDERERS[geometries[i].getType()];
|
||||
geometryRenderer(replayGroup, geometries[i], style, feature);
|
||||
geometryRenderer(
|
||||
replayGroup,
|
||||
geometries[i],
|
||||
style,
|
||||
feature,
|
||||
opt_declutterBuilderGroup
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,8 +232,15 @@ function renderGeometryCollectionGeometry(
|
||||
* @param {import("../geom/LineString.js").default|import("../render/Feature.js").default} geometry Geometry.
|
||||
* @param {import("../style/Style.js").default} style Style.
|
||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||
* @param {import("../render/canvas/BuilderGroup.js").default=} opt_declutterBuilderGroup Builder for decluttering.
|
||||
*/
|
||||
function renderLineStringGeometry(builderGroup, geometry, style, feature) {
|
||||
function renderLineStringGeometry(
|
||||
builderGroup,
|
||||
geometry,
|
||||
style,
|
||||
feature,
|
||||
opt_declutterBuilderGroup
|
||||
) {
|
||||
const strokeStyle = style.getStroke();
|
||||
if (strokeStyle) {
|
||||
const lineStringReplay = builderGroup.getBuilder(
|
||||
@@ -220,7 +252,7 @@ function renderLineStringGeometry(builderGroup, geometry, style, feature) {
|
||||
}
|
||||
const textStyle = style.getText();
|
||||
if (textStyle) {
|
||||
const textReplay = builderGroup.getBuilder(
|
||||
const textReplay = (opt_declutterBuilderGroup || builderGroup).getBuilder(
|
||||
style.getZIndex(),
|
||||
BuilderType.TEXT
|
||||
);
|
||||
@@ -234,8 +266,15 @@ function renderLineStringGeometry(builderGroup, geometry, style, feature) {
|
||||
* @param {import("../geom/MultiLineString.js").default|import("../render/Feature.js").default} geometry Geometry.
|
||||
* @param {import("../style/Style.js").default} style Style.
|
||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||
* @param {import("../render/canvas/BuilderGroup.js").default=} opt_declutterBuilderGroup Builder for decluttering.
|
||||
*/
|
||||
function renderMultiLineStringGeometry(builderGroup, geometry, style, feature) {
|
||||
function renderMultiLineStringGeometry(
|
||||
builderGroup,
|
||||
geometry,
|
||||
style,
|
||||
feature,
|
||||
opt_declutterBuilderGroup
|
||||
) {
|
||||
const strokeStyle = style.getStroke();
|
||||
if (strokeStyle) {
|
||||
const lineStringReplay = builderGroup.getBuilder(
|
||||
@@ -247,7 +286,7 @@ function renderMultiLineStringGeometry(builderGroup, geometry, style, feature) {
|
||||
}
|
||||
const textStyle = style.getText();
|
||||
if (textStyle) {
|
||||
const textReplay = builderGroup.getBuilder(
|
||||
const textReplay = (opt_declutterBuilderGroup || builderGroup).getBuilder(
|
||||
style.getZIndex(),
|
||||
BuilderType.TEXT
|
||||
);
|
||||
@@ -261,8 +300,15 @@ function renderMultiLineStringGeometry(builderGroup, geometry, style, feature) {
|
||||
* @param {import("../geom/MultiPolygon.js").default} geometry Geometry.
|
||||
* @param {import("../style/Style.js").default} style Style.
|
||||
* @param {import("../Feature.js").default} feature Feature.
|
||||
* @param {import("../render/canvas/BuilderGroup.js").default=} opt_declutterBuilderGroup Builder for decluttering.
|
||||
*/
|
||||
function renderMultiPolygonGeometry(builderGroup, geometry, style, feature) {
|
||||
function renderMultiPolygonGeometry(
|
||||
builderGroup,
|
||||
geometry,
|
||||
style,
|
||||
feature,
|
||||
opt_declutterBuilderGroup
|
||||
) {
|
||||
const fillStyle = style.getFill();
|
||||
const strokeStyle = style.getStroke();
|
||||
if (strokeStyle || fillStyle) {
|
||||
@@ -275,7 +321,7 @@ function renderMultiPolygonGeometry(builderGroup, geometry, style, feature) {
|
||||
}
|
||||
const textStyle = style.getText();
|
||||
if (textStyle) {
|
||||
const textReplay = builderGroup.getBuilder(
|
||||
const textReplay = (opt_declutterBuilderGroup || builderGroup).getBuilder(
|
||||
style.getZIndex(),
|
||||
BuilderType.TEXT
|
||||
);
|
||||
@@ -289,9 +335,22 @@ function renderMultiPolygonGeometry(builderGroup, geometry, style, feature) {
|
||||
* @param {import("../geom/Point.js").default|import("../render/Feature.js").default} geometry Geometry.
|
||||
* @param {import("../style/Style.js").default} style Style.
|
||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||
* @param {import("../render/canvas/BuilderGroup.js").default=} opt_declutterBuilderGroup Builder for decluttering.
|
||||
*/
|
||||
function renderPointGeometry(builderGroup, geometry, style, feature) {
|
||||
function renderPointGeometry(
|
||||
builderGroup,
|
||||
geometry,
|
||||
style,
|
||||
feature,
|
||||
opt_declutterBuilderGroup
|
||||
) {
|
||||
const imageStyle = style.getImage();
|
||||
const textStyle = style.getText();
|
||||
let sharedData;
|
||||
if (opt_declutterBuilderGroup) {
|
||||
builderGroup = opt_declutterBuilderGroup;
|
||||
sharedData = imageStyle && textStyle ? {} : undefined;
|
||||
}
|
||||
if (imageStyle) {
|
||||
if (imageStyle.getImageState() != ImageState.LOADED) {
|
||||
return;
|
||||
@@ -300,16 +359,15 @@ function renderPointGeometry(builderGroup, geometry, style, feature) {
|
||||
style.getZIndex(),
|
||||
BuilderType.IMAGE
|
||||
);
|
||||
imageReplay.setImageStyle(imageStyle);
|
||||
imageReplay.setImageStyle(imageStyle, sharedData);
|
||||
imageReplay.drawPoint(geometry, feature);
|
||||
}
|
||||
const textStyle = style.getText();
|
||||
if (textStyle) {
|
||||
const textReplay = builderGroup.getBuilder(
|
||||
style.getZIndex(),
|
||||
BuilderType.TEXT
|
||||
);
|
||||
textReplay.setTextStyle(textStyle);
|
||||
textReplay.setTextStyle(textStyle, sharedData);
|
||||
textReplay.drawText(geometry, feature);
|
||||
}
|
||||
}
|
||||
@@ -319,9 +377,22 @@ function renderPointGeometry(builderGroup, geometry, style, feature) {
|
||||
* @param {import("../geom/MultiPoint.js").default|import("../render/Feature.js").default} geometry Geometry.
|
||||
* @param {import("../style/Style.js").default} style Style.
|
||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||
* @param {import("../render/canvas/BuilderGroup.js").default=} opt_declutterBuilderGroup Builder for decluttering.
|
||||
*/
|
||||
function renderMultiPointGeometry(builderGroup, geometry, style, feature) {
|
||||
function renderMultiPointGeometry(
|
||||
builderGroup,
|
||||
geometry,
|
||||
style,
|
||||
feature,
|
||||
opt_declutterBuilderGroup
|
||||
) {
|
||||
const imageStyle = style.getImage();
|
||||
const textStyle = style.getText();
|
||||
let sharedData;
|
||||
if (opt_declutterBuilderGroup) {
|
||||
builderGroup = opt_declutterBuilderGroup;
|
||||
sharedData = imageStyle && textStyle ? {} : undefined;
|
||||
}
|
||||
if (imageStyle) {
|
||||
if (imageStyle.getImageState() != ImageState.LOADED) {
|
||||
return;
|
||||
@@ -330,16 +401,15 @@ function renderMultiPointGeometry(builderGroup, geometry, style, feature) {
|
||||
style.getZIndex(),
|
||||
BuilderType.IMAGE
|
||||
);
|
||||
imageReplay.setImageStyle(imageStyle);
|
||||
imageReplay.setImageStyle(imageStyle, sharedData);
|
||||
imageReplay.drawMultiPoint(geometry, feature);
|
||||
}
|
||||
const textStyle = style.getText();
|
||||
if (textStyle) {
|
||||
const textReplay = builderGroup.getBuilder(
|
||||
const textReplay = (opt_declutterBuilderGroup || builderGroup).getBuilder(
|
||||
style.getZIndex(),
|
||||
BuilderType.TEXT
|
||||
);
|
||||
textReplay.setTextStyle(textStyle);
|
||||
textReplay.setTextStyle(textStyle, sharedData);
|
||||
textReplay.drawText(geometry, feature);
|
||||
}
|
||||
}
|
||||
@@ -349,8 +419,15 @@ function renderMultiPointGeometry(builderGroup, geometry, style, feature) {
|
||||
* @param {import("../geom/Polygon.js").default|import("../render/Feature.js").default} geometry Geometry.
|
||||
* @param {import("../style/Style.js").default} style Style.
|
||||
* @param {import("../Feature.js").FeatureLike} feature Feature.
|
||||
* @param {import("../render/canvas/BuilderGroup.js").default=} opt_declutterBuilderGroup Builder for decluttering.
|
||||
*/
|
||||
function renderPolygonGeometry(builderGroup, geometry, style, feature) {
|
||||
function renderPolygonGeometry(
|
||||
builderGroup,
|
||||
geometry,
|
||||
style,
|
||||
feature,
|
||||
opt_declutterBuilderGroup
|
||||
) {
|
||||
const fillStyle = style.getFill();
|
||||
const strokeStyle = style.getStroke();
|
||||
if (fillStyle || strokeStyle) {
|
||||
@@ -363,7 +440,7 @@ function renderPolygonGeometry(builderGroup, geometry, style, feature) {
|
||||
}
|
||||
const textStyle = style.getText();
|
||||
if (textStyle) {
|
||||
const textReplay = builderGroup.getBuilder(
|
||||
const textReplay = (opt_declutterBuilderGroup || builderGroup).getBuilder(
|
||||
style.getZIndex(),
|
||||
BuilderType.TEXT
|
||||
);
|
||||
|
||||
@@ -549,6 +549,7 @@ class RasterSource extends ImageSource {
|
||||
this.frameState_ = {
|
||||
animate: false,
|
||||
coordinateToPixelTransform: createTransform(),
|
||||
declutterTree: null,
|
||||
extent: null,
|
||||
index: 0,
|
||||
layerIndex: 0,
|
||||
|
||||
Reference in New Issue
Block a user