手把手带你开发Cesium三维场景【3D智慧城市警情预警】

📢 鸿蒙专栏:想学鸿蒙的,冲

📢 C语言专栏:想学C语言的,冲

📢 VUE专栏:想学VUE的,冲这里

📢 CSS专栏:想学CSS的,冲这里

📢 Krpano专栏:想学VUE的,冲这里

🔔 上述专栏,都在不定期持续更新中!!!!!!!!!!!!!

​​

效果演示

警情模拟示例

✨ 一、 前言

本文主要用于构建Cesium三维地图场景,主要实现了以下功能:

1、初始化三维地图控件

        使用Cesium.Viewer和Cesium.Scene等对象初始化三维地图,设置地图纹理、视角位置、阴影参数等配置信息,进行三维场景的初始化。

2、封装场景操作类

        封装D3类对场景进行管理,实现场景配置、数据加载、事件绑定等功能,以更好地控制三维场景。

3、CSS3渲染标注

        使用CSS3渲染在三维场景中添加Html标注信息。

4、实现视角导航动画

        实现不同场景状态下的视角平滑导航动画,使用flyTo方法实现过渡动画效果。

5、构建交互界面

        使用dat.GUI构建交互界面,可以控制场景效果参数、添加各种内容。

6、警情场景模拟

        实现警情监控、预报、分析、调度四个场景状态的模拟。

7、封装视觉效果方法

        封装添加模型、粒子、标注、墙体等常用场景效果的方法。

二、关键技术要点

1. 初始化三维场景

/**
 * 初始化
 */
D3.prototype.init = function (opt = {}) {

    if (configs.mapDom && configs.mapUrl) {

        Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJlYTQ2ZjdjNS1jM2E0LTQ1M2EtOWM0My1mODMzNzY3YjYzY2YiLCJpZCI6MjkzMjcsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1OTE5NDIzNjB9.RzKlVTVDTQ9r7cqCo-PDydgUh8Frgw0Erul_BVxiS9c';
		// 添加镜像服务
		// mapbox.satellite 卫星图像
		// mapbox.streets 街道图像
        this._viewer = new Cesium.Viewer(configs.mapDom, configs.mapOptions);

        this._util = new Cesium.Utils(this._viewer)
		//BingMapsImageryProvider  Bing地图影像,可以指定mapStyle,详见BingMapsStyle类
		//  其中可以指定mapStyle,选择多种风格,目前Cesium中支持AERIAL、AERIAL_WITH_LABELS、ROAD、ORDNANCE_SURVEY、COLLINS_BART五种。
        this._viewer.imageryLayers.addImageryProvider(new Cesium.BingMapsImageryProvider({
            url: 'https://dev.virtualearth.net',
            mapStyle: Cesium.BingMapsStyle.AERIAL,
            key: URL_CONFIG.BING_MAP_KEY
        }))

        this._scene = this._viewer.scene

        this._scene.skyBox = this._util.setOneGroundSkyBox()

        // this._util.setSnowEffect()

        this.config(opt) //默认开始配置

        this.loadScene() //加载场景

        // this.addThreeObject() //加载three obj
    } else {

        alert("请配置地图参数")
    }

}

使用Cesium中常用的Viewer、Scene等对象初始化三维视图,主要进行以下配置:

  • 设置地图纹理为Bing地图
  • 配置场景的光照、阴影参数
  • 关闭默认的天空背景、大气效果
  • 开启各种画质优化效果

相关API:

  • Cesium.Viewer:三维场景的主要容器
new Cesium.Viewer(container, options)

        用于构建应用程序的基本小部件。它将所有标准 Cesium 组件组合到一个可重用的包中。小部件总是可以通过使用mixins来扩展,它可以添加对各种应用程序有用的功能。 

NameTypeDescription
containerElement | string包含小部件的DOM元素或ID。
optionsViewer.ConstructorOptions描述初始化选项的对象

✨ Throws:

  1. DeveloperError:文档中不存在id为“container”的元素。 
  2. DeveloperError:选项。当不使用BaseLayerPicker小部件时,selectedImageryProviderViewModel不可用,请指定选项。baseLayer代替。
  3. DeveloperError:选项。当不使用BaseLayerPicker小部件时,selectedTerrainProviderViewModel不可用,请指定选项。terrainProvider代替。

示例:

//使用几个自定义选项和mixins初始化查看器小部件。
try {
  const viewer = new Cesium.Viewer("cesiumContainer", {
    // 从Columbus Viewer开始
    sceneMode: Cesium.SceneMode.COLUMBUS_VIEW,
    // 使用Cesium World地形
    terrain: Cesium.Terrain.fromWorldTerrain(),
    // 隐藏底层选择器
    baseLayerPicker: false,
    // 使用 OpenStreetMaps
    baseLayer: new Cesium.ImageryLayer(new Cesium.OpenStreetMapImageryProvider({
      url: "https://tile.openstreetmap.org/"
    })),
    skyBox: new Cesium.SkyBox({
      sources: {
        positiveX: "stars/TychoSkymapII.t3_08192x04096_80_px.jpg",
        negativeX: "stars/TychoSkymapII.t3_08192x04096_80_mx.jpg",
        positiveY: "stars/TychoSkymapII.t3_08192x04096_80_py.jpg",
        negativeY: "stars/TychoSkymapII.t3_08192x04096_80_my.jpg",
        positiveZ: "stars/TychoSkymapII.t3_08192x04096_80_pz.jpg",
        negativeZ: "stars/TychoSkymapII.t3_08192x04096_80_mz.jpg"
      }
    }),
    // 显示 Columbus View map 与 Web Mercator projection
    mapProjection: new Cesium.WebMercatorProjection()
  });
} catch (error) {
  console.log(error);
}

// 添加基本的拖放功能
viewer.extend(Cesium.viewerDragDropMixin);

// 如果在处理删除的文件时遇到错误,则显示弹出警报
viewer.dropError.addEventListener(function(dropHandler, name, error) {
  console.log(error);
  window.alert(error);
});
  • Scene:场景的主要渲染对象
new Cesium.Scene(options)

        所有的3 d图形对象的容器和国家Cesium虚拟场景。一般来说都不是直接创建的;它是由CesiumWidget隐式创建。 

options对象:

NameTypeDefaultDescription
canvasHTMLCanvasElement用于创建场景的HTML cancas元素。
contextOptionsContextOptions环境和WebGL创建属性
creditContainerElement用于显示服务描述信息的HTML元素。
creditViewportElement要在其中显示信用弹出窗口的HTML元素。如果未指定,则视口将作为画布的兄弟添加。
mapProjectionMapProjection在二维和Columbus 视图模式下使用的地图投影。
orderIndependentTranslucencybooleantrue如果此项设置为true,并且使用设备支持,将使用与顺序无关的半透明。
scene3DOnlybooleanfalse如果此项设置为true,将优化三维模式的内存使用和性能,但禁止使用二维或Columbus视图功能。
shadowsbooleanfalse确定阴影是否由太阳投射形成。
mapMode2DMapMode2D确定二维地图是可旋转的或是可以在在水平方向上无限滚动。
requestRenderModebooleanfalse
maximumRenderTimeChangenumber0.0
depthPlaneEllipsoidOffsetnumber0.0
msaaSamplesnumber1

✨ Throws: 

✨ 示例:

// 创建场景没有各向异性纹理过滤
const scene = new Cesium.Scene({
  canvas : canvas,
  contextOptions : {
    allowTextureFilterAnisotropic : false
  }
});
  • Cesium.BingMapsImageryProvider:Bing地图服务提供者
new Cesium.BingMapsImageryProvider(options)

要构造一个BingMapsImageryProvider,调用BingMapsImageryProvider. fromurl。不要直接调用构造函数。

        使用必应地图图像REST API提供平铺图像。 

NameTypeDescription
optionsBingMapsImageryProvider.ConstructorOptions描述初始化选项的对象

示例:

const bing = await Cesium.BingMapsImageryProvider.fromUrl(
  "https://dev.virtualearth.net", {
    key: "get-yours-at-https://www.bingmapsportal.com/",
    mapStyle: Cesium.BingMapsStyle.AERIAL
});

2. 场景操作类

/**
 * 场景配置
 * 
 * @param opt
 */
D3.prototype.config = function (opt) {

    if (this._scene) {

        //设置第二重烘焙纹理的效果(明暗程度)
        this._scene.shadowMap.darkness = 1.275;

        //设置环境光
        this._scene.lightSource.ambientLightColor = opt.ambientLightColor || new Cesium.Color(0.7, 0.7, 0.7, 1);

        //深度检测
        this._scene.globe.depthTestAgainstTerrain = true;

        //地面调节
        //this._scene.globe.baseColor = Cesium.Color.BLACK;
        this._scene.globe.globeAlpha = 0.5;
        this._scene.undergroundMode = true;
        this._scene.terrainProvider.isCreateSkirt = false;

        //调节场景环境
        this._scene.sun.show = false;
        this._scene.moon.show = false;
        // this._scene.skyBox.show = false;
        this._scene.skyAtmosphere.show = false;
        this._scene.fxaa = true;

        //开启颜色校正
        this._scene.colorCorrection.show = opt.colorCorrection || false;
        this._scene.colorCorrection.saturation = opt.saturation || 3.1;
        this._scene.colorCorrection.brightness = opt.brightness || 1.8;
        this._scene.colorCorrection.contrast = opt.contrast || 1.2;
        this._scene.colorCorrection.hue = opt.hue || 0;

        //开启泛光和HDR
        this._scene.bloomEffect.show = opt.bloomEffect || false;
        this._scene.hdrEnabled = opt.hdrEnabled || true;
        this._scene.bloomEffect.threshold = 1;
        this._scene.bloomEffect.bloomIntensity = 2;

        //最大距离
        this._scene.screenSpaceCameraController.maximumZoomDistance = 5000.0
    }
}

封装D3类对场景进行管理,其中主要功能有:

  • init:场景初始化,包含场景配置、数据加载等
  • config:场景参数配置,如环境光、阴影等
  • loadScene:加载场景数据,如3Dtiles、模型
  • bindHandle:绑定交互事件
  • closeScene:关闭场景,释放资源

使用面向对象的方式对复杂场景进行模块化管理。

3. 事件处理

/**
 * 事件处理
 */
D3.prototype.bindHandle = function () {
	// ScreenSpaceEventHandler提供ScreenSpaceEventType一种监听用户输入并对用户输入进行分类的方法
	// 这段代码是在Cesium中使用ScreenSpaceEventHandler来处理canvas的点击事件。
	
	// 具体来说:
	
	// _handler 是ScreenSpaceEventHandler的一个实例,用来处理canvas上的屏幕空间事件。
	// 它通过Cesium.ScreenSpaceEventHandler构造函数传入viewer的scene的canvas来创建。
	// 设置了一个左击事件的回调函数,在回调函数中可以获取到点击事件的一些信息,如点击位置的movement对象。
	// 接着使用scene的pick方法根据点击位置拾取到对象。
	// 如果拾取到的对象id名称为“警情分析”,则调用analysis()方法进行后续处理。
	// 所以这段代码的作用是:在Cesium场景canvas上,当点击“警情分析”对象时,调用analysis()方法进行响应。
	
	// ScreenSpaceEventHandler还可以处理其他事件类型,如右击、移动、长按等。事件类型包括:
	
	// LEFT_CLICK
	// RIGHT_CLICK
	// MIDDLE_CLICK
	// WHEEL
	// PINCH_START
	// PINCH_END
	// PINCH_MOVE
	// MOUSE_MOVE 等。

    this._handler = new Cesium.ScreenSpaceEventHandler(this._viewer.scene.canvas)
    this._handler.setInputAction((movement) => {

        // var cartesian = this._util.getCatesian3FromPX(movement.position)

        // console.log(this._util.transformCartesianToWGS84(cartesian))
        var obj = this._scene.pick(movement.position);
        if (obj && obj.id && obj.id.name && "警情分析" == obj.id.name) this.analysis()

    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    // this._viewer.scene.camera.moveEnd.addEventListener((move) => {

    //     console.log(this._util.getCameraPosition())

    // });



    // this._util.getHandelPosition((position,handel)=>{
    //     console.log(position)

    // })

    // this._util.setScanCircleEffect({
    //     position: new Cesium.Cartesian3.fromDegrees(106.50642721790797, 29.658575326606123, 5.0)
    // })

    // this._util.drawLine((value) => {
    //     console.log(value)
    // })
}

主要使用两种事件处理方式:

  • ScreenSpaceEventHandler:处理鼠标交互事件

new Cesium.ScreenSpaceEventHandler(element)

        处理用户输入事件。可以添加自定义函数,以便在用户输入时执行。 

NameTypeDefaultDescription
elementHTMLCanvasElementdocument要向其中添加事件的元素。
// 处理左击事件
this._handler.setInputAction((movement) => {
    // 事件处理逻辑
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  • 相机移动回调函数:处理飞行结束事件

Related API:

  • Cesium.ScreenSpaceEventHandler
  • Camera.moveEnd

4. 视角控制

D3.prototype.loadCameraPath = function (callback) {

    this._util.setView({

        position: { x: -2180840.640119748, y: 4381647.215317032, z: 4091216.503229185 },
        orientation: {
            heading: Cesium.Math.toRadians(356.76499726379865),
            pitch: Cesium.Math.toRadians(-22.735599006353922),
            roll: Cesium.Math.toRadians(0.00133659048757427)
        }
    });

    setTimeout(() => {
        this._util.flyTo({
            position: { x: -2178897.313757382, y: 4381397.305312672, z: 4091462.297319925 },
            orientation: {
                heading: Cesium.Math.toRadians(46.527000640600505),
                pitch: Cesium.Math.toRadians(-5.17091508581087),
                roll: Cesium.Math.toRadians(1.90833280887811)
            },
            duration: 5,
            callback: () => {
                this._util.flyTo({
                    position: { x: -2178132.972253719, y: 4380734.091723098, z: 4093209.132147421 },
                    orientation: {
                        heading: Cesium.Math.toRadians(105.62030224024655),
                        pitch: Cesium.Math.toRadians(-21.59096416111003),
                        roll: Cesium.Math.toRadians(359.9987311314987)
                    },
                    duration: 5,
                    callback: () => {
                        this._util.flyTo({
                            position: { x: -2179780.958069727, y: 4379145.05670711, z: 4093251.679035389 },
                            orientation: {
                                heading: Cesium.Math.toRadians(202.12146484437022),
                                pitch: Cesium.Math.toRadians(-4.367558356924628),
                                roll: Cesium.Math.toRadians(0.0006130606451948047)
                            },
                            duration: 5,
                            callback: () => {
                                this._util.flyTo({
                                    position: { x: -2182832.9113919945, y: 4380248.782123272, z: 4093233.182007854 },
                                    orientation: {
                                        heading: Cesium.Math.toRadians(282.56605551019436),
                                        pitch: Cesium.Math.toRadians(-38.5875540173017),
                                        roll: Cesium.Math.toRadians(359.99999999993923)
                                    },
                                    duration: 5,
                                    callback: callback
                                })
                            }
                        })
                    }
                })
            }
        })
    }, 2000)

}

主要通过flyTo方法实现视角的平滑飞行过渡动画。

// fly to 函数
_util.flyTo({
  destination : {
      x : 1.0,
      y : 2.0,
      z : 3.0
  },
  duration : 5
});

设置destination目标位置和orientation朝向,以及duration动画时间实现视角导航动画。

5. CSS3渲染

使用Cesium.Css3Renderer实现CSS3渲染:

// 创建CSS3渲染器
var cssRenderer = new Cesium.Css3Renderer();

// 添加标签层
cssRenderer.addLayer({
    position : [1.0, 2.0, 3.0],
    element : document.getElementById('label')
});

将Html标签元素渲染到指定三维位置。

相关API:

  • Cesium.Css3Renderer

6. 状态管理

使用状态码管理不同的场景状态,在状态切换时校验当前状态。

// 状态码
this._STATECODE = {
  zero : 0,
  one : 1,
  //...
};

// 当前状态  
this._state = this._STATECODE.zero;

// 状态检查
if (this._STATECODE.zero !== this._state) {
  // 错误提示
  return ;
}

这样可以确保场景按顺序演示。

三、场景实现要点

1. 警情监控

/**
 * 警情监控
 */
D3.prototype.monitoring = function (callback) {

    if (this._STATECODE.zero !== this._state & this._STATECODE.all !== this._state) {

        this._layers.msg('请先初始化场景 ', {
            time: 2500,
        });
        return false
    }

    var start = () => {

        this.postProcess.push(this._util.setRadarScanEffect({
            position: Cesium.Cartesian3.fromDegrees(116.45141573633816, 39.91324637901737, 10.0),
            color: Cesium.Color.RED,
            radius: 500
        }))

        this._util.flyTo({
            position: { x: -2178693.2564594974, y: 4380220.704344869, z: 4093694.8169494905 },
            orientation: {
                heading: Cesium.Math.toRadians(154.12738034665816),
                pitch: Cesium.Math.toRadians(-36.860896281659365),
                roll: Cesium.Math.toRadians(5.0125541779865026)
            },
            duration: 5,
            callback: () => {

                this._scene.camera.flyCircle(Cesium.Cartesian3.fromDegrees(116.45141573633816, 39.91324637901737, 100.0));

                if (typeof callback === 'function') {

                    setTimeout(() => {

                        callback()
                    }, 3000)
                } else {


                    // update state
                    this._state = this._STATECODE.one
                }
            }
        })
    }

    if (this.postProcess.length > 0) {


        this.closeScene(() => {

            start()
        })
    } else {

        start()
    }


}

主要实现:

  • 添加雷达扫描效果
  • 相机环绕目标点

使用 Cesium.PostProcessStage 添加雷达扫描后期效果:

// 添加雷达扫描特效
scene.postProcessStages.add(Cesium.PostProcessStageLibrary.createRadarScanStage());

并设置相机环绕点飞行实现监控效果:

// 相机环绕点飞行
camera.flyCircle(center);

2. 警情预报

/**
 * 
 * 警情预报
 */
D3.prototype.startSceneOne = function (callback) {


    if (this._STATECODE.one !== this._state & this._STATECODE.all !== this._state) {

        this._layers.msg('请先预览完警情监控 ', {
            time: 3000,
        });
        return false
    }
    this._viewer.scene.camera.stopFlyCircle();

    this._util.flyTo({
        position: { x: -2178463.9456453873, y: 4381002.914153181, z: 4093890.133365481 },
        orientation: {
            heading: Cesium.Math.toRadians(128.64858283790525),
            pitch: Cesium.Math.toRadians(-44.65849475098552),
            roll: Cesium.Math.toRadians(0.0023842018258495466)
        },
        duration: 5,
        callback: () => {
            this._util.flyTo({
                position: { x: -2179037.6221276326, y: 4380549.292276369, z: 4092247.909965294 },
                orientation: {
                    heading: Cesium.Math.toRadians(38.73496800382902),
                    pitch: Cesium.Math.toRadians(-21.116761624408735),
                    roll: Cesium.Math.toRadians(0.00018448854527482853)
                },
                duration: 5,
                callback: () => {

                    this._util.flyTo({
                        position: { x: -2179048.567425212, y: 4380181.227054535, z: 4092287.128962158 },
                        orientation: {
                            heading: Cesium.Math.toRadians(57.01126750844415),
                            pitch: Cesium.Math.toRadians(-4.698058614089507),
                            roll: Cesium.Math.toRadians(0.00009319620311191225)
                        },
                        duration: 5,
                        callback: () => {
                            if (typeof callback === 'function') {

                                setTimeout(() => {

                                    callback()
                                }, 3000)
                            } else {

                                // update state
                                this._state = this._STATECODE.tow
                            }
                        }
                    })
                }
            })
        }
    })

    //异常提示
    new Promise((resolve, reject) => {

        this.addDynamicEntity({
            position: Cesium.Cartesian3.fromDegrees(116.450217639056, 39.912527799624065, 130.0),
            model: { lng: 116.450217639056, lat: 39.912527799624065, alt: 130.0 },
            m_color: Cesium.Color.RED.withAlpha(0.5),
            label: true,
            billboard: true,
            text: ' 商务办公楼 ',
            l_font: '14px sans-serif',
            l_fillColor: Cesium.Color.WHITE,
            l_backgroundColor: Cesium.Color.RED,
            l_pixelOffset: new Cesium.Cartesian2(0, -5),
            l_showBackground: false
        })

        var cricleEntity = this._util.createDynamicCricle({
            center: { lng: 116.450217639056, lat: 39.912527799624065, alt: 130.0 },
            material: new Cesium.CircleWaveMaterialProperty({
                color: Cesium.Color.RED,
                count: 3,
                gradient: 0.9
            }),
            height: 100,
            radius: 50,
            rotateAmount: 0.01
        })
        this._viewer.entities.add(cricleEntity)

    })

    // 模拟效果
    new Promise((resolve, reject) => {

        //添加火焰粒子
        this.primitives.push(this._util.setFlameParticle({
            position: Cesium.Cartesian3.fromDegrees(116.4499827986952, 39.91231248171688, 0),
            tx: 0, ty: 0, tz: 50
        }))

        this.primitives.push(this._util.setFlameParticle({
            position: Cesium.Cartesian3.fromDegrees(116.45045144518653, 39.91234434075017, 0),
            tx: 0, ty: 0, tz: 50
        }))
		
		// position:表示模型在场景中的位置,使用经度、纬度和高度来定义。在这个示例中,使用 Cesium.Cartesian3.fromDegrees 方法将经度和纬度转换为笛卡尔坐标系。
		// m_url:表示模型的 URI,即模型数据的地址。在这个示例中,使用了相对路径 'examples/SampleData/gltf/man/walk.gltf'。
		// m_scale:表示模型在三个轴上的缩放比例。在这个示例中,模型将按照原始比例的 3 倍进行缩放。
		// m_minimumPixelSize:以像素为单位,表示模型渲染时的最小尺寸。在这个示例中,指定最小尺寸为 1 个像素。

        //添加人群
        this._viewer.entities.add(this._util.createModel({
            position: Cesium.Cartesian3.fromDegrees(116.45007132823285, 39.91223440231512, 5.0),
            m_url: 'examples/SampleData/gltf/man/walk.gltf',
            m_scale: 3,
            m_minimumPixelSize: 1,
        }))

        this._viewer.entities.add(this._util.createModel({
            position: Cesium.Cartesian3.fromDegrees(116.45026244256015, 39.91226094401238, 5.0),
            m_url: 'examples/SampleData/gltf/man/walk.gltf',
            m_scale: 3,
            m_minimumPixelSize: 1,
        }))

        this._viewer.entities.add(this._util.createModel({
            position: Cesium.Cartesian3.fromDegrees(116.45059442902425, 39.912284437562185, 5.0),
            m_url: 'examples/SampleData/gltf/man/walk.gltf',
            m_scale: 3,
            m_minimumPixelSize: 1,
        }))

    })

}

主要实现:

  • 添加火焰、人群3D模型
  • 显示建筑物异常情况

使用 Entity 系统添加模型:

// 添加3D人群
viewer.entities.add({
  position : ...,
  model : {
    uri : '...',
    scale : ... 
  }
});

并通过添加模型颜色、动画圈等方式显示异常情况。

3. 警情分析

/**
 * 警情分析
 */
D3.prototype.analysis = function (callback) {

    if (this._STATECODE.tow !== this._state & this._STATECODE.all !== this._state) {

        this._layers.msg('请先预览完警情预报', {
            time: 3000,
        });
        return false
    }
    this._viewer.scene.camera.stopFlyCircle();
	
	// 下面这段代码是在Cesium中使用flyTo方法进行视角飞行,并在飞行完成后添加一些视觉效果。
	
	// 具体来看:
	
	// _util.flyTo 是Cesium中的视角飞行方法。
	// position: 设定飞行完成后视角的笛卡尔空间坐标。
	// orientation: 设定飞行完成后视角的方向,包括航向角、俯仰角、滚转角。
	// duration: 整个飞行动画的持续时间,单位是秒。
	// callback: 飞行完成后的回调函数。
	// 在回调函数中添加了CSS3标签、相机环绕飞行的动画效果。
	// 还添加了警示线材质的Entity。
	// 如果传入了callback函数则在动画结束后执行。
	// flyTo除了上述参数,还可以设置:
	
	// maximumHeight: 飞行高度限制。
	// pitchAdjustHeight: 调整相机飞行高度的距离。
	// flyOverLongitude 和 flyOverLatitude: 飞行路线的经纬度。
	// 总之,这段代码实现了一个视角飞行的动画效果,并在飞行后的回调里增添了视觉效果,实现了一个较为复杂的动画场景。
    this._util.flyTo({
        position: { x: -2178835.9901788016, y: 4380941.406850311, z: 4092044.408504874 },
        orientation: {
            heading: Cesium.Math.toRadians(45.453049548959),
            pitch: Cesium.Math.toRadians(-15.610707989693905),
            roll: Cesium.Math.toRadians(359.99999999987216)
        },
        duration: 5,
        callback: () => {
            // css3 实现标牌

            let css3Elements = [];
            this._css3Renderer = new Cesium.Css3Renderer(css3Elements, true) //第三个参数为当标签在地球背面时候会隐藏

            this._css3Renderer.addEntityLayer({
                id: 'labelTip',
                position: [116.450217639056, 39.912527799624065, 130.0],//高度为 boxHeightMax
                element: `<div class='ysc-dynamic-layer ys-css3-box' id='labelTip'>
                       <div class='line'></div>
                       <div class='main' style="font-size:25px">
                            <div class="" style="color:#ff9800">警情分析</div>
                           <div class="">该楼七层发生火灾</div>
                           <div class="">指派救生队伍支援!</div>
                       </div>
                   </div>`,
                offset: [10, -250],
                boxShow: false,
                circleShow: false,
            })

            this._viewer.scene.camera.speedRatio = 0.1
            this._viewer.scene.camera.flyCircle(Cesium.Cartesian3.fromDegrees(116.450217639056, 39.912527799624065, 100.0));

            // 警示线特效
            var warn = [
                116.45121972426787, 39.912280505197565, 30.0,
                116.449751129691, 39.912270436562736, 30.0,
                116.44971753510406, 39.91321324258255, 30.0,
                116.45131361499521, 39.91317812427803, 30.0,
                116.45127073097758, 39.91221994119961, 30.0,
            ]
            this._viewer.entities.add({
                wall: {
                    positions: Cesium.Cartesian3.fromDegreesArrayHeights(warn),
                    material: new Cesium.WarnLinkMaterialProperty({ freely: 'cross', color: Cesium.Color.YELLOW, duration: 1000, count: 1.0, direction: '-' }),
                }
            });

            if (typeof callback === 'function') {

                setTimeout(() => {

                    callback()
                }, 3000)
            } else {
                // update state
                this._state = this._STATECODE.three
            }
        }
    })
}

主要实现:

  • 添加HTML标注
  • 相机环绕目标点

使用CSS3渲染添加HTML标注:

// 添加标注
cssRenderer.addLayer({
  position : [116.xx, 39.xx, 130.0],
  element : document.getElementById('label')
});

并设置相机环绕飞行完成对目标区域的分析。

4. 警情调度

/**
 * 警情调度
 */
D3.prototype.scheduling = function () {

    if (this._STATECODE.three !== this._state & this._STATECODE.all !== this._state) {

        this._layers.msg('请先预览完警情分析 ', {
            time: 3000,
        });
        return false
    }
    // 救护车
    var paths = [{ lon: 116.44753798513237, lat: 39.91329913144483, alt: 5.0, time: 0 },
    { lon: 116.44754831320495, lat: 39.91205867447874, alt: 5.0, time: 120 },
    { lon: 116.44919669983119, lat: 39.91207453317339, alt: 5.0, time: 240 },
    { lon: 116.45021742143986, lat: 39.91208239585685, alt: 5.0, time: 360 },
    { lon: 116.45021742143986, lat: 39.91208239585685, alt: 5.0, time: 480 }]


    // 消防车
    var paths2 = [{ lon: 116.4552207886404, lat: 39.91205626109142, alt: 5.0, time: 0 },
    { lon: 116.4531359117942, lat: 39.9120348488425, alt: 5.0, time: 120 },
    { lon: 116.45169757241298, lat: 39.91202452492026, alt: 5.0, time: 240 },
    { lon: 116.45021328751454, lat: 39.91201148288871, alt: 5.0, time: 360 },
    { lon: 116.45021328751454, lat: 39.91201148288871, alt: 5.0, time: 480 }]
    // 添加引导线
    this._util.addMaterialLine({
        positions: this._util.transformWGS84ArrayToCartesianArray(paths),
        width: 50,
        material: new Cesium.PolylineCityLinkMaterialProperty({
            color: Cesium.Color.RED,
            duration: 20000
        }),
        clampToGround: true
    })
    this._util.addMaterialLine({
        positions: this._util.transformWGS84ArrayToCartesianArray(paths2),
        width: 50,
        material: new Cesium.PolylineCityLinkMaterialProperty({
            color: Cesium.Color.RED,
            duration: 20000
        }),
        clampToGround: true
    })


    var addMan = () => {

        this._viewer.clock.multiplier = 1.0
        this._viewer.entities.add(this._util.createModel({
            position: Cesium.Cartesian3.fromDegrees(116.4500554199689, 39.91221044177715, 5.0),
            m_url: 'examples/data/model/xiaofangyuan-run.gltf',
            m_scale: 7,
            m_minimumPixelSize: 1,
        }))
        this._viewer.entities.add(this._util.createModel({
            position: Cesium.Cartesian3.fromDegrees(116.45035101783887, 39.912153073679924, 5.0),
            m_url: 'examples/data/model/xiaofangyuan-run.gltf',
            m_scale: 7,
            m_minimumPixelSize: 1,
        }))
    }

    // 添加车辆
    new Promise(() => {

        this._util.setPathRoaming({
            paths: paths,
            model: true,
            m_url: 'examples/data/model/qiche.gltf',
            m_scale: 0.1,
            m_minimumPixelSize: 1,
            label: true,
            l_text: '救护车',
            l_pixelOffset: new Cesium.Cartesian2(52, -48),
            l_fillColor: Cesium.Color.WHITE,
            l_outlineWidth: 3,
            billboard: true,
            b_img: 'examples/images/Textures/bp2.png',
            b_width: 55,
            b_height: 80,
            b_scale: 2,
            b_pixelOffset: new Cesium.Cartesian2(30, 0)
        })
        this._util.setPathRoaming({
            paths: paths2,
            model: true,
            m_url: 'examples/data/model/qiche.gltf',
            m_scale: 0.1,
            m_minimumPixelSize: 1,
            label: true,
            l_text: '消防车',
            l_pixelOffset: new Cesium.Cartesian2(52, -48),
            l_fillColor: Cesium.Color.WHITE,
            l_outlineWidth: 3,
            billboard: true,
            b_img: 'examples/images/Textures/bp.png',
            b_width: 55,
            b_height: 80,
            b_scale: 2,
            b_pixelOffset: new Cesium.Cartesian2(30, 0)
        })
    })

    //街道漫游
    this._viewer.scene.camera.stopFlyCircle();
    // 远景
    this._util.flyTo({
        position: { x: -2178725.2326817405, y: 4380717.691272014, z: 4092429.139375593 },
        orientation: {
            heading: Cesium.Math.toRadians(74.6359315563435),
            pitch: Cesium.Math.toRadians(-31.233145079364085),
            roll: Cesium.Math.toRadians(0.00021380944582563688)
        },
        duration: 5,
        callback: () => {
            //街道
            this._util.flyTo({
                position: { x: -2178718.4499226217, y: 4380320.631065833, z: 4092296.741367402 },
                orientation: {
                    heading: Cesium.Math.toRadians(83.59293245172353),
                    pitch: Cesium.Math.toRadians(-2.0635543646730805),
                    roll: Cesium.Math.toRadians(359.9999999999991)
                },
                duration: 5,
                callback: () => {
                    //漫游 1
                    this._util.flyTo({
                        position: { x: -2178907.5523918574, y: 4380227.944369431, z: 4092295.2689017 },
                        orientation: {
                            heading: Cesium.Math.toRadians(83.59451489628981),
                            pitch: Cesium.Math.toRadians(-2.0635543646734114),
                            roll: Cesium.Math.toRadians(359.99999999999926)
                        },
                        duration: 15,

                        callback: () => {
                            //漫游 2
                            this._util.flyTo({
                                position: { x: -2179038.3685479737, y: 4380186.554328359, z: 4092297.2180936695 },
                                orientation: {
                                    heading: Cesium.Math.toRadians(56.38807730423329),
                                    pitch: Cesium.Math.toRadians(-11.712623638749156),
                                    roll: Cesium.Math.toRadians(359.9999999999992)
                                },
                                duration: 10,
                                callback: () => {

                                    addMan() // 添加消防员
                                    //漫游 3
                                    this._util.flyTo({
                                        position: { x: -2178996.2169643864, y: 4380316.564571191, z: 4092306.3786329245 },
                                        orientation: {
                                            heading: Cesium.Math.toRadians(64.96210850602627),
                                            pitch: Cesium.Math.toRadians(-21.931507732669104),
                                            roll: Cesium.Math.toRadians(359.9998957985643)
                                        },
                                        duration: 8,
                                        callback: () => {
                                            // 弹出结果信息
                                            this._layers.open({
                                                type: 1
                                                , title: false
                                                , closeBtn: 1
                                                , shade: false
                                                , shadeClose: true
                                                , anim: 2
                                                , area: ['500px', '300px']
                                                , offset: 'auto'
                                                , content: `<div style="color:white"><h2 style="text-align:center;padding:10px">警情报告</h2>
                                                <div style="font-size:15px;padding:20px">
                                                <p style="padding-bottom:20px">xxx大楼七层于29日下午发生火灾,报告如下:</p>
                                                <p>原因:    七层火锅商铺电器泄露引起火灾;</p>
                                                <p>损失:    七层共7家商铺受影响,其中两家紧靠起火地较严重;</p>
                                                <p>出警:    接警后10分钟救护车与消防车到达现场,10分钟灭火救险;</p>
                                                <p>伤亡:    无重大伤亡,5人轻伤.</p>
                                                </div></div>`
                                                , btn: ['确认']
                                                , btn1: function () {
                                                    layer.closeAll();
                                                }
                                            });
                                        }
                                    })
                                }
                            })
                        }
                    })
                }
            })

        }
    })
}

主要实现:

  • 添加车辆模型
  • 实现车辆运动、街道漫游动画

使用 Entity 系统添加车辆模型,配合贝塞尔曲线实现运动动画:

// 添加车辆实体
var car = viewer.entities.add({
  // 贝塞尔曲线位置
  position : Cesium.CallbackProperty(...), 
  model : {...}
})

通过飞行设置视角完成街道漫游动画效果。

✨ 源码

 请点击上方绑定的资源下载

✨ 结语

  1. Cesium是一个功能强大的三维地图引擎,使用它可以快速实现各种三维可视化应用。
  2. 使用面向对象方式进行代码封装,可以很好地模块化复杂的三维场景。
  3. 利用Cesium提供的各种对象和效果实现丰富的场景和动画。
  4. 状态管理可以确保场景动画的顺序展现。
  5. Cesium中提供了许多辅助类和工具,可以大幅简化三维开发。

我们改日再会

  

  • 84
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 34
    评论
Cesium三维系统是一种基于Web的开源工具,用于构建和展示高度可交互的3D地球、地图和场景。它采用JavaScript编写,并使用开放式地理空间数据标准,如地理数据编码(Geographic Data Encoding,简称GeoJSON)和地理内联编码(Geographic Markup Language,简称GML),以实现地理信息的可视化和呈现。 Cesium三维系统具有许多功能和应用领域。首先,它可以用于浏览和分析地球上的各种地理数据,包括卫星影像、地图、地形、建筑物和矢量数据等。通过可视化这些数据,用户可以更深入地理解地球表面的各种地貌和特征。 其次,Cesium可以帮助开发者构建虚拟地球应用程序,如地球游戏、地理信息系统(Geographic Information System,简称GIS)和其他虚拟现实(Virtual Reality,简称VR)应用。开发者可以利用Cesium的丰富的API和库,为用户提供交互性强、视觉效果好的3D体验。 此外,Cesium还为用户提供了一套丰富的工具和功能,如地理空间数据可视化、相机控制、路径动画、地形分析和3D模型导入等。通过这些功能,用户可以根据自己的需求定制和展示各种地球场景。 总之,Cesium三维系统是一个功能强大的Web工具,用于构建和展示高度可交互的3D地球、地图和场景。它在地理数据可视化、虚拟地球应用程序开发和丰富的工具和功能等方面具有广泛的应用前景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱蹦跶的大A阿

你的打赏就是我蹦跶的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值