Cesium的Property机制总结

前言

Cesium官方教程中有一篇叫《空间数据可视化》(Visualizing Spatial Data)。该文文末简单提到了Cesium的Property机制,然后话锋一转,宣告此教程的第二部分将重点讲解Property机制。但是呢,第二部分还没有写好,说在等待的过程中,可以先看下Cesium对影像和地形的支持。。

可以看官方教程中的说法,如下图所示:

 
Cesium说教程的第二部分说Property的。。

于是,我苦等了一年啦。。官方教程的第二部分还是没能看到。。毕竟这是Cesium官方推荐使用的Entity API中最重要的部分之一。。居然这么久了也不给更新下。。

我想还是自己总结一下得好。。

为什么要用Property?

还是举个例子来说吧。

比如我想在地球上的某个位置加一个盒子,可以这样写代码:

// 创建盒子
var blueBox = viewer.entities.add({
    name : 'Blue box',
    position: Cesium.Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
    box : {
        dimensions : new Cesium.Cartesian3(400000.0, 300000.0, 500000.0),
        material : Cesium.Color.BLUE,
        outline: true,
    }
});

最终的效果如图所示:

 
box加在固定位置

但是呢,如果我想让这个盒子逐渐变长,该怎么操作呢?如下图所示:

 
盒子逐渐变长

方法是有的,就是可以不停地去修改blueBox.position,类似这样:
setInterval(function(){ blueBox.box.dimensions = xxx; }, 3000);

如果场景中有很多物体,在不同的时间段要发生各种走走停停地运动时,这样操作可能会很累人。那么Cesium就提供一种机制,让dimensions可以随时间自动发生变化,自动赋予不同的数值(位置)。这也就是property的作用了。以下代码的加入,就可以让盒子如上图所示做线性运动了。

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

    property.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), 
        new Cesium.Cartesian3(400000.0, 300000.0, 200000.0));
    
    property.addSample(Cesium.JulianDate.fromIso8601('2019-01-03T00:00:00.00Z'), 
        new Cesium.Cartesian3(400000.0, 300000.0, 700000.0));

    blueBox.box.dimensions = property;

以上代码的意思就是在两个不同的时间点分别赋予不同的位置,用SampledProperty包装成一个property,最后赋给blueBox.box.dimensions。

由此可见,Property最大的特点是和时间相互关联,在不同的时间可以动态地返回不同的属性值。而Entity则可以感知这些Property的变化,在不同的时间驱动物体进行动态展示。

Cesium宣称自己是数据驱动和time-dynamic visualization,这些可都是仰仗Property系统来实现的。

当然,Property可不只是这么简单,以下再详细论述。

Property的分类

Cesium的Property不止有刚才示例代码中的SampleProperty,还有很多其他的类型。如果搜索一下Cesium的API文档,会有很多。。如下图所示:

 
Cesium API文档搜索Property

我们简单分类一下

 
image.png

Property虚基类

Property是所有Property类型的虚基类。它定义了以下接口。

 
Property类型的公共接口

getValue 是一个方法,用来获取某个时间点的特定属性值。它有两个参数:第一个是time,用来传递一个时间点;第二个是result,用来存储属性值,当然也可以是undefined。这个result是Cesium的scratch机制,主要是用来避免频繁创建和销毁对象而导致内存碎片。Cesium就是通过调用getValue类似的一些函数来感知Property的变化的,当然这个方法我们在外部也是可以使用的。

isConstant 用来判断该属性是否会随时间变化,是一个布尔值。Cesium会通过这个变量来决定是否需要在场景更新的每一帧中都获取该属性的数值,从而来更新三维场景中的物体。如果isConstant为true,则只会获取一次数值,除非definitionChanged事件被触发。

definitionChanged 是一个事件,可以通过该事件,来监听该Property自身所发生的变化,比如数值发生修改。

equals 是一个方法,用来检测属性值是否相等。

基本Property类型

SampleProperty

我们最早在上述示例中使用的就是它,用来通过给定多个不同时间点的Sample,然后在每两个时间点之间进行线性插值的一种Property。代码写法如下:

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

    property.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), 
        new Cesium.Cartesian3(400000.0, 300000.0, 200000.0));
    
    property.addSample(Cesium.JulianDate.fromIso8601('2019-01-03T00:00:00.00Z'), 
        new Cesium.Cartesian3(400000.0, 300000.0, 700000.0));

    blueBox.box.dimensions = property;

效果如下所示:

 
盒子逐渐变长

TimeIntervalCollectionProperty

该Property用来指定各个具体的时间段的属性值,每个时间段内的属性值是恒定的,并不会发生变化,除非已经进入到下一个时间段。拿创建的盒子示例来说,表现出来的特点就是盒子尺寸的变化时跳跃式的。效果如下:

 
2.gif

代码如下:

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

    property.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
        iso8601 : '2019-01-01T00:00:00.00Z/2019-01-01T12:00:00.00Z',
        isStartIncluded : true,
        isStopIncluded : false,
        data : new Cesium.Cartesian3(400000.0, 300000.0, 200000.0)
    }));
    property.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
        iso8601 : '2019-01-01T12:00:01.00Z/2019-01-02T00:00:00.00Z',
        isStartIncluded : true,
        isStopIncluded : false,
        data : new Cesium.Cartesian3(400000.0, 300000.0, 400000.0)
    }));
    property.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
        iso8601 : '2019-01-02T00:00:01.00Z/2019-01-02T12:00:00.00Z',
        isStartIncluded : true,
        isStopIncluded : false,
        data : new Cesium.Cartesian3(400000.0, 300000.0, 500000.0)
    }));
    property.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
        iso8601 : '2019-01-02T12:00:01.00Z/2019-01-03T00:00:00.00Z',
        isStartIncluded : true,
        isStopIncluded : true,
        data : new Cesium.Cartesian3(400000.0, 300000.0, 700000.0)
    }));

    blueBox.box.dimensions = property;

ConstantProperty

通过对TimeIntervalCollectionProperty和SampleProperty的描述,读者应该基本了解Property的特点。我们回过头来说下ConstantProperty,其实这才是最常用的Property。

示例代码如下:

blueBox.box.dimensions = new Cesium.Cartesian3(400000.0, 300000.0, 200000.0);

以上代码貌似没有使用ConstantProperty,实际上他是等同于:

blueBox.box.dimensions = new ConstantProperty(new Cesium.Cartesian3(400000.0, 300000.0, 200000.0));

也就是Entity的box.dimensions类型并不是Cartesian3,而是一个Property。虽然我们赋值了一个Cartesian3,但是Cesium内部会隐晦地转化成了一个ConstantProperty。注意只会隐晦地转化成ConstantProperty,而不是SampleProperty,更不是TimeIntervalCollectionProperty。

虽然叫ConstantProperty,但是,这里Constant的意思并不是说这个Property不可改变,而是说它不会随时间发生变化。

举个例子,我们可以通过 property.getValue(viewer.clock.currentTime) 方法来获取某个时间点property的属性值。如果property是SampleProperty或者TimeIntervalCollectionProperty的话,不同的时间点,可能getValue出不同的数值。但是如果这个property是ConstantProperty,那么无论什么时间(getValue的第一个参数不起作用),最后返回的数值都是一样的。

但是不会随时间变化,并不代表不可改变。ConstantProperty还有一个setValue的方法,开发者可以通过调用它,来在适当的时候改变property的值。

比如,我可以通过点击按钮来修改ConstantProperty,代码如下:

blueBox.box.dimensions.setValue(new Cesium.Cartesian3(400000.0, 300000.0, 700000.0));

需要注意的是,虽然最终效果一样,但是以下两种写法的意义是不一样的。

blueBox.box.dimensions = new Cesium.Cartesian3(400000.0, 300000.0, 200000.0);

blueBox.box.dimensions.setValue(new Cesium.Cartesian3(400000.0, 300000.0, 700000.0));

前者会创建一个新的ConstantProperty,后者则会修改原有的ConstantProperty的值。

CompositeProperty

CompositeProperty的意思是组合的Property,可以把多种不同类型的ConstantProperty、SampleProperty、TimeIntervalCollectionProperty等Property组合在一起来操作。比如前一个时间段需要线性运动,后一段时间再跳跃式运动。则可以使用类似下面这段代码来实现。

    // 1 sampledProperty
    var sampledProperty = new Cesium.SampledProperty(Cesium.Cartesian3);
    sampledProperty.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), 
        new Cesium.Cartesian3(400000.0, 300000.0, 200000.0));
    
    sampledProperty.addSample(Cesium.JulianDate.fromIso8601('2019-01-02T00:00:00.00Z'), 
        new Cesium.Cartesian3(400000.0, 300000.0, 400000.0));

    // 2 ticProperty
    var ticProperty = new Cesium.TimeIntervalCollectionProperty();
    ticProperty.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
        iso8601 : '2019-01-02T00:00:00.00Z/2019-01-02T06:00:00.00Z',
        isStartIncluded : true,
        isStopIncluded : false,
        data : new Cesium.Cartesian3(400000.0, 300000.0, 400000.0)
    }));
    ticProperty.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
        iso8601 : '2019-01-02T06:00:00.00Z/2019-01-02T12:00:00.00Z',
        isStartIncluded : true,
        isStopIncluded : false,
        data : new Cesium.Cartesian3(400000.0, 300000.0, 500000.0)
    }));
    ticProperty.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
        iso8601 : '2019-01-02T12:00:00.00Z/2019-01-02T18:00:00.00Z',
        isStartIncluded : true,
        isStopIncluded : false,
        data : new Cesium.Cartesian3(400000.0, 300000.0, 600000.0)
    }));
    ticProperty.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
        iso8601 : '2019-01-02T18:00:00.00Z/2019-01-03T23:00:00.00Z',
        isStartIncluded : true,
        isStopIncluded : true,
        data : new Cesium.Cartesian3(400000.0, 300000.0, 700000.0)
    }));

    // 3 compositeProperty
    var compositeProperty = new Cesium.CompositeProperty();
    compositeProperty.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
        iso8601 : '2019-01-01T00:00:00.00Z/2019-01-02T00:00:00.00Z',
        data : sampledProperty
    }));
    compositeProperty.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
        iso8601 : '2019-01-02T00:00:00.00Z/2019-01-03T00:00:00.00Z',
        isStartIncluded : false,
        isStopIncluded : false,
        data : ticProperty
    }));

    // 4 设置position
    blueBox.box.dimensions = compositeProperty;

最终实现的效果如下:

 
CompositeProperty

PositionProperty

以上示例可以看到,我们一直在用SampledProperty、ConstantProperty等来修改Entity的box.dimensions属性。基本上可以得出结论:大部分Property都是可以赋值给Entity的box.dimensions的。

PositionProperty和Property一样,是一个虚类,并不能直接实例化,他扩展了Property的接口,增加了referenceFrame,同时只能用来表示position。

 
PositionProperty

referenceFrame是用来表示position的参考架。目前Cesium有以下两种参考架。

 
image.png

我们常用的是FIXED这种默认类型,它相当于以地球的中心作为坐标系的原点,x轴正向指向赤道和本初子午线的交点。(可能描述不准确。。)这样我们给定一个笛卡尔坐标(x, y, z),它在地球上的位置是固定的。

而INERTIAL这种类型,则相当于以太阳系的质心为原点的坐标架偏移到地球的中心来,如果给定一个笛卡尔坐标(x, y, z),那么它在不同的时间表示的是地球上的不同位置。。(我的理解,可能有误。。)

一般情况下,我们用不上INERTIAL。但是如果真的给定了INERTIAL下的坐标点,Cesium内部会通过PositionProperty,把它转成同一个FIXED下的坐标点来使用,这些不需要我们操作。

但是,因为普通的Property是没有办法进行这种参考架的自动转换的,所以Cesium派生了一批PositionProperty类型。

基于PositionProperty的类型有以下几种:
CompositePositionProperty
ConstantPositionProperty
PositionProperty
PositionPropertyArray
SampledPositionProperty
TimeIntervalCollectionPositionProperty

稍加留意,就会发现,和普通的Property相比,只是多了一个Position,所以用法上也大同小异,只不过他们是用来专门表示位置的。

SampledPositionProperty

SampledPositionProperty的用法,不多解释,直接看代码吧:

    var property = new Cesium.SampledPositionProperty();

    property.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), 
        Cesium.Cartesian3.fromDegrees(-114.0, 40.0, 300000.0));
    
    property.addSample(Cesium.JulianDate.fromIso8601('2019-01-03T00:00:00.00Z'), 
        Cesium.Cartesian3.fromDegrees(-114.0, 45.0, 300000.0));

    blueBox.position = property;

效果如下:

 
SampledPositionProperty

SampleProperty和SampledPositionProperty有一个特有的方法:setInterpolationOptions,用来修改不同的插值方式。以下是以Cesium的Interpolation示例中的截图来说明他们的不同之处。

线性插值
 
线性插值

代码写法如下:

entity.position.setInterpolationOptions({
    interpolationDegree : 1,
    interpolationAlgorithm : Cesium.LinearApproximation
});
Lagrange插值
 
Lagrange插值
entity.position.setInterpolationOptions({
    interpolationDegree : 5,
    interpolationAlgorithm : Cesium.LagrangePolynomialApproximation
});
Hermite插值
 
Hermite插值
entity.position.setInterpolationOptions({
    interpolationDegree : 2,
    interpolationAlgorithm : Cesium.HermitePolynomialApproximation
});

MaterialProperty

MaterialProperty是用来专门表示材质的Property,它对Property进行了扩展,增加了getType方法,用来获取材质类型。

 
image.png

MaterialProperty也是一个虚基类,派生类有:
CheckerboardMaterialProperty
ColorMaterialProperty
CompositeMaterialProperty
GridMaterialProperty
ImageMaterialProperty
MaterialProperty
PolylineArrowMaterialProperty
PolylineDashMaterialProperty
PolylineGlowMaterialProperty
PolylineOutlineMaterialProperty
StripeMaterialProperty

使用上大同小异,我们以ColorMaterialProperty来说明一下。

ColorMaterialProperty

    blueBox.box.material = new Cesium.ColorMaterialProperty(new Cesium.Color(0, 1, 0));
    // 以上代码等同于
    // blueBox.box.material = new Cesium.Color(0, 1, 0);

效果如下:

 
ColorMaterialProperty

ColorMaterialProperty的动态变化

如果希望Color动起来的话,也是可以的。ColorMaterialProperty的内部有一个color属性,可以赋予一个SampledProperty来实现动态效果。

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

    colorProperty.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), 
        new Cesium.Color(0, 1, 0));
    
        colorProperty.addSample(Cesium.JulianDate.fromIso8601('2019-01-03T00:00:00.00Z'), 
        new Cesium.Color(0, 0, 1));

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

效果如下:

 
ColorMaterialProperty的动态变化

其他类型的Property

CallbackProperty

CallbackProperty是自由度最高的一种Property,让用户通过自定义,回调函数,来返回需要的值。回调函数中,用户可以使用time来给定value,也可以以自己的方式给给定。

以下代码就是不通过time,自己手动调整dimension的示例。

    var l = 200000.0;
    var property = new Cesium.CallbackProperty(function (time, result) {
        result = result || new Cesium.Cartesian3(0, 0, 0);

        l += 10000.0;
        if (l > 700000.0) {
            l = 200000.0;
        }

        result.x = 400000.0;
        result.y = 300000.0;
        result.z = l;

        return result;
    }, false);
    
    blueBox.box.dimensions = property;

效果如下:

 
盒子逐渐变长

ReferenceProperty

该Property可以直接链接到别的对象的Property上,相当于引用,省得自己构建了。比如这里我创建了一个红色的盒子redBox,希望它和之前的蓝色盒子一起变大。那么可以使用以下代码:

    var collection = viewer.entities;
    redBox.box.dimensions = new Cesium.ReferenceProperty(collection, blueBox.id, ['box', 'dimensions']);

效果如下:

 
ReferenceProperty的使用
 
ReferenceProperty构造函数的参数

ReferenceProperty构造函数的参数有三个。第一个参数用来指定需要引用的对象所属的collection,如果没有自己专门创建EntityCollection的话,可以直接使用viewer.entities。第二个参数传递所指对象的id。第三个参数指定属性的位置的数组,如果是有层级的属性,可以依次写入。比如 ['billboard', 'scale'] 指定的是entity.billboard.scale 属性。当然还有其他设置方式,可以参见Cesium的api文档。

PropertyBag

PropertyBag虽然不是以Property结尾,但实际上也是一个Property。它的特点是可以包装一个对象(JS中的对象概念),该对象的每一个属性(JS中的属性概念),都可以作为一个动态的Property。

比如之前修改dimensions的话,dimensions是作为一个Cartesian3类型变量整体封装到Property中去的,如果我们只想修改dimensions的x。则可以使用PropertyBag来实现,代码如下:

    var zp = new Cesium.SampledProperty(Number);
    zp.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), 200000.0);
    zp.addSample(Cesium.JulianDate.fromIso8601('2019-01-03T00:00:00.00Z'), 700000.0);
    
    blueBox.box.dimensions = new Cesium.PropertyBag({
        x: 400000.0,
        y: 300000.0,
        z: zp
    });
 
PropertyBag的使用

效果和sampleProperty类似,但是修改的只是dimensions的x。

PropertyArray

PropertyArray和上述的PropertyBag类似,只是其内部封装了一个数组而已。这里不再赘述。

VelocityOrientationProperty

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

VelocityVectorProperty

与上面的Property类似,把速度方向转成Vector。使用示例如下:

    blueBox.box.show = false;
    blueBox.billboard = {
        scale: 0.05,
        image : 'https://upload-images.jianshu.io/upload_images/80648-5dfe8a3ea2c250be.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/540/format/webp',
        alignedAxis : new Cesium.VelocityVectorProperty(blueBox.position, true) // alignedAxis must be a unit vector
    };

可见图像的摆放方向和位置移动的方向保持一致。效果如下:

 
VelocityVectorProperty

附录

本文在github上的源码


欢迎关注 Cesium实验室 ,QQ群号:595512567

 
image.png



posted @ 2018-12-04 11:13  Cesium实验室  阅读(8395)  评论(3编辑  收藏  举报