cesuim 可視化項目

西安市3d全景視頻組件:包含加載3d titles 模型, 搜索監控點位,監控點位視頻播放,視頻貼地模型播放等功能

1. cesuim 可視化項目:開發示例(部分):

2. 使用的技術是VUE + Cesuim

部分代碼示例:

3dtitles 模型加載, 模型貼地,視頻點位加載放置,視頻播放,視頻投影;

PreviewOutLoad: 無人機加載組件

<template>
<div class="container" @click="domClick">
  <div id="cesiumContainer"></div>
  <div class="search-pane">
    <div class="cesium-viewer-geocoderContainer">
      <form id='search-form' @submit.prevent="search">
        <input v-model="keyWord" class="cesium-geocoder-input cesium-geocoder-input-wide" placeholder="請輸入點位名稱查詢"/>
        <span class="cesium-geocoder-searchButton" @click="search">
          <svg class="cesium-svgPath-svg" width="32" height="32" viewBox="0 0 32 32">
            <path d="M29.772,26.433l-7.126-7.126c0.96-1.583,1.523-3.435,1.524-5.421C24.169,8.093,19.478,3.401,13.688,3.399C7.897,3.401,3.204,8.093,3.204,13.885c0,5.789,4.693,10.481,10.484,10.481c1.987,0,3.839-0.563,5.422-1.523l7.128,7.127L29.772,26.433zM7.203,13.885c0.006-3.582,2.903-6.478,6.484-6.486c3.579,0.008,6.478,2.904,6.484,6.486c-0.007,3.58-2.905,6.476-6.484,6.484C10.106,20.361,7.209,17.465,7.203,13.885z"></path>
          </svg>
        </span>
      </form>
      <div class="search-results" id="results">
        <ul v-show="showSearchResults">
          <li v-for="item in positions" :key="item.indexCode" :class="{'active': curCameraIndexCode === item.indexCode}" @click="selectPositin(item)"><span v-html="item.name"></span></li>
        </ul>
      </div>
    </div>
  </div>
  <div id="latlng_show">
    <div class="geo-info">
      <div>經度:{{longitude}}</div>
      <div>緯度:{{latitude}}</div>
      <div>視角高:{{altitude * 1000}}米</div>
    </div>
  </div>
  <PreviewOutLoad class="preview-out-load" v-if="loading"/>
  <div id="toolbar">
      <video
        id="video"
        style="display: none"
        class="video-js vjs-default-skin box"
        preload="auto"
        muted
      >
      </video>

      <video
        v-for="item in this.cameras"
        :key="item.indexCode"
        :id="'wallVideo' + item.indexCode"
        style="display: none"
        class="video-js vjs-default-skin box"
        autoplay
        muted
      >
      </video>
  </div>
</div>
</template>

<script>
/* eslint-disable */
import Api from '@/api'
import videojs from 'video.js'
import 'videojs-contrib-hls'
import PreviewOutLoad from '@/components/common/PreviewOutLoad.vue'
//漢化原生cesium
import { loadCesiumZH } from '@/plugins/class/cesium-zh'
import CesiumNavigation from 'cesium-navigation-es6'
export default {
  name: 'ModelScence',
  props: {
    msg: String
  },
  components: {
    PreviewOutLoad
  },
  data() {
    return {
      primitiveDict: {},
      cameras: [],
      positions: [],
      keyWord: '',
      videoObj: null,
      loading: false,
      curCameraIndexCode: '',
      longitude: 0,
      latitude: 0,
      altitude: 0,
      showSearchResults: true,
      worldImg: require('../assets/images/world.jpg'),
      iconUrl: require('../assets/images/camera.svg'),
      // videoSrc: 'https://10.195.238.122/EUrl/V3AtBKg/live.m3u8',
      //videoSrc: require('../assets/test.mp4')
      //videoSrc: 'https://61.27.16.15/EUrl/Znu1yKs/live.m3u8'
    }
  },
  created () {
  },
  mounted () {
    this.initView()
    loadCesiumZH()
    this.showLonLatHeiAttr()
    this.resetHomeButtonBehavior()
    this.loadModel()
    this.handlerClickEvent()
  },
  methods: {
    /**
     * @description 初始化場景
     */
    initView () {
      this.viewer = new Cesium.Viewer('cesiumContainer', {
        selectionIndicator : false,
        vrButton: true,
        animation : false,                           //是否創建動畫小器件,左下角儀表
        baseLayerPicker : false,                     //是否顯示圖層選擇器
        fullscreenButton : true,                     //是否顯示全屏按鈕
        geocoder : false,                            //是否顯示geocoder小器件,右上角查詢按鈕
        homeButton : true,                           //是否顯示Home按鈕
        infoBox : false,                             //是否顯示信息框
        sceneModePicker : false,                     //是否顯示3D/2D選擇器
        timeline : false,                            //是否顯示時間軸
        navigationHelpButton : true,                 //是否顯示右上角的幫助按鈕
        scene3DOnly : false,
        automaticallyTrackDataSourceClocks : true,   //自動追蹤最近添加;
        sceneMode : Cesium.SceneMode.SCENE3D,        //初始場景模式
        imageryProvider: new Cesium.SingleTileImageryProvider({
          url: this.worldImg
        })
      })
      // Bounding sphere
      this.boundingSphere = new Cesium.BoundingSphere(Cesium.Cartesian3.fromDegrees(108.9388, 34.2613, 0), 25)
      // 羅盤配置
      let options = {}
      // 用於在使用重置導航重置地圖視圖時設置默認視圖控制。接受的值是Cesium.Cartographic 和Cesium.Rectangle.
      options.defaultResetView = new Cesium.Cartographic.fromDegrees(108.9388, 34.2613, 0)
      // 用於啓用或禁用羅盤。true是啓用羅盤,false是禁用羅盤。默認值爲true。如果將選項設置爲false,則羅盤將不會添加到地圖中。
      options.enableCompass= true
      // 用於啓用或禁用縮放控件。true是啓用,false是禁用。默認值爲true。如果將選項設置爲false,則縮放控件 將不會添加到地圖中。
      options.enableZoomControls= false
      // 用於啓用或禁用距離圖例。true是啓用,false是禁用。默認值爲true。如果將選項設置爲false,距離圖例將不會添加到地圖中。
      options.enableDistanceLegend= false
      // 用於啓用或禁用指南針外環。true是啓用,false是禁用。默認值爲true。如果將選項設置爲false,則該環將可見但無效。
      options.enableCompassOuterRing= true
      CesiumNavigation(this.viewer, options)

      this.loading = true
      this.viewer.scene.globe.depthTestAgainstTerrain = false
      this.viewer._cesiumWidget._creditContainer.style.display = "none"   //	去除版權信息
      this.viewer.scene.screenSpaceCameraController.minimumZoomDistance = 40
      // cesium的label的清晰度
      this.viewer.scene.postProcessStages.fxaa.enabled = false
      // 圖塊集固定在Cesium World Terrain上
      // this.viewer.terrainProvider = Cesium.createWorldTerrain()
      // this.viewer.scene.screenSpaceCameraController.maximumZoomDistance = 500
      // this.viewer.scene.screenSpaceCameraController._minimumZoomRate = 50 // 設置相機縮小時的速率
      // this.viewer.scene.screenSpaceCameraController._maximumZoomRate = 5       //設置相機放大時的速率
     
      this.viewer.camera.flyToBoundingSphere(this.boundingSphere, { duration: 200000 })
    },
    /**
     * @description 設置cesium homeBotton 行爲
     */
    resetHomeButtonBehavior () {
      this.viewer.homeButton.viewModel.command.beforeExecute.addEventListener( commandInfo => {
        // Fly to custom position
        this.viewer.camera.flyToBoundingSphere(this.boundingSphere)
        // Tell the home button not to do anything
        commandInfo.cancel = true
      })
    },
    /**
     * @description 加載模型
     */
    loadModel () {
      let tileset = this.viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
        url: 'http://127.0.0.1:5500/bigTitle/tileset.json',
        //url: 'tileset.json',
        maximumMemoryUsage: 8192,
        // dynamicScreenSpaceError: true,
        // dynamicScreenSpaceErrorFactor: 16.0,
        // skipLevelOfDetail: true,
        // loadSiblings: true,
        // preferLeaves: true
        //maximumScreenSpaceError: 2,            //最大的屏幕空間誤差
        //preloadWhenHidden: true,
        //maximumNumberOfLoadedTiles: 1000,       //最大加載瓦片個數,
        // cullRequestsWhileMovingMultiplier: 10.0 // 移動淘汰係數, 越大淘汰越積極
      }))

      tileset.readyPromise.then( () => {
        let titleBoundingSphere = tileset.boundingSphere
        this.viewer.camera.viewBoundingSphere(titleBoundingSphere, new Cesium.HeadingPitchRange(0.5, -0.2, titleBoundingSphere.radius * 1.0))
        this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY)
        this.changeHeight(0, tileset)
        this.loading = false
        this.viewer.camera.flyToBoundingSphere(this.boundingSphere)
        this.addCrameras()
        this.addVideo()
      }).otherwise(function (error) {
        throw (error)
      })
    },
    /**
     * @description 添加點位信息
     */
    addCrameras () {
      Api.post('searchCamera', {keyWord: ''}).then(res => {
        if (res.code === '0') {
          this.cameras = res.data
          let options = {
            camera : this.viewer.scene.camera,
            canvas : this.viewer.scene.canvas,
            clampToGround: true //開啓貼地
          }
          let dataSource = new Cesium.CzmlDataSource()
          this.viewer.dataSources.add(dataSource, options)
          res.data.forEach(item => {
            let entry = {
              name : item.name,
              code: item.indexCode,
              longitude: item.longitude,
              latitude: item.latitude,
              position : Cesium.Cartesian3.fromDegrees( item.longitude, item.latitude, -15 ),
              heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
              label : { //文字標籤
                text : item.name,
                font : '14pt monospace',
                horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
                verticalOrigin : Cesium.VerticalOrigin.BOTTOM, //垂直方向以底部來計算標籤的位置
                pixelOffset : new Cesium.Cartesian2( 0, -30 )   //偏移量
              },
              billboard : { //圖標
                image  : this.iconUrl,
                width : 32,
                height : 40,
                verticalOrigin : Cesium.VerticalOrigin.BOTTOM
              }
            }
            dataSource.entities.add(entry)
          })
        }
      })
    },
    search () {
      Api.post('searchCamera', {keyWord: this.keyWord}).then(res => {
        if (res.code === '0') {
          this.positions =  res.data
          this.showSearchResults = true
        }
      })
    },
    addVideo () {
      Api.post('getVideoUrl', {cameraIndexCode: '61010456001310692396', protocol: 'hls'}).then(res => {
        if (res.code === '0') {
          this.addGroupVideo(res.data.url)
        }
      })
    },
    addGroupVideo (url) {
      let material = Cesium.Material.fromType('Image')
      let videoElement = document.getElementById("video")
      material.uniforms.image = videoElement //'../img/worldimage.jpg'; //videoElement
      function createPrimitive() {
        let instance = new Cesium.GeometryInstance({
            geometry: new Cesium.RectangleGeometry({
              ellipsoid : Cesium.Ellipsoid.WGS84,
              rectangle: Cesium.Rectangle.fromDegrees(108.93684, 34.260755, 108.93722, 34.26095),
              vertexFormat: Cesium.MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat,
              height: -30.0,
              // rotation: 1,
              // stRotation: Cesium.Math.toRadians(90)
            })
        });
        //使用抽象的Primitive而不是RectanglePrimitive
        let item = new Cesium.Primitive({
            geometryInstances: instance,
            appearance: new Cesium.MaterialAppearance({
              material: material
            })
        })
        return item
      }

      let layer = videojs('video')
      layer.src({src: url, type: "application/x-mpegURL"})
      layer.play()
      this.viewer.scene.primitives.add(createPrimitive())
    },
    playVideo (code, lon, lat) {
      let self = this
      if (this.primitiveDict[code]) {
        if (this.curCameraIndexCode === code) {
          this.primitiveDict[code].show = !this.primitiveDict[code].show
        } else {
          this.primitiveDict[code].show = true
        }
        this.curCameraIndexCode = code
        return
      }
      this.curCameraIndexCode = code
      Api.post('getVideoUrl', {cameraIndexCode: code, protocol: 'hls'}).then(res => {
        if (res.code === '0') {
          this.wallVideoSrc = res.data.url
          this.$nextTick(() => {
            self.addWallVideo(lon, lat, code)
          })
        }
      })
    },
    addWallVideo (lon, lat, code) {
      const self = this
      lon = Number(lon)
      lat = Number(lat)
      let pArray = [
        lon - 0.00020, lat, 25,
        lon + 0.00020, lat, 25
      ]
      let material = Cesium.Material.fromType('Image')
      let videoElement = document.getElementById('wallVideo' + code)
      material.uniforms.image = videoElement //'../img/worldimage.jpg'; //videoElement
      function createPrimitive() {
        let instance = new Cesium.GeometryInstance({
            geometry: new Cesium.WallGeometry({
              positions: Cesium.Cartesian3.fromDegreesArrayHeights(pArray),
              // minimumHeights: [1, 1]
            })
        });
        //使用抽象的Primitive而不是RectanglePrimitive
        let item = new Cesium.Primitive({
            geometryInstances: instance,
            appearance: new Cesium.MaterialAppearance({
              material: material
            })
        })
        return item
      }
      this.wallPlayer = videojs('wallVideo' + code)
      this.wallPlayer.src({src: this.wallVideoSrc, type: "application/x-mpegURL"})
      this.wallPlayer.play()
      let wallPrimitive = createPrimitive()
      this.primitiveDict[code] = wallPrimitive
      self.viewer.scene.primitives.add(wallPrimitive)
    },
    selectPositin (pos) {
      let boundingSphere = new Cesium.BoundingSphere(Cesium.Cartesian3.fromDegrees(pos.longitude,pos.latitude, 0), 25)
      this.viewer.camera.flyToBoundingSphere(boundingSphere)
      if (this.curCameraIndexCode === pos.indexCode) {
        return
      } 
      this.playVideo(pos.indexCode, pos.longitude, pos.latitude)
    },
    /**
     * @description 處理點擊事件
     */
    handlerClickEvent () {
      let handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
      handler.setInputAction( click => {
          let pick = this.viewer.scene.pick(click.position);
          //選中某模型   pick選中的對象
          if(pick && pick.id) {
            this.playVideo(pick.id._code, pick.id._longitude, pick.id._latitude)
            let boundingSphere = new Cesium.BoundingSphere(Cesium.Cartesian3.fromDegrees(pick.id._longitude, pick.id._latitude, 0), 25)
            this.viewer.camera.flyToBoundingSphere(boundingSphere)
          }

      }, Cesium.ScreenSpaceEventType.LEFT_CLICK  )
    },
    /**
     * @description 設置模型和地圖圖層的高度
     */
    changeHeight (height, tileset) {
      height = Number(height)
      if (isNaN(height)) {
        return
      }
      
      let cartographic = Cesium.Cartographic.fromCartesian(tileset.boundingSphere.center)
      let surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, cartographic.height)
      let offset = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude,height)
      let translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3())
      tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation)
    },
    /**
     * @description 顯示經緯度及高度信息
     */
    showLonLatHeiAttr () {
      let viewer = this.viewer
      const self = this
      let canvas = viewer.scene.canvas
      //具體事件的實現
      let ellipsoid=viewer.scene.globe.ellipsoid
      let handler = new Cesium.ScreenSpaceEventHandler(canvas)
      handler.setInputAction(function(movement) {
        //捕獲橢球體,將笛卡爾二維平面座標轉爲橢球體的笛卡爾三維座標,返回球體表面的點
        let cartesian = viewer.camera.pickEllipsoid(movement.endPosition, ellipsoid)
        if (cartesian) {
          //將笛卡爾三維座標轉爲地圖座標(弧度)
          let cartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(cartesian)
          //將地圖座標(弧度)轉爲十進制的度數
          let lat_String = Cesium.Math.toDegrees(cartographic.latitude).toFixed(4)
          let log_String = Cesium.Math.toDegrees(cartographic.longitude).toFixed(4)
          let alti_String = (viewer.camera.positionCartographic.height/1000).toFixed(2)
          self.longitude = log_String
          self.latitude = lat_String
          self.altitude = alti_String
        }
      }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
    },
    domClick (e) {
      this.showSearchResults = e.target.nodeName === 'INPUT'
    }
  },
  watch: {
    keyWord(newVal, oldVal) {
      if (!newVal) {
        this.positions = []
      }
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss" scoped>
.container {
  width: 100%; height: 100%; overflow: hidden;
}
#cesiumContainer {
  width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden;
}
#latlng_show {
  position: absolute;
  left: 0px;
  right: 0px;
  bottom: 0px;
  z-index: 991;
  padding: 3px 10px;
  font-size: 13px;
  color: #e9e9e9;
  text-shadow: 2px 2px 2px #000;
  background-color: rgba(0,0,0,.4);
  pointer-events: none;
  .geo-info {
    float: right;
    div {
      float: left;
      margin-right: 30px;
    }
  }
}

.search-pane {
  display: block;
  position: absolute;
  top: 10px;
  right: 10px;
  .cesium-viewer-geocoderContainer {
    position: relative;
    display: inline-block;
    margin: 0px 3px;
    .cesium-geocoder-input {
      background-color: rgba(40, 40, 40, 0.7);
      color: rgb(255, 255, 255);
      display: inline-block;
      vertical-align: middle;
      width: 0px;
      height: 32px;
      box-sizing: border-box;
      -webkit-appearance: none;
      border-width: 1px;
      border-style: solid;
      border-color: rgb(68, 68, 68);
      border-image: initial;
      margin: 0px;
      padding: 0px 32px 0px 0px;
      border-radius: 0px;
      transition: width 0.25s ease-in-out 0s, background-color 0.2s ease-in-out 0s;
    }
    .cesium-geocoder-input-wide {
      padding-left: 4px;
      width: 250px;
    }
    .cesium-geocoder-searchButton {
      background-color: rgb(48, 51, 54);
      display: inline-block;
      position: absolute;
      cursor: pointer;
      width: 32px;
      top: 1px;
      right: 1px;
      height: 30px;
      vertical-align: middle;
      fill: rgb(237, 255, 255);
      .cesium-svgPath-svg {
        position: absolute;
        top: 0px;
        left: 0px;
        width: 100%;
        height: 100%;
        overflow: hidden;
      }
    }

    .search-results {
      background-color: rgba(45,58,66,0.7);
      color: #ffffff;
      opacity: 1;
      z-index: 2;
      border-top: 0px;
      width: 100%;
      overflow-y: auto;
      max-height: 600px;
      ul {
        display: block;
        list-style-type: none;
        li {
          display: list-item;
          font-size: 14px;
          padding: 3px 10px;
          text-align: left;
          &:hover {
            cursor: pointer;
            background: rgb(68, 136, 187);
          }

          &:active {
            background: rgb(68, 136, 187);
          }
        }
      }
    }
  }
}

</style>
<style>
.cesium-viewer-toolbar {
  top: auto;
  left: auto;
  right: 26px;
  bottom: 100px;
  display: block;
  position: absolute;
}
.cesium-viewer-toolbar>.cesium-toolbar-button, .cesium-navigationHelpButton-wrapper, .cesium-viewer-geocoderContainer {
  margin-bottom: 5px;
  float: right;
  clear: both;
  text-align: center;
}
.cesium-viewer-fullscreenContainer {
  position: absolute;
  bottom: 228px;
  right: 29px;
  padding: 0;
  width: 32px;
  height: 32px;
  overflow: hidden;
}
.cesium-viewer-vrContainer {
  position: absolute;
  bottom: 186px;
  right: 29px!important;
  padding: 0;
  width: 32px;
  height: 32px;
  overflow: hidden;
}
</style>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章