本文中使用版本号:
"vue": "2.6.10",
"cesium": "^1.68.0",
"webpack": "^4.41.2",
"copy-webpack-plugin": "^5.0.5",
"terser-webpack-plugin": "^4.2.3",
1.安装cesium
npm install cesium@1.68.0 --save
2.在main.js中引用样式和自己封装的cesium方法
import { MyCesium } from "@/util/3dMap/MyCesium"
import 'cesium/Widgets/widgets.css'
Vue.prototype.$MyCesium = MyCesium;
vue.config.js配置
/**
* 配置该文件可以参考:
* https://cli.vuejs.org/zh/config/#%E7%9B%AE%E6%A0%87%E6%B5%8F%E8%A7%88%E5%99%A8
*
*/
const path = require("path");
const webpack = require("webpack");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const cesiumSource = "node_modules/cesium/Source";
const cesiumWorkers = "../Build/Cesium/Workers";
const resolve = (dir) => {
return path.join(__dirname, dir);
};
//配置路径json文件
const TerserPlugin = require('terser-webpack-plugin');
const cfgJson = require('./serverconfig.json') //这里引入的地址就是后面新建serverconfig.json的地址
var createServerConfig = function (compilation) {
return JSON.stringify(cfgJson);
}
console.log('path', path.join('./dist', 'rrr'))
let devServeUrl = require('./src/config/devServe')
// 基础路径,发布前修改这里,当前配置打包出来的资源都是相对路径
// let publicPath = './'
let publicPath = process.env.NODE_ENV === "development" ? "/" : "./"
module.exports = {
publicPath: publicPath,
lintOnSave: true,
productionSourceMap: false,
css: {
// 忽略 CSS order 顺序警告
extract: {ignoreOrder: true}
},
chainWebpack: config => {
const entry = config.entry('app')
entry
.add('babel-polyfill')
.end()
entry
.add('classlist-polyfill')
.end()
config
.plugin('GenerateAssetPlugin')
.use('generate-asset-webpack-plugin', [{
filename: 'serverconfig.json',
fn: (compilation, cb) => {
cb(null, createServerConfig(compilation));
},
extraFiles: []
}])
},
// 配置转发代理
devServer: {
port: 8520,
proxy: {
'/api': {
target: devServeUrl.baseUrl,
ws: true,
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
},
'/video': {
target: devServeUrl.videoUrl,
ws: true,
pathRewrite: {
'^/video': '/video'
}
}
}
},
configureWebpack: {
resolve: {
alias: {
'@': resolve('src'),
// cesium 1
cesium: path.resolve(__dirname, cesiumSource) // 用source会导致每次build所有cesium js;用DebugDir,则存在widgets.css引用问题
}
},
amd: {
// cesium 2
toUrlUndefined: true
},
module: {
// cesium 3 不加这个配置会报require引入警告
unknownContextCritical: false,
rules: [{test: /\.(gltf)$/,loader: 'url-loader'}],
},
// cesium 4
plugins: [
new webpack.DefinePlugin({
// Define relative base path in cesium for loading assets
CESIUM_BASE_URL: JSON.stringify("./")
}), // 对build生效,拷贝到dist目录下。如:dist/Assets
new CopyWebpackPlugin([{
from: path.join(cesiumSource, cesiumWorkers),
// from: path.join(cesiumSource, "Workers"),
to: "Workers"
}]),
new CopyWebpackPlugin([{
from: path.join(cesiumSource, "Assets"),
to: "Assets"
}]),
new CopyWebpackPlugin([{
from: path.join(cesiumSource, "Widgets"),
to: "Widgets",
// to: path.join('./dist', "Widgets")
}]),
new webpack.ProvidePlugin({
Cesium: ["cesium/Cesium"], // Cesium对象实例可在每个js中使用而无须import
}),
],
optimization:{
minimizer: [new TerserPlugin({ terserOptions: { compress: { drop_console: true } } })]
// minimize: process.env.NODE_ENV === "production" ? true : false
}
}
}
3.封装方法:
使用到的方法封装
3.1:MyCesium.js(地图公共方法及初始化viewer)
vue + cesium: 地图公共方法及初始化viewer(MyCesium.js) - 掘金 (juejin.cn)
3.2:params.js(地图参数)
vue+Cesium: 地图参数(params.js) - 掘金 (juejin.cn)
3.3:transform.js(地图转换方法)
vue + cesium: 地图转换方法(transform.js) - 掘金 (juejin.cn)
3.4:infoWindow.js(地图点击弹框框方法封装)
vue + cesium: 地图点击弹框框方法封装(infoWindow.js) - 掘金 (juejin.cn)
3.5:init.js(加载3d渔港全景)
vue + cesium:加载3d模型(init.js) - 掘金 (juejin.cn)
3.6: initShipBoat.js(加载渔船)
vue + cesium: 加载渔船(initShipBoat.js) - 掘金 (juejin.cn)
3.7: draw.js(绘制点线面 方法封装)
vue + cesium: 绘制点线面 方法封装(draw.js) - 掘金 (juejin.cn)
3.8: 渔船轨迹绘制
vue+cesium: 渔船轨迹绘制(可运动)(TrailLineAnimate.js) - 掘金 (juejin.cn) vue + cesium: 渔船轨迹(无动画)(trackPlayback.js) - 掘金 (juejin.cn)
4.使用
本文中有多个页面使用3d地图 因此封装为组件 传入routerData 和路由进行匹配
<map3d v-show="show3dMap" style="height: 100%" :routerData="routerData" id="cesiumContainerFishery"></map3d>
routerData: {
moduleName: '模块名称',
modulePath: '模块地址',
}
组件
<template>
<div>
<div :ref="id" :id="id" style="height: 100%"></div>
<!-- 绘制弹窗 -->
<edit-draw-dialog moduleName="BerthManage" v-if="showTool" :show.sync="showTool" :currentInfo="currentInfo" @handleClose="handleClose" :isEditDraw="isEditDraw" :editDrawData="editDrawData"></edit-draw-dialog>
<!-- 需要跟随鼠标移动的 加上style="position: absolute" 不需要则不加 -->
<!-- 泊位信息弹窗 -->
<div id="portInfoWindow" style="position: absolute">
<port-info v-show="showPortInfo" :show.sync="showPortInfo" :portId="portDetailInfo.id" @portClose="handlePortClose"></port-info>
</div>
<!-- 渔船信息弹窗 -->
<div id="3dDrawPopup" style="position: absolute">
<shipPopup v-show="showShipHoverPopup" :is3dMap="true" :show-ship-hover-info="showShipHoverInfo" :show-ship-hover-popup="showShipHoverPopup"></shipPopup>
</div>
<div id="3dShipInfo" >
<ship-info ref="shipInfo" v-if="showShipInfo" :show.sync="showShipInfo" :ship-id="shipInfo.shipId" :shipProps="shipProps" :showFooter="false" @beforeClose="closeShipInfo" :is3dMap="true"></ship-info>
</div>
<!--地图摄像头点击 弹框-->
<div id="videoMonitorWindow" style="position: absolute">
<camera-popup
ref="cameraPopup3d"
id="cameraPopup3d"
@removePopup="removePopup"
v-show="showCamera"
videoId="video3dIdOut"
></camera-popup>
</div>
<!-- 水质检测 -->
<div id="waterModalWindow" style="position: absolute">
<WaterModal v-if="showWater" :show.sync="showWater" :waterId="showWaterInfo.id" :is3dMap="true" @waterClose="waterClose"></WaterModal>
</div>
<!-- 视频 -->
<MapVideoModal
:show.sync="showVideoCom"
v-if="showVideoCom"
:videoData="videoData"
ref="MapVideoModal"
></MapVideoModal>
</div>
</template>
<script>
import {initLoadModel} from '@/util/3dMap/init'
import EditDrawDialog from "@/views/mapPlatform/components/editDrawModal/index";
import {$Params} from "@/util/3dMap/params";
import {initLoadShipBoatModel} from "@/util/3dMap/initShipBoat";
import {getBerthShipPositions} from '@/api/fisheryAreaSupervision'
import {subjectSelectAllPort} from "@/api/map/drawSubject";
import {getShipBerthIotLayout} from "@/api/fisheryAreaSupervision";
import {transFormDataFromResponse} from "@/util/3dMap/transform";
import PortInfo from "@/views/mapPlatform/components/portInfo/portInfo3d";
import shipPopup from "@/views/mapPlatform/components/shipPopup/index";
import ShipInfo from "@/views/mapPlatform/components/shipInfo/shipInfo";
import CameraPopup from "@/views/mapPlatform/components/cameraPopup/cameraPopup";
import WaterModal from "@/views/mapPlatform/components/waterModal";
import MapVideoModal from "@/views/mapPlatform/components/mapVideoModal";
import { getEquipSingle } from "@/api/baseInfo";
export default {
components: {EditDrawDialog, PortInfo, shipPopup, ShipInfo, CameraPopup, WaterModal, MapVideoModal},
props: {
id: {
type: String,
default: 'cesiumContainerFishery'
},
routerData: {
type: Object,
default: () => {}
}
},
data () {
return {
showTool: false,
//当前标绘物编辑弹框信息
currentInfo: {},
showPortInfo: false,
portDetailInfo: {},
isEditDraw: false,
editDrawData: {},
showShipInfo: false,
showHoverPopup: false,
hoverInfo: {},
shipProps: {},
shipInfo: {},
showShipHoverInfo: {},
showShipHoverPopup: false,
showCamera: false,
showWater: false,
showWaterInfo: {},
showVideoCom: false, //视频弹窗组件
videoData: {}, //首页单个视频数据
}
},
mounted () {
// 获取渔船点位
this.drawLayer()
// 初始化3d地图
this.$refs[this.id].oncontextmenu = function () { return false }
this.$MyCesium.init(this.id)
this.$MyCesium.viewer.entities.collectionChanged.addEventListener(this.onChanged);
//加载3d模型和动态水域
initLoadModel({viewer: this.$MyCesium.viewer})
// 泊位绘制
this.handlePort()
// 设备 摄像头、水质监测点
this.handleDevice()
// 通过$Bus监听地图点击事件
// 水质检测
this.$Bus.$off('3dshowWaterPopup') && this.$Bus.$on('3dshowWaterPopup', (data, info) => {
this.showWater = data
this.showWaterInfo = info
})
// 渔船双击
this.$Bus.$off('3dshipClick').$on('3dshipClick', (data, props) => {
this.shipProps = props
if (data) {
console.log('shipClick', data)
this.$store.commit('SET_isShowIcon', false)
this.showShipInfo = true
this.shipInfo = props
this.$nextTick(() => {
this.$refs['shipInfo'].showShipTrack = false
})
} else {
this.showShipInfo = false
this.$store.commit('SET_isShowIcon', true)
}
})
//渔船鼠标移入事件
this.$Bus.$off('3dshowShipPopup') && this.$Bus.$on('3dshowShipPopup', (data, info) => {
this.showShipHoverPopup = data
this.showShipHoverInfo = info
})
this.$Bus.$off('3dhandleDraw3d') && this.$Bus.$on('3dhandleDraw3d', (methods, type) => {
if(methods == 'draw') {
this.$store.commit('SET_isShowIcon', false)
this.showTool = !this.showTool
this.currentInfo = {
type:$Params.drawType[type],
methods: methods,
drawType: type
}
}
})
this.$Bus.$off('3deditPortInfo') && this.$Bus.$on('3deditPortInfo', (value)=>{
console.log(value, 'pppppppppp')
this.$store.commit('SET_isShowIcon', !value.isEdit)
this.showTool = value.isEdit
this.isEditDraw = value.isEdit
this.editDrawData = value.data
})
// // 泊位点击事件
this.$Bus.$off("3dhandlePortDialog") && this.$Bus.$on("3dhandlePortDialog", (data) => {
console.log(data, '泊位点击事件')
if (!data) return this.showPortInfo = false
else {
this.showPortInfo = true;
this.portDetailInfo = data;
}
console.log(this.showPortInfo, 'showPortInfo')
});
this.$Bus.$off("3ddeviceClick").$on("3ddeviceClick", (show, data) => {
console.log('设备点击3d', show, data)
// this.camera.show = true
this.getDeviceVideo(data);
this.showCamera = show;
})
//触发视频监控弹窗按钮
this.$Bus.$off("show3dVideoDialog").$on("show3dVideoDialog", (isShow, data) => {
this.showVideoCom = isShow;
this.videoData = data;
this.showHideLabelCommon(false);
})
},
methods: {
showHideLabelCommon (value) {
this.$store.commit('setLeftShowIcon', value)
this.$store.commit('setRightShowIcon', value)
},
waterClose () {
this.showWater = false
this.$MyCesium.isShowSelectionIndicator(false)
},
// 单个设备视频流
getDeviceVideo(data) {
console.log(data, 'ppppppppppppppp')
let id = data.id;
getEquipSingle(id).then((res) => {
if (res.data.code == 0) {
this.$nextTick(() => {
this.$refs.cameraPopup3d.deviceName = res.data.data.iotDeviceName;
this.$refs.cameraPopup3d.urlLinuxData = res.data.data;
});
}
});
},
removePopup(data) {
this.showCamera = data;
this.$MyCesium.isShowSelectionIndicator(false)
},
closeShipInfo(){
this.$store.commit('SET_isShowIcon', true)
this.$MyCesium.isShowSelectionIndicator(false)
},
handlePortClose () {
this.showPortInfo = false
this.$MyCesium.isShowSelectionIndicator(false)
},
showHideLabelCommon (isShowIcon) {
this.$store.commit("SET_isShowIcon", isShowIcon);
},
onChanged(collection, added, removed, changed){
var msg = 'Added ids';
for(var i = 0; i < added.length; i++) {
msg += '\n' + added[i].id;
}
},
handleClose() {
this.showTool = false
this.$store.commit('SET_isShowIcon', true)
},
// 绘制点位
async drawLayer(timestamp = 0) {
const res = await this.getCrewPosition(timestamp)
console.log('drawLayer', res)
if (!res || !res.positions || res.positions.length === 0) return this.$message.error('渔船点位数据不存在')
// 递归函数
const digui = () => {
let timer = null
const run = () => {
timer = setTimeout(() => {
if (this.$MyCesium.viewer) {
initLoadShipBoatModel(timestamp, res.positions)
this.getCrewPositionDelay(res.timestamp)
} else digui()
}, 400)
}
const stop = () => {
clearTimeout(timer)
timer = null
}
return {
run: run,
stop: stop
}
}
let moduleName = this.$route.name // 记录当前模块名称
let bool = this.isHomeToCur(this.routerData.modulePath)
if (bool) {
moduleName = this.routerData.moduleName
}
const executor = digui() // 获取执行器
if (!res) return executor.stop() // 接口报错时递归清除
if (moduleName === this.routerData.moduleName) { // 避免板块切换时接口轮询
executor.run()
}
},
// 获取船员位置数据
getCrewPosition(timestamp) {
return new Promise((resolve) => {
getBerthShipPositions({timestamp: timestamp}).then(res => {
let data = res.data
console.log('getCrewPosition', data)
if (data.success) {
if (data.data && data.data.positions) {
resolve(data.data)
} else {
resolve(false)
}
}
}).catch(err => console.log(err))
})
},
// 每隔一段时间获取一次增量的船员位置数据
getCrewPositionDelay: (() => {
let timer = null
return function (maxTimestamp) {
clearTimeout(timer)
let moduleName = this.$route.name // 记录当前模块名称
let bool = this.isHomeToCur(this.routerData.modulePath)
if (bool) {
moduleName = this.routerData.moduleName
}
if (moduleName === this.routerData.moduleName) { // 避免板块切换时接口轮询
timer = setTimeout(() => {
this.drawLayer(maxTimestamp)
}, 60000)
}
}
})(),
handlePort () {
subjectSelectAllPort().then(res=>{
if (res.data.code == 0) {
this.$MyCesium.removeDataSource('DrawPort')
this.$MyCesium.addEntitysCollection('DrawPort', transFormDataFromResponse(res.data.data))
}
})
},
handleDevice () {
getShipBerthIotLayout().then(res=>{
if (res.data.code == 0) {
// 绘制摄像头
this.$MyCesium.removeDataSource('VideoMonitor')
this.$MyCesium.addDeviceEntitysCollection('VideoMonitor',transFormDataFromResponse(res.data.data.camera))
// 绘制水质监测点
this.$MyCesium.removeDataSource('waterQuality')
this.$MyCesium.addDeviceEntitysCollection('waterQuality',transFormDataFromResponse(res.data.data.waterQuality))
}
})
}
},
beforeDestroy () {
this.$MyCesium.removeDataSources()
this.$MyCesium.viewer = undefined
this.$MyCesium.handler = undefined
this.$MyCesium.curentEntity = null
this.$MyCesium.model = null
}
}
</script>
5. 绘制点、面、线
import {$Draw} from "@/util/3dMap/draw";
var DRAW_TYPE = {
Point: 'Point', // 点
PolyLine: 'PolyLine', // 线
Polygon: 'Polygon', // 面
Marker: 'Marker', // 标记
};
draw(type) {
$Draw().draw({drawingMode: type})
},
6.渔船轨迹
import TrailLineAnimate from "@/util/3dMap/TrailLineAnimate";
data(){
return{
//轨迹实例
TrailLineAnimateInstance: null,
}
},
// 使用
// 初始化
this.TrailLineAnimateInstance = new TrailLineAnimate(this.$MyCesium.viewer)
// 获取到数据后进行播放
this.TrailLineAnimateInstance.play(this.shipTrackList, this.speed)
// 清除实例
this.TrailLineAnimateInstance.clear()
7.遇到的问题
7.1 发布线上环境报错
在本地开发时3d地图加载是正常的 发布到线上环境时报错 原因:因为在vue.config.js复制的文件被加载到底层 未能找到 解决方法:在node_modules/cesium下面复制Assets、ThirdParty、Widgets、Workers文件夹到public/static文件夹下面 并在index.html文件中引入
7.2 使用new Cesium.Cesium3DTileset时模型未加载
解决办法: 把ThirdParty复制到public文件夹下