meface/docs/article/gis/cesiumjs/cesiumProperty.md

511 lines
21 KiB
Markdown
Raw Normal View History

2023-11-17 10:54:23 +08:00
## Cesium的Property机制总结
> 转自:[https://zhuanlan.zhihu.com/p/50534090](https://zhuanlan.zhihu.com/p/50534090)
<img src="./images/v2-191d0dcc18de95eb11e79b7884c7445d_720w.jpg" alt="Cesium的Property机制总结" style="zoom: 67%;" />
### 1.前言
Cesium官方教程中有一篇叫《空间数据可视化》([Visualizing Spatial Data](https://link.zhihu.com/?target=https%3A//cesiumjs.org/tutorials/Visualizing-Spatial-Data/))。该文文末简单提到了Cesium的Property机制然后话锋一转宣告此教程的第二部分将重点讲解Property机制。但是呢第二部分还没有写好说在等待的过程中可以先看下Cesium对影像和地形的支持。。
可以看官方教程中的说法,如下图所示:
![img](./images/v2-662a2dce7f49b5f3302a5dfd8aa84d12_720w.webp)
于是我苦等了一年啦。。官方教程的第二部分还是没能看到。。毕竟这是Cesium官方推荐使用的Entity API中最重要的部分之一。。居然这么久了也不给更新下。。
我想还是自己总结一下得好。。
### 2.为什么要用Property
还是举个例子来说吧。
比如我想在地球上的某个位置加一个盒子,可以这样写代码:
```javascript
// 创建盒子
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,
}
});
```
![img](./images/v2-9427fa60c2d561793c3144688b701981_720w.webp)
但是呢,如果我想让这个盒子逐渐变长,该怎么操作呢?如下图所示:
![动图](./images/v2-f27bbcb37047dae8ef5775f223503abb_b.webp)
方法是有的就是可以不停地去修改blueBox.position类似这样
```javascript
setInterval(function(){ blueBox.box.dimensions = xxx; }, 3000);
```
如果场景中有很多物体,在不同的`时间段`要发生各种走走停停地运动时这样操作可能会很累人。那么Cesium就提供一种机制让dimensions可以随时间自动发生变化自动赋予不同的数值位置。这也就是property的作用了。以下代码的加入就可以让盒子如上图所示做线性运动了。
```javascript
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可不只是这么简单以下再详细论述。
### 3.Property的分类
Cesium的Property不止有刚才示例代码中的SampleProperty还有很多其他的类型。如果搜索一下Cesium的API文档会有很多。。如下图所示
![img](./images/v2-b70b0d43f35231f5eb36febafff1c6d0_720w.webp)
我们简单分类一下
![img](./images/v2-c83680caaff317ec55ebd5e79056fa60_720w.webp)
#### 3.1Property虚基类
Property是所有Property类型的虚基类。它定义了以下接口。
![img](./images/v2-ed2ace748efa89a624bbbf5689290899_720w.webp)
- **`getValue`** 是一个方法用来获取某个时间点的特定属性值。它有两个参数第一个是time用来传递一个时间点第二个是result用来存储属性值当然也可以是undefined。这个result是Cesium的scratch机制主要是用来避免频繁创建和销毁对象而导致内存碎片。**Cesium就是通过调用getValue类似的一些函数来感知Property的变化的**,当然这个方法我们在外部也是可以使用的。
- **`isConstant`** 用来判断该属性是否会随时间变化是一个布尔值。Cesium会通过这个变量来决定是否需要在场景更新的每一帧中都获取该属性的数值从而来更新三维场景中的物体。如果isConstant为true则只会获取一次数值除非definitionChanged事件被触发。
- **`definitionChanged`** 是一个事件可以通过该事件来监听该Property自身所发生的变化比如数值发生修改。
- **`equals`** 是一个方法,用来检测属性值是否相等。
#### 3.2基本Property类型
##### 3.2.1SampleProperty
我们最早在上述示例中使用的就是它用来通过给定多个不同时间点的Sample然后在每两个时间点之间进行线性插值的一种Property。代码写法如下
```javascript
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;
```
效果如下所示:
<img src="./images/v2-f27bbcb37047dae8ef5775f223503abb_b.webp" alt="动图" />
##### 3.2.2TimeIntervalCollectionProperty
该Property用来指定各个具体的时间段的属性值每个时间段内的属性值是恒定的并不会发生变化除非已经进入到下一个时间段。拿创建的盒子示例来说表现出来的特点就是盒子尺寸的变化时跳跃式的。效果如下
<img src="./images/v2-dcc7d3a82c1e58673837bfbb9c27becd_b.webp" alt="动图" style="zoom: 50%;" />
代码如下:
```javascript
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;
```
##### 3.2.3ConstantProperty
通过对TimeIntervalCollectionProperty和SampleProperty的描述读者应该基本了解Property的特点。我们回过头来说下ConstantProperty其实这才是最常用的Property。
示例代码如下:
```javascript
blueBox.box.dimensions = new Cesium.Cartesian3(400000.0, 300000.0, 200000.0);
```
以上代码貌似没有使用ConstantProperty实际上他是等同于
```javascript
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代码如下
```javascript
blueBox.box.dimensions.setValue(new Cesium.Cartesian3(400000.0, 300000.0, 700000.0));
```
需要注意的是,虽然最终效果一样,但是以下两种写法的意义是不一样的:
```javascript
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的值。
##### 3.2.4CompositeProperty
CompositeProperty的意思是组合的Property可以把多种不同类型的ConstantProperty、SampleProperty、TimeIntervalCollectionProperty等Property组合在一起来操作。比如前一个时间段需要线性运动后一段时间再跳跃式运动。则可以使用类似下面这段代码来实现。
```javascript
// 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;
```
最终实现的效果如下:
![动图](./images/v2-64d2de93509dd72fa2bd520a84c236c0_b.webp)
#### 3.3 PositionProperty
以上示例可以看到我们一直在用SampledProperty、ConstantProperty等来修改Entity的box.dimensions属性。基本上可以得出结论**大部分Property都是可以赋值给Entity的box.dimensions的**。
PositionProperty和Property一样是一个虚类并不能直接实例化他扩展了Property的接口增加了referenceFrame同时只能用来表示`position`。
![img](./images/v2-c1217233d5e37d6471c733ae6b87b33b_720w.webp)
`referenceFrame`是用来表示position的参考架。目前Cesium有以下两种参考架。
![img](./images/v2-745a51e13cf50b947c6a9414674d6c07_720w.webp)
我们常用的是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所以用法上也大同小异只不过他们是用来专门表示位置的。
##### 3.3.1 SampledPositionProperty
SampledPositionProperty的用法不多解释直接看代码吧
```javascript
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;
```
效果如下:
![动图](./images/v2-6a822141b0418080e0da3dff89048c51_b.webp)
SampleProperty和SampledPositionProperty有一个特有的方法setInterpolationOptions用来修改不同的插值方式。以下是以Cesium的Interpolation示例中的截图来说明他们的不同之处。
- **线性插值**
![img](./images/v2-66b22bdf4bdaf61bb644aef429ad6491_720w.webp)
```javascript
entity.position.setInterpolationOptions({
interpolationDegree : 1,
interpolationAlgorithm : Cesium.LinearApproximation
});
```
- **Lagrange插值**
![img](./images/v2-060e5140a134c618ec79ed98e77ce56d_720w.webp)
```javascript
entity.position.setInterpolationOptions({
interpolationDegree : 5,
interpolationAlgorithm : Cesium.LagrangePolynomialApproximation
});
```
- **Hermite插值**
![img](./images/v2-653d0d3e480c7ecaa6cae8f8896e57c6_720w.webp)
```javascript
entity.position.setInterpolationOptions({
interpolationDegree : 2,
interpolationAlgorithm : Cesium.HermitePolynomialApproximation
});
```
#### 3.4 MaterialProperty
MaterialProperty是用来专门表示材质的Property它对Property进行了扩展增加了getType方法用来获取材质类型。
![img](./images/v2-bbcd5c27c1d7bcfbe78837f2cffc9bb8_720w.webp)
MaterialProperty也是一个虚基类派生类有
- CheckerboardMaterialProperty
- ColorMaterialProperty
- CompositeMaterialProperty
- GridMaterialProperty
- ImageMaterialProperty
- MaterialProperty
- PolylineArrowMaterialProperty
- PolylineDashMaterialProperty
- PolylineGlowMaterialProperty
- PolylineOutlineMaterialProperty
- StripeMaterialProperty
使用上大同小异,我们以`ColorMaterialProperty`来说明一下。
**ColorMaterialProperty**
```javascript
blueBox.box.material = new Cesium.ColorMaterialProperty(new Cesium.Color(0, 1, 0));
// 以上代码等同于
// blueBox.box.material = new Cesium.Color(0, 1, 0);
```
效果如下:
![img](./images/v2-6aa45b3854a3274c0f9e6a0401e19094_720w.webp)
**ColorMaterialProperty的动态变化**
如果希望Color动起来的话也是可以的。ColorMaterialProperty的内部有一个color属性可以赋予一个SampledProperty来实现动态效果。
```javascript
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);
```
效果如下:
![动图](./images/v2-58a229c972c55e3b961d0e7f520645d6_b.webp)
#### 3.5 其他类型的Property
##### 3.5.1 CallbackProperty
CallbackProperty是自由度最高的一种Property让用户通过自定义回调函数来返回需要的值。回调函数中用户可以使用time来给定value也可以以自己的方式给给定。
以下代码就是不通过time自己手动调整dimension的示例。
```javascript
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;
```
效果如下:
![动图](./images/v2-f27bbcb37047dae8ef5775f223503abb_b.webp)
##### 3.5.2ReferenceProperty
该Property可以直接链接到别的对象的Property上相当于引用省得自己构建了。比如这里我创建了一个红色的盒子redBox希望它和之前的蓝色盒子一起变大。那么可以使用以下代码
```javascript
var collection = viewer.entities;
redBox.box.dimensions = new Cesium.ReferenceProperty(collection, blueBox.id, ['box', 'dimensions']);
```
效果如下:
<img src="./images/v2-b57ae739c9371f5a46f71df9ba620eee_b.webp" alt="动图" style="zoom:50%;" />
![img](./images/v2-a5026e1c8bcfe4c5415ebf0ed021a2cc_720w.webp)
ReferenceProperty构造函数的参数有三个:
- 第一个参数用来指定需要引用的对象所属的collection如果没有自己专门创建EntityCollection的话可以直接使用viewer.entities。
- 第二个参数传递所指对象的id。
- 第三个参数指定属性的位置的数组,如果是有层级的属性,可以依次写入。比如 `['billboard', 'scale']` 指定的是entity.billboard.scale 属性。当然还有其他设置方式可以参见Cesium的api文档。
##### 3.5.3 PropertyBag
PropertyBag虽然不是以Property结尾但实际上也是一个Property。它的特点是可以包装一个对象(JS中的对象概念),该对象的每一个属性(JS中的属性概念)都可以作为一个动态的Property。
比如之前修改dimensions的话dimensions是作为一个Cartesian3类型变量整体封装到Property中去的如果我们只想修改dimensions的x。则可以使用PropertyBag来实现代码如下
```javascript
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
});
```
![动图](./images/v2-1422b8902c403c3d4094b26c1a6d108e_b.webp)
效果和sampleProperty类似但是修改的只是dimensions的x。
##### 3.5.4 PropertyArray
PropertyArray和上述的PropertyBag类似只是其内部封装了一个数组而已。这里不再赘述。
##### 3.5.5 VelocityOrientationProperty
该Property用来Entity的position的位置变化来计算出移动的方向最后把速度方向输出成Orientation。Cesium自带的示例中有一个Interpolation中有其用法不再赘述。
##### 3.5.6 VelocityVectorProperty
与上面的Property类似把速度方向转成Vector。使用示例如下
```javascript
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
};
```
可见图像的摆放方向和位置移动的方向保持一致。效果如下:
<img src="./images/v2-1d415e24a5c34c2f70b49ea953b8b9a2_b.webp" alt="动图" style="zoom:67%;" />