<template>
|
<div class="cesium-compass" ref="compass" @mousedown.prevent="startDrag">
|
<!-- 指针 -->
|
<div class="needle" :style="needleStyle"></div>
|
|
<!-- 方向标 -->
|
<div class="dir-label dir-n">N</div>
|
<div class="dir-label dir-e">E</div>
|
<div class="dir-label dir-s">S</div>
|
<div class="dir-label dir-w">W</div>
|
|
<!-- 当前度数 -->
|
<div class="heading-label">{{ headingDegRounded }}°</div>
|
</div>
|
</template>
|
|
<script>
|
export default {
|
name: 'CesiumCompass',
|
props: {
|
viewer: { required: true } // 传入 Cesium.Viewer 实例
|
},
|
data() {
|
return {
|
headingDeg: 0,
|
dragging: false,
|
dragStartX: 0,
|
startHeading: 0,
|
rafId: null
|
};
|
},
|
computed: {
|
needleStyle() {
|
return {
|
transform: `rotate(${this.headingDeg}deg)`
|
};
|
},
|
headingDegRounded() {
|
// 保证角度在0~360
|
let deg = (this.headingDeg % 360 + 360) % 360;
|
return deg.toFixed(0);
|
}
|
},
|
mounted() {
|
const update = () => {
|
if (this.viewer && this.viewer.camera) {
|
try {
|
const rad = this.viewer.camera.heading;
|
if (typeof rad === 'number') {
|
this.headingDeg = -Cesium.Math.toDegrees(rad);
|
}
|
} catch (e) {}
|
}
|
this.rafId = requestAnimationFrame(update);
|
};
|
this.rafId = requestAnimationFrame(update);
|
|
window.addEventListener('mouseup', this.stopDrag);
|
window.addEventListener('mousemove', this.onMouseMove);
|
},
|
beforeDestroy() {
|
if (this.rafId) cancelAnimationFrame(this.rafId);
|
window.removeEventListener('mouseup', this.stopDrag);
|
window.removeEventListener('mousemove', this.onMouseMove);
|
},
|
methods: {
|
startDrag(e) {
|
this.dragging = true;
|
this.dragStartX = e.clientX;
|
this.startHeading = this.headingDeg;
|
if (this.viewer && this.viewer.scene && this.viewer.scene.screenSpaceCameraController) {
|
this.viewer.scene.screenSpaceCameraController.enableRotate = false;
|
}
|
},
|
onMouseMove(e) {
|
if (!this.dragging) return;
|
const dx = e.clientX - this.dragStartX;
|
const newHeading = this.startHeading + dx;
|
this.setCameraHeading(-newHeading);
|
},
|
stopDrag() {
|
if (!this.dragging) return;
|
this.dragging = false;
|
if (this.viewer && this.viewer.scene && this.viewer.scene.screenSpaceCameraController) {
|
this.viewer.scene.screenSpaceCameraController.enableRotate = true;
|
}
|
},
|
setCameraHeading(deg) {
|
if (!this.viewer) return;
|
const rad = Cesium.Math.toRadians(deg);
|
const camera = this.viewer.camera;
|
const orientation = {
|
heading: rad,
|
pitch: camera.pitch,
|
roll: camera.roll
|
};
|
this.viewer.camera.setView({ orientation });
|
this.headingDeg = -Cesium.Math.toDegrees(this.viewer.camera.heading || 0);
|
}
|
}
|
};
|
</script>
|
|
<style scoped>
|
.cesium-compass {
|
position: absolute;
|
top: 100px;
|
right: 50px;
|
width: 80px;
|
height: 80px;
|
border-radius: 50%;
|
background: rgba(255,255,255,0.9);
|
box-shadow: 0 2px 6px rgba(0,0,0,0.25);
|
display:flex;
|
align-items:center;
|
justify-content:center;
|
cursor: grab;
|
z-index: 9999;
|
font-family: sans-serif;
|
}
|
.cesium-compass:active { cursor: grabbing; }
|
|
/* 指针 */
|
.needle {
|
width: 2px;
|
height: 30px;
|
background: red;
|
transform-origin: 50% 80%;
|
position: absolute;
|
top: 15px;
|
transition: transform 0.05s linear;
|
border-radius: 1px;
|
}
|
|
/* 方向文字 */
|
.dir-label {
|
position: absolute;
|
font-weight: bold;
|
font-size: 12px;
|
color: black;
|
user-select: none;
|
}
|
.dir-n { top: 4px; left: 50%; transform: translateX(-50%); }
|
.dir-e { right: 4px; top: 50%; transform: translateY(-50%); }
|
.dir-s { bottom: 4px; left: 50%; transform: translateX(-50%); }
|
.dir-w { left: 4px; top: 50%; transform: translateY(-50%); }
|
|
/* 当前角度文字 */
|
.heading-label {
|
position: absolute;
|
bottom: 4px;
|
left: 50%;
|
transform: translateX(-50%);
|
font-size: 12px;
|
font-weight: bold;
|
color: black;
|
}
|
</style>
|