
视频: 第一人称漫游管理
2.设计思路点击绘制开始在地图上绘制漫游的路径点位,双击结束后可编辑漫游路径名称。点击飞行,以第一视角飞行,可暂停、继续和退出飞行,删除时清除地图上的漫游路径。
3.具体代码 绘制 飞行 暂停 继续 退出 确定 import * as Cesium from 'cesium'; import { ElMessage, ElMessageBox } from "element-plus"; import { onMounted, onUnmounted, reactive, ref } from 'vue'; import * as api from "/@/api/main/manYGL"; import { initClock } from '/@/utils/cesium-utils'; const loading = ref(false); const props = defineProps(['viewer']); const dialogFormVisible = ref(false); var handler: any = null; const formRef = ref(); const rules = { title: { required: true, message: '请输入漫游路径名称', trigger: 'blur' }, }; // 视点名称 const form = reactive({ title: '', height: 100, }); //是否开始绘制 const drawing = ref(false); //列表数据 const dataList: any = reactive([]); var entities: any = []; //point实体列表 var pointEntities: any = []; //线实体列表 var linesEntities: any = []; var activeShapePoints: any = []; var customMarks: any = []; var Exection: any = null; const pitchValue = -20; var marksIndex = 1; var flytime = 10; var changeCameraTime = 7; var floatingPoint: any = undefined; var activeShape: any = undefined; const startFly = (item: any, index: number) => { if (Exection) { props.viewer.clock.onTick.removeEventListener(Exection); } initClock(props.viewer); flyExtent(item.positions); }; //开始飞行 const flyExtent = (marks: any) => { // 相机看点的角度,如果大于0那么则是从地底往上看,所以要为负值 const pitch = Cesium.Math.toRadians(pitchValue); // 时间间隔2秒钟 setExtentTime(10); Exection = function TimeExecution() { let preIndex = marksIndex - 1; if (marksIndex == 0) { preIndex = marks.length - 1; } //计算俯仰角 let heading = bearing(marks[preIndex].lat, marks[preIndex].lng, marks[marksIndex].lat, marks[marksIndex].lng); heading = Cesium.Math.toRadians(heading); // 当前已经过去的时间,单位s const delTime = Cesium.JulianDate.secondsDifference(props.viewer.clock.currentTime, props.viewer.clock.startTime); const originLat = marksIndex == 0 ? marks[marks.length - 1].lat : marks[marksIndex - 1].lat; const originLng = marksIndex == 0 ? marks[marks.length - 1].lng : marks[marksIndex - 1].lng; const endPosition = Cesium.Cartesian3.fromDegrees( originLng + ((marks[marksIndex].lng - originLng) / flytime) * delTime, originLat + ((marks[marksIndex].lat - originLat) / flytime) * delTime, marks[marksIndex].height ); props.viewer.scene.camera.setView({ destination: endPosition, orientation: { heading: heading, pitch: pitch, }, }); if (Cesium.JulianDate.compare(props.viewer.clock.currentTime, props.viewer.clock.stopTime) >= 0) { props.viewer.clock.onTick.removeEventListener(Exection); //有个转向的功能 changeCameraHeading(marks); } }; props.viewer.clock.onTick.addEventListener(Exection); }; //停止飞行 const stopFly = () => { props.viewer.clock.shouldAnimate = false; }; //继续飞行 const continueFly = () => { props.viewer.clock.shouldAnimate = true; }; const finishFly = (item: any, index: number) => { if (Exection) { props.viewer.clock.onTick.removeEventListener(Exection); } initClock(props.viewer); }; // 设置飞行的时间到viewer的时钟里 const setExtentTime = (time: any) => { const startTime = Cesium.JulianDate.fromDate(new Date()); const stopTime = Cesium.JulianDate.addSeconds(startTime, time, new Cesium.JulianDate()); props.viewer.clock.startTime = startTime.clone(); // 开始时间 props.viewer.clock.stopTime = stopTime.clone(); // 结速时间 props.viewer.clock.currentTime = startTime.clone(); // 当前时间 props.viewer.clock.clockRange = Cesium.ClockRange.CLAMPED; // 行为方式-达到终止时间后停止 props.viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK; // 时钟设置为当前系统时间; 忽略所有其他设置。 }; //计算俯仰角 const bearing = (startLat: any, startLng: any, destLat: any, destLng: any) => { startLat = toRadians(startLat); startLng = toRadians(startLng); destLat = toRadians(destLat); destLng = toRadians(destLng); let y = Math.sin(destLng - startLng) * Math.cos(destLat); let x = Math.cos(startLat) * Math.sin(destLat) - Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng); let brng = Math.atan2(y, x); let brngDgr = toDegrees(brng); return (brngDgr + 360) % 360; }; // 相机原地定点转向 const changeCameraHeading = (marks: any) => { let nextIndex = marksIndex + 1; if (marksIndex == marks.length - 1) { nextIndex = 0; } // 计算两点之间的方向 const heading = bearing(marks[marksIndex].lat, marks[marksIndex].lng, marks[nextIndex].lat, marks[nextIndex].lng); // 相机看点的角度,如果大于0那么则是从地底往上看,所以要为负值 const pitch = Cesium.Math.toRadians(pitchValue); // 给定飞行一周所需时间,比如10s, 那么每秒转动度数 const angle = (heading - Cesium.Math.toDegrees(props.viewer.camera.heading)) / changeCameraTime; // 时间间隔2秒钟 setExtentTime(changeCameraTime); // 相机的当前heading const initialHeading = props.viewer.camera.heading; Exection = function TimeExecution() { // 当前已经过去的时间,单位s const delTime = Cesium.JulianDate.secondsDifference(props.viewer.clock.currentTime, props.viewer.clock.startTime); const heading = Cesium.Math.toRadians(delTime * angle) + initialHeading; props.viewer.scene.camera.setView({ orientation: { heading: heading, pitch: pitch, }, }); if (Cesium.JulianDate.compare(props.viewer.clock.currentTime, props.viewer.clock.stopTime) >= 0) { props.viewer.clock.onTick.removeEventListener(Exection); marksIndex = ++marksIndex >= marks.length ? 0 : marksIndex; if (marksIndex != 0) { flyExtent(marks); } } }; props.viewer.clock.onTick.addEventListener(Exection); }; /** * 删除已绘制的图形 */ const delEntity = async (item: any, index: number) => { await Delete(item, index); }; /** * 点击确定 */ const submitForm = async (formEl: any) => { const valid = await formEl.validate(); if (valid) { Se(form.title, customMarks); dialogFormVisible.value = false; formEl.resetFields(); } }; //绘制线路 const drawLineRoad = () => { drawing.value = true; handler = new Cesium.ScreenSpaceEventHandler(props.viewer.scene.canvas); //鼠标左键 handler.setInputAction(function (event: any) { if (drawing.value) { var earthPosition = props.viewer.scene.pickPosition(event.position); if (Cesium.defined(earthPosition)) { if (activeShapePoints.length === 0) { floatingPoint = createPoint(earthPosition); activeShapePoints.push(earthPosition); var dynamicPositions = new Cesium.CallbackProperty(function () { return activeShapePoints; }, false); activeShape = drawShape(dynamicPositions); //绘制动态图 //线实体集合 linesEntities.push(activeShape); } activeShapePoints.push(earthPosition); //点实体集合 pointEntities.push(createPoint(earthPosition)); } } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); //鼠标移动 handler.setInputAction(function (event: any) { if (Cesium.defined(floatingPoint)) { var newPosition = props.viewer.scene.pickPosition(event.endPosition); if (Cesium.defined(newPosition)) { floatingPoint.position.setValue(newPosition); activeShapePoints.pop(); activeShapePoints.push(newPosition); } } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); handler.setInputAction(function () { if (drawing.value) { drawing.value = false; terminateShape(); } }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK); }; //绘制点 const createPoint = (worldPosition: any) => { var point = props.viewer.entities.add({ position: worldPosition, point: { color: Cesium.Color.RED, pixelSize: 10, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, }, }); return point; }; //绘制线 const drawShape = (positionData: any) => { var shape = props.viewer.entities.add({ polyline: { with: 10, color: Cesium.Color.RED, positions: positionData, clampToGround: true, }, }); return shape; }; const terminateShape = () => { activeShapePoints.pop(); //去除最后一个动态点 if (activeShapePoints.length) { customMarks = []; for (const position of activeShapePoints) { const latitude = toDegrees(Cesium.Cartographic.fromCartesian(position).latitude); const longitude = toDegrees(Cesium.Cartographic.fromCartesian(position).longitude); customMarks.push({ lat: latitude, lng: longitude, height: 300 }); } linesEntities.push(drawShape(activeShapePoints)); //绘制最终图 } dialogFormVisible.value = true; //弹出对话框 props.viewer.entities.remove(floatingPoint); //去除动态点图形(当前鼠标点) props.viewer.entities.remove(activeShape); //去除动态图形 props.viewer.trackedEntity = null;//为了去除双击后锁定无法移动视角 floatingPoint = undefined; activeShape = undefined; activeShapePoints = []; }; // 弧度转角度 const toDegrees = (radians: any) => { return (radians * 180) / Math.PI; }; // 角度转弧度 const toRadians = (degrees: any) => { return (degrees * Math.PI) / 180; }; onMounted(async () => { await handleQuery(); }); onUnmounted(() => { if (Exection) { props.viewer.clock.onTick.removeEventListener(Exection); } //清除绘制的内容 props.viewer.entities.removeAll(); if (handler != null) { handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK); handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK); } }); /** * 删除信息 */ const Delete = async (item: any, index: any) => { ElMessageBox.confirm(`确定要删除吗?`, "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }).then(async () => { loading.value = true; var res = await api.deleteManYGL({ id: item.id }); if (res.data.type == "success") { //退出飞行 if (Exection) { props.viewer.clock.onTick.removeEventListener(Exection); } for (const obj of entities[index].pointEntities) { props.viewer.entities.remove(obj); } for (const obj of entities[index].linesEntities) { props.viewer.entities.remove(obj); } entities.splice(index, 1); dataList.splice(index, 1); ElMessage.success("删除成功"); } loading.value = false; }).catch(() => { }); } /** * 查询 */ const handleQuery = async () => { loading.value = true; var res = await api.listManYGL(); console.log(res); if (res.data.code == 200 && res.data.result) { for (const item of res.data.result) { showResult(item.manYMCh, item._CoordinateInfoList, item.id); } } loading.value = false; } /** * 保存漫游 */ const Se = async (name: string, positions: any,) => { var pointList: any = []; for (const item of positions) { pointList.push({ x: item.lng, y: item.lat, z: item.height, }); } var param = { 'manYMCh': name, '_CoordinateInfoList': pointList }; console.log(param); var res = await api.addManYGL(param); console.log(res); if (res.data.code == 200 && res.data.result) { ElMessage.success("漫游添加成功"); addElectronicFence(res.data.result.manYMCh, res.data.result._CoordinateInfoList, res.data.result.id); } } /** * 添加 */ var addElectronicFence = (name: string, positions: any, id: any) => { var customMarks = []; for (const position of positions) { const latitude = position.y; const longitude = position.x; const height = position.z; customMarks.push({ lat: latitude, lng: longitude, height: height }); } //点实体和线实体的集合 entities.push({ pointEntities: pointEntities, linesEntities: linesEntities, }); dataList.push({ id: id, name: name, positions: customMarks, }); pointEntities = []; linesEntities = []; }; /** * 显示查询结果 */ var showResult = (name: string, positions: any, id: any) => { var pointEntities: any = []; var linesEntities: any = []; var points: any = []; for (const iterator of positions) { var point = Cesium.Cartesian3.fromDegrees(iterator.x, iterator.y, iterator.z); points.push(point); pointEntities.push(createPoint(point)); } console.log(pointEntities); linesEntities.push(drawShape(points)); var customMarks = []; for (const position of positions) { const latitude = position.y; const longitude = position.x; const height = position.z; customMarks.push({ lat: latitude, lng: longitude, height: height }); } //点实体和线实体的集合 entities.push({ pointEntities: pointEntities, linesEntities: linesEntities, }); dataList.push({ id: id, name: name, positions: customMarks, }); }; .page { position: absolute; right: 10px; top: 10px; color: #fff; background: #fff; padding: 10px; border-radius: 5px; width: 400px; } 1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.87.88.89.90.91.92.93.94.95.96.97.98.99.100.101.102.103.104.105.106.107.108.109.110.111.112.113.114.115.116.117.118.119.120.121.122.123.124.125.126.127.128.129.130.131.132.133.134.135.136.137.138.139.140.141.142.143.144.145.146.147.148.149.150.151.152.153.154.155.156.157.158.159.160.161.162.163.164.165.166.167.168.169.170.171.172.173.174.175.176.177.178.179.180.181.182.183.184.185.186.187.188.189.190.191.192.193.194.195.196.197.198.199.200.201.202.203.204.205.206.207.208.209.210.211.212.213.214.215.216.217.218.219.220.221.222.223.224.225.226.227.228.229.230.231.232.233.234.235.236.237.238.239.240.241.242.243.244.245.246.247.248.249.250.251.252.253.254.255.256.257.258.259.260.261.262.263.264.265.266.267.268.269.270.271.272.273.274.275.276.277.278.279.280.281.282.283.284.285.286.287.288.289.290.291.292.293.294.295.296.297.298.299.300.301.302.303.304.305.306.307.308.309.310.311.312.313.314.315.316.317.318.319.320.321.322.323.324.325.326.327.328.329.330.331.332.333.334.335.336.337.338.339.340.341.342.343.344.345.346.347.348.349.350.351.352.353.354.355.356.357.358.359.360.361.362.363.364.365.366.367.368.369.370.371.372.373.374.375.376.377.378.379.380.381.382.383.384.385.386.387.388.389.390.391.392.393.394.395.396.397.398.399.400.401.402.403.404.405.406.407.408.409.410.411.412.413.414.415.416.417.418.419.420.421.422.423.424.425.426.427.428.429.430.431.432.433.434.435.436.437.438.439.440.441.442.443.444.445.446.447.448.449.450.451.452.453.454.455.456.457.458.459.460.461.462.463.464.465.466.467.468.469.470.471.472.473.474.475.476.477.478.479.480.481.482.483.484.485.486.487.488.489.490.491.492.493.494.495.496.497.498.499.500.501.502.503. 4.问题第一人称角度偏转旋转时还存在计算问题,需继续优化。