Merge pull request #13566 from CNS-Solutions/decluttering
Decluttering mode by style
This commit is contained in:
@@ -808,17 +808,21 @@ class Executor {
|
|||||||
instruction[12]
|
instruction[12]
|
||||||
);
|
);
|
||||||
let width = /** @type {number} */ (instruction[13]);
|
let width = /** @type {number} */ (instruction[13]);
|
||||||
const declutterImageWithText =
|
const declutterMode =
|
||||||
/** @type {import("../canvas.js").DeclutterImageWithText} */ (
|
/** @type {"declutter"|"obstacle"|"none"|undefined} */ (
|
||||||
instruction[14]
|
instruction[14]
|
||||||
);
|
);
|
||||||
|
const declutterImageWithText =
|
||||||
|
/** @type {import("../canvas.js").DeclutterImageWithText} */ (
|
||||||
|
instruction[15]
|
||||||
|
);
|
||||||
|
|
||||||
if (!image && instruction.length >= 19) {
|
if (!image && instruction.length >= 20) {
|
||||||
// create label images
|
// create label images
|
||||||
text = /** @type {string} */ (instruction[18]);
|
text = /** @type {string} */ (instruction[19]);
|
||||||
textKey = /** @type {string} */ (instruction[19]);
|
textKey = /** @type {string} */ (instruction[20]);
|
||||||
strokeKey = /** @type {string} */ (instruction[20]);
|
strokeKey = /** @type {string} */ (instruction[21]);
|
||||||
fillKey = /** @type {string} */ (instruction[21]);
|
fillKey = /** @type {string} */ (instruction[22]);
|
||||||
const labelWithAnchor = this.drawLabelWithPointPlacement_(
|
const labelWithAnchor = this.drawLabelWithPointPlacement_(
|
||||||
text,
|
text,
|
||||||
textKey,
|
textKey,
|
||||||
@@ -827,10 +831,10 @@ class Executor {
|
|||||||
);
|
);
|
||||||
image = labelWithAnchor.label;
|
image = labelWithAnchor.label;
|
||||||
instruction[3] = image;
|
instruction[3] = image;
|
||||||
const textOffsetX = /** @type {number} */ (instruction[22]);
|
const textOffsetX = /** @type {number} */ (instruction[23]);
|
||||||
anchorX = (labelWithAnchor.anchorX - textOffsetX) * this.pixelRatio;
|
anchorX = (labelWithAnchor.anchorX - textOffsetX) * this.pixelRatio;
|
||||||
instruction[4] = anchorX;
|
instruction[4] = anchorX;
|
||||||
const textOffsetY = /** @type {number} */ (instruction[23]);
|
const textOffsetY = /** @type {number} */ (instruction[24]);
|
||||||
anchorY = (labelWithAnchor.anchorY - textOffsetY) * this.pixelRatio;
|
anchorY = (labelWithAnchor.anchorY - textOffsetY) * this.pixelRatio;
|
||||||
instruction[5] = anchorY;
|
instruction[5] = anchorY;
|
||||||
height = image.height;
|
height = image.height;
|
||||||
@@ -840,15 +844,15 @@ class Executor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let geometryWidths;
|
let geometryWidths;
|
||||||
if (instruction.length > 24) {
|
if (instruction.length > 25) {
|
||||||
geometryWidths = /** @type {number} */ (instruction[24]);
|
geometryWidths = /** @type {number} */ (instruction[25]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let padding, backgroundFill, backgroundStroke;
|
let padding, backgroundFill, backgroundStroke;
|
||||||
if (instruction.length > 16) {
|
if (instruction.length > 17) {
|
||||||
padding = /** @type {Array<number>} */ (instruction[15]);
|
padding = /** @type {Array<number>} */ (instruction[16]);
|
||||||
backgroundFill = /** @type {boolean} */ (instruction[16]);
|
backgroundFill = /** @type {boolean} */ (instruction[17]);
|
||||||
backgroundStroke = /** @type {boolean} */ (instruction[17]);
|
backgroundStroke = /** @type {boolean} */ (instruction[18]);
|
||||||
} else {
|
} else {
|
||||||
padding = defaultPadding;
|
padding = defaultPadding;
|
||||||
backgroundFill = false;
|
backgroundFill = false;
|
||||||
@@ -902,39 +906,43 @@ class Executor {
|
|||||||
? /** @type {Array<*>} */ (lastStrokeInstruction)
|
? /** @type {Array<*>} */ (lastStrokeInstruction)
|
||||||
: null,
|
: null,
|
||||||
];
|
];
|
||||||
let imageArgs;
|
|
||||||
let imageDeclutterBox;
|
|
||||||
if (opt_declutterTree && declutterImageWithText) {
|
|
||||||
const index = dd - d;
|
|
||||||
if (!declutterImageWithText[index]) {
|
|
||||||
// We now have the image for an image+text combination.
|
|
||||||
declutterImageWithText[index] = args;
|
|
||||||
// Don't render anything for now, wait for the text.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
imageArgs = declutterImageWithText[index];
|
|
||||||
delete declutterImageWithText[index];
|
|
||||||
imageDeclutterBox = getDeclutterBox(imageArgs);
|
|
||||||
if (opt_declutterTree.collides(imageDeclutterBox)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
opt_declutterTree &&
|
|
||||||
opt_declutterTree.collides(dimensions.declutterBox)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (imageArgs) {
|
|
||||||
// We now have image and text for an image+text combination.
|
|
||||||
if (opt_declutterTree) {
|
|
||||||
opt_declutterTree.insert(imageDeclutterBox);
|
|
||||||
}
|
|
||||||
// Render the image before we render the text.
|
|
||||||
this.replayImageOrLabel_.apply(this, imageArgs);
|
|
||||||
}
|
|
||||||
if (opt_declutterTree) {
|
if (opt_declutterTree) {
|
||||||
opt_declutterTree.insert(dimensions.declutterBox);
|
if (declutterMode === 'none') {
|
||||||
|
// not rendered in declutter group
|
||||||
|
continue;
|
||||||
|
} else if (declutterMode === 'obstacle') {
|
||||||
|
// will always be drawn, thus no collision detection, but insert as obstacle
|
||||||
|
opt_declutterTree.insert(dimensions.declutterBox);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
let imageArgs;
|
||||||
|
let imageDeclutterBox;
|
||||||
|
if (declutterImageWithText) {
|
||||||
|
const index = dd - d;
|
||||||
|
if (!declutterImageWithText[index]) {
|
||||||
|
// We now have the image for an image+text combination.
|
||||||
|
declutterImageWithText[index] = args;
|
||||||
|
// Don't render anything for now, wait for the text.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
imageArgs = declutterImageWithText[index];
|
||||||
|
delete declutterImageWithText[index];
|
||||||
|
imageDeclutterBox = getDeclutterBox(imageArgs);
|
||||||
|
if (opt_declutterTree.collides(imageDeclutterBox)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (opt_declutterTree.collides(dimensions.declutterBox)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (imageArgs) {
|
||||||
|
// We now have image and text for an image+text combination.
|
||||||
|
opt_declutterTree.insert(imageDeclutterBox);
|
||||||
|
// Render the image before we render the text.
|
||||||
|
this.replayImageOrLabel_.apply(this, imageArgs);
|
||||||
|
}
|
||||||
|
opt_declutterTree.insert(dimensions.declutterBox);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.replayImageOrLabel_.apply(this, args);
|
this.replayImageOrLabel_.apply(this, args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,12 @@ class CanvasImageBuilder extends CanvasBuilder {
|
|||||||
*/
|
*/
|
||||||
this.width_ = undefined;
|
this.width_ = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {"declutter"|"obstacle"|"none"|undefined}
|
||||||
|
*/
|
||||||
|
this.declutterMode_ = undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data shared with a text builder for combined decluttering.
|
* Data shared with a text builder for combined decluttering.
|
||||||
* @private
|
* @private
|
||||||
@@ -132,6 +138,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
|||||||
(this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_,
|
(this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_,
|
||||||
],
|
],
|
||||||
Math.ceil(this.width_ * this.imagePixelRatio_),
|
Math.ceil(this.width_ * this.imagePixelRatio_),
|
||||||
|
this.declutterMode_,
|
||||||
this.declutterImageWithText_,
|
this.declutterImageWithText_,
|
||||||
]);
|
]);
|
||||||
this.hitDetectionInstructions.push([
|
this.hitDetectionInstructions.push([
|
||||||
@@ -150,6 +157,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
|||||||
this.rotation_,
|
this.rotation_,
|
||||||
this.scale_,
|
this.scale_,
|
||||||
this.width_,
|
this.width_,
|
||||||
|
this.declutterMode_,
|
||||||
this.declutterImageWithText_,
|
this.declutterImageWithText_,
|
||||||
]);
|
]);
|
||||||
this.endGeometry(feature);
|
this.endGeometry(feature);
|
||||||
@@ -187,6 +195,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
|||||||
(this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_,
|
(this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_,
|
||||||
],
|
],
|
||||||
Math.ceil(this.width_ * this.imagePixelRatio_),
|
Math.ceil(this.width_ * this.imagePixelRatio_),
|
||||||
|
this.declutterMode_,
|
||||||
this.declutterImageWithText_,
|
this.declutterImageWithText_,
|
||||||
]);
|
]);
|
||||||
this.hitDetectionInstructions.push([
|
this.hitDetectionInstructions.push([
|
||||||
@@ -205,6 +214,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
|||||||
this.rotation_,
|
this.rotation_,
|
||||||
this.scale_,
|
this.scale_,
|
||||||
this.width_,
|
this.width_,
|
||||||
|
this.declutterMode_,
|
||||||
this.declutterImageWithText_,
|
this.declutterImageWithText_,
|
||||||
]);
|
]);
|
||||||
this.endGeometry(feature);
|
this.endGeometry(feature);
|
||||||
@@ -255,6 +265,7 @@ class CanvasImageBuilder extends CanvasBuilder {
|
|||||||
this.rotation_ = imageStyle.getRotation();
|
this.rotation_ = imageStyle.getRotation();
|
||||||
this.scale_ = imageStyle.getScaleArray();
|
this.scale_ = imageStyle.getScaleArray();
|
||||||
this.width_ = size[0];
|
this.width_ = size[0];
|
||||||
|
this.declutterMode_ = imageStyle.getDeclutterMode();
|
||||||
this.declutterImageWithText_ = opt_sharedData;
|
this.declutterImageWithText_ = opt_sharedData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
|||||||
this.textRotation_,
|
this.textRotation_,
|
||||||
[1, 1],
|
[1, 1],
|
||||||
NaN,
|
NaN,
|
||||||
|
undefined,
|
||||||
this.declutterImageWithText_,
|
this.declutterImageWithText_,
|
||||||
padding == defaultPadding
|
padding == defaultPadding
|
||||||
? defaultPadding
|
? defaultPadding
|
||||||
@@ -406,6 +407,7 @@ class CanvasTextBuilder extends CanvasBuilder {
|
|||||||
this.textRotation_,
|
this.textRotation_,
|
||||||
[scale, scale],
|
[scale, scale],
|
||||||
NaN,
|
NaN,
|
||||||
|
undefined,
|
||||||
this.declutterImageWithText_,
|
this.declutterImageWithText_,
|
||||||
padding,
|
padding,
|
||||||
!!textState.backgroundFill,
|
!!textState.backgroundFill,
|
||||||
|
|||||||
+48
-14
@@ -362,16 +362,29 @@ function renderPointGeometry(
|
|||||||
const textStyle = style.getText();
|
const textStyle = style.getText();
|
||||||
/** @type {import("../render/canvas.js").DeclutterImageWithText} */
|
/** @type {import("../render/canvas.js").DeclutterImageWithText} */
|
||||||
let declutterImageWithText;
|
let declutterImageWithText;
|
||||||
if (opt_declutterBuilderGroup) {
|
|
||||||
builderGroup = opt_declutterBuilderGroup;
|
|
||||||
declutterImageWithText =
|
|
||||||
imageStyle && textStyle && textStyle.getText() ? {} : undefined;
|
|
||||||
}
|
|
||||||
if (imageStyle) {
|
if (imageStyle) {
|
||||||
if (imageStyle.getImageState() != ImageState.LOADED) {
|
if (imageStyle.getImageState() != ImageState.LOADED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const imageReplay = builderGroup.getBuilder(
|
let imageBuilderGroup = builderGroup;
|
||||||
|
if (opt_declutterBuilderGroup) {
|
||||||
|
const declutterMode = imageStyle.getDeclutterMode();
|
||||||
|
if (declutterMode !== 'none') {
|
||||||
|
imageBuilderGroup = opt_declutterBuilderGroup;
|
||||||
|
if (declutterMode === 'obstacle') {
|
||||||
|
// draw in non-declutter group:
|
||||||
|
const imageReplay = builderGroup.getBuilder(
|
||||||
|
style.getZIndex(),
|
||||||
|
BuilderType.IMAGE
|
||||||
|
);
|
||||||
|
imageReplay.setImageStyle(imageStyle, declutterImageWithText);
|
||||||
|
imageReplay.drawPoint(geometry, feature);
|
||||||
|
} else if (textStyle && textStyle.getText()) {
|
||||||
|
declutterImageWithText = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const imageReplay = imageBuilderGroup.getBuilder(
|
||||||
style.getZIndex(),
|
style.getZIndex(),
|
||||||
BuilderType.IMAGE
|
BuilderType.IMAGE
|
||||||
);
|
);
|
||||||
@@ -379,7 +392,11 @@ function renderPointGeometry(
|
|||||||
imageReplay.drawPoint(geometry, feature);
|
imageReplay.drawPoint(geometry, feature);
|
||||||
}
|
}
|
||||||
if (textStyle && textStyle.getText()) {
|
if (textStyle && textStyle.getText()) {
|
||||||
const textReplay = builderGroup.getBuilder(
|
let textBuilderGroup = builderGroup;
|
||||||
|
if (opt_declutterBuilderGroup) {
|
||||||
|
textBuilderGroup = opt_declutterBuilderGroup;
|
||||||
|
}
|
||||||
|
const textReplay = textBuilderGroup.getBuilder(
|
||||||
style.getZIndex(),
|
style.getZIndex(),
|
||||||
BuilderType.TEXT
|
BuilderType.TEXT
|
||||||
);
|
);
|
||||||
@@ -406,16 +423,29 @@ function renderMultiPointGeometry(
|
|||||||
const textStyle = style.getText();
|
const textStyle = style.getText();
|
||||||
/** @type {import("../render/canvas.js").DeclutterImageWithText} */
|
/** @type {import("../render/canvas.js").DeclutterImageWithText} */
|
||||||
let declutterImageWithText;
|
let declutterImageWithText;
|
||||||
if (opt_declutterBuilderGroup) {
|
|
||||||
builderGroup = opt_declutterBuilderGroup;
|
|
||||||
declutterImageWithText =
|
|
||||||
imageStyle && textStyle && textStyle.getText() ? {} : undefined;
|
|
||||||
}
|
|
||||||
if (imageStyle) {
|
if (imageStyle) {
|
||||||
if (imageStyle.getImageState() != ImageState.LOADED) {
|
if (imageStyle.getImageState() != ImageState.LOADED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const imageReplay = builderGroup.getBuilder(
|
let imageBuilderGroup = builderGroup;
|
||||||
|
if (opt_declutterBuilderGroup) {
|
||||||
|
const declutterMode = imageStyle.getDeclutterMode();
|
||||||
|
if (declutterMode !== 'none') {
|
||||||
|
imageBuilderGroup = opt_declutterBuilderGroup;
|
||||||
|
if (declutterMode === 'obstacle') {
|
||||||
|
// draw in non-declutter group:
|
||||||
|
const imageReplay = builderGroup.getBuilder(
|
||||||
|
style.getZIndex(),
|
||||||
|
BuilderType.IMAGE
|
||||||
|
);
|
||||||
|
imageReplay.setImageStyle(imageStyle, declutterImageWithText);
|
||||||
|
imageReplay.drawMultiPoint(geometry, feature);
|
||||||
|
} else if (textStyle && textStyle.getText()) {
|
||||||
|
declutterImageWithText = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const imageReplay = imageBuilderGroup.getBuilder(
|
||||||
style.getZIndex(),
|
style.getZIndex(),
|
||||||
BuilderType.IMAGE
|
BuilderType.IMAGE
|
||||||
);
|
);
|
||||||
@@ -423,7 +453,11 @@ function renderMultiPointGeometry(
|
|||||||
imageReplay.drawMultiPoint(geometry, feature);
|
imageReplay.drawMultiPoint(geometry, feature);
|
||||||
}
|
}
|
||||||
if (textStyle && textStyle.getText()) {
|
if (textStyle && textStyle.getText()) {
|
||||||
const textReplay = (opt_declutterBuilderGroup || builderGroup).getBuilder(
|
let textBuilderGroup = builderGroup;
|
||||||
|
if (opt_declutterBuilderGroup) {
|
||||||
|
textBuilderGroup = opt_declutterBuilderGroup;
|
||||||
|
}
|
||||||
|
const textReplay = textBuilderGroup.getBuilder(
|
||||||
style.getZIndex(),
|
style.getZIndex(),
|
||||||
BuilderType.TEXT
|
BuilderType.TEXT
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import RegularShape from './RegularShape.js';
|
|||||||
* (positive rotation clockwise, meaningful only when used in conjunction with a two dimensional scale).
|
* (positive rotation clockwise, meaningful only when used in conjunction with a two dimensional scale).
|
||||||
* @property {boolean} [rotateWithView=false] Whether to rotate the shape with the view
|
* @property {boolean} [rotateWithView=false] Whether to rotate the shape with the view
|
||||||
* (meaningful only when used in conjunction with a two dimensional scale).
|
* (meaningful only when used in conjunction with a two dimensional scale).
|
||||||
|
* @property {"declutter"|"obstacle"|"none"|undefined} [declutterMode] Declutter mode
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,6 +42,7 @@ class CircleStyle extends RegularShape {
|
|||||||
options.rotateWithView !== undefined ? options.rotateWithView : false,
|
options.rotateWithView !== undefined ? options.rotateWithView : false,
|
||||||
displacement:
|
displacement:
|
||||||
options.displacement !== undefined ? options.displacement : [0, 0],
|
options.displacement !== undefined ? options.displacement : [0, 0],
|
||||||
|
declutterMode: options.declutterMode,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +61,7 @@ class CircleStyle extends RegularShape {
|
|||||||
rotation: this.getRotation(),
|
rotation: this.getRotation(),
|
||||||
rotateWithView: this.getRotateWithView(),
|
rotateWithView: this.getRotateWithView(),
|
||||||
displacement: this.getDisplacement().slice(),
|
displacement: this.getDisplacement().slice(),
|
||||||
|
declutterMode: this.getDeclutterMode(),
|
||||||
});
|
});
|
||||||
style.setOpacity(this.getOpacity());
|
style.setOpacity(this.getOpacity());
|
||||||
return style;
|
return style;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {getUid} from '../util.js';
|
|||||||
* @property {import("../size.js").Size} [imgSize] Image size in pixels. Only required if `img` is set and `src` is not, and
|
* @property {import("../size.js").Size} [imgSize] Image size in pixels. Only required if `img` is set and `src` is not, and
|
||||||
* for SVG images in Internet Explorer 11. The provided `imgSize` needs to match the actual size of the image.
|
* for SVG images in Internet Explorer 11. The provided `imgSize` needs to match the actual size of the image.
|
||||||
* @property {string} [src] Image source URI.
|
* @property {string} [src] Image source URI.
|
||||||
|
* @property {"declutter"|"obstacle"|"none"|undefined} [declutterMode] Declutter mode
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,6 +87,7 @@ class Icon extends ImageStyle {
|
|||||||
displacement:
|
displacement:
|
||||||
options.displacement !== undefined ? options.displacement : [0, 0],
|
options.displacement !== undefined ? options.displacement : [0, 0],
|
||||||
rotateWithView: rotateWithView,
|
rotateWithView: rotateWithView,
|
||||||
|
declutterMode: options.declutterMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+17
-1
@@ -11,7 +11,7 @@ import {toSize} from '../size.js';
|
|||||||
* @property {number} rotation Rotation.
|
* @property {number} rotation Rotation.
|
||||||
* @property {number|import("../size.js").Size} scale Scale.
|
* @property {number|import("../size.js").Size} scale Scale.
|
||||||
* @property {Array<number>} displacement Displacement.
|
* @property {Array<number>} displacement Displacement.
|
||||||
*/
|
* @property {"declutter"|"obstacle"|"none"|undefined} declutterMode Declutter mode: `declutter`, `obstacle`, 'none */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @classdesc
|
* @classdesc
|
||||||
@@ -61,6 +61,12 @@ class ImageStyle {
|
|||||||
* @type {Array<number>}
|
* @type {Array<number>}
|
||||||
*/
|
*/
|
||||||
this.displacement_ = options.displacement;
|
this.displacement_ = options.displacement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {"declutter"|"obstacle"|"none"|undefined}
|
||||||
|
*/
|
||||||
|
this.declutterMode_ = options.declutterMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,6 +82,7 @@ class ImageStyle {
|
|||||||
rotation: this.getRotation(),
|
rotation: this.getRotation(),
|
||||||
rotateWithView: this.getRotateWithView(),
|
rotateWithView: this.getRotateWithView(),
|
||||||
displacement: this.getDisplacement().slice(),
|
displacement: this.getDisplacement().slice(),
|
||||||
|
declutterMode: this.getDeclutterMode(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +139,15 @@ class ImageStyle {
|
|||||||
return this.displacement_;
|
return this.displacement_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the declutter mode of the shape
|
||||||
|
* @return {"declutter"|"obstacle"|"none"|undefined} Shape's declutter mode
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
getDeclutterMode() {
|
||||||
|
return this.declutterMode_;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the anchor point in pixels. The anchor determines the center point for the
|
* Get the anchor point in pixels. The anchor determines the center point for the
|
||||||
* symbolizer.
|
* symbolizer.
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
* @property {boolean} [rotateWithView=false] Whether to rotate the shape with the view.
|
* @property {boolean} [rotateWithView=false] Whether to rotate the shape with the view.
|
||||||
* @property {number|import("../size.js").Size} [scale=1] Scale. Unless two dimensional scaling is required a better
|
* @property {number|import("../size.js").Size} [scale=1] Scale. Unless two dimensional scaling is required a better
|
||||||
* result may be obtained with appropriate settings for `radius`, `radius1` and `radius2`.
|
* result may be obtained with appropriate settings for `radius`, `radius1` and `radius2`.
|
||||||
|
* @property {"declutter"|"obstacle"|"none"|undefined} [declutterMode] Declutter mode
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,6 +70,7 @@ class RegularShape extends ImageStyle {
|
|||||||
scale: options.scale !== undefined ? options.scale : 1,
|
scale: options.scale !== undefined ? options.scale : 1,
|
||||||
displacement:
|
displacement:
|
||||||
options.displacement !== undefined ? options.displacement : [0, 0],
|
options.displacement !== undefined ? options.displacement : [0, 0],
|
||||||
|
declutterMode: options.declutterMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -159,6 +161,7 @@ class RegularShape extends ImageStyle {
|
|||||||
rotateWithView: this.getRotateWithView(),
|
rotateWithView: this.getRotateWithView(),
|
||||||
scale: Array.isArray(scale) ? scale.slice() : scale,
|
scale: Array.isArray(scale) ? scale.slice() : scale,
|
||||||
displacement: this.getDisplacement().slice(),
|
displacement: this.getDisplacement().slice(),
|
||||||
|
declutterMode: this.getDeclutterMode(),
|
||||||
});
|
});
|
||||||
style.setOpacity(this.getOpacity());
|
style.setOpacity(this.getOpacity());
|
||||||
return style;
|
return style;
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ describe('ol.render.canvas.TextBuilder', function () {
|
|||||||
builder.drawText(feature.getGeometry(), feature);
|
builder.drawText(feature.getGeometry(), feature);
|
||||||
expect(builder.coordinates).to.have.length(2);
|
expect(builder.coordinates).to.have.length(2);
|
||||||
expect(builder.instructions).to.have.length(3);
|
expect(builder.instructions).to.have.length(3);
|
||||||
const geometryWidths = builder.instructions[1][24];
|
const geometryWidths = builder.instructions[1][25];
|
||||||
expect(geometryWidths).to.have.length(1);
|
expect(geometryWidths).to.have.length(1);
|
||||||
expect(geometryWidths[0]).to.be(120);
|
expect(geometryWidths[0]).to.be(120);
|
||||||
});
|
});
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
@@ -0,0 +1,191 @@
|
|||||||
|
import CircleStyle from '../../../../src/ol/style/Circle.js';
|
||||||
|
import Feature from '../../../../src/ol/Feature.js';
|
||||||
|
import Fill from '../../../../src/ol/style/Fill.js';
|
||||||
|
import Map from '../../../../src/ol/Map.js';
|
||||||
|
import Point from '../../../../src/ol/geom/Point.js';
|
||||||
|
import Stroke from '../../../../src/ol/style/Stroke.js';
|
||||||
|
import Style from '../../../../src/ol/style/Style.js';
|
||||||
|
import Text from '../../../../src/ol/style/Text.js';
|
||||||
|
import VectorLayer from '../../../../src/ol/layer/Vector.js';
|
||||||
|
import VectorSource from '../../../../src/ol/source/Vector.js';
|
||||||
|
import View from '../../../../src/ol/View.js';
|
||||||
|
|
||||||
|
const center = [1825927.7316762917, 6143091.089223046];
|
||||||
|
const map = new Map({
|
||||||
|
pixelRatio: 1,
|
||||||
|
target: 'map',
|
||||||
|
view: new View({
|
||||||
|
center: center,
|
||||||
|
zoom: 12.7,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const sourceBlue = new VectorSource();
|
||||||
|
sourceBlue.addFeatures([
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([center[0] + 1000 + 540, center[1] + 900 - 600]),
|
||||||
|
text: 'top-blue',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
// on-top blue circle.
|
||||||
|
// shows that objects (red layer) will not serve as obstacles for layers on-top.
|
||||||
|
map.addLayer(
|
||||||
|
new VectorLayer({
|
||||||
|
zIndex: 4,
|
||||||
|
declutter: true,
|
||||||
|
source: sourceBlue,
|
||||||
|
style: function (feature) {
|
||||||
|
return new Style({
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 10,
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: 'blue',
|
||||||
|
width: 8,
|
||||||
|
}),
|
||||||
|
declutterMode: 'declutter',
|
||||||
|
}),
|
||||||
|
text: new Text({
|
||||||
|
text: feature.get('text'),
|
||||||
|
font: 'italic bold 18px Ubuntu',
|
||||||
|
textBaseline: 'bottom',
|
||||||
|
offsetY: -15,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const sourceRed = new VectorSource();
|
||||||
|
sourceRed.addFeatures([
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([center[0] + 1000, center[1] + 1000 - 200]),
|
||||||
|
text: 'c-red',
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([center[0] + 1000 - 540, center[1] + 1000]),
|
||||||
|
text: 'w-red',
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([center[0] + 1000 + 540, center[1] + 1000 - 400]),
|
||||||
|
text: 'e-red',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
// red circles are always drawn, but serve as obstacles.
|
||||||
|
// however, they cannot serve as obstacles for layers on-top (blue layer).
|
||||||
|
// texts are decluttered against each other and the circles.
|
||||||
|
// circles are drawn on non-declutter executor, i.e. behind decluttered labels and objects.
|
||||||
|
map.addLayer(
|
||||||
|
new VectorLayer({
|
||||||
|
zIndex: 3,
|
||||||
|
declutter: true,
|
||||||
|
source: sourceRed,
|
||||||
|
style: function (feature) {
|
||||||
|
return new Style({
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 10,
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: 'red',
|
||||||
|
width: 8,
|
||||||
|
}),
|
||||||
|
declutterMode: 'obstacle',
|
||||||
|
}),
|
||||||
|
text: new Text({
|
||||||
|
text: feature.get('text'),
|
||||||
|
font: 'italic bold 18px Ubuntu',
|
||||||
|
textBaseline: 'bottom',
|
||||||
|
offsetY: -15,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const sourceOrange = new VectorSource();
|
||||||
|
sourceOrange.addFeatures([
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([center[0], center[1]]),
|
||||||
|
text: 'c-orange',
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([center[0] - 540, center[1]]),
|
||||||
|
text: 'w-orange',
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([center[0] + 540, center[1]]),
|
||||||
|
text: 'e-orange',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
// orange circles are always drawn.
|
||||||
|
// texts are decluttered against each other and the blue/red layer circles/texts.
|
||||||
|
map.addLayer(
|
||||||
|
new VectorLayer({
|
||||||
|
zIndex: 2,
|
||||||
|
declutter: true,
|
||||||
|
source: sourceOrange,
|
||||||
|
style: function (feature) {
|
||||||
|
return new Style({
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 15,
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'orange',
|
||||||
|
}),
|
||||||
|
declutterMode: 'none',
|
||||||
|
}),
|
||||||
|
text: new Text({
|
||||||
|
text: feature.get('text'),
|
||||||
|
font: 'italic bold 18px Ubuntu',
|
||||||
|
textBaseline: 'bottom',
|
||||||
|
offsetX: -25,
|
||||||
|
offsetY: -17,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const sourceCyan = new VectorSource();
|
||||||
|
sourceCyan.addFeatures([
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([center[0] + 1000 - 700, center[1] - 100]),
|
||||||
|
text: 'w-cyan',
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([center[0] + 1000, center[1] - 400]),
|
||||||
|
text: 'c-cyan',
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
geometry: new Point([center[0] + 1000 + 700, center[1] - 700]),
|
||||||
|
text: 'e-cyan',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
// cyan circles are always drawn.
|
||||||
|
// texts are decluttered against each others (and blue/red/orange layers).
|
||||||
|
// the circles of the orange layer and this layer are no obstactles for texts.
|
||||||
|
// the texts are decluttered and thus above the circles of the orange layer.
|
||||||
|
map.addLayer(
|
||||||
|
new VectorLayer({
|
||||||
|
zIndex: 1,
|
||||||
|
declutter: true,
|
||||||
|
source: sourceCyan,
|
||||||
|
style: function (feature) {
|
||||||
|
return new Style({
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 15,
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'cyan',
|
||||||
|
}),
|
||||||
|
declutterMode: 'none',
|
||||||
|
}),
|
||||||
|
text: new Text({
|
||||||
|
text: feature.get('text'),
|
||||||
|
font: 'italic bold 18px Ubuntu',
|
||||||
|
textBaseline: 'middle',
|
||||||
|
textAlign: 'right',
|
||||||
|
offsetX: -19,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
render({tolerance: 0.007});
|
||||||
Reference in New Issue
Block a user