Cesium的Property机制究竟有多香

5,088 阅读6分钟

这是我参与更文挑战的第4天,活动详情查看: 更文挑战

前言

这两天在研究Cesium中如何实现沿线飞行或漫游功能,上网查资料发现有好多博主的解决办法都包含了什么什么Property……勾起了我强烈的好奇心,遂去了Cesium官网一探究竟,一下我就被第一句话震惊到了。

All values we define for our entities are stored as Property objects.

实体的所有值都被维护成了Property对象,这让我不得不往下看,但我越看越气,这么重要的东西,你这啥也没讲啊。算了还是我自己研究吧。

image-20210603163511040.png

什么是Property机制

个人感觉Cesium的Property本质上与Object.defineProperties类似,defineProperties直接封装了基本类型,如果是Object,则是引用形式。通过Property的封装,将引用或复制的权力交给了设计者,同时提供一些特殊的功能以满足需求。

为什么要用Property

首先用一个例子来简单展示一下Property的作用。

// 创建盒子
var box = viewer.entities.add({
    name: "box",
    position: Cesium.Cartesian3.fromDegrees(121.54035, 38.92146, 2000),
    box: {
        dimensions: new Cesium.Cartesian3(1000.0, 1000.0, 1000.0),
    },
});

假如我们想实现盒子的大小随时间变化而变化,第一想到的应该就是用setInterval去改变盒子的dimensions,但这样对性能是个不小的挑战。而Cesium提供一种机制,可以让其随时间自动变化并赋值,这就是Property。以下代码是在5秒内让盒子大小变化。

var property = new Cesium.SampledProperty(Cesium.Cartesian3);

property.addSample(
    Cesium.JulianDate.fromDate(new Date()),
    new Cesium.Cartesian3(1000.0, 1000.0, 1000.0)
);

property.addSample(
    Cesium.JulianDate.addSeconds(
        Cesium.JulianDate.fromDate(new Date()),
        5,
        new Cesium.JulianDate()
    ),
    new Cesium.Cartesian3(2000.0, 2000.0, 2000.0)
);

box.box.dimensions = property;

我们通过addSample向Property实例添加关键帧并定义想要修改的属性,最后赋值给box的dimensions,效果如下

property1.gif

所以说,Property可以和时间轴进行关联,并根据时间返回对应的属性值,而Entity则可以通过返回的值动态改变实体的位置、大小等。

Property分类及使用方法

上文中我们举例使用的SampledProperty提供了插值功能,还有很多Property的类型,我们可以在API文档中搜索一下Property,整整有29个之多。

image-20210604095802707.png

简单归类一下可以分为几类:

  • 基本类型:ConstantPropertySampledPropertyTimeIntervalCollectionPropertyCompositeProperty
  • 其他类型:CallbackPropertyReferencePropertyPropertyArrayPropertyBagVelocityOrientationPropertyVelocityVectorProperty
  • 材质类型:MaterialProperty及带material字样的
  • 位置类型:带Position字样的

同时Cesium提供了一个Property类作为所有类型的基类,并定义了几个公共属性及接口。

getValue(time, result) → Cartesian3

获取特定时间点下的属性值

NameTypeDescription
timeJulianDate检索值的时间
resultCartesian3将值存储到的对象。若缺省则创建并返回新实例

equal

用来判断属性值是否相等。

接下来我们就一些常用的Property做一下讲解和代码实现。

基本类型

SampledProperty

第一个例子中我们使用的就是它,通过添加不同时间点的Sample,在每两个时间点之间进行线性插值,这里不做演示,代码和效果都在第一个例子中了。

TimeIntervalCollectionProperty

用来指定具体时间段内的属性值,每个时间段内属性值不变。所以和SampledProperty不同,它呈现出的变化为跳跃式。

property2.gif

var property = new Cesium.TimeIntervalCollectionProperty(
    Cesium.Cartesian3
);


property.intervals.addInterval(
    new Cesium.TimeInterval({
        start: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            2,
            new Cesium.JulianDate()
        ),
        stop: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            20,
            new Cesium.JulianDate()
        ),
        isStartIncluded:true,
        isStopIncluded:false,
        data: new Cesium.Cartesian3(1000.0, 1000.0, 1000.0),
    })
);
property.intervals.addInterval(
    new Cesium.TimeInterval({
        start: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            6,
            new Cesium.JulianDate()
        ),
        stop: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            20,
            new Cesium.JulianDate()
        ),
        isStartIncluded:true,
        isStopIncluded:false,
        data: new Cesium.Cartesian3(2000.0, 2000.0, 2000.0),
    })
);
property.intervals.addInterval(
    new Cesium.TimeInterval({
        start: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            10,
            new Cesium.JulianDate()
        ),
        stop: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            20,
            new Cesium.JulianDate()
        ),
        isStartIncluded:true,
        isStopIncluded:true,
        data: new Cesium.Cartesian3(3000.0, 3000.0, 3000.0),
    })
);

box.box.dimensions = property;

ConstantProperty

不随时间的变化而变化的属性。

相对于上述两种Property,更加常用的可能就是这个ConstantProperty了,在我们平常设置实体的属性时一般都是如下设置:

box.box.dimensions = new Cesium.Cartesian3(100, 100, 100);

但是实际上其实完整的写法应该是:

box.box.dimensions = new ConstantProperty(new Cesium.Cartesian3(100, 100, 100));

这么看我们可以发现,box中的dismensions属性其实是Property类型,在Cesium内部偷偷的将我们传入的Cartesian3转化成了ConstantProperty类型。

ConstantProperty也并非不能更改,它提供了setValue方法去修改属性值,利用setValue会修改原有的属性值,而非创建新的ConstantProperty

CompositeProperty

property3.gif

顾名思义这是个复合属性,它可以将多种Property进行组合操作,例如在一段时间内需要跳跃性变化,然后进行平滑变化,则可以使用这种类型。看代码:

var lineProperty = new Cesium.SampledProperty(Cesium.Cartesian3);

lineProperty.addSample(
    Cesium.JulianDate.fromDate(new Date()),
    new Cesium.Cartesian3(1000.0, 1000.0, 1000.0)
);

lineProperty.addSample(
    Cesium.JulianDate.addSeconds(
        Cesium.JulianDate.fromDate(new Date()),
        5,
        new Cesium.JulianDate()
    ),
    new Cesium.Cartesian3(3000.0, 3000.0, 3000.0)
);

var timeProperty = new Cesium.TimeIntervalCollectionProperty(
    Cesium.Cartesian3
);

timeProperty.intervals.addInterval(
    new Cesium.TimeInterval({
        start: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            5,
            new Cesium.JulianDate()
        ),
        stop: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            20,
            new Cesium.JulianDate()
        ),
        isStartIncluded: true,
        isStopIncluded: false,
        data: new Cesium.Cartesian3(3000.0, 3000.0, 3000.0),
    })
);
timeProperty.intervals.addInterval(
    new Cesium.TimeInterval({
        start: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            10,
            new Cesium.JulianDate()
        ),
        stop: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            20,
            new Cesium.JulianDate()
        ),
        isStartIncluded: true,
        isStopIncluded: false,
        data: new Cesium.Cartesian3(4000.0, 4000.0, 4000.0),
    })
);
timeProperty.intervals.addInterval(
    new Cesium.TimeInterval({
        start: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            15,
            new Cesium.JulianDate()
        ),
        stop: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            20,
            new Cesium.JulianDate()
        ),
        isStartIncluded: true,
        isStopIncluded: true,
        data: new Cesium.Cartesian3(5000.0, 5000.0, 5000.0),
    })
);

var compositeProperty = new Cesium.CompositeProperty();
compositeProperty.intervals.addInterval(
    new Cesium.TimeInterval({
        start: Cesium.JulianDate.fromDate(new Date()),
        stop: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            5,
            new Cesium.JulianDate()
        ),
        isStartIncluded: false,
        isStopIncluded: false,
        data: lineProperty,
    })
);
compositeProperty.intervals.addInterval(
    new Cesium.TimeInterval({
        start: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            5,
            new Cesium.JulianDate()
        ),
        stop: Cesium.JulianDate.addSeconds(
            Cesium.JulianDate.fromDate(new Date()),
            20,
            new Cesium.JulianDate()
        ),
        isStartIncluded: false,
        isStopIncluded: false,
        data: timeProperty,
    })
);

box.box.dimensions = compositeProperty;

位置类型

PositionProperty

同Property,PositionProperty是一个虚基类,不能直接实例化,它增加了referenceFrame,只能表示position

referenceFrame用来获取position的参考系,目前Cesium提供两种参考系FIXEDINERTIAL。默认使用的FIXED参考系,即坐标在地球上的位置是固定的。

基于PositionProperty的类型有以下几种:

  • CompositePositionProperty
  • ConstantPositionProperty
  • PositionProperty
  • PositionPropertyArray
  • SampledPositionProperty
  • TimeIntervalCollectionPositionProperty

用法上和基本类型基本相同,只不过它们专门用来表示位置信息。这里不做举例。

材质类型

MaterialProperty

专门用来表示材质,扩展了getType方法来获取材质类型。

同样它也有很多派生类,比如ColorMaterialPropertyImageMaterialProperty等等,我们在平时的demo和项目中也都有使用。

我们可以利用基本类型和材质类型实现一个颜色的动态效果。

property4.gif

var colorProperty = new Cesium.SampledProperty(Cesium.Color);

colorProperty.addSample(
    Cesium.JulianDate.fromDate(new Date()),
    new Cesium.Color(0, 1, 0)
);

colorProperty.addSample(
    Cesium.JulianDate.addSeconds(
        Cesium.JulianDate.fromDate(new Date()),
        5,
        new Cesium.JulianDate()
    ),
    new Cesium.Color(0, 0, 1)
);

box.box.material = new Cesium.ColorMaterialProperty(colorProperty);

其他类型

CallbackProperty

这里主要介绍一下CallbackProperty,它是自由度最高的一种类型,我们只需要提供一个回调函数来返回我们需要的值即可,在回调函数中我们可以随意进行操作。在这我们实现一个随机变化颜色并且不断增高的box。

property5.gif

let l = 2000.0;
box.box.dimensions = new Cesium.CallbackProperty(function (time, result) {
    result = result || new Cesium.Cartesian3(0, 0, 0);

    l += 20.0;
    if (l > 7000.0) {
        l = 2000.0;
    }

    result.x = 4000.0;
    result.y = 3000.0;
    result.z = l;
    return result;
}, false);
box.box.material = new Cesium.ColorMaterialProperty(
    new Cesium.CallbackProperty(function () {
        return Cesium.Color.fromRandom({
            alpha: 1.0,
        });
    }, false)
);

ReferenceProperty

该property可以直接链接到另一个对象的Property,做引用效果。

参数类型描述
targetCollectionEntityCollection将用于解析引用的实体集合。
targetIdString被引用的实体的 id。
targetPropertyNamesArray.将使用的目标实体上的属性名称。

PropertyBag

它用来对一个对象进行包装,使得该对象的每一个属性都可作为一个动态的Property进行修改。比如之前修改dimensions的话,dimensions是作为一个Cartesian3类型变量整体封装到Property中去的,如果我们只想修改dimensions的x。则可以使用PropertyBag来实现。

var zp = new Cesium.SampledProperty(Number);
zp.addSample(Cesium.JulianDate.fromDate(new Date()), 2000.0);
zp.addSample(Cesium.JulianDate.addSeconds(
    Cesium.JulianDate.fromDate(new Date()),
    5,
    new Cesium.JulianDate()
),, 7000.0);

box.box.dimensions = new Cesium.PropertyBag({
    x: 4000.0,
    y: 3000.0,
    z: zp
});

VelocityOrientationProperty

该Property用来Entity的position的位置变化,来计算出移动的方向,最后把速度方向输出成Orientation。Cesium自带的示例中有一个Interpolation中有其用法,不再赘述。

使用场景

Property机制很强大,我们可以在很多场景中使用它,比如实现一些沿线飞行、路径漫游或者实体的大小属性变化等。可以说只要有修改属性的地方我们都可以用到它。