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

发布时间:2023年12月28日

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

📢 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中提供了许多辅助类和工具,可以大幅简化三维开发。

我们改日再会

??

文章来源:https://blog.csdn.net/qq_41152573/article/details/135267343
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。