supermap 動態(tài)圖層實現(xiàn)動態(tài)三維動畫、動畫跟蹤及軌跡繪制

三維技術(shù)在前端的應(yīng)用,不僅局限于“靜態(tài)”模型的呈現(xiàn),其在模型動畫方面的應(yīng)用,也有巨大的前景和作用。三維技術(shù)結(jié)合監(jiān)控設(shè)備、傳感設(shè)備,能夠?qū)崟r的將某一區(qū)域內(nèi)所有目標(biāo)物的運動狀況和軌跡呈現(xiàn)在三維場景中,實現(xiàn)三維可視化的監(jiān)控、管理和預(yù)測,對于大型停車場、交通仿真、港口船舶等場景將帶來極大的便利。

參考資料:
交通仿真在線范例:http://support.supermap.com.cn:8090/webgl/examples/editor.html#trafficSimulation


一、使用動態(tài)圖層(Dynamic Layer)方法實現(xiàn)三維動畫

1、實現(xiàn)三維動畫的兩種方式
(1)KML 模型節(jié)點動畫
這種方式向 viewer對象 的 dataSources 合集里面添加 KML 格式的模型文件,將其變成一個 entity 實體,再通過改變 entities 實體的 position、orientation 等屬性實現(xiàn)動畫,也可以建模的時候在模型上先預(yù)置好動畫。這種方式適合呈現(xiàn)預(yù)置的、不需要進(jìn)行代碼控制的動畫,直接改變 position 等屬性實現(xiàn)的動畫效果較為粗糙,詳細(xì)代碼可查看超圖的 KML模型節(jié)點動畫在線范例
(2)S3M 模型動態(tài)圖層動畫
該方式的原理和上述有所不同,是通過新建一個 DynamicLayer3D 的圖層,然后把 s3m 模型放置到這個圖層中,再使用 updateObjectWithModel 方法讓模型根據(jù)指定的路徑信息(一般由后臺生成,包括經(jīng)緯度、高呈等)不斷進(jìn)行更新從而實現(xiàn)模型動畫效果。由于 supermap 對動態(tài)圖層上的模型變化做了平緩過渡的優(yōu)化,這種方式呈現(xiàn)出來的動畫質(zhì)量非常好,而且同一個模型可以根據(jù)生成的不同路線進(jìn)行復(fù)用,非常適合于大型仿真和實況場景的可視化展示。

2、動態(tài)圖層代碼實現(xiàn)動畫
(1)創(chuàng)建 s3m 動態(tài)圖層

// s3m模型引入路徑集合
var urls = [
    './models/ship1.s3m', 
    './models/ship2.s3m',
    './models/ship4.s3m',
];

 // 創(chuàng)造s3m動態(tài)圖層
var layerS3m = new Cesium.DynamicLayer3D(viewer.scene.context, urls);
viewer.scene.primitives.add(layerS3m);

(2)s3m 模型初始化
首先定義 s3m 模型的初始路徑點數(shù)據(jù),主要是定義路徑的起點、轉(zhuǎn)折點及終點這些關(guān)鍵點的經(jīng)緯度,url 指定使用的模型類型,這些數(shù)據(jù)一般都由后臺提供

// s3m模型初始點路徑數(shù)據(jù)
var s3mRoute = [
  {
    id: 1,                                      //  模型 id
    url: './models/ship1.s3m',               // 指定模型的類型
    longitude: [113.429257753643, 113.432889398072, 113.435648587521],       // 模型關(guān)鍵點的經(jīng)度數(shù)據(jù)
    latitude: [22.179584024493, 22.1819691979702, 22.1822381312213]            // 模型關(guān)鍵點的緯度數(shù)據(jù)
  },
  {
    id: 2,
    url: './models/ship1.s3m',                // 注意,模型是可以復(fù)用的
    longitude: [113.429257753643, 113.431963627038, 113.437962937247],
    latitude: [22.179584024493, 22.1810110654651, 22.1800193185396]
  },
  {
    id: 3,
    url: './models/ship2.s3m',
    longitude: [113.429257753643, 113.431881938968, 113.43604789432, 113.435539632918],
    latitude: [22.179584024493, 22.1810614918177, 22.1788091037995, 22.178002280521]
  },
  {
    id: 4,
    url: './models/ship4.s3m',
    longitude: [113.430553206872, 113.432291642287],
    latitude: [22.1802826334056, 22.1823216226192]
  },
];

下面是s3m 模型初始化

// 初始化s3m模型
var s3mKeymap = initS3mShip(s3mRoute, urls)

// 處理s3m模型初始化函數(shù)
function initS3mShip (data, urls) {
  var keymap = {};
  for (var i = 0; i < urls.length; i++) {
    keymap[urls[i]] = [];
  }
  var state;
  for (var i = 0; i < data.length; i++) {
    var length = data[i].longitude.length - 1;
    switch(data[i].url) {                       // 對每種模型進(jìn)行配置
      case './models/ship1.s3m': {
        state = new Cesium.DynamicObjectState({
          longitude: data[i].longitude[length],       // 經(jīng)度
          latitude: data[i].latitude[length],           // 緯度
          altitude: -4,                // 海拔
          scale: new Cesium.Cartesian3(1, 1, 1),       // 模型大小
          heading: Cesium.Math.toRadians(230),        // 方位角
          id: data[i].id                    // 模型 id 
        });
        break;
      };
      case './models/ship2.s3m': {
        state = new Cesium.DynamicObjectState({
          longitude: data[i].longitude[length],
          latitude: data[i].latitude[length],
          altitude: 0,
          scale: new Cesium.Cartesian3(1, 1, 1),
          heading: Cesium.Math.toRadians(50),
          id: data[i].id
        });
        break;
      };
      case './models/ship4.s3m': {
        state = new Cesium.DynamicObjectState({
          longitude: data[i].longitude[length],
          latitude: data[i].latitude[length],
          altitude: 0,
          scale: new Cesium.Cartesian3(1, 1, 1),
          id: data[i].id
        });
        break;
      }
    }
    keymap[data[i].url].push(state);
  }
  for (var key in keymap) {
    layerS3m.updateObjectWithModel(key, keymap[key]);        //更新模型空間屬性
  }
  return keymap;            // 返回模型空間屬性配置數(shù)據(jù)
}

(3)處理路徑數(shù)據(jù)
一般后臺只提供一些關(guān)鍵點的經(jīng)緯度信息,需要前端將這些數(shù)據(jù)根據(jù)指定步長分解為更準(zhǔn)確的一個個路徑點,再提供給動態(tài)圖層進(jìn)行渲染。

// 根據(jù)所給的步長和關(guān)鍵點數(shù)據(jù)生成完整路徑點集合
function generateRoutePoints (step, data) {
  var longStep = step;
  var pointsList = [];
  var points;
  for (var i = 0; i < data.length; i++) {
    points = [];
    for (var j = 0; j < data[i].longitude.length - 1; j++) {
       // 根據(jù)給出的經(jīng)度步長計算出每2個關(guān)鍵點之間的移動次數(shù)
      var moveTimes = Math.abs(parseInt((data[i].longitude[j] - data[i].longitude[j + 1]) / longStep)); 
       // 根據(jù)經(jīng)度算出的移動次數(shù)計算出每2個關(guān)鍵點之間緯度的移動步長
      var latStep = parseFloat((data[i].latitude[j] - data[i].latitude[j + 1]) / moveTimes).toFixed(8);
      if (data[i].longitude[j] > data[i].longitude[j + 1]) {          // 向西運動將經(jīng)度步長設(shè)為負(fù)值,否則相反          
        longStep = Math.abs(longStep);
      } else {
        longStep = -Math.abs(longStep);
      }
      for (var k = 0; k < moveTimes; k++) {      // 將每一次根據(jù)指定步長移動后的經(jīng)緯度數(shù)據(jù)存放在 points 中
        points.push({
          longitude: data[i].longitude[j] - k * longStep,
          latitude: data[i].latitude[j] - k * latStep
        })
      }
    }
    pointsList.push({           // 計算完所有關(guān)鍵點后,將模型的完整路徑數(shù)據(jù)存放在 pointsList 中
      id: data[i].id,
      url: data[i].url,
      points: points
    })
  }
  return pointsList;        // 返回生成的完整數(shù)據(jù)
}

// 生成漁船路徑數(shù)據(jù)
var s3mLonStep = 0.00008966;
var s3mRoutePoints = generateRoutePoints (s3mLonStep, s3mRoute);

(4)加載路徑數(shù)據(jù)
點擊模型加載指定模型的路徑數(shù)據(jù),也可以一次性加載全部數(shù)據(jù),視業(yè)務(wù)需求而定

// 渲染模型路徑動畫,傳入模型 id、完整路徑數(shù)據(jù)、模型空間屬性數(shù)據(jù)
function generateActionShip (id, data, keymap) {
  var url = data[id - 1].url;        // 獲取點擊模型的模型存放位置
  var updateTimes = data[id - 1].points.length;   // 模型更新次數(shù)
  var updateIndex = -1;
  var flag = 0;
  for (var i = 0; i < data.length; i++) {         // 由于復(fù)用了相同的模型,要根據(jù) id 來判斷更新的是模型空間中的第幾個模型
    if (data[i].url === url) {
      updateIndex++;
    }
    if (i === id - 1) {         // 因為上面的 id 是從1 開始賦值的,所以這里判斷的時候是對比 id - 1
      break;
    }
  }

  // 更新s3m圖層信息
  _s3mUpdateInterval = setInterval(function(){
    layerS3m.updateObjectWithModel(url, keymap[url]);            // 循環(huán)調(diào)用 updateObjectWithModel 更新模型空間數(shù)據(jù)
    keymap[url][updateIndex].longitude = data[id - 1].points[flag].longitude;        // 更新模型空間的經(jīng)緯度信息
    keymap[url][updateIndex].latitude = data[id - 1].points[flag].latitude;
    flag++;
    if (flag === updateTimes - 1) {
      clearInterval(_s3mUpdateInterval);       // 渲染完畢清除定時器
    }
  }, 200);
}

layerS3m.updateInterval = 200;    // 設(shè)置動態(tài)圖層更新頻率

// 點擊模型顯示動畫
handleS3mId = new Cesium.ScreenSpaceEventHandler(scene.canvas);
handleS3mId.setInputAction(function(click) {                // 監(jiān)聽點擊操作
  var pickedObject = scene.pick(click.position);
  if (Cesium.defined(pickedObject) && pickedObject.id) {          // 獲取模型的 id 并顯示動畫
    var s3mPickedId = pickedObject.id
    generateActionShip(s3mPickedId, s3mRoutePoints, s3mKeymap); 
  } else {
    console.log(pickedObject);
  }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
二、動畫軌跡繪制

我們的業(yè)務(wù)需求是實現(xiàn)點擊某個模型,能讓模型移動并顯示出移動軌跡,如果僅需實現(xiàn)動畫可忽略這部分。

// 繪制模型路徑,傳入當(dāng)前點擊模型的關(guān)鍵點路徑數(shù)據(jù)
function handlePolyline(data){
  var length = data.longitude.length      // 獲取關(guān)鍵點的個數(shù)
  var blueLine = [];
  for (var i = 0; i < length - 1; i++) {
    blueLine[i] = viewer.entities.add({         // 添加路徑實體
     id: 100 + i,                     // 路徑 id 
      name : 'line on the surface',     // 名稱
      polyline : {
        positions : Cesium.Cartesian3.fromDegreesArrayHeights([data.longitude[i] , data.latitude[i], 2, data.longitude[i + 1], data.latitude[i + 1], 2]),          // 傳入路徑兩端的經(jīng)緯度數(shù)據(jù)
        width : 2,                        // 路徑寬度
        material : new Cesium.PolylineDashMaterialProperty({
         color: Cesium.Color.CYAN          // 路徑顏色
        })
      }
    });
  }
}

// 繪制路徑,可以在點擊模型之后調(diào)用
handlePolyline(s3mPickedId - 1)

// 移除繪制的路徑,在模型路徑渲染完畢時調(diào)用
for (var k = 0; k < s3mRoute[s3mPickedId - 1].longitude.length - 1; k++) {
  viewer.entities.removeById(100 + k);
}
三、動態(tài)模型跟蹤

可以通過 viewer 對象的 trackedEntity 實現(xiàn)對模型的跟蹤,使用后視角將切換到模型上,能增強沉浸式體驗,但是在這個視角下模型運動的過渡緩動效果還有待優(yōu)化,需斟酌使用。

// 將以下代碼復(fù)制到點擊模型獲取到 id 的后面即可
var selectedPrimitive = pickedObject.primitive; // 選中的圖元
var ownerGroup = selectedPrimitive._ownerGroup; // 圖元所在的組信息
var stateList = ownerGroup.stateList; // 狀態(tài)信息列表
var state = stateList.get(pickedObject.id);

if(!trackedEntity) {
    trackedEntity = viewer.entities.add({
        id: 'tracked-entity',
        position: state.position,
        point: {
            pixelSize: 1,
            show: true // 不能設(shè)為false
        },
        viewFrom: new Cesium.Cartesian3(-100, -150, 100) // 觀察位置的偏移量
    });
} else {
    trackedEntity.position = state.position;
}
viewer.trackedEntity = trackedEntity;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容