1. marker
这个示例用来演示如何创建绘制标记的装饰器。这个功能可用于视觉上在感兴趣的点设定指示或提醒注意。
1.1.1. 目的
此示例的目的是演示以下:
- 创建一个Marker装饰器,在视口中显示应用程序生成的图形。
- 实现将内容用canvas多行展示。
- 创建一个世界坐标系中的WorldDecoration,可随着视图缩放。
- 创建一个MarkerCluster的标记,缩放时会自动合并成一个MarkerCluster显示,可对整个集群进行删除。
- 创建一个MarkerSet管理所有的标记点:添加、删除。
- 实现自定义的Marker,将用户的输入内容以标记的形式显示在视图中。
1.1.2. 描述
标记是一种常见的技术,可在特定的空间位置中吸引用户注意。标记通常组织成标记集,由装饰器显示。
以下示例演示了构建标记集的两种方法。第一种:绘制设定的图片标记;第二种:绘制用户输入的信息自定义标记内容。用户可通过鼠标点击在所在位置绘制标志。
1.1.3. 实现
(1) 添加一个简单的Marker
所谓点标记 Marker ,就是在地图上指定的一个坐标上打上一个 Marker(如显示一个水滴图形,或者自定义的其他任何图形),一个地图上可以定义很多个Marker。 IncidentMarker 表示一个Marker实例,onMouseButton实现鼠标点击事件,询问是否删除当前标记。
代码如下:
class IncidentMarker extends Marker {
private static _size = Point2d.create(20, 20);
private static _imageSize = Point2d.create(30, 30);
constructor(location: Point3d, icon?: HTMLImageElement, label?: string, title?: string) {
super(location, IncidentMarker._size);
icon && this.setImage(icon);
this.imageSize = IncidentMarker._imageSize;
this.setScaleFactor({ low: 1, high: 1 });
this.title = title || ''; // tooltip显示的文字, 空字符串不显示
this.label = label || ''; // 直接显示的文字,空字符串不显示
}
public onMouseButton(ev: BeButtonEvent) {
if (
InputSource.Mouse === ev.inputSource &&
ev.isDown &&
ev.viewport !== undefined &&
ev.viewport.view instanceof ViewState3d
) {
// Do something...
if (ev.isAltKey && ev.isControlKey) { // 按住alt和ctrl并点击时,出现删除提示
if (window.confirm("Delete the IncidentMarker")) {
IncidentMarkerManager.removeMarker(this);
}
}
}
// Don't allow clicks to be sent to active tool...
return true;
}
}
(2) 创建一个可随缩放改变尺寸的 marker
若想要创建的Marker随着视图的缩放而改变尺寸,需要添加WorldDecoration,WorldDecoration 位于世界坐标中,因此当您放大/缩小时,该圆将更改大小。此外,它们是启用 z 缓冲器绘制的,因此 圆可能被视图前面的其他几何形状遮盖。
代码如下:
public override addMarker(context: DecorateContext) {
super.addMarker(context);
const builder = context.createGraphicBuilder(GraphicType.WorldDecoration);
const ellipse = Arc3d.createScaledXYColumns(this.worldLocation, context.viewport.rotation.transpose(), .2, .2, IncidentMarker._sweep360);
// draw the circle the color of the marker, but with some transparency.
let color = this._color;
builder.setSymbology(ColorDef.white, color, 1);
color = color.withTransparency(200);
builder.addArc(ellipse, false, false);
builder.setBlankingFill(color);
builder.addArc(ellipse, true, true);
context.addDecorationFromBuilder(builder);
}
(3) 实现多行文字显示
Marker的 label 和 title 都是单行显示,若是想要实现多行显示的 label,可以重载 drawFunc方法绘制 canvas 实现。
代码实现如下:
public drawFunc(ctx: CanvasRenderingContext2D){
ctx.beginPath();
ctx.strokeStyle = "red";
ctx.fillStyle = "wheat";
ctx.lineWidth = 5;
const txHash = "A Marker is a visual indicator whose position in a view follows a fixed location in world space. The name and the concept derive from the same type in the Google Maps api. Markers may also provide actions performed when the mouse moves over them, and when the are clicked.";
ctx.arc(0, 0, 13, 0, Math.PI * 2);
this.drawText(ctx, txHash, IncidentMarker._x, IncidentMarker._y, 100, 110);
ctx.stroke();
}
public drawText(ctx: CanvasRenderingContext2D, str: string, leftWidth: number, initHeight: number, titleHeight: number, canvasWidth: number){
var lineWidth = 0;
var lastSubStrIndex = 0; //每次开始截取的字符串的索引
for (let i = 0; i < str.length; i++) {
lineWidth += ctx.measureText(str[i]).width;
if (lineWidth > canvasWidth) {
ctx.fillText(str.substring(lastSubStrIndex, i), leftWidth, initHeight); //绘制截取部分
initHeight += 16; //16为字体的高度
lineWidth = 0;
lastSubStrIndex = i;
titleHeight += 30;
}
if (i === str.length - 1) { //绘制剩余部分
ctx.fillText(str.substring(lastSubStrIndex, i + 1), leftWidth, initHeight);
}
}
// 标题border-bottom 线距顶部距离
titleHeight = titleHeight + 10;
return titleHeight;
}
(4) 多个marker聚合成一个marker显示
IncidentClusterMarker也表示一个 Marker 实例,当视图缩小的一定比例时,预定距离内的点会自动合并成一个点显示。点聚合本质上是若干个点的集合,可以对一个集合进行删除,但无法针对其中的特定一个点进行个性化操作。
代码实现如下:
class IncidentClusterMarker extends Marker { // 标签的 簇
private _cluster: Cluster<IncidentMarker>;
// draw the cluster as a white circle with an outline color based on what's in the cluster
public drawFunc(ctx: CanvasRenderingContext2D) {
ctx.beginPath();
ctx.strokeStyle = "red";
ctx.fillStyle = "white";
ctx.lineWidth = 5;
ctx.arc(0, 0, 13, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
/** Create a new cluster marker with label and color based on the content of the cluster */
constructor(
location: XYAndZ,
size: XAndY,
cluster: Cluster<IncidentMarker>,
image: Promise<MarkerImage>,
) {
super(location, size);
this._cluster = cluster;
this.imageOffset = new Point3d(0, 28);
this.imageSize = new Point2d(30, 30);
this.label = cluster.markers.length.toLocaleString();
this.labelColor = "black";
this.labelFont = "bold 14px san-serif";
const maxLen = 10; // 最多显示title的个数
let title = "";
cluster.markers.forEach((marker) => {
if (title !== "") title += ", ";
title += marker.title;
});
if (cluster.markers.length > maxLen) title += ",...";
this.title = title;
this.setImage(image);
}
public onMouseButton(ev: BeButtonEvent): boolean { // 簇的删除方法
if (ev.button === BeButton.Data) {
if (ev.isDown) {
if (ev.isAltKey && ev.isControlKey) {
if (window.confirm("Delete multiple IncidentMarkers")) {
this._cluster.markers.forEach((marker) => {
// tslint:disable-next-line: no-floating-promises
IncidentMarkerManager.removeMarker(marker);
});
}
}
}
}
return true;
}
}
IncidentMarkerSet继承了MarkerSet,是一个Marker集群。经常有一种情况,在相近位置放有很多标记。当标记集变大时,或者当用户放大远离标记位置时,它们往往相互重叠并造成杂乱无章。为此,类标记集提供了将相关标记集组合在一起的方法,以便它们重叠的组形成一个群集。MarkerSet 为您提供图形以直观地指示它所代表的标记集的技术 。
/** A MarkerSet to hold incidents. This class supplies to `getClusterMarker` method to create IncidentClusterMarkers. */
class IncidentMarkerSet extends MarkerSet<IncidentMarker> {
public minimumClusterSize = 5;
public getClusterMarker(cluster: Cluster<IncidentMarker>) {
return IncidentClusterMarker.makeFrom( // 簇 的初始化
cluster.markers[0],
cluster,
cluster.markers[0].image
);
}
(5)创建自定义内容的marker
IncidentMarkerManager来管理Set,实现主要逻辑。注意这里区分了类方法和实例方法。主要实现Marker的添加和删除,预先设定了五种图片类型和自定义内容类型。
代码实现如下:
export class IncidentMarkerManager implements Decorator { // 对标 UrlMarkerManager
private static types = [
"Hazard_biological",
"Hazard_electric",
"Hazard_flammable",
"Hazard_toxic",
"Hazard_tripping",
];
private static _images: HTMLImageElement[] = [];
private static _decorator: IncidentMarkerManager | undefined = undefined;
private _markerSet: IncidentMarkerSet;
constructor(vp: ScreenViewport) {
this._markerSet = new IncidentMarkerSet(vp);
}
decorate(context: DecorateContext) {
if (this._markerSet !== undefined) {
this._markerSet.addDecoration(context);
}
}
draw(viewport: ScreenViewport, location: Point3d, icon?: HTMLImageElement, label?: string, title?: string) {
const currentViewport = this._markerSet.viewport;
if (currentViewport !== viewport) {
this._markerSet.changeViewport(viewport);
}
this._markerSet.markers.add(new IncidentMarker(location, icon, label, title));
this._markerSet.markDirty();
IModelApp.viewManager.invalidateDecorationsAllViews();
IModelApp.requestNextAnimation();
}
static removeMarker(incidentMarker: IncidentMarker) {
this._decorator?._markerSet.markers.delete(incidentMarker)
}
static async loadImage(index: number) {
if (this._images[index]) {
return this._images[index];
}
try {
const img = await imageElementFromUrl(
"/markers/" + this.types[index] + ".svg"
);
this._images[index] = img;
return img;
} catch (error) {
console.log(error);
}
return undefined;
}
static async add(vp: ScreenViewport, location: Point3d, markTypeIndex: number) {
if (IncidentMarkerManager._decorator === undefined) {
IncidentMarkerManager._decorator = new IncidentMarkerManager(vp);
IModelApp.viewManager.addDecorator(IncidentMarkerManager._decorator);
}
if (markTypeIndex === 5) { // 5代表Text类型
confirm({
title: 'Please input the labelText',
content: React.createElement('input', { id: 'labelText', class: 'ant-input'}),
onOk() {
let labelText = ''
const labelTextElement: HTMLInputElement | null = document.getElementById('labelText') as HTMLInputElement
labelText = labelTextElement?.value
if (labelText && labelText !== '') {
IncidentMarkerManager._decorator!.draw(vp, location, undefined, labelText);
}
},
onCancel() {
Modal.destroyAll();
},
});
} else {
const img = await this.loadImage(markTypeIndex);
IncidentMarkerManager._decorator.draw(vp, location, img);
}
}
static clear(_vp: ScreenViewport) {
if (IncidentMarkerManager._decorator !== undefined) {
IModelApp.viewManager.dropDecorator(IncidentMarkerManager._decorator);
IncidentMarkerManager._decorator = undefined;
}
}
}