Cesium+earthSD實現相機飛行動畫
效果:
原理:
1.通過earthsdk將在兩個點之間畫出飛線,得到飛線點集數據
2.通過飛線點集數據,計算出每個點上的攝像機方向,得到攝像機方向集合
注意:在經過經度180度線的時候,會有攝像機反向問題,需特別處理
3.將飛線點集數據和攝像機方向集合生成 Path 對象,完成攝像機貼線飛行
4.在飛線路徑外圍添加 polylineVolume 管道對象達成效果
注意:當管道長度很長的時候,中間會出現管道扭曲,未找到原因。
解決辦法:將飛線沒兩個點拆成一個線段建立多個管道。由於entityid不能重復,所以需要使用 CustomDataSource 創建實體集合
代碼:
調用:
createCameraFollow(item)
1.創建地球
init() { XE.ready() .then(this.startup) .then(() => { this._earth.camera.position = [2.0991215, 0.549, 2000] this._earth.camera.rotation = [0, -0.41493986255762305, 0] this.mapInit = true }) }, startup() { let earth = new XE.Earth('earthContainer') this._earth = earth earth.weather.atmosphere.enabled = false earth.interaction.picking.enabled = true earth.interaction.picking.hoverEnable = false const bloom = earth.postProcess.bloom bloom.enabled = true bloom.glowOnly = false bloom.contrast = 119 bloom.brightness = -0.4 bloom.delta = 0.9 bloom.sigma = 3.78 bloom.stepSize = 5 bloom.isSelected = false earth.sceneTree.root = { children: [ { czmObject: { name: '影像', xbsjType: 'Imagery', xbsjImageryProvider: { XbsjImageryProvider: { url: 'https://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}', srcCoordType: 'WGS84', dstCoordType: 'WGS84' } } } } ] } var tileset = earth.sceneTree.$refs.tileset.czmObject },
2.主要方法:
// 創建相機跟隨 createCameraFollow(line) { // var p1 = [ // [[DtoR(-122.174699), DtoR(37.433888), 0], [ 2.0991215, 0.5497211,50] ] // ]; if (line.tgtIp === '' || line.srcIp === line.tgtIp) { return } var p1 = [ [ [this.DtoR(line.srcIpLon * 1), this.DtoR(line.srcIpLat * 1), 0], [this.DtoR(line.clueLon * 1), this.DtoR(line.clueLat * 1), 0] ] ]; // 獲取飛行距離 // let distance = this.getDistance(p1[0]) let parms = [ this.RtoD(p1[0][0][0]), this.RtoD(p1[0][0][1]), this.RtoD(p1[0][1][0]), this.RtoD(p1[0][1][1]), ] let distance = this.getFlatternDistance(...parms) // 獲取飛線路徑 let flyLine = this.getFlyLine(this._earth, p1) // 添加管道 this.createCylinder(this._earth, flyLine) // 攝像機貼線飛行 setTimeout(() => { this.cameraFly(flyLine, distance) }, 100) }, // 攝像機貼線飛行 cameraFly (flyLine, distance) { // 計算攝像機方向 let rotations = this.countRotations(flyLine) const leafConfig = { ref: 'path1', czmObject: { xbsjType: 'Path', positions: flyLine, rotations: rotations, show: false, // 顯示路徑 loop: false, // 是否為環線 showDirection: false, // 顯示方向(默認為true) // 是否處於播放狀態 // 如果屬性值為true,則所有'current'開頭的相關屬性會動態發生變化。 可以通過手動設置為false,來結束播放狀態。 當loopPlay屬性為false時,playing屬性會在路徑播放到最后一個關鍵點的位置時,自動變為false。 playing: true, // 是否循環播放 // 如果為false,則playing設置為true時,會從當前位置播放到最后一個關鍵點,並停止播放,此時playing屬性會自動變成false。 若此屬性為true時,播放到最后一個關鍵點以后,將自動重第一個關鍵點繼續播放。 loopPlay: true } } this._earth.sceneTree.root.children.push(leafConfig) var path1 = this._earth.sceneTree.$refs.path1.czmObject; // path1.flyTo(); path1.show = false; // 是否顯示 path1.currentSpeed = (distance)/5; // 運行速度 path1.currentD = 1; // 當前位置,單位米 path1.cameraAttached = true; // 綁定相機 path1.playing = true; // 飛行 // path1._currentPosition = true; // 飛行 path1.preUpdateEvalString = ` if (p.currentD === 0) { var path1 = p.earth.sceneTree.$refs.path1.czmObject; path1.flyTo(); let dataSources = p.earth.czm.viewer.dataSources dataSources.removeAll() p.playing = false path1.destroy(); } `; // 定義一個pin用來跟蹤路徑 // const pin = new XE.Obj.Pin(this._earth); // XE.MVVM.track(pin, 'position', path1, 'currentPosition'); }, // 添加管道-圓柱形-三角柱 createCylinder(earth, line) { var viewer = earth.czm.viewer console.log(line) // 添加點 function addpoint(viewer, item) { function RtoD(val) { let r = 180 * val / Math.PI return r } viewer.entities.add({ name: 'shin_point2', position: Cesium.Cartesian3.fromDegrees(RtoD(item[0]), RtoD(item[1]), item[2]), point: { show: true, color: Cesium.Color.RED.withAlpha(0.3), pixelSize: 2 } }) } for(let item of line){ // addpoint(viewer, item) } // 將飛線每兩個點處理成一條線段 let line1 = JSON.parse(JSON.stringify(line)) let lineArr =[] for (let i in line1) { if (i>0) { let lineS = [] lineS.push(this.RtoD(line1[i-1][0])) lineS.push(this.RtoD(line1[i-1][1])) lineS.push(line1[i-1][2] - 30000) lineS.push(this.RtoD(line1[i][0])) lineS.push(this.RtoD(line1[i][1])) lineS.push(line1[i][2] - 30000) lineArr.push(lineS) } } // 兩種顏色交替材質 /*var stripeMaterial = new Cesium.StripeMaterialProperty({ evenColor: Cesium.Color.WHITE.withAlpha(0.5), oddColor: Cesium.Color.YELLOW.withAlpha(1), repeat: 20.0, // 交替次數 // orientation: Cesium.StripeOrientation.VERTICAL, // 材質方向 // repeat: 1500.0, // 交替次數 });*/ var stripeMaterial = new Cesium.ImageMaterialProperty({ image:'/images/MS12.png', repeat: new Cesium.Cartesian2(1, 1.0), color: Cesium.Color.fromCssColorString('#3fffe4').withAlpha(0.6), transparent: true }); // 計算圓 function computeCircle(radius) { var positions = []; for (var i = 0; i < 360; i++) { var radians = Cesium.Math.toRadians(i); positions.push( new Cesium.Cartesian2( radius * Math.cos(radians), radius * Math.sin(radians) ) ); } return positions; } // 計算棱柱 arms 棱柱角的數量 rOuter 外角突出距離 rInner 內角收縮距離 function computeStar(arms, rOuter, rInner) { var angle = Math.PI / arms; var length = 2 * arms; var positions = new Array(length); for (var i = 0; i < length; i++) { var r = i % 2 === 0 ? rOuter : rInner; positions[i] = new Cesium.Cartesian2( Math.cos(i * angle) * r, Math.sin(i * angle) * r ); } return positions; } // 創建實體集合 var dataSource = new Cesium.CustomDataSource('lines'); viewer.dataSources.add(dataSource); for (let i=0; i< lineArr.length;i++) { dataSource.entities.add({ polylineVolume: { positions: Cesium.Cartesian3.fromDegreesArrayHeights(lineArr[i]), // shape: computeCircle(60000), // 圓柱子 // shape: computeStar(3, 26000, 50000), // 三角形柱子 // shape: computeStar(2, 50000, 50000), // 正方型柱子 shape: computeStar(5, 40000, 50000), // 五邊形柱子 // shape: computeStar(5, 70000, 40000), // 五角星柱子 material: stripeMaterial, outlineColor: Cesium.Color.WHITE, outlineWidth: 1 }, }); } }, // 清除管道和攝像機綁定 clearCylinder(earth){ let dataSources = earth.czm.viewer.dataSources dataSources.removeAll() var path1 = this._earth.sceneTree.$refs.path1.czmObject; path1.playing = false },
3.工具方法:
// 創建飛線 - 返回飛線點數據集合 -earthsdk方法 getFlyLine(earth, data) { var busLines = []; // var p = [ // [ [ 1.5990215, 0.5493211, 0 ], [ 2.0991215, 0.5497211, 50 ] ], // /*[ [ 2.0992215, 0.5499211, 0 ], [ 2.0991215, 0.5497211, 50 ] ],*/ // ]; var p = data var positionsCollection = p.map(e => { const toDegree = 180.0 / Math.PI; // Cesium.xbsjCreateTransmitPolyline 根據 首末端點生成弧線, // 參數有: // startPosition, 端點1 // endPosition, 端點2 // minDistance, 計算出的線段的最小間隔距離 增大該值可減少點數量 // heightRatio=1.0 弧線高度抬升程度,值越大,抬高得越明顯 // 返回值是cartesian類型的坐標數組 const cartesians = Cesium.xbsjCreateTransmitPolyline(e[0], e[1], 100000.0, 1.0); const poss = cartesians.map(ee => { const carto = Cesium.Cartographic.fromCartesian(ee); return [carto.longitude, carto.latitude, carto.height]; }); return poss; }); return positionsCollection[0] }, // 計算攝像機角度數組 -- 忽略俯仰角面映射 line:線路徑 countRotations(line) { // 將點的單位統一為度 let l = line.map(res => { return [this.RtoD(res[0]), this.RtoD(res[1]), this.MtoD(res[2])] }) // 計算偏航角 function countHornX(point1, point2) { var x0=point1[0]; var y0=point1[1]; var x1=point2[0]; var y1=point2[1]; let a,x,y // 防止相鄰兩個點跨越赤道或子午線 if ((x1 > 0 && x0 > 0) || (x1<0 && x0 < 0)) { x = x1-x0 } else { x = x0 - x1 } if ((y1 > 0 && y0 > 0) || (y1<0 && y0 < 0)) { y = y1-y0 } else { y = y0 - y1 } a = Math.atan2(x,y); return a } // 計算俯仰角 function countHornY(point1, point2) { var x0=point1[0]; var y0=point1[1]; var h0=point1[2]; var x1=point2[0]; var y1=point2[1]; var h1=point2[2]; let a a = Math.atan2((h1 - h0),(Math.sqrt(Math.pow((x1 - x0),2) + Math.pow((y1 - y0),2)))) * 10 return a } // 計算rotations let rotations = [] for (let i = 0; i < l.length - 1; i++) { let rx = countHornX(l[i], l[i * 1 + 1]) let ry = countHornY(l[i], l[i * 1 + 1]) rotations.push([rx, ry, 0]) } rotations.push(rotations[rotations.length - 1]) return rotations }, // 獲取兩點間的距離 getFlatternDistance(lat1,lng1,lat2,lng2){ var EARTH_RADIUS = 6378137.0; //單位M var PI = Math.PI; function getRad(d){ return d*PI/180.0; } var f = getRad((lat1 + lat2)/2); var g = getRad((lat1 - lat2)/2); var l = getRad((lng1 - lng2)/2); var sg = Math.sin(g); var sl = Math.sin(l); var sf = Math.sin(f); var s,c,w,r,d,h1,h2; var a = EARTH_RADIUS; var fl = 1/298.257; sg = sg*sg; sl = sl*sl; sf = sf*sf; s = sg*(1-sl) + (1-sf)*sl; c = (1-sg)*(1-sl) + sf*sl; w = Math.atan(Math.sqrt(s/c)); r = Math.sqrt(s*c)/w; d = 2*w*a; h1 = (3*r -1)/2/c; h2 = (3*r +1)/2/s; return d*(1 + fl*(h1*sf*(1-sg) - h2*(1-sf)*sg)); }, // 度轉弧度 DtoR(val) { let r = val * Math.PI / 180 return r }, // 弧度轉度 RtoD(val) { let r = 180 * val / Math.PI return r }, // 米轉為度 MtoD(val) { let r = val / 1112000 return r }, // 米轉為度 DtoM(val) { let r = val * 1112000 return r }, // 弧度轉米 RtoM (val) { return this.DtoM(this.RtoD(val)) }
鑽研不易,轉載請注明出處。。。。。。