相信所有人在 cesium 开发的过程中都会用到地图标签,用来在地图上标记东西。
常见标签
大部分人都会选择使用 cesium 的 billboard 和 label ,把二者搭配结合的实体 entity 添加到场景中,类似这种效果。然后修改修改标签和文字的对齐方式以及偏移距离,基本就满足了日常的需求。
javascript
var enetity = viewer.entities.add({
name: '标点',
position: Cesium.Cartesian3.fromDegrees(113.122717,23.028762,10),
label: { //文字标签
text: "文字标签文字标签文字标签",
font: '500 30px Helvetica',// 15pt monospace
scale: 0.5,
style: Cesium.LabelStyle.FILL,
fillColor: Cesium.Color.WHITE,
pixelOffset: new Cesium.Cartesian2(0, -75), //偏移量
showBackground: true,
backgroundColor: new Cesium.Color(0.5, 0.6, 1, 1.0)
},
billboard:{
image: '/images/normal_point_128.png',
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
scale: 0.5,
}
})
自定义标签
可是总有些时候上述的方法无法满足你的需求或者产品经理的需求,就比如我接下来要讲的这个标签,功能并不复杂,但就是刚好上述办法不好实现。
使用场景
在地图上测绘测量等操作完成后显示计算的结果。
分析需求
- 内容长度行数自定义
- 标签下方有竖线
- 需要根据距离隐藏或显示
- 计算改点到地球背面时需要隐藏
- 标签文字没有点击事件
开始
综合分析以上需求,我决定使用HTML来实现,自由度高且容易。
首先,新建一个类就叫 AnalysisBillboard ,该类具有以下属性:
- viewer: Viewer 类型,表示一个查看器对象。
- position: Cartesian3 类型,表示一个笛卡尔坐标系中的三维位置。
- content: string 类型,表示广告牌的内容。
- id: string 类型,表示广告牌的唯一标识符。
- element: HTMLDivElement | undefined 类型,表示广告牌的 HTML 元素。
- maxRenderDis: number 类型,表示广告牌的最大渲染距离,默认值为 500000。
- show: boolean 类型,表示广告牌是否显示,默认值为 true。
代码如下:
js
class AnalysisBillboard {
protected viewer: Viewer;
protected position: Cartesian3;
protected content: string;
private id: string;
private element: HTMLDivElement | undefined;
private maxRenderDis: number = 500000;
private show: boolean;
constructor(viewer: Viewer, position: GeoPositon, content: string) {
...
}
}
然后得在构造函数中,传入了三个参数:viewer、position 和 content,分别用于初始化 viewer、position 和 content 属性。同时,还计算了 maxRenderDis 属性的值(获取当前镜头的高度然后乘5,当镜头高度超过当前高度的五倍后就隐藏这个标签。),并使用 nanoid() 函数生成了一个唯一的 id。最后调用了 initBillboard() 方法进行初始化操作。
在 initBillboard() 方法中首先创建了一个 div
元素,并将其 id 设置为类实例的 id
属性。然后将该元素的样式设置为绝对定位,并给它添加了 resultMarker
类名。接着将类实例的 content
属性作为 HTML 表格插入到该元素中,最后将该元素添加到 viewer 的容器中。
接下来,该方法为 viewer 场景的 postRender 事件添加了一个监听器,当渲染完成后会自动调用 updateBillboardLocation()
方法来实时更新广告牌的位置。
js
constructor(viewer: Viewer, position: GeoPositon, content: string) {
this.viewer = viewer;
this.position = GraphTransform.transformWGS84ToCartesian(position);
this.content = content;
this.maxRenderDis =
Math.round(viewer.camera.positionCartographic.height) * 5;
this.id = nanoid(10);
this.show = true;
this.initBillboard();
}
private initBillboard() {
this.element = document.createElement("div");
this.element.id = this.id;
this.element.style.position = "absolute";
this.element.className = "resultMarker";
let HTMLTable = this.content;
this.element.innerHTML = HTMLTable;
this.viewer.cesiumWidget.container.appendChild(this.element);
//实时更新位置
this.viewer.scene.postRender.addEventListener(
this.updateBillboardLocation,
this
);
}
updateBillboardLocation() 更新一个广告牌的位置。 updateBillboardLocation()
方法首先检查广告牌元素是否存在,如果存在则获取 canvas 的高度,并创建一个 Cartesian2 对象来存储窗口坐标系下的位置信息。然后使用 SceneTransforms.wgs84ToWindowCoordinates()
方法将广告牌的位置从 WGS84 坐标系转换为窗口坐标系,并计算出广告牌在窗口坐标系下的纵坐标,并将其设置为广告牌元素的底部位置。接着计算广告牌元素的宽度,并将广告牌元素的左边距设置为窗口坐标系下的横坐标减去广告牌元素宽度的一半,是的自定义的标签能在定位部分居中
js
private updateBillboardLocation() {
if (this.element) {
const canvasHeight = this.viewer.scene.canvas.height;
const windowPosition = new Cartesian2();
SceneTransforms.wgs84ToWindowCoordinates(
this.viewer.scene,
this.position,
windowPosition
);
this.element.style.bottom = canvasHeight - windowPosition.y + "px";
const elWidth = this.element.offsetWidth;
this.element.style.left = windowPosition.x - elWidth / 2 + "px";
const camerPosition = this.viewer.camera.position;
let height =
this.viewer.scene.globe.ellipsoid.cartesianToCartographic(
camerPosition
).height;
height += this.viewer.scene.globe.ellipsoid.maximumRadius;
if (this.show) {
if (
!(Cartesian3.distance(camerPosition, this.position) > height) &&
this.viewer.camera.positionCartographic.height < this.maxRenderDis
) {
this.element.style.display = "block";
} else {
this.element.style.display = "none";
}
} else {
this.element.style.display = "none";
}
}
}
接下来,获取 viewer 相机的位置,并计算相机高度加上地球椭球体的最大半径得到新的高度。如果 show
属性为真,则根据相机与广告牌位置的距离和相机高度与最大渲染距离的大小关系来决定是否显示广告牌元素。否则直接将广告牌元素隐藏。
至此一个自定义的标签基础功能基本完成了。
使用
使用就非常简单啦,直接 new 一个就行。
js
this.billboard = new AnalysisBillboard(this.viewer, position, content);
最后说明一下,这里面有些用到了第三方的库,或是自己封装的别的类和方法,比如 nanoid
是第三方的、 GraphTransform
是自己定义的工具类,还有 GeoPositon
的类型定义是自己定义的。但这些对基础功能没影响,你随便换就行。