supermap 动态图层实现动态三维动画、动画跟踪及轨迹绘制

三维技术在前端的应用,不仅局限于“静态”模型的呈现,其在模型动画方面的应用,也有巨大的前景和作用。三维技术结合监控设备、传感设备,能够实时的将某一区域内所有目标物的运动状况和轨迹呈现在三维场景中,实现三维可视化的监控、管理和预测,对于大型停车场、交通仿真、港口船舶等场景将带来极大的便利。

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


一、使用动态图层(Dynamic Layer)方法实现三维动画

1、实现三维动画的两种方式
(1)KML 模型节点动画
这种方式向 viewer对象 的 dataSources 合集里面添加 KML 格式的模型文件,将其变成一个 entity 实体,再通过改变 entities 实体的 position、orientation 等属性实现动画,也可以建模的时候在模型上先预置好动画。这种方式适合呈现预置的、不需要进行代码控制的动画,直接改变 position 等属性实现的动画效果较为粗糙,详细代码可查看超图的 KML模型节点动画在线范例
(2)S3M 模型动态图层动画
该方式的原理和上述有所不同,是通过新建一个 DynamicLayer3D 的图层,然后把 s3m 模型放置到这个图层中,再使用 updateObjectWithModel 方法让模型根据指定的路径信息(一般由后台生成,包括经纬度、高呈等)不断进行更新从而实现模型动画效果。由于 supermap 对动态图层上的模型变化做了平缓过渡的优化,这种方式呈现出来的动画质量非常好,而且同一个模型可以根据生成的不同路线进行复用,非常适合于大型仿真和实况场景的可视化展示。

2、动态图层代码实现动画
(1)创建 s3m 动态图层

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

 // 创造s3m动态图层
var layerS3m = new Cesium.DynamicLayer3D(viewer.scene.context, urls);
viewer.scene.primitives.add(layerS3m);

(2)s3m 模型初始化
首先定义 s3m 模型的初始路径点数据,主要是定义路径的起点、转折点及终点这些关键点的经纬度,url 指定使用的模型类型,这些数据一般都由后台提供

// s3m模型初始点路径数据
var s3mRoute = [
  {
    id: 1,                                      //  模型 id
    url: './models/ship1.s3m',               // 指定模型的类型
    longitude: [113.429257753643, 113.432889398072, 113.435648587521],       // 模型关键点的经度数据
    latitude: [22.179584024493, 22.1819691979702, 22.1822381312213]            // 模型关键点的纬度数据
  },
  {
    id: 2,
    url: './models/ship1.s3m',                // 注意,模型是可以复用的
    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模型初始化函数
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) {                       // 对每种模型进行配置
      case './models/ship1.s3m': {
        state = new Cesium.DynamicObjectState({
          longitude: data[i].longitude[length],       // 经度
          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;            // 返回模型空间属性配置数据
}

(3)处理路径数据
一般后台只提供一些关键点的经纬度信息,需要前端将这些数据根据指定步长分解为更准确的一个个路径点,再提供给动态图层进行渲染。

// 根据所给的步长和关键点数据生成完整路径点集合
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++) {
       // 根据给出的经度步长计算出每2个关键点之间的移动次数
      var moveTimes = Math.abs(parseInt((data[i].longitude[j] - data[i].longitude[j + 1]) / longStep)); 
       // 根据经度算出的移动次数计算出每2个关键点之间纬度的移动步长
      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]) {          // 向西运动将经度步长设为负值,否则相反          
        longStep = Math.abs(longStep);
      } else {
        longStep = -Math.abs(longStep);
      }
      for (var k = 0; k < moveTimes; k++) {      // 将每一次根据指定步长移动后的经纬度数据存放在 points 中
        points.push({
          longitude: data[i].longitude[j] - k * longStep,
          latitude: data[i].latitude[j] - k * latStep
        })
      }
    }
    pointsList.push({           // 计算完所有关键点后,将模型的完整路径数据存放在 pointsList 中
      id: data[i].id,
      url: data[i].url,
      points: points
    })
  }
  return pointsList;        // 返回生成的完整数据
}

// 生成渔船路径数据
var s3mLonStep = 0.00008966;
var s3mRoutePoints = generateRoutePoints (s3mLonStep, s3mRoute);

(4)加载路径数据
点击模型加载指定模型的路径数据,也可以一次性加载全部数据,视业务需求而定

// 渲染模型路径动画,传入模型 id、完整路径数据、模型空间属性数据
function generateActionShip (id, data, keymap) {
  var url = data[id - 1].url;        // 获取点击模型的模型存放位置
  var updateTimes = data[id - 1].points.length;   // 模型更新次数
  var updateIndex = -1;
  var flag = 0;
  for (var i = 0; i < data.length; i++) {         // 由于复用了相同的模型,要根据 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]);            // 循环调用 updateObjectWithModel 更新模型空间数据
    keymap[url][updateIndex].longitude = data[id - 1].points[flag].longitude;        // 更新模型空间的经纬度信息
    keymap[url][updateIndex].latitude = data[id - 1].points[flag].latitude;
    flag++;
    if (flag === updateTimes - 1) {
      clearInterval(_s3mUpdateInterval);       // 渲染完毕清除定时器
    }
  }, 200);
}

layerS3m.updateInterval = 200;    // 设置动态图层更新频率

// 点击模型显示动画
handleS3mId = new Cesium.ScreenSpaceEventHandler(scene.canvas);
handleS3mId.setInputAction(function(click) {                // 监听点击操作
  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);
二、动画轨迹绘制

我们的业务需求是实现点击某个模型,能让模型移动并显示出移动轨迹,如果仅需实现动画可忽略这部分。

// 绘制模型路径,传入当前点击模型的关键点路径数据
function handlePolyline(data){
  var length = data.longitude.length      // 获取关键点的个数
  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]),          // 传入路径两端的经纬度数据
        width : 2,                        // 路径宽度
        material : new Cesium.PolylineDashMaterialProperty({
         color: Cesium.Color.CYAN          // 路径颜色
        })
      }
    });
  }
}

// 绘制路径,可以在点击模型之后调用
handlePolyline(s3mPickedId - 1)

// 移除绘制的路径,在模型路径渲染完毕时调用
for (var k = 0; k < s3mRoute[s3mPickedId - 1].longitude.length - 1; k++) {
  viewer.entities.removeById(100 + k);
}
三、动态模型跟踪

可以通过 viewer 对象的 trackedEntity 实现对模型的跟踪,使用后视角将切换到模型上,能增强沉浸式体验,但是在这个视角下模型运动的过渡缓动效果还有待优化,需斟酌使用。

// 将以下代码复制到点击模型获取到 id 的后面即可
var selectedPrimitive = pickedObject.primitive; // 选中的图元
var ownerGroup = selectedPrimitive._ownerGroup; // 图元所在的组信息
var stateList = ownerGroup.stateList; // 状态信息列表
var state = stateList.get(pickedObject.id);

if(!trackedEntity) {
    trackedEntity = viewer.entities.add({
        id: 'tracked-entity',
        position: state.position,
        point: {
            pixelSize: 1,
            show: true // 不能设为false
        },
        viewFrom: new Cesium.Cartesian3(-100, -150, 100) // 观察位置的偏移量
    });
} else {
    trackedEntity.position = state.position;
}
viewer.trackedEntity = trackedEntity;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342