cesium 动态线

动态线、流动线是智慧城市项目中,比较常见的可视化效果,cesium 中对于线图元提供了几种材质,但是并没有流动材质效果。我们可以通过自定义MaterialProperty材质来实现该效果。、

1. MaterialProperty类

Cesium 自定义MaterialProperty原理解析篇里已经详细介绍了MaterialProperty的原理,这里不在详述。

2. 自定义DnamicImageMaterialProperty类

这里的动态线是通过向shader里传入一张图片,设置图片的重复比率来,在每一帧更新中,动态采样,实现流动效果。

2.1. 自定义 CustomColorMaterialProperty 类
/*
 * @Description: 
 * @Author: maizi
 * @Date: 2024-08-16 15:06:46
 * @LastEditTime: 2024-08-16 17:28:51
 * @LastEditors: maizi
 */

function DnamicImageMaterialProperty(options) {
  options = Cesium.defaultValue(options, Cesium.defaultValue.EMPTY_OBJECT);

  this._definitionChanged = new Cesium.Event();
  this._image = undefined;
  this._repeat = undefined;
  this._color = undefined;
  this._speed = undefined;

  this.image = options.image;
  this.repeat = new Cesium.Cartesian2(
    options.repeat?.x || 1,
    options.repeat?.y || 1
  )
  this.color = options.color || Cesium.Color.fromBytes(0, 255, 255, 255);
  this.speed = options.speed || 1;
}

Object.defineProperties(DnamicImageMaterialProperty.prototype, {
  isConstant: {
    get: function () {
      return (
        Cesium.Property.isConstant(this._image) 
        && Cesium.Property.isConstant(this._repeat) 
        && Cesium.Property.isConstant(this._color) 
        && Cesium.Property.isConstant(this._speed)
      );
    },
  },

  definitionChanged: {
    get: function () {
      return this._definitionChanged;
    },
  },

  image: Cesium.createPropertyDescriptor("image"),
  repeat: Cesium.createPropertyDescriptor("repeat"),
  color: Cesium.createPropertyDescriptor("color"),
  speed: Cesium.createPropertyDescriptor("speed"),
});


DnamicImageMaterialProperty.prototype.getType = function (time) {
  return "DnamicImage";
};

DnamicImageMaterialProperty.prototype.getValue = function (time, result) {
  if (!Cesium.defined(result)) {
    result = {};
  }

  result.image = Cesium.Property.getValueOrUndefined(this._image, time);
  result.repeat = Cesium.Property.getValueOrUndefined(this._repeat, time);
  result.color = Cesium.Property.getValueOrClonedDefault(this._color, time);
  result.speed = Cesium.Property.getValueOrClonedDefault(this._speed, time);

  return result;
};


DnamicImageMaterialProperty.prototype.equals = function (other) {
  const res = this === other || (other instanceof DnamicImageMaterialProperty &&
    Cesium.Property.equals(this._image, other._image) &&
    Cesium.Property.equals(this._repeat, other._repeat) &&
    Cesium.Property.equals(this._color, other._color) &&
    Cesium.Property.equals(this._speed, other._speed))
  return res;
};
export default DnamicImageMaterialProperty;
2.2. 材质shader
uniform sampler2D image;
uniform float speed;
uniform vec4 color;
uniform vec2 repeat;

czm_material czm_getMaterial(czm_materialInput materialInput){
   czm_material material = czm_getDefaultMaterial(materialInput);
   vec2 st = repeat * materialInput.st;
   float time = fract(czm_frameNumber * speed / 1000.0);
   vec4 colorImage = texture(image, vec2(fract(st.s - time), st.t));
   if(color.a == 0.0){
     if(colorImage.rgb == vec3(1.0) || colorImage.rgb == vec3(0.0)){
       discard;
     }
    material.alpha = colorImage.a;
    material.diffuse = colorImage.rgb;
   }else{
    material.alpha = colorImage.a * color.a;
    material.diffuse = max(color.rgb * material.alpha * 3.0, color.rgb);
   }
   return material;
}
2.3. 添加到缓存
/*
 * @Description: 
 * @Author: maizi
 * @Date: 2024-08-16 16:21:55
 * @LastEditTime: 2024-08-16 16:41:44
 * @LastEditors: maizi
 */

import DnamicImageMaterial from '../shader/DnamicImageMaterial.glsl'

Cesium.Material.DnamicImage = 'DnamicImage'
Cesium.Material._materialCache.addMaterial(
  Cesium.Material.DnamicImage,
  {
    fabric: {
      type: Cesium.Material.DnamicImage,
      uniforms: {
        color: new Cesium.Color(1.0, 0.0, 0.0, 0.7),
        image: Cesium.Material.DefaultImageId,
        speed: 1,
        repeat: new Cesium.Cartesian2(1, 1),
      },
      source: DnamicImageMaterial,
    },
    translucent: function (material) {
      return true
    },
  }
)

3. 完整示例代码

DnamicLine.js

/*
 * @Description:
 * @Author: maizi
 * @Date: 2022-05-27 11:36:22
 * @LastEditTime: 2024-08-22 15:27:11
 * @LastEditors: maizi
 */
const merge = require('deepmerge')
import { DnamicImageMaterialProperty } from '../../materialProperty/index.js'
const defaultStyle = {
  speed: 10,
  repeat: {
    x: 1,
    y: 1
  },
  color: "#ffff00",
  type:0,
  lineWidth: 8
}

class DnamicLine {
  constructor(viewer, coords, options = {}) {
    this.viewer = viewer
    this.coords = coords;
    this.options = options;
    this.props = this.options.props;
    this.style = merge(defaultStyle, this.options.style || {});
   
    this.entity = null;
    this.material = null
    this.points = []
    this.init();
  }

  init() {
    this.createMaterial();
    this.entity = new Cesium.Entity({
      id: Math.random().toString(36).substring(2),
      type: "dnamic_line",
      polyline: {
        positions: this.getPositons(),
        width: this.style.lineWidth,
        material: this.material,
        //clampToGround: true
      }
    });
  }

  addPoints() {
    this.coords.forEach((coord) => {
      const point = new Cesium.Entity({
        position: Cesium.Cartesian3.fromDegrees(coord[0],coord[1],coord[2]),
        point: {
          color: Cesium.Color.DARKBLUE.withAlpha(.4),
          pixelSize: 6,
          outlineColor: Cesium.Color.YELLOW.withAlpha(.8),
          outlineWidth: 4
        }     
      }); 
      this.viewer.entities.add(point)
      this.points.push(point)
    })
  }

  removePoints() {
    this.points.forEach((point) => {
      this.viewer.entities.remove(point)
    })
    this.points = []
  }

  getPositons() {
    const positions = []
    this.coords.forEach((coord) => {
      positions.push(Cesium.Cartesian3.fromDegrees(coord[0], coord[1], coord[2]));
    })
    return positions
  }

  createMaterial() {
    switch(this.style.type){
      case 0:
        this.material = new DnamicImageMaterialProperty({
          color:new Cesium.Color.fromCssColorString(this.style.color),
          image:require('@/assets/img/arrow_line.png'),
          speed:this.style.speed,
          repeat: this.style.repeat
        });
        break;
      case 1:
        this.material = new DnamicImageMaterialProperty({
          color:new Cesium.Color.fromCssColorString(this.style.color),
          image:require('@/assets/img/link_pulse_line.png'),
          speed:this.style.speed,
          repeat: this.style.repeat
        });
        break;
    }
  }

  updateStyle(style) {
    this.style = merge(defaultStyle, style);
    this.entity.polyline.width = this.style.lineWidth
    this.material.color = new Cesium.Color.fromCssColorString(this.style.color)
    this.material.repeat =this.style.repeat
    this.material.speed = this.style.speed
  }

  setSelect(enabled) {
    if (enabled) {
      this.addPoints()
    } else {
      this.removePoints()
    }
  }
}

export {
  DnamicLine
}

MapWorks.js

import GUI from 'lil-gui'; 
// 初始视图定位在中国
import { DnamicLine } from './DnamicLine'

Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(90, -20, 110, 90);

let gui = null
// const params = {
//   color : '#ffff00',
//   lineWidth: 8,
//   repeat: {
//     x: 1,
//     y: 1
//   },
//   speed: 10
// }


let viewer = null;
let graphicLayer = null
let graphicList = []
let selectGraphic = null
let eventHandler = null

function initMap(container) {
  viewer = new Cesium.Viewer(container, {
    animation: false,
    baseLayerPicker: false,
    fullscreenButton: false,
    geocoder: false,
    homeButton: false,
    infoBox: false,
    sceneModePicker: false,
    selectionIndicator: false,
    timeline: false,
    navigationHelpButton: false, 
    scene3DOnly: true,
    orderIndependentTranslucency: false,
    contextOptions: {
      webgl: {
        alpha: true
      }
    }
  })
  viewer._cesiumWidget._creditContainer.style.display = 'none'
  viewer.scene.fxaa = true
  viewer.scene.postProcessStages.fxaa.enabled = true
  if (Cesium.FeatureDetection.supportsImageRenderingPixelated()) {
    // 判断是否支持图像渲染像素化处理
    viewer.resolutionScale = window.devicePixelRatio
  }
  // 移除默认影像
  removeAll()
  // 地形深度测试
  viewer.scene.globe.depthTestAgainstTerrain = true
  // 背景色
  viewer.scene.globe.baseColor = new Cesium.Color(0.0, 0.0, 0.0, 0)
  // 太阳光照
  viewer.scene.globe.enableLighting = true;

  // 初始化图层
  initLayer()
  // 初始化鼠标事件
  initClickEvent()
  //调试
  window.viewer = viewer
}

function initGui() {
  let params = {
    ...selectGraphic.style
  }
  gui = new GUI()
  let layerFolder = gui.title('样式设置')
  layerFolder.add(params.repeat, 'x', 1, 20).step(1.0).onChange(function (value) {
    selectGraphic.updateStyle(params)
  })
  layerFolder.add(params, 'speed', 0, 100).step(1.0).onChange(function (value) {
    selectGraphic.updateStyle(params)
  })
  layerFolder.add(params, 'lineWidth', 1, 30).step(1).onChange(function (value) {
    selectGraphic.updateStyle(params)
  })
  layerFolder.addColor(params, 'color').onChange(function (value) {
    selectGraphic.updateStyle(params)
  })
}


function initLayer() {
  const layerProvider = new Cesium.ArcGisMapServerImageryProvider({
    url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
  });
  viewer.imageryLayers.addImageryProvider(layerProvider);
  graphicLayer = new Cesium.CustomDataSource('graphicLayer')
  viewer.dataSources.add(graphicLayer)
}

function loadDnamicLine(lines) {
  lines.forEach(line => {
    const dnamicLine = new DnamicLine(viewer, line.points, {
      style: line.style
    })
    graphicList.push(dnamicLine)
    graphicLayer.entities.add(dnamicLine.entity)
  });
  viewer.flyTo(graphicLayer)
}

function initClickEvent() {
  eventHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  initLeftClickEvent()
  initMouseMoveEvent()
}

function initLeftClickEvent() {
  eventHandler.setInputAction((e) => {
    if (selectGraphic) {
      selectGraphic.setSelect(false)
      selectGraphic = null
    }
    if (gui) {
      gui.destroy()
    }
    let pickedObj = viewer.scene.pick(e.position);
    if (pickedObj && pickedObj.id) {
      if (pickedObj.id.type === 'dnamic_line') {
        selectGraphic = getGraphicById(pickedObj.id.id)
        if (selectGraphic) {
          selectGraphic.setSelect(true)
          initGui()
        }
      }
    }
  },Cesium.ScreenSpaceEventType.LEFT_CLICK)
}

function initMouseMoveEvent() {
  eventHandler.setInputAction((e) => {
    const pickedObj = viewer.scene.pick(e.endPosition);
    if (pickedObj && pickedObj.id) {
      if (pickedObj.id.type === 'dnamic_line') {
        // 改变鼠标状态
        viewer._element.style.cursor = "";
        document.body.style.cursor = "pointer";
      } else {
        viewer._element.style.cursor = "";
        document.body.style.cursor = "default";
      }
    } else {
      viewer._element.style.cursor = "";
      document.body.style.cursor = "default";
    }
  },Cesium.ScreenSpaceEventType.MOUSE_MOVE)
}

function getGraphicById(id) {
  let line = null
  for (let i = 0; i < graphicList.length; i++) {
    if (graphicList[i].entity.id === id) {
      line = graphicList[i]
      break
    } 
  }
  return line
}

function removeAll() {
  viewer.imageryLayers.removeAll();
}

function destroy() {
  viewer.entities.removeAll();
  viewer.imageryLayers.removeAll();
  viewer.destroy();
}




export {
  initMap,
  loadDnamicLine,
  destroy
}

DnamicLine.vue

<!--
 * @Description: 
 * @Author: maizi
 * @Date: 2023-04-07 17:03:50
 * @LastEditTime: 2024-08-16 17:25:18
 * @LastEditors: maizi
-->

<template>
  <div id="container">
  </div>
</template>

<script>
import * as MapWorks from './js/MapWorks'
export default {
  name: 'DnamicLine',
  mounted() {
    this.init();
  },
  methods:{
    init(){
      let container = document.getElementById("container");
      MapWorks.initMap(container)
      //创建
      let lines = [
        {
          points: [
            [104.068822,30.654807,10],
            [104.088822,30.654807,10],
          ],
          style: {
            type: 0
          },

        },
        {
          points: [
            [104.068822,30.655807,10],
            [104.088822,30.655807,10],
          ],
          style: {
            type: 1
          },
        },
      ];
      MapWorks.loadDnamicLine(lines)
    }
  },

  beforeDestroy(){
    //实例被销毁前调用,页面关闭、路由跳转、v-if和改变key值
    MapWorks.destroy();
  }
}
</script>

<style lang="scss" scoped>
#container{
  width: 100%;
  height: 100%;
  background: rgba(7, 12, 19, 1);
  overflow: hidden;
  background-size: 40px 40px, 40px 40px;
  background-image: linear-gradient(hsla(0, 0%, 100%, 0.05) 1px, transparent 0), linear-gradient(90deg, hsla(0, 0%, 100%, 0.05) 1px, transparent 0);
}


</style>

4. 运行结果

Cesium是一个用于创建和显示地球上地理数据的JavaScript库。在Cesium中,可以使用纹理来创建动态线流动线效果。首先,你可以使用CesiumLab将你的数据转换为Cesium可用的3dtiles数据格式。然后,你可以向Cesium.Material中添加你刚刚新建的纹理,以创建动态线流动线效果。你可以使用Cesium.Material._materialCache.addMaterial方法来添加材质,参数中包括纹理的颜色、图像和时间等属性。最后,你可以使用纹理来绘制线或面,实现动态线流动线的效果。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Cesium实现流动线/动态纹理](https://blog.csdn.net/weixin_42066016/article/details/105426932)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [济南建筑数据模型可直接使用cesiumlab转换为cesium可用的3dtiles数据](https://download.csdn.net/download/zslsh44/88278569)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值