当我们需要在数字世界中重现真实城市的立体景观时,单纯的几何展示往往难以传递建筑背后的数据故事。Cesium 3D Tiles Styling语言就像一把神奇的画笔,能够将建筑高度、功能类型等属性转化为直观的视觉语言。想象一下,当整座城市的建筑根据高度梯度呈现彩虹色阶,或按商业/住宅用途显示不同透明度时,数据洞察变得触手可及。
在开始编写样式规则前,我们需要搭建基础环境。假设已通过Cesium ion获取了纽约建筑的3D Tiles数据集(Asset ID: 75343),初始化代码如下:
javascript复制const viewer = new Cesium.Viewer('cesiumContainer');
const tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: Cesium.IonResource.fromAssetId(75343)
})
);
提示:若使用自定义数据,需先将模型转换为3D Tiles格式上传至Cesium ion。支持的原始格式包括:
- 三维模型:glTF(.glb/.gltf)、COLLADA(.dae)、OBJ(.obj)
- 地理数据:CityGML、KML
- 点云:LAS(.las/.laz)
常见的位置偏移问题可通过调整modelMatrix解决:
javascript复制tileset.readyPromise.then(function() {
const boundingSphere = tileset.boundingSphere;
const cartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center);
const surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0);
const offset = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, -32);
const translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());
tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
});
建筑高度是最直观的可视化维度之一。下面我们实现从冷色到暖色的渐变方案,反映不同高度区间的建筑分布:
javascript复制const heightStyle = new Cesium.Cesium3DTileStyle({
color: {
conditions: [
["${Height} >= 300", "rgba(45, 0, 75, 0.8)"], // 深紫色-超高层
["${Height} >= 200", "rgb(102, 71, 151)"], // 紫罗兰-高层
["${Height} >= 100", "rgb(170, 162, 204)"], // 浅紫色-中高层
["${Height} >= 50", "rgb(224, 226, 238)"], // 淡蓝色-中层
["${Height} >= 25", "rgb(252, 230, 200)"], // 米色-低层
["${Height} >= 10", "rgb(248, 176, 87)"], // 橙色-多层
["${Height} >= 5", "rgb(198, 106, 11)"], // 褐色-低矮
["true", "rgb(127, 59, 8)"] // 深褐色-平房
]
},
meta: {
description: "'${Height}米'"
}
});
tileset.style = heightStyle;
关键参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| conditions | Array | 条件表达式数组,按顺序匹配 |
| $ | 变量 | 建筑高度属性字段 |
| rgba() | 函数 | 带透明度的颜色值 |
| meta | Object | 鼠标悬停时显示的元信息 |
注意:条件表达式应采用从严格到宽松的顺序排列,最后的"true"作为默认情况
除了高度,我们还可以结合建筑年代、功能类型等属性创建复合可视化方案。首先检查数据集中可用的属性字段:
javascript复制tileset.readyPromise.then(function() {
const properties = tileset.metadata.getPropertyIds();
console.log("可用属性:", properties);
// 典型输出: ["Height", "YearBuilt", "Function", "Floors"]
});
将建造年代与高度结合,用色相表示年代,明度表示高度:
javascript复制const yearHeightStyle = new Cesium.Cesium3DTileStyle({
color: {
conditions: [
["${YearBuilt} < 1950 && ${Height} > 50", "rgb(215, 25, 28)"], // 红色-老建筑高层
["${YearBuilt} < 1950", "rgb(253, 174, 97)"], // 橙色-老建筑低层
["${YearBuilt} < 2000 && ${Height} > 50", "rgb(44, 123, 182)"],// 蓝色-中期高层
["${YearBuilt} < 2000", "rgb(171, 217, 233)"], // 浅蓝-中期低层
["${Height} > 50", "rgb(0, 109, 44)"], // 深绿-新建高层
["true", "rgb(102, 189, 99)"] // 浅绿-新建低层
]
}
});
通过show属性控制不同类型建筑的显隐:
javascript复制const functionStyle = new Cesium.Cesium3DTileStyle({
show: "${Function} === 'Commercial'",
color: "color('purple', 0.7)"
});
// 或使用多条件判断
const multiFunctionStyle = new Cesium.Cesium3DTileStyle({
show: {
conditions: [
["${Function} === 'Residential'", "true"],
["${Function} === 'Mixed Use'", "true"],
["true", "false"] // 隐藏其他类型
]
}
});
静态可视化只是开始,创建交互控制面板能让用户自主探索数据:
html复制<div id="controlPanel">
<select id="colorBy">
<option value="height">按高度着色</option>
<option value="year">按年代着色</option>
<option value="function">按功能着色</option>
</select>
<label>高度阈值: <input type="range" id="heightThreshold" min="0" max="500" value="100"></label>
<fieldset>
<legend>显示类型:</legend>
<label><input type="checkbox" name="functionType" value="Residential" checked> 住宅</label>
<label><input type="checkbox" name="functionType" value="Commercial" checked> 商业</label>
<label><input type="checkbox" name="functionType" value="Mixed Use" checked> 混合用途</label>
</fieldset>
</div>
对应的JavaScript控制逻辑:
javascript复制document.getElementById('colorBy').addEventListener('change', function(e) {
switch(e.target.value) {
case 'height': tileset.style = heightStyle; break;
case 'year': tileset.style = yearHeightStyle; break;
case 'function': tileset.style = functionStyle; break;
}
});
document.getElementById('heightThreshold').addEventListener('input', function(e) {
const threshold = parseInt(e.target.value);
tileset.style = new Cesium.Cesium3DTileStyle({
color: {
conditions: [
[`\${Height} >= ${threshold}`, "rgb(255, 0, 0)"],
["true", "rgb(200, 200, 200)"]
]
}
});
});
document.querySelectorAll('input[name="functionType"]').forEach(checkbox => {
checkbox.addEventListener('change', function() {
const selectedTypes = Array.from(
document.querySelectorAll('input[name="functionType"]:checked')
).map(el => `'\${Function} === \\'${el.value}\\''`).join(' || ');
tileset.style = new Cesium.Cesium3DTileStyle({
show: `Boolean(${selectedTypes})`
});
});
});
当处理大规模城市数据时,需特别注意渲染性能:
细节层级控制:
javascript复制tileset.maximumScreenSpaceError = 2; // 默认16,值越小细节越高
tileset.dynamicScreenSpaceError = true;
tileset.dynamicScreenSpaceErrorDensity = 0.00278;
按需加载策略:
tileset.style而非单独修改每个Feature内存管理:
javascript复制// 视口外的建筑使用简化样式
const lodStyle = new Cesium.Cesium3DTileStyle({
color: {
conditions: [
["${distanceToCamera} > 1000", "rgb(200, 200, 200)"],
["true", heightStyle.color]
]
}
});
Web Worker预处理:
对于超大规模数据集,可将样式计算移至Web Worker:
javascript复制const styleWorker = new Worker('styleWorker.js');
styleWorker.onmessage = function(e) {
tileset.style = new Cesium.Cesium3DTileStyle(e.data);
};
// 在styleWorker.js中处理复杂的条件逻辑
在实际项目中,我发现最影响性能的往往是属性查询而非样式应用。一个200MB的纽约建筑数据集,在合理配置下可实现60fps的流畅交互。当出现卡顿时,首先检查控制台是否有"skipped frames"警告,然后逐步简化样式条件复杂度。