前言
最近开始学习 Cesium,首先我在b站找了一个教程 Cesium快速上手(2020/02)_哔哩哔哩_bilibili,发现这个教程并没有讲的特别清楚。《 Cesium 学习笔记 》系列文章,将按照这个教程的顺序,结合cesium源码,对教程内容做一个学习总结和扩展。
文章中出现的 Demo链接 和环境都基于 Cesium教程中 的官方案例,安装过程可以参考 Cesium快速上手(2020/02)_哔哩哔哩_bilibili 的第一集。
本篇文章《 Billboard, Label, Polylines 图元使用讲解 》主要是对 Cesium快速上手(2020/02)_哔哩哔哩_bilibili 的第三集进行一个总结和扩展。也就是 Primitives 中的 Billboard, Label, PointPrimitives
Billboard
1. Billboard & Cesium.BillboardCollection
代码与展示效果
1. 代码
function addBillboard() {
Sandcastle.declare(addBillboard);
const billboards = scene.primitives.add(
new Cesium.BillboardCollection()
);
billboards.add({
image: "../images/Cesium_Logo_overlay.png",
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883, 3000),
});
billboards.add({
image: "../images/Cesium_Logo_overlay.png",
position: Cesium.Cartesian3.fromDegrees(-75.69777, 40.13883, 9000),
});
console.log(billboards)
console.log(billboards.length)
}
2. log输出和图片
Billboard的特性
1. 面朝屏幕的图片
创建的 billboard,无论是否转动 camera ,始终面朝屏幕
2. BillboardCollection 对象
const billboards = scene.primitives.add( new Cesium.BillboardCollection() );
billboards 变量是一个 BillboardCollection 对象, 创建一个 billboard,并通过调用 BillboardCollection#add
设置其初始属性。比如后续执行了n次 billboards.add({options})
, BillboardCollection 对象的 length 属性就为 n。
其他属性设置可参考以下代码:
function setBillboardProperties() {
Sandcastle.declare(setBillboardProperties);
const billboards = scene.primitives.add(
new Cesium.BillboardCollection()
);
billboards.add({
image: "../images/Cesium_Logo_overlay.png", // default: undefined
show: true, // default
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
pixelOffset: new Cesium.Cartesian2(0, -50), // default: (0, 0)
eyeOffset: new Cesium.Cartesian3(0.0, 0.0, 0.0), // default
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // default
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // default: CENTER
scale: 2.0, // default: 1.0
color: Cesium.Color.LIME, // default: WHITE
rotation: Cesium.Math.PI_OVER_FOUR, // default: 0.0
alignedAxis: Cesium.Cartesian3.ZERO, // default
width: 100, // default: undefined
height: 25, // default: undefined
sizeInMeters: false, // default
});
}
3. BillboardCollection 的更新
调用 BillboardCollection#update
时会导致 CPU 到 GPU 有大量的数据流量。无论更新了多少属性,每个 billboard 的流量都是相同的。如果集合中的大多数 billboard 需要更新,使用 BillboardCollection#removeAll 清除集合并添加新的 billboard 而不是修改每个 billboard 可能会更有效。
2. scaleByDistance 和 translucencyByDistance
有时候,当地球作为背景进行缩放时,billboard 的像素值并不会发生改变,所以可能会导致以下图片的情况发生。
我们可以使用 scaleByDistance
设置屏幕像素缩放比.150米的时候 billboard 放大一倍,150000米的时候,billboard 缩放到0.5,用代码表示也就是 scaleByDistance: new Cesium.NearFarScalar(1.5e2, 2.0, 1.5e7, 0.5)
。此时,对整个地球进行缩放,billboard 的像素值大小是较为合理的。
对 translucencyByDistance
进行设置可以提升观感,也就是当 camera 往高空移动时(也就是对地球进行缩小时),billboard 可以降低透明度以减少视觉干扰
还有一个叫做 pixelOffsetScaleByDistance
的属性在以下代码中没有体现,但是在 官方 demo 中是有的。当地球表面有两个不同 billboard时,我们需要 设置一个合适的pixelOffsetScaleByDistance
来保持在地球缩放过程中,两个 billboard 一个正确的相对位置。
pixelOffsetScaleByDistance: new Cesium.NearFarScalar(
1.0e3,
1.0,
1.5e6,
0.0
),
function scaleByDistance() {
Sandcastle.declare(scaleByDistance);
const billboards = scene.primitives.add(
new Cesium.BillboardCollection()
);
billboards.add({
image: "../images/facility.gif",
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
// Cesium.NearFarScalar四个值,最近的距离,最远的距离,缩放比例范围
scaleByDistance: new Cesium.NearFarScalar(1.5e2, 2.0, 1.5e7, 0.5),
translucencyByDistance : new Cesium.NearFarScalar(1.5e2, 1.0, 1.5e7, 0.2)
});
}
3. 使用canvas 添加点的 billboard
代码与展示效果
function addPointBillboards() {
Sandcastle.declare(addPointBillboards);
// canvas 的操作部分可见 MDN 文档,这里的主要操作就是画一个圆的 canvas
const canvas = document.createElement("canvas");
canvas.width = 16;
canvas.height = 16;
const context2D = canvas.getContext("2d");
context2D.beginPath();
context2D.arc(8, 8, 8, 0, Cesium.Math.TWO_PI, true);
context2D.closePath();
context2D.fillStyle = "rgb(255, 255, 255)";
context2D.fill();
const billboards = scene.primitives.add(
new Cesium.BillboardCollection()
);
billboards.add({
imageId: "custom canvas point",
image: canvas, // 这里不再是图片的 url
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
color: Cesium.Color.RED, // billboard 的纹理,每个 billboard 应用不同的颜色
scale: 0.5, // 缩放以更改点的外观。
});
billboards.add({
imageId: "custom canvas point",
image: canvas,
position: Cesium.Cartesian3.fromDegrees(-80.5, 35.14),
color: Cesium.Color.BLUE,
});
billboards.add({
imageId: "custom canvas point",
image: canvas,
position: Cesium.Cartesian3.fromDegrees(-80.12, 25.46),
color: Cesium.Color.LIME,
scale: 2,
});
}
4. 使用图片切片做 billboard
代码与展示效果
function addMarkerBillboards() {
Sandcastle.declare(addMarkerBillboards);
const billboards = scene.primitives.add(
new Cesium.BillboardCollection()
);
billboards.add({
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(49, 43, 18, 18),
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
color: Cesium.Color.LIME,
});
billboards.add({
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(61, 23, 18, 18),
position: Cesium.Cartesian3.fromDegrees(-84.0, 39.0),
color: new Cesium.Color(0, 0.5, 1.0, 1.0),
});
billboards.add({
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(67, 80, 14, 14),
position: Cesium.Cartesian3.fromDegrees(-70.0, 41.0),
color: new Cesium.Color(0.5, 0.9, 1.0, 1.0),
});
billboards.add({
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(27, 103, 22, 22),
position: Cesium.Cartesian3.fromDegrees(-73.0, 37.0),
color: Cesium.Color.RED,
});
billboards.add({
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(105, 105, 18, 18),
position: Cesium.Cartesian3.fromDegrees(-79.0, 35.0),
color: Cesium.Color.YELLOW,
});
}
"../images/whiteShapes.png"引用的图片是
展现的效果如图
billboard 的 imageSubRegion 属性
我并没有在 billboard 的文档中找到 imageSubRegion 属性的解释。
但是我在源码发现 imageSubRegion 属性 实际上是借用 BoundingRectangle 来实现图片的切割的。该属性定义用于 billboard 图像的子区域,而不是整个图像,从左下角开始以像素为单位测量。
我的理解就是使用矩形切割 image ,形成的子图像作为 marker 实现 billboard 的效果。
Label
面朝屏幕的文字,其实这部分我觉得是比较简单的,主要是熟悉 options 配置,在此就不给实例图片了
注意创建的是集群对象 Cesium.LabelCollection(),Label对象只能用在LabelCollection当中
1. 字体和 Properties 配置
function setFont() {
Sandcastle.declare(setFont);
scene.primitives.removeAll();
const labels = scene.primitives.add(new Cesium.LabelCollection());
labels.add({
position: Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222),
text: "Philadelphia",
font: "24px Helvetica", // 设置字体
fillColor: new Cesium.Color(0.6, 0.9, 1.0), // 设置字体颜色
outlineColor: Cesium.Color.BLACK, // 设置外轮廓颜色
outlineWidth: 2, // 设置外轮廓宽度
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
});
}
function setProperties() {
Sandcastle.declare(setProperties);
scene.primitives.removeAll();
const labels = scene.primitives.add(new Cesium.LabelCollection());
const l = labels.add({ // l 是一个 Label 对象
position: Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222),
text: "Philadelphia",
});
l.position = Cesium.Cartesian3.fromDegrees(
-75.1641667,
39.9522222,
300000.0
);
l.scale = 2.0; // 设置放大倍数
}
2. offsetByDistance 和 fadeByDistance 的设置
function offsetByDistance() {
Sandcastle.declare(offsetByDistance);
scene.primitives.removeAll();
const image = new Image();
image.onload = function () {
const billboards = scene.primitives.add(
new Cesium.BillboardCollection()
);
billboards.add({
position: Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222),
scaleByDistance: new Cesium.NearFarScalar(1.5e2, 5.0, 1.5e7, 0.5),
image: image,
});
const labels = scene.primitives.add(new Cesium.LabelCollection());
labels.add({
position: Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222),
text: "Label on top of scaling billboard",
font: "20px sans-serif",
showBackground: true,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
pixelOffset: new Cesium.Cartesian2(0.0, -image.height),
pixelOffsetScaleByDistance: new Cesium.NearFarScalar(
1.5e2,
3.0,
1.5e7,
0.5
),
});
};
image.src = "../images/facility.gif";
}
function fadeByDistance() {
Sandcastle.declare(fadeByDistance);
scene.primitives.removeAll();
const labels = scene.primitives.add(new Cesium.LabelCollection());
labels.add({
position: Cesium.Cartesian3.fromDegrees(-73.94, 40.67),
text: "New York",
translucencyByDistance: new Cesium.NearFarScalar(
1.5e2,
1.0,
1.5e8,
0.0
),
});
labels.add({
position: Cesium.Cartesian3.fromDegrees(-84.39, 33.75),
text: "Atlanta",
translucencyByDistance: new Cesium.NearFarScalar(
1.5e5,
1.0,
1.5e7,
0.0
),
});
}
Polylines
1. 使用折线在地球表面画一个三角形
代码和展示效果
const polylines = scene.primitives.add(
new Cesium.PolylineCollection()
);
// A simple polyline with two points.
const polyline = polylines.add({
positions: Cesium.PolylinePipeline.generateCartesianArc({
positions: Cesium.Cartesian3.fromDegreesArray([
0.0, 0.0,
10.0, 0.0,
0.0, 20.0,
0.0, 0.0,
]),
}),
material: Cesium.Material.fromType("Color", {
color: new Cesium.Color(1.0, 1.0, 1.0, 1.0),
}),
});
关于 generateCartesianArc
在表现上来看,使用了 generateCartesianArc
,折线就会“贴合”在地球表面,也就是直线变成曲线贴合在地球表面,观感会好很多
我们可以看一下上述代码中分解一下
const polylines = scene.primitives.add(
new Cesium.PolylineCollection()
);
const fromDegreesArray = Cesium.Cartesian3.fromDegreesArray([
0.0, 0.0,
10.0, 0.0,
0.0, 20.0,
0.0, 0.0,
])
const generateCartesianArc = Cesium.PolylinePipeline.generateCartesianArc({
positions: fromDegreesArray,
})
const polyline = polylines.add({
positions: generateCartesianArc,
material: Cesium.Material.fromType("Color", {
color: new Cesium.Color(1.0, 1.0, 1.0, 1.0),
}),
});
变量 fromDegreesArray 是一个包含4个 Cartesian3 坐标的数组,这四个坐标就是画这个三角形经过的四个点的坐标。
Cesium.PolylinePipeline.generateCartesianArc
接收了变量 fromDegreesArray 作为 position 属性,然后返回了一个包含了54个 Cartesian3 坐标的数组。个人理解 generateCartesianArc
应该是一种类似于“微分”的操作,使得原有的折线“微分”成一个近似曲线,从而可以贴近地球平面。
如果不使用 generateCartesianArc
,因为地球的表面是圆的,所以当距离较大时,折线的中间部分可能会出现在地底下
2. 多段折线的轮廓材质
代码和展示效果
const widePolyline = polylines.add({
positions: Cesium.PolylinePipeline.generateCartesianArc({
positions: Cesium.Cartesian3.fromDegreesArray([
-105.0,
40.0,
-100.0,
38.0,
-105.0,
35.0,
]),
}),
material: Cesium.Material.fromType(
Cesium.Material.PolylineOutlineType,
{
outlineColor: new Cesium.Color(0.0, 1.0, 0.0, 1.0),
outlineWidth: 10.0,
}
),
width: 15.0,
});
关于 fromType
Cesium 提供了23种现成的 Material 类型,可通过 Material.fromType 方法和 Fabric 两种方式去获取并设置几何对象材质。
Cesium.Material.PolylineOutlineType
就是一种现成的 PolylineOutlineType 材质预设,下面一段代码是 PolylineOutlineType
的材质预设。
Material.PolylineOutlineType = "PolylineOutline";
Material._materialCache.addMaterial(Material.PolylineOutlineType, {
fabric: {
type: Material.PolylineOutlineType,
uniforms: {
color: new Color(1.0, 1.0, 1.0, 1.0),
outlineColor: new Color(1.0, 0.0, 0.0, 1.0),
outlineWidth: 1.0,
},
source: PolylineOutlineMaterial,
},
translucent: function (material) {
const uniforms = material.uniforms;
return uniforms.color.alpha < 1.0 || uniforms.outlineColor.alpha < 1.0;
},
});
在原本的预设中,该折线的边缘轮廓线的颜色应该是透明度为1的红色,其宽度为1。但是我通过 outlineColor: new Cesium.Color(0.0, 1.0, 0.0, 1.0), outlineWidth: 10.0
,将原本预设中的 outlineColor 属性和 outlineWidth 属性进行了一个覆盖。因此,在呈现的效果中,折线的边缘轮廓线是透明度为1,宽度为10的绿色轮廓线。
材质相关的内容会在后续系列文章中详细阐述。
3.画出一个有方向指向的箭头
代码和展示效果
const localPolylines = scene.primitives.add(
new Cesium.PolylineCollection()
);
const center = Cesium.Cartesian3.fromDegrees(-80.0, 35.0, 200000);
localPolylines.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(
center
);
const localPolyline = localPolylines.add({
positions: [
new Cesium.Cartesian3(0.0, 0.0, 0.0),
new Cesium.Cartesian3(1000000.0, 0.0, 0.0),
],
width: 10.0,
material: Cesium.Material.fromType(
Cesium.Material.PolylineArrowType
),
});
以不同的坐标系作为参考进行折线绘制
与画普通 polyline 不同的是,箭头是具有方向的。
此前的例子都是基于地球坐标系,使用转化为 Cartesian3 坐标的两端进行画线,但是在这个例子中,参考坐标系发生了改变
在这个例子中,首先使用 eastNorthUpToFixedFrame 来确定该箭头直线的原点和自身方向坐标系,x轴指向本地的东向、y轴指向本地的北向、z轴指向穿过该位置的椭球曲面法线方向。
localPolylines.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame ( center )
此时再进行 position的设置,,就不再是以地球坐标系来画线了,而是以使用 eastNorthUpToFixedFrame 确立的该直线自身的坐标系来进行画线操作。
positions: [ new Cesium.Cartesian3(0.0, 0.0, 0.0), new Cesium.Cartesian3(1000000.0, 0.0, 0.0), ]
也就是说 画笔会从 西经80、北纬35、高度200000 的点,沿着地球的切线,向东画出长度为1000000的一条线。
然后再使用 Cesium.Material.PolylineArrowType
画出箭头样式
4.含有渐变的折线
这个应该都能通过代码理解,就不再赘述了
const fadingPolyline = polylines.add({
positions: Cesium.PolylinePipeline.generateCartesianArc({
positions: Cesium.Cartesian3.fromDegreesArrayHeights([
-75,
43,
500000,
-125,
43,
500000,
]),
}),
width: 5,
material: Cesium.Material.fromType(Cesium.Material.FadeType, {
repeat: true,
fadeInColor: Cesium.Color.CYAN,
fadeOutColor: Cesium.Color.CYAN.withAlpha(0),
time: new Cesium.Cartesian2(0.0, 0.0),
fadeDirection: {
x: true,
y: false,
},
}),
});
5. generateCartesianRhumbArc 和 generateCartesianArc
代码和展示效果
const rhumbLine = polylines.add({
positions: Cesium.PolylinePipeline.generateCartesianRhumbArc({
positions: Cesium.Cartesian3.fromDegreesArray([
-130.0,
30.0,
-75.0,
30.0,
]),
}),
width: 5,
material: Cesium.Material.fromType("Color", {
color: new Cesium.Color(0.0, 1.0, 0.0, 1.0), // 绿色
}),
});
const polyline = polylines.add({
positions: Cesium.PolylinePipeline.generateCartesianArc({
positions: Cesium.Cartesian3.fromDegreesArray([
-130.0,
30.0,
-75.0,
30.0,
]),
}),
width: 5,
material: Cesium.Material.fromType("Color", {
color: new Cesium.Color(1.0, 0.0, 0.0, 1.0), // 红色
}),
});
generateCartesianRhumbArc
创造的是 Rhumb同向线,弧线切线方向都是一致的;若拿着罗盘针的话,航线都是一致的。
在这个例子中,可以看到,generateCartesianRhumbArc
和纬度线是重合的