项目级大屏地球
This commit is contained in:
@ -112,4 +112,4 @@ export const getWeather = (projectId) => {
|
|||||||
url: '/money/big/screen/weather/' + projectId,
|
url: '/money/big/screen/weather/' + projectId,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
};
|
};
|
@ -64,3 +64,27 @@ export const getScreenGeneralize = (projectId: number | string) => {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取gps数据
|
||||||
|
export const getGps = (projectId) => {
|
||||||
|
return request({
|
||||||
|
url: '/project/big/screen/getClientList/' + projectId,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 选中列表
|
||||||
|
export const getSelectList = (params) => {
|
||||||
|
return request({
|
||||||
|
url: '/project/big/screen/getList',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 设置选中
|
||||||
|
export const setSelect = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/project/big/screen/setList',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="centerPage">
|
<div class="centerPage">
|
||||||
<div class="topPage">
|
<div class="topPage">
|
||||||
<div id="earth" style="width: 100%;height: 100%;"></div>
|
<newMap></newMap>
|
||||||
</div>
|
</div>
|
||||||
<div class="endPage" :class="{ 'slide-out-down': isHide }">
|
<div class="endPage" :class="{ 'slide-out-down': isHide }">
|
||||||
<Title title="AI安全巡检">
|
<Title title="AI安全巡检">
|
||||||
@ -35,7 +35,7 @@ import { ref, onMounted, toRefs, getCurrentInstance } from "vue"
|
|||||||
import Title from './title.vue'
|
import Title from './title.vue'
|
||||||
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
|
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
|
||||||
import { getScreenSafetyInspection } from '@/api/projectScreen'
|
import { getScreenSafetyInspection } from '@/api/projectScreen'
|
||||||
|
import newMap from "./newmap.vue"
|
||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
const { violation_level_type } = toRefs(proxy?.useDict('violation_level_type'));
|
const { violation_level_type } = toRefs(proxy?.useDict('violation_level_type'));
|
||||||
|
|
||||||
@ -95,60 +95,8 @@ const getInspectionList = async () => {
|
|||||||
inspectionList.value = data
|
inspectionList.value = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 创建地球
|
|
||||||
const createEarth = () => {
|
|
||||||
window.YJ.on({
|
|
||||||
ws: true,
|
|
||||||
// host: getIP(), //资源所在服务器地址
|
|
||||||
// username: this.loginForm.username, //用户名 可以不登录(不填写用户名),不登录时无法加载服务端的数据
|
|
||||||
// password: md5pass, //密码 生成方式:md5(用户名_密码)
|
|
||||||
}).then((res) => {
|
|
||||||
let earth = new YJ.YJEarth("earth");
|
|
||||||
window.Earth1 = earth;
|
|
||||||
YJ.Global.openRightClick(window.Earth1);
|
|
||||||
YJ.Global.openLeftClick(window.Earth1);
|
|
||||||
let view = {
|
|
||||||
"position": {
|
|
||||||
"lng": 102.03643298211526,
|
|
||||||
"lat": 34.393586474501,
|
|
||||||
"alt": 11298179.51993155
|
|
||||||
},
|
|
||||||
"orientation": {
|
|
||||||
"heading": 360,
|
|
||||||
"pitch": -89.94481747201486,
|
|
||||||
"roll": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadBaseMap(earth.viewer)
|
|
||||||
YJ.Global.CesiumContainer(window.Earth1, {
|
|
||||||
compass: false, //罗盘
|
|
||||||
});
|
|
||||||
// YJ.Global.flyTo(earth, view);
|
|
||||||
// YJ.Global.setDefaultView(earth.viewer, view)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 加载底图
|
|
||||||
const loadBaseMap = (viewer) => {
|
|
||||||
// 创建瓦片提供器
|
|
||||||
const imageryProvider = new Cesium.UrlTemplateImageryProvider({
|
|
||||||
url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
|
|
||||||
// 可选:设置瓦片的格式
|
|
||||||
fileExtension: 'png',
|
|
||||||
// 可选:设置瓦片的范围和级别
|
|
||||||
minimumLevel: 0,
|
|
||||||
maximumLevel: 18,
|
|
||||||
// 可选:设置瓦片的投影(默认为Web Mercator)
|
|
||||||
projection: Cesium.WebMercatorProjection,
|
|
||||||
// 可选:如果瓦片服务需要跨域请求,设置请求头部
|
|
||||||
credit: new Cesium.Credit('卫星图数据来源')
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加图层到视图
|
|
||||||
const layer = viewer.imageryLayers.addImageryProvider(imageryProvider);
|
|
||||||
}
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getInspectionList()
|
getInspectionList()
|
||||||
createEarth()
|
|
||||||
if (swiperContent.value && swiperContent.value.children.length > 0) {
|
if (swiperContent.value && swiperContent.value.children.length > 0) {
|
||||||
swiperItemWidth.value = swiperContent.value.children[0].clientWidth + 20
|
swiperItemWidth.value = swiperContent.value.children[0].clientWidth + 20
|
||||||
}
|
}
|
||||||
|
280
src/views/projectLarge/ProjectScreen/components/newmap.vue
Normal file
280
src/views/projectLarge/ProjectScreen/components/newmap.vue
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, onUnmounted } from 'vue';
|
||||||
|
import CesiumImageLabelEntity from '../js/CesiumImageLabelEntity.js';
|
||||||
|
import CesiumFlyToRoamingController from '../js/CesiumFlyToRoamingController.js';
|
||||||
|
import { setSelect, getSelectList, getGps } from '@/api/projectScreen/index.ts'
|
||||||
|
const defaultExpandedKeys = [1, 2, 3] //默认展开第一级节点
|
||||||
|
const defaultCheckedKeys = ref([]) //默认选中节点
|
||||||
|
const data = ref([]);
|
||||||
|
const defaultProps = {
|
||||||
|
children: 'children',
|
||||||
|
label: 'label',
|
||||||
|
}
|
||||||
|
let entityManager = null;
|
||||||
|
window.deviceMap = new Map();
|
||||||
|
let list = ref([]);
|
||||||
|
// 漫游实例
|
||||||
|
let roamingController = null;
|
||||||
|
// 获取GPS数据
|
||||||
|
function getGpsData() {
|
||||||
|
getGps('1897160897167638529').then(res => {
|
||||||
|
console.log('res', res);
|
||||||
|
if (res.code === 200) {
|
||||||
|
data.value = res.data;
|
||||||
|
if (res.data.length > 0) {
|
||||||
|
res.data.forEach(element => {
|
||||||
|
list.value = [...list.value, ...element.children]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 设置选中节点
|
||||||
|
function setCheckedNode(idList) {
|
||||||
|
let obj = {
|
||||||
|
projectId: '1897160897167638529',
|
||||||
|
idList
|
||||||
|
}
|
||||||
|
setSelect(obj).then(res => {
|
||||||
|
console.log('res', res);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 获取选中节点
|
||||||
|
function getCheckedNode() {
|
||||||
|
getSelectList({
|
||||||
|
projectId: '1897160897167638529'
|
||||||
|
}).then(res => {
|
||||||
|
if (res.code == 200) {
|
||||||
|
defaultCheckedKeys.value = res.data || []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 渲染无人机、摄像头、定位设备
|
||||||
|
function renderDevice(item) {
|
||||||
|
const imageEntity = new CesiumImageLabelEntity(Earth1.viewer, {
|
||||||
|
id: item.id,
|
||||||
|
position: {
|
||||||
|
lng: Number(item.lng),
|
||||||
|
lat: Number(item.lat),
|
||||||
|
height: 0
|
||||||
|
},
|
||||||
|
name: item.label || item.id,
|
||||||
|
imageUrl: "/assets/demo/avatar.png",
|
||||||
|
onClick: entityClickHandler
|
||||||
|
});
|
||||||
|
window.deviceMap.set(item.id, imageEntity);
|
||||||
|
}
|
||||||
|
// 实体的点击事件
|
||||||
|
function entityClickHandler(entity) {
|
||||||
|
console.log('entity', entity);
|
||||||
|
}
|
||||||
|
// 初始化地球
|
||||||
|
function initEarth() {
|
||||||
|
YJ.on({
|
||||||
|
ws: true,
|
||||||
|
host: '', //资源所在服务器地址
|
||||||
|
username: '', //用户名 可以不登录(不填写用户名),不登录时无法加载服务端的数据
|
||||||
|
password: '', //密码 生成方式:md5(用户名_密码)
|
||||||
|
}).then((res) => {
|
||||||
|
let earth = new YJ.YJEarth("earth");
|
||||||
|
|
||||||
|
window.Earth1 = earth;
|
||||||
|
// 加载底图
|
||||||
|
// earth.viewer.terrainProvider = Cesium.createWorldTerrain();
|
||||||
|
// Earth1.viewer
|
||||||
|
addArcgisLayer(Earth1.viewer, 'img_w')
|
||||||
|
// 添加倾斜数据
|
||||||
|
// loadTiltData(Earth1.viewer)
|
||||||
|
// 获取中心点
|
||||||
|
YJ.Global.CesiumContainer(window.Earth1, {
|
||||||
|
compass: false,//罗盘
|
||||||
|
legend: false, //图例
|
||||||
|
});
|
||||||
|
// 创建实体管理器实例
|
||||||
|
list.value.forEach(item => {
|
||||||
|
if (defaultCheckedKeys.value.includes(item.id)) {
|
||||||
|
console.log("defaultCheckedKeys", item.id);
|
||||||
|
renderDevice(item)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
roamingController = new CesiumFlyToRoamingController(window.Earth1.viewer, {
|
||||||
|
duration: 5, // 每个点之间飞行5秒
|
||||||
|
pitch: -89 // 20度俯角
|
||||||
|
});
|
||||||
|
window.roamingController = roamingController;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 加载倾斜数据
|
||||||
|
function loadTiltData(viewer) {
|
||||||
|
viewer.terrainProvider = new Cesium.CesiumTerrainProvider({
|
||||||
|
// url: 'http://192.168.110.2:8895/yjearth4.0/data/pak/e904acb32aaa8b872c64866ebaaaf5e2',
|
||||||
|
// url:"http://58.17.134.85:7363/yjearth4.0/data/pak/e904acb32aaa8b872c64866ebaaaf5e2"
|
||||||
|
url: import.meta.env.VITE_EARTH_URL + "/yjearth4.0/data/pak/4eb21d3fc02873092e75640e261544b3"
|
||||||
|
});
|
||||||
|
// var tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
|
||||||
|
// // url: 'http://58.17.134.85:7363/zm/api/v1/data/tileset/b15be28d053fcdd98308bed74c6108e6/tileset.json',
|
||||||
|
// url:'http://58.17.134.85:7363/yjearth4.0/data/tileset/9c25649daa610a0cfb89e2cbea74bc55/tileset.json'
|
||||||
|
// }));
|
||||||
|
// console.log("加载倾斜数据:", tileset);
|
||||||
|
}
|
||||||
|
// 获取ArcGIS服务的URL
|
||||||
|
function getArcGisUrlByType(type) {
|
||||||
|
switch (type) {
|
||||||
|
//影像
|
||||||
|
case "img_w":
|
||||||
|
return "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer";
|
||||||
|
//电子
|
||||||
|
case "vec_w":
|
||||||
|
return "https://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer";
|
||||||
|
//蓝色底图
|
||||||
|
case "vec_blue":
|
||||||
|
return "http://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetPurplishBlue/MapServer";
|
||||||
|
//灰色底图
|
||||||
|
case "vec_gray":
|
||||||
|
return "http://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetGray/MapServer";
|
||||||
|
//暖色底图
|
||||||
|
case "vec_warm":
|
||||||
|
return "http://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetWarm/MapServer";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 添加ArcGIS图层
|
||||||
|
function addArcgisLayer(viewer, type) {
|
||||||
|
let url = getArcGisUrlByType(type)
|
||||||
|
const layerProvider = new Cesium.ArcGisMapServerImageryProvider({
|
||||||
|
url: url
|
||||||
|
});
|
||||||
|
viewer.imageryLayers.addImageryProvider(layerProvider);
|
||||||
|
}
|
||||||
|
// 节点单击事件
|
||||||
|
function handleNodeClick(data) {
|
||||||
|
console.log('data', data);
|
||||||
|
let entity = window.deviceMap.get(data.id);
|
||||||
|
if (entity) {
|
||||||
|
entity.flyTo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 复选框选中事件
|
||||||
|
function handleCheck(checkedNodes, nodes) {
|
||||||
|
console.log('check', checkedNodes, nodes);
|
||||||
|
// 处理单个节点的通用函数
|
||||||
|
const handleNode = (node) => {
|
||||||
|
if (!window.deviceMap.has(node.id)) {
|
||||||
|
console.log("defaultCheckedKeys", node.id);
|
||||||
|
renderDevice(node);
|
||||||
|
} else {
|
||||||
|
const device = window.deviceMap.get(node.id);
|
||||||
|
// 根据当前显示状态切换显示/隐藏
|
||||||
|
device[device.entity.show ? 'hide' : 'show']();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理选中的节点(可能是单个节点或包含子节点的集合)
|
||||||
|
if (checkedNodes?.children?.length) {
|
||||||
|
console.log('children', checkedNodes.children);
|
||||||
|
checkedNodes.children.forEach(handleNode);
|
||||||
|
} else {
|
||||||
|
handleNode(checkedNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCheckedNode(nodes.checkedKeys);
|
||||||
|
}
|
||||||
|
// 开始漫游
|
||||||
|
function startRoaming() {
|
||||||
|
if (roamingController) {
|
||||||
|
roamingController.startPathRoaming([
|
||||||
|
Cesium.Cartesian3.fromDegrees(106.49556855602525, 29.534393226355515, 200),
|
||||||
|
Cesium.Cartesian3.fromDegrees(106.49142431645038, 29.534472802500083, 200),
|
||||||
|
Cesium.Cartesian3.fromDegrees(106.49142125177437, 29.541881138875755, 200)
|
||||||
|
], 3, false);
|
||||||
|
} else {
|
||||||
|
console.log('请先初始化地球');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 停止漫游
|
||||||
|
function stopRoaming() {
|
||||||
|
if (roamingController) {
|
||||||
|
roamingController.stopRoaming();
|
||||||
|
} else {
|
||||||
|
console.log('请先初始化地球');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
// 获取选中节点
|
||||||
|
getCheckedNode();
|
||||||
|
// 获取GPS数据
|
||||||
|
getGpsData();
|
||||||
|
// 初始化地球
|
||||||
|
initEarth();
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.deviceMap.forEach((item) => {
|
||||||
|
item.destroy();
|
||||||
|
})
|
||||||
|
window.deviceMap.clear();
|
||||||
|
window.roamingController.destroy();
|
||||||
|
window.Earth1.destroy();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="earth-container-big">
|
||||||
|
<div class="earth" id="earth"></div>
|
||||||
|
<div class="left">
|
||||||
|
<div style="width: 100%;height: 100%;">
|
||||||
|
<el-button type="primary" @click="startRoaming">开始漫游</el-button>
|
||||||
|
<el-button type="primary" @click="stopRoaming">停止漫游</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<el-tree show-checkbox :data="data" :props="defaultProps" node-key="id" :expand-on-click-node="false"
|
||||||
|
:check-on-click-node="false" :check-on-click-leaf="false" :default-expanded-keys="defaultExpandedKeys"
|
||||||
|
:default-checked-keys="defaultCheckedKeys" @check="handleCheck" @node-click="handleNodeClick" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="scss">
|
||||||
|
.earth-container-big {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.earth {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right,
|
||||||
|
.left {
|
||||||
|
position: absolute;
|
||||||
|
width: 200px;
|
||||||
|
height: 100%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background-color: #00000052;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.el-tree {
|
||||||
|
background-color: transparent;
|
||||||
|
--el-tree-node-hover-bg-color: transparent;
|
||||||
|
--el-tree-text-color: #fff;
|
||||||
|
.el-text {
|
||||||
|
color: azure;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tree-node__content:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,293 @@
|
|||||||
|
export default class CesiumFlyToRoamingController {
|
||||||
|
/**
|
||||||
|
* 构造函数 - 创建基于flyTo的漫游控制器
|
||||||
|
* @param {Cesium.Viewer} viewer - Cesium Viewer实例
|
||||||
|
* @param {Object} [options] - 漫游配置选项
|
||||||
|
* @param {number} [options.duration=3] - 飞行持续时间(秒)
|
||||||
|
* @param {number} [options.pitch=-30] - 俯仰角(度)
|
||||||
|
* @param {number} [options.headingOffset=0] - 航向偏移(度)
|
||||||
|
*/
|
||||||
|
constructor(viewer, options = {}) {
|
||||||
|
if (!viewer || !(viewer instanceof Cesium.Viewer)) {
|
||||||
|
throw new Error('必须提供有效的Cesium Viewer实例');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.viewer = viewer;
|
||||||
|
this.isRoaming = false;
|
||||||
|
this.path = [];
|
||||||
|
this.currentIndex = -1; // 初始为-1,表示尚未开始
|
||||||
|
this.loop = false;
|
||||||
|
this.duration = options.duration || 3;
|
||||||
|
this.pitch = Cesium.Math.toRadians(options.pitch || -30);
|
||||||
|
this.headingOffset = Cesium.Math.toRadians(options.headingOffset || 0);
|
||||||
|
this.flyToOptions = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始路径漫游 - 严格按照路径点顺序,首先飞入第一个点位
|
||||||
|
* @param {Array} path - 路径点数组(按顺序排列)
|
||||||
|
* @param {number} [duration] - 飞行持续时间(秒)
|
||||||
|
* @param {boolean} [loop=false] - 是否循环漫游
|
||||||
|
*/
|
||||||
|
startPathRoaming(path, duration, loop = false) {
|
||||||
|
if (!path || path.length < 1) {
|
||||||
|
throw new Error('路径漫游需要至少1个路径点');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止当前可能的漫游
|
||||||
|
this.stopRoaming();
|
||||||
|
|
||||||
|
// 初始化参数
|
||||||
|
this.path = [...path];
|
||||||
|
this.loop = loop;
|
||||||
|
this.currentIndex = -1; // 重置为初始状态
|
||||||
|
|
||||||
|
if (duration !== undefined) {
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isRoaming = true;
|
||||||
|
|
||||||
|
// 第一步:飞到第一个点位
|
||||||
|
this.flyToFirstPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专门用于飞到第一个点位的方法
|
||||||
|
*/
|
||||||
|
flyToFirstPoint() {
|
||||||
|
if (!this.isRoaming || this.path.length === 0) return;
|
||||||
|
|
||||||
|
const firstPointIndex = 0;
|
||||||
|
const firstPoint = this.path[firstPointIndex];
|
||||||
|
|
||||||
|
// 计算朝向:如果有第二个点,则面向第二个点,否则保持当前朝向
|
||||||
|
let orientation;
|
||||||
|
if (this.path.length > 1) {
|
||||||
|
orientation = this.calculateOrientation(firstPoint, this.path[1]);
|
||||||
|
} else {
|
||||||
|
orientation = {
|
||||||
|
heading: this.viewer.camera.heading,
|
||||||
|
pitch: this.pitch,
|
||||||
|
roll: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 飞行到第一个点
|
||||||
|
this.flyToOptions = {
|
||||||
|
destination: firstPoint,
|
||||||
|
orientation: orientation,
|
||||||
|
duration: this.duration,
|
||||||
|
complete: () => {
|
||||||
|
// 第一个点到达后更新索引
|
||||||
|
this.currentIndex = firstPointIndex;
|
||||||
|
|
||||||
|
// 如果有更多点,继续飞行到下一个点
|
||||||
|
if (this.path.length > 1) {
|
||||||
|
this.flyToNextPoint();
|
||||||
|
} else {
|
||||||
|
// 只有一个点时,完成后停止漫游
|
||||||
|
this.isRoaming = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancel: () => {
|
||||||
|
this.isRoaming = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.viewer.camera.flyTo(this.flyToOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 飞行到下一个路径点
|
||||||
|
*/
|
||||||
|
flyToNextPoint() {
|
||||||
|
if (!this.isRoaming || this.currentIndex === -1) return;
|
||||||
|
|
||||||
|
// 计算下一个点的索引
|
||||||
|
const nextIndex = this.currentIndex + 1;
|
||||||
|
|
||||||
|
// 检查是否超出路径范围
|
||||||
|
if (nextIndex >= this.path.length) {
|
||||||
|
if (this.loop) {
|
||||||
|
// 循环模式:回到第一个点
|
||||||
|
this.currentIndex = -1;
|
||||||
|
this.flyToFirstPoint();
|
||||||
|
} else {
|
||||||
|
// 非循环模式:到达终点,停止漫游
|
||||||
|
this.isRoaming = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新当前索引并获取目标点
|
||||||
|
this.currentIndex = nextIndex;
|
||||||
|
const targetPoint = this.path[this.currentIndex];
|
||||||
|
|
||||||
|
// 计算朝向
|
||||||
|
let orientation;
|
||||||
|
if (this.currentIndex < this.path.length - 1) {
|
||||||
|
// 面向下一个点
|
||||||
|
orientation = this.calculateOrientation(targetPoint, this.path[this.currentIndex + 1]);
|
||||||
|
} else if (this.loop) {
|
||||||
|
// 最后一个点且循环模式,面向第一个点
|
||||||
|
orientation = this.calculateOrientation(targetPoint, this.path[0]);
|
||||||
|
} else {
|
||||||
|
// 最后一个点且不循环,保持当前朝向
|
||||||
|
orientation = {
|
||||||
|
heading: this.viewer.camera.heading,
|
||||||
|
pitch: this.pitch,
|
||||||
|
roll: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行飞行
|
||||||
|
this.flyToOptions = {
|
||||||
|
destination: targetPoint,
|
||||||
|
orientation: orientation,
|
||||||
|
duration: this.duration,
|
||||||
|
complete: () => this.flyToNextPoint(),
|
||||||
|
cancel: () => { this.isRoaming = false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.viewer.camera.flyTo(this.flyToOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算相机朝向
|
||||||
|
* @param {Cesium.Cartesian3} position - 相机位置
|
||||||
|
* @param {Cesium.Cartesian3} lookAtPoint - 看向的点
|
||||||
|
* @returns {Object} 朝向配置
|
||||||
|
*/
|
||||||
|
calculateOrientation(position, lookAtPoint) {
|
||||||
|
const direction = Cesium.Cartesian3.subtract(lookAtPoint, position, new Cesium.Cartesian3());
|
||||||
|
const heading = Math.atan2(direction.x, direction.y) + this.headingOffset;
|
||||||
|
|
||||||
|
return {
|
||||||
|
heading: heading,
|
||||||
|
pitch: this.pitch,
|
||||||
|
roll: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止漫游
|
||||||
|
*/
|
||||||
|
stopRoaming() {
|
||||||
|
if (this.isRoaming) {
|
||||||
|
this.isRoaming = false;
|
||||||
|
if (this.flyToOptions) {
|
||||||
|
this.viewer.camera.cancelFlight();
|
||||||
|
this.flyToOptions = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂停漫游
|
||||||
|
*/
|
||||||
|
pauseRoaming() {
|
||||||
|
if (this.isRoaming) {
|
||||||
|
this.isRoaming = false;
|
||||||
|
if (this.flyToOptions) {
|
||||||
|
this.viewer.camera.cancelFlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复漫游
|
||||||
|
*/
|
||||||
|
resumeRoaming() {
|
||||||
|
if (!this.isRoaming && this.path.length > 0) {
|
||||||
|
this.isRoaming = true;
|
||||||
|
if (this.currentIndex === -1) {
|
||||||
|
this.flyToFirstPoint();
|
||||||
|
} else {
|
||||||
|
this.flyToNextPoint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加路径点
|
||||||
|
* @param {Cesium.Cartesian3} point - 路径点
|
||||||
|
*/
|
||||||
|
addPathPoint(point) {
|
||||||
|
if (point instanceof Cesium.Cartesian3) {
|
||||||
|
this.path.push(point);
|
||||||
|
} else {
|
||||||
|
console.warn('路径点必须是Cesium.Cartesian3类型');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除路径
|
||||||
|
*/
|
||||||
|
clearPath() {
|
||||||
|
this.stopRoaming();
|
||||||
|
this.path = [];
|
||||||
|
this.currentIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置飞行持续时间
|
||||||
|
* @param {number} duration - 持续时间(秒)
|
||||||
|
*/
|
||||||
|
setDuration(duration) {
|
||||||
|
if (typeof duration === 'number' && duration > 0) {
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁控制器
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
this.stopRoaming();
|
||||||
|
this.viewer = null;
|
||||||
|
this.path = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例:
|
||||||
|
// 假设已经有一个Cesium Viewer实例叫做viewer
|
||||||
|
//
|
||||||
|
// // 创建漫游控制器
|
||||||
|
// const roamingController = new CesiumRoamingController(viewer, {
|
||||||
|
// speed: 20,
|
||||||
|
// pitch: -20, // 20度俯角
|
||||||
|
// headingOffset: 0
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// // 示例1: 自由漫游
|
||||||
|
// // 开始自由漫游
|
||||||
|
// document.getElementById('startFreeRoam').addEventListener('click', () => {
|
||||||
|
// roamingController.startFreeRoaming(15); // 速度15米/秒
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// // 示例2: 路径漫游
|
||||||
|
// // 创建路径点
|
||||||
|
// const pathPoints = [
|
||||||
|
// Cesium.Cartesian3.fromDegrees(116.3, 39.9, 200),
|
||||||
|
// Cesium.Cartesian3.fromDegrees(116.4, 39.9, 200),
|
||||||
|
// Cesium.Cartesian3.fromDegrees(116.4, 40.0, 200),
|
||||||
|
// Cesium.Cartesian3.fromDegrees(116.3, 40.0, 200)
|
||||||
|
// ];
|
||||||
|
//
|
||||||
|
// // 开始路径漫游
|
||||||
|
// document.getElementById('startPathRoam').addEventListener('click', () => {
|
||||||
|
// roamingController.startPathRoaming(pathPoints, 30, true); // 速度30米/秒,循环漫游
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// // 停止漫游
|
||||||
|
// document.getElementById('stopRoam').addEventListener('click', () => {
|
||||||
|
// roamingController.stopRoaming();
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// // 调整速度
|
||||||
|
// document.getElementById('speedUp').addEventListener('click', () => {
|
||||||
|
// const currentSpeed = roamingController.speed;
|
||||||
|
// roamingController.setSpeed(currentSpeed + 5);
|
||||||
|
// });
|
||||||
|
|
@ -0,0 +1,293 @@
|
|||||||
|
export default class CesiumImageLabelEntity {
|
||||||
|
/**
|
||||||
|
* 构造函数 - 创建带有图片和名称的Cesium Entity
|
||||||
|
* @param {Cesium.Viewer} viewer - Cesium Viewer实例
|
||||||
|
* @param {Object} options - 配置选项
|
||||||
|
* @param {Cesium.Cartesian3} options.position - 实体位置坐标
|
||||||
|
* @param {string} options.name - 实体名称(标签文本)
|
||||||
|
* @param {string} options.imageUrl - 图片URL
|
||||||
|
* @param {Function} [options.onClick] - 左击事件回调函数
|
||||||
|
* @param {string} [options.id] - 实体ID,可选
|
||||||
|
* @param {number} [options.imageWidth=64] - 图片宽度
|
||||||
|
* @param {number} [options.imageHeight=64] - 图片高度
|
||||||
|
* @param {Cesium.Color} [options.imageColor=Cesium.Color.WHITE] - 图片颜色(用于色调调整)
|
||||||
|
* @param {Cesium.Color} [options.labelColor=Cesium.Color.WHITE] - 标签颜色
|
||||||
|
* @param {string} [options.labelFont='16px sans-serif'] - 标签字体
|
||||||
|
* @param {number} [options.labelOffsetY=-70] - 标签Y轴偏移量(相对于图片)
|
||||||
|
* @param {boolean} [options.show=true] - 是否显示实体
|
||||||
|
* @param {Cesium.HorizontalOrigin} [options.horizontalOrigin=Cesium.HorizontalOrigin.CENTER] - 水平对齐方式
|
||||||
|
* @param {Cesium.VerticalOrigin} [options.verticalOrigin=Cesium.VerticalOrigin.BOTTOM] - 垂直对齐方式
|
||||||
|
*/
|
||||||
|
constructor(viewer, options) {
|
||||||
|
// 验证必要参数
|
||||||
|
if (!viewer || !(viewer instanceof Cesium.Viewer)) {
|
||||||
|
throw new Error('必须提供有效的Cesium Viewer实例');
|
||||||
|
}
|
||||||
|
if (!options || !options.position) {
|
||||||
|
throw new Error('必须提供实体位置信息');
|
||||||
|
}
|
||||||
|
if (!options.name) {
|
||||||
|
throw new Error('必须提供实体名称');
|
||||||
|
}
|
||||||
|
if (!options.imageUrl) {
|
||||||
|
throw new Error('必须提供图片URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.viewer = viewer;
|
||||||
|
this.onClickCallback = options.onClick || null;
|
||||||
|
this.options = {
|
||||||
|
// 默认配置
|
||||||
|
id: options.id || `image-label-entity-${Date.now()}`,
|
||||||
|
imageWidth: options.imageWidth || 64,
|
||||||
|
imageHeight: options.imageHeight || 64,
|
||||||
|
imageColor: options.imageColor || Cesium.Color.WHITE,
|
||||||
|
labelColor: options.labelColor || Cesium.Color.WHITE,
|
||||||
|
labelFont: options.labelFont || '16px sans-serif',
|
||||||
|
labelOffsetY: options.labelOffsetY || -80,
|
||||||
|
show: options.show !== undefined ? options.show : true,
|
||||||
|
horizontalOrigin: options.horizontalOrigin || Cesium.HorizontalOrigin.CENTER,
|
||||||
|
verticalOrigin: options.verticalOrigin || Cesium.VerticalOrigin.BOTTOM,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建实体
|
||||||
|
this.entity = this.createEntity();
|
||||||
|
|
||||||
|
// 初始化点击事件监听
|
||||||
|
this.initClickHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建实体
|
||||||
|
* @returns {Cesium.Entity} 创建的实体对象
|
||||||
|
*/
|
||||||
|
createEntity() {
|
||||||
|
const entity = new Cesium.Entity({
|
||||||
|
id: this.options.id,
|
||||||
|
position: Cesium.Cartesian3.fromDegrees(
|
||||||
|
this.options.position.lng,
|
||||||
|
this.options.position.lat,
|
||||||
|
this.options.position.alt
|
||||||
|
),
|
||||||
|
show: this.options.show,
|
||||||
|
|
||||||
|
// 图片属性
|
||||||
|
billboard: {
|
||||||
|
image: this.options.imageUrl,
|
||||||
|
width: this.options.imageWidth,
|
||||||
|
height: this.options.imageHeight,
|
||||||
|
color: this.options.imageColor,
|
||||||
|
horizontalOrigin: this.options.horizontalOrigin,
|
||||||
|
verticalOrigin: this.options.verticalOrigin,
|
||||||
|
// 允许实体被选中
|
||||||
|
pickable: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// 名称标签属性
|
||||||
|
label: {
|
||||||
|
text: this.options.name,
|
||||||
|
font: this.options.labelFont,
|
||||||
|
fillColor: this.options.labelColor,
|
||||||
|
horizontalOrigin: this.options.horizontalOrigin,
|
||||||
|
verticalOrigin: Cesium.VerticalOrigin.TOP,
|
||||||
|
pixelOffset: new Cesium.Cartesian2(0, this.options.labelOffsetY),
|
||||||
|
// 添加背景
|
||||||
|
backgroundColor: new Cesium.Color(0, 0, 0, 0),
|
||||||
|
backgroundPadding: new Cesium.Cartesian2(5, 5),
|
||||||
|
showBackground: true,
|
||||||
|
// 允许标签被选中
|
||||||
|
pickable: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将实体添加到viewer
|
||||||
|
this.viewer.entities.add(entity);
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化点击事件处理器
|
||||||
|
*/
|
||||||
|
initClickHandler() {
|
||||||
|
// 存储当前实例的引用,方便在回调中使用
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
// 注册左击事件
|
||||||
|
this.viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) {
|
||||||
|
// 检测是否点击了当前实体
|
||||||
|
const pickedObject = self.viewer.scene.pick(movement.position);
|
||||||
|
|
||||||
|
// 检查是否点击了当前实体或其实体的子组件(billboard、label等)
|
||||||
|
if (Cesium.defined(pickedObject) &&
|
||||||
|
(pickedObject.id === self.entity ||
|
||||||
|
pickedObject.id === self.entity.billboard ||
|
||||||
|
pickedObject.id === self.entity.label)) {
|
||||||
|
|
||||||
|
// 如果设置了点击回调,则执行
|
||||||
|
if (self.onClickCallback && typeof self.onClickCallback === 'function') {
|
||||||
|
self.onClickCallback(self.entity, movement.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置点击事件回调函数
|
||||||
|
* @param {Function} callback - 回调函数,接收(entity, position)参数
|
||||||
|
*/
|
||||||
|
setOnClick(callback) {
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
this.onClickCallback = callback;
|
||||||
|
} else {
|
||||||
|
console.warn('回调函数必须是一个函数');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平滑飞行到实体当前位置
|
||||||
|
* @param {Object} [options] - 飞行参数配置
|
||||||
|
* @param {number} [options.duration=3] - 飞行持续时间(秒)
|
||||||
|
* @param {number} [options.offsetDistance=1000] - 距离目标点的距离(米)
|
||||||
|
* @param {Cesium.HeadingPitchRange} [options.headingPitchRange] - 方向、俯仰和范围,优先级高于offsetDistance
|
||||||
|
* @param {Function} [options.complete] - 飞行完成后的回调函数
|
||||||
|
* @param {Function} [options.cancel] - 飞行被取消后的回调函数
|
||||||
|
*/
|
||||||
|
flyTo(options = {}) {
|
||||||
|
// 获取实体当前位置(考虑可能已更新的情况)
|
||||||
|
const currentPosition = this.entity?.position?.getValue(Cesium.JulianDate.now());
|
||||||
|
|
||||||
|
if (!currentPosition) {
|
||||||
|
console.warn('无法飞行到实体,实体或实体位置不存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认飞行参数
|
||||||
|
const defaultOptions = {
|
||||||
|
duration: 3,
|
||||||
|
offsetDistance: 1000,
|
||||||
|
complete: () => {},
|
||||||
|
cancel: () => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 合并用户配置和默认配置
|
||||||
|
const flyOptions = { ...defaultOptions, ...options };
|
||||||
|
|
||||||
|
// 计算飞行视角
|
||||||
|
let headingPitchRange;
|
||||||
|
if (flyOptions.headingPitchRange) {
|
||||||
|
headingPitchRange = flyOptions.headingPitchRange;
|
||||||
|
} else {
|
||||||
|
// 默认视角:从上方稍远处看向实体
|
||||||
|
headingPitchRange = new Cesium.HeadingPitchRange(
|
||||||
|
0, // 方向角(弧度)
|
||||||
|
Cesium.Math.toRadians(-30), // 俯仰角(弧度),负值表示向下看
|
||||||
|
flyOptions.offsetDistance // 距离目标点的距离
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行飞行到当前位置
|
||||||
|
this.viewer.flyTo(this.entity, {
|
||||||
|
destination: currentPosition, // 明确指定当前位置作为目标
|
||||||
|
duration: flyOptions.duration,
|
||||||
|
offset: headingPitchRange,
|
||||||
|
complete: flyOptions.complete,
|
||||||
|
cancel: flyOptions.cancel
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新实体位置
|
||||||
|
* @param {Cesium.Cartesian3} position - 新的位置坐标
|
||||||
|
*/
|
||||||
|
updatePosition(position) {
|
||||||
|
if (position && this.entity) {
|
||||||
|
this.entity.position = position;
|
||||||
|
// 更新options中的位置,保持同步
|
||||||
|
this.options.position = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新实体名称
|
||||||
|
* @param {string} name - 新的名称
|
||||||
|
*/
|
||||||
|
updateName(name) {
|
||||||
|
if (name && this.entity && this.entity.label) {
|
||||||
|
this.entity.label.text = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新实体图片
|
||||||
|
* @param {string} imageUrl - 新的图片URL
|
||||||
|
*/
|
||||||
|
updateImage(imageUrl) {
|
||||||
|
if (imageUrl && this.entity && this.entity.billboard) {
|
||||||
|
this.entity.billboard.image = imageUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示实体
|
||||||
|
*/
|
||||||
|
show() {
|
||||||
|
if (this.entity) {
|
||||||
|
this.entity.show = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏实体
|
||||||
|
*/
|
||||||
|
hide() {
|
||||||
|
if (this.entity) {
|
||||||
|
this.entity.show = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除实体
|
||||||
|
*/
|
||||||
|
remove() {
|
||||||
|
if (this.entity) {
|
||||||
|
this.viewer.entities.remove(this.entity);
|
||||||
|
this.entity = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前实体
|
||||||
|
* @returns {Cesium.Entity} 当前实体对象
|
||||||
|
*/
|
||||||
|
getEntity() {
|
||||||
|
return this.entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例:
|
||||||
|
// 假设已经有一个Cesium Viewer实例叫做viewer
|
||||||
|
// // 创建实体
|
||||||
|
// const initialPosition = Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100);
|
||||||
|
// const imageEntity = new CesiumImageLabelEntity(viewer, {
|
||||||
|
// position: initialPosition,
|
||||||
|
// name: "可移动点",
|
||||||
|
// imageUrl: "path/to/your/image.png",
|
||||||
|
// onClick: function(entity) {
|
||||||
|
// console.log("点击了实体,飞向当前位置");
|
||||||
|
// entity.flyTo(); // 飞向当前位置
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// // 一段时间后更新位置
|
||||||
|
// setTimeout(() => {
|
||||||
|
// const newPosition = Cesium.Cartesian3.fromDegrees(116.45, 39.92, 100);
|
||||||
|
// imageEntity.updatePosition(newPosition);
|
||||||
|
// console.log("实体位置已更新");
|
||||||
|
// }, 2000);
|
||||||
|
//
|
||||||
|
// // 调用flyTo将飞向最新的位置
|
||||||
|
// setTimeout(() => {
|
||||||
|
// imageEntity.flyTo({duration: 2});
|
||||||
|
// }, 4000);
|
||||||
|
|
Reference in New Issue
Block a user