一、项目介绍
项目需要一个指南针,用鼠标按住旋转指针,会让3d地图旋转,点击中心点重置方位。当然可以使用插件cesium-navigation-es6,但是样式不好修改,所以我自定义了一个指南针。
实现思路
在Cesium中,相机的方向由 heading、pitch 和 roll 三个角度参数控制,它们共同定义了相机在3D空间中的朝向。要让地图旋转,只要控制相机的角度即可。
heading(偏航角),绕(z)轴旋转,类似于指南针的方向,范围:0 到 2π 弧度(或 0° 到 360°)。
0或2π表示正北方向(默认Cesium的初始朝向)。π/2(90°)表示正东。π(180°)表示正南。3π/2(270°)表示正西。
Pitch(俯仰角),绕(y)轴旋转,控制相机的“抬头”或“低头”。 范围:-π/2 到 π/2,超过会导致视角翻转。
-
0表示相机水平朝前(平行于地面)。 -
π/2(90°)表示相机垂直向下(俯视地面)。 -
-π/2(-90°)表示相机垂直向上(仰视天空)。
Roll(翻滚角),绕(X)轴旋转的角度(倾斜左右)。在飞行模拟中模拟飞机倾斜,但Cesium中较少直接使用(默认通常为 0)
所以我们要先计算鼠标点击指南针时向左向右偏移的角度,将这个角度值转为相机旋转的弧度值,并重新设置给相机。
指南针实现
首先先创建一个div和css样式画出一个指南针:
<div class="compass-container">
<div
class="compass-outer-ring"
ref="compassOuterRing"
@click="handleClick"
@mousedown="onMouseDown"
@mouseup="onMouseUp"
@mousemove="onMouseMove"
>
<div class="compass-inner-ring">
<div class="compass-arrow">
<div class="compass-arrow-icon"></div>
<div class="compass-arrow-icon-bottom"></div>
<div class="compass-arrow-north">N</div>
<div class="compass-arrow-south">S</div>
<div class="compass-arrow-west">w</div>
<div class="compass-arrow-east">E</div>
</div>
</div>
</div>
</div>
指南针的css样式如下:
/* 指北针样式 */
.compass-container {
position: relative;
width: 40px;
height: 40px;
z-index: 999;
margin: 20px 0;
cursor: pointer;
}
.compass-outer-ring {
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background: #fff;
}
.compass-inner-ring {
position: absolute;
top: 10%;
left: 10%;
width: 80%;
height: 80%;
border-radius: 50%;
background: #fff;
border: 2px solid rgb(243, 240, 37);
}
.compass-arrow {
position: absolute;
top: 50%;
left: 50%;
width: 60%;
height: 60%;
transform-origin: center;
transform: translate(-50%, -50%);
}
.compass-arrow-north {
position: absolute;
top: -30px;
left: 50%;
font-size: 14px;
transform: translateX(-50%);
color: #fff;
font-weight: bold;
text-shadow: 0 0 3px white;
}
.compass-arrow-west {
position: absolute;
top: 10%;
left: -24px;
font-size: 14px;
transform: translateX(-50%);
color: #fff;
font-weight: bold;
text-shadow: 0 0 3px white;
}
.compass-arrow-east {
position: absolute;
top: 10%;
right: -29px;
font-size: 14px;
transform: translateX(-50%);
color: #fff;
font-weight: bold;
text-shadow: 0 0 3px white;
}
.compass-arrow-south {
position: absolute;
bottom: -30px;
left: 50%;
font-size: 14px;
transform: translateX(-50%);
color: white;
font-weight: bold;
text-shadow: 0 0 3px black;
}
.compass-arrow-icon {
position: absolute;
top: 0;
left: 50%;
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 16px solid orange;
transform: translate(-50%, -50%);
}
.compass-arrow-icon-bottom {
content: " ";
position: absolute;
top: 100%;
left: 50%;
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 16px solid gray;
transform: translate(-50%, -50%);
}
/* 长按旋转时的反馈效果 */
.compass-rotating {
border: 2px solid yellow;
box-shadow: 0 0 15px rgba(255, 255, 0, 0.7);
}
监听鼠标点击指南的事件,获取元素中点位置,并计算指南针中心点与鼠标的初始角度。
监听鼠标按下移动事件,实时计算指南针中心点与鼠标的角度,将角度转为弧度,并设置相机的heading值。
监听相机位置,同时旋转指南针的角度
const compassOuterRing = ref(null); //指南针元素
const isRotating = ref(false); //是否正在旋转
const startAngle = ref(0); //初始角度
const startHeading = ref(0); //初始弧度
const longPressTimer = ref(null); //计时
const longPressDuration = 300; // 长按判定时间(毫秒)
// 计算两点之间的角度
const calculateAngle = (center, point) => {
return (Math.atan2(point.y - center.y, point.x - center.x) * 180) / Math.PI;
};
// 获取指南针的中心点坐标
const getElementCenter = (element) => {
const rect = element.getBoundingClientRect();
return {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2,
};
};
// 鼠标按下事件
const onMouseDown = (e) => {
e.preventDefault();
const center = getElementCenter(compassOuterRing.value);
startAngle.value = calculateAngle(center, { x: e.clientX, y: e.clientY });
startHeading.value = Cesium.Math.toDegrees(window.viewer.camera.heading);
// 设置长按计时器
longPressTimer.value = setTimeout(() => {
isRotating.value = true;
compassOuterRing.value.classList.add("compass-rotating");
}, longPressDuration);
};
// 鼠标移动事件
const onMouseMove = (e) => {
if (!isRotating.value) return;
const center = getElementCenter(compassOuterRing.value);
const currentAngle = calculateAngle(center, { x: e.clientX, y: e.clientY });
const angleDiff = currentAngle - startAngle.value;
const compassArrow = document.querySelector(".compass-arrow");
compassArrow.style.transform = `translate(-50%, -50%) rotate(${angleDiff}deg)`;
// 计算新的heading角度(转换为弧度)
const newHeading = Cesium.Math.toRadians(startHeading.value - angleDiff);
// 设置相机新的方向
window.viewer.camera.setView({
orientation: {
heading: newHeading,
pitch: window.viewer.camera.pitch,
roll: window.viewer.camera.roll,
},
});
};
// 鼠标松开事件
const onMouseUp = () => {
// 清除长按计时器
clearTimeout(longPressTimer.value);
if (isRotating.value) {
isRotating.value = false;
compassOuterRing.value.classList.remove("compass-rotating");
}
};
//单击回正
const handleClick = () => {
if (isRotating.value) return; // 如果正在旋转,不执行点击事件
window.viewer.camera.setView({
orientation: {
heading: 0, // 朝北
pitch: window.viewer.camera.pitch,
roll: window.viewer.camera.roll,
},
});
const compassArrow = document.querySelector(".compass-arrow");
compassArrow.style.transform = `translate(-50%, -50%) rotate(${0}deg)`;
};
onMounted(() => {
// 添加全局鼠标事件
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
document.addEventListener("mouseleave", onMouseUp);
});
// 清理
onBeforeUnmount(() => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
document.removeEventListener("mouseleave", onMouseUp);
});
//监听相机事件
viewer.scene.camera.changed.addEventListener(function () {
updateCompass();
});
// 更新罗盘方向
const updateCompass = () => {
// 获取罗盘DOM元素
const compassArrow = document.querySelector(".compass-arrow");
// 获取相机当前的heading角度(弧度)
const heading = window.viewer.camera.heading;
// 将弧度转换为角度(注意:Cesium的heading是顺时针方向,而CSS旋转是逆时针方向)
const headingDegrees = Cesium.Math.toDegrees(heading);
// 旋转罗盘箭头,使其指向北方
compassArrow.style.transform = `translate(-50%, -50%) rotate(${-headingDegrees}deg)`;
};
点击罗盘移动鼠标,地图会跟着旋转。