西安市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>