fix
This commit is contained in:
1 parent
7b76924b29
commit
a6ddcd4a35
4 files changed
+871
-51
No files matched your search
+644
-20
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue'
|
import { onMounted, watch, ref } from 'vue'
|
||||||
import { Viewer, ScreenSpaceEventHandler, ScreenSpaceEventType, defined, Cartographic, sampleTerrainMostDetailed, Cartesian3, Math as cesiumMath, Ion } from 'cesium'
|
import { Viewer, ScreenSpaceEventHandler, ScreenSpaceEventType, defined, Cartographic, sampleTerrainMostDetailed, Cartesian3, Math as cesiumMath, Ion } from 'cesium'
|
||||||
import { Wayline } from './wayline'
|
import { Wayline } from './wayline'
|
||||||
import { useStore } from './store'
|
import { useStore } from './store'
|
||||||
@@ -7,6 +7,142 @@ import { createViewer } from './basemap'
|
|||||||
import * as egm96 from 'egm96-universal'
|
import * as egm96 from 'egm96-universal'
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
const isDraggingSlider = ref(false)
|
||||||
|
const isMinimapExpanded = ref(false)
|
||||||
|
|
||||||
|
// 声明全局变量类型
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
wayline?: Wayline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听镜头模式变化,切换时重置变焦
|
||||||
|
watch(() => store.lensMode, (newMode) => {
|
||||||
|
if (window.wayline) {
|
||||||
|
const minimapViewer = window.wayline.minimapViewer
|
||||||
|
if (minimapViewer) {
|
||||||
|
function fovFromZoom(zoom: number, fov1: number = 1.3418): number {
|
||||||
|
const k = Math.tan(fov1 / 2)
|
||||||
|
return 2 * Math.atan(k / zoom)
|
||||||
|
}
|
||||||
|
if (newMode === 'wide') {
|
||||||
|
// 切换到广角模式时,重置为1x
|
||||||
|
const currentFov = fovFromZoom(1)
|
||||||
|
store.updateZoomFactor(1)
|
||||||
|
minimapViewer.camera.frustum.fov = currentFov
|
||||||
|
} else {
|
||||||
|
// 切换到变焦模式时,如果当前是1x,设置为1x(保持不变)
|
||||||
|
if (store.zoomFactor === 1) {
|
||||||
|
const currentFov = fovFromZoom(1)
|
||||||
|
minimapViewer.camera.frustum.fov = currentFov
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听变焦倍数变化,更新滑块位置
|
||||||
|
watch(() => store.zoomFactor, () => {
|
||||||
|
// 滑块位置会自动通过getSliderPosition()计算更新
|
||||||
|
})
|
||||||
|
|
||||||
|
// 变焦滑块相关函数
|
||||||
|
function getSliderPosition(): number {
|
||||||
|
if (store.lensMode === 'wide') {
|
||||||
|
return 0 // 广角模式固定在1x位置
|
||||||
|
}
|
||||||
|
// 变焦模式:1x在底部(0%),56x在顶部(100%)
|
||||||
|
const minZoom = 1
|
||||||
|
const maxZoom = 56
|
||||||
|
const zoom = store.zoomFactor
|
||||||
|
const percentage = ((zoom - minZoom) / (maxZoom - minZoom)) * 100
|
||||||
|
// bottom: 0% => 1x, bottom: 100% => 56x
|
||||||
|
return percentage
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomFromPosition(percentage: number): number {
|
||||||
|
// percentage: 0% = 底部(1x), 100% = 顶部(56x)
|
||||||
|
const minZoom = 1
|
||||||
|
const maxZoom = 56
|
||||||
|
const zoom = minZoom + (percentage / 100) * (maxZoom - minZoom)
|
||||||
|
return Math.round(Math.max(minZoom, Math.min(maxZoom, zoom)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSliderMouseDown(event: MouseEvent) {
|
||||||
|
if (store.lensMode === 'wide') return // 广角模式不允许调整
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
if (!target.classList.contains('minimap-zoom-track') && !target.classList.contains('zoom-track')) return
|
||||||
|
isDraggingSlider.value = true
|
||||||
|
updateZoomFromEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSliderMouseMove(event: MouseEvent) {
|
||||||
|
if (!isDraggingSlider.value || store.lensMode === 'wide') return
|
||||||
|
updateZoomFromEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSliderMouseUp() {
|
||||||
|
isDraggingSlider.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHandleMouseDown(event: MouseEvent) {
|
||||||
|
if (store.lensMode === 'wide') return
|
||||||
|
event.stopPropagation()
|
||||||
|
isDraggingSlider.value = true
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
if (isDraggingSlider.value && store.lensMode !== 'wide') {
|
||||||
|
// 找到对应的track元素
|
||||||
|
const handle = event.target as HTMLElement
|
||||||
|
const track = handle.closest('.minimap-zoom-track') || handle.closest('.zoom-track')
|
||||||
|
if (track) {
|
||||||
|
const fakeEvent = { ...e, currentTarget: track, target: track } as MouseEvent
|
||||||
|
updateZoomFromEvent(fakeEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
isDraggingSlider.value = false
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp)
|
||||||
|
}
|
||||||
|
document.addEventListener('mousemove', handleMouseMove)
|
||||||
|
document.addEventListener('mouseup', handleMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateZoomFromEvent(event: MouseEvent) {
|
||||||
|
if (store.lensMode === 'wide') return
|
||||||
|
|
||||||
|
// 查找滑块轨道元素(支持minimap-zoom-track和zoom-track)
|
||||||
|
let sliderElement = (event.currentTarget || event.target) as HTMLElement
|
||||||
|
if (!sliderElement || (!sliderElement.classList.contains('zoom-track') && !sliderElement.classList.contains('minimap-zoom-track'))) {
|
||||||
|
// 如果点击的是handle,查找父元素中的track
|
||||||
|
sliderElement = sliderElement.closest('.minimap-zoom-track') as HTMLElement || sliderElement.closest('.zoom-track') as HTMLElement
|
||||||
|
if (!sliderElement) return
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = sliderElement.getBoundingClientRect()
|
||||||
|
const y = event.clientY - rect.top
|
||||||
|
const percentage = Math.max(0, Math.min(100, (y / rect.height) * 100))
|
||||||
|
|
||||||
|
const newZoom = zoomFromPosition(percentage)
|
||||||
|
if (window.wayline) {
|
||||||
|
const minimapViewer = window.wayline.minimapViewer
|
||||||
|
if (minimapViewer) {
|
||||||
|
function fovFromZoom(zoom: number, fov1: number = 1.3418): number {
|
||||||
|
const k = Math.tan(fov1 / 2)
|
||||||
|
return 2 * Math.atan(k / zoom)
|
||||||
|
}
|
||||||
|
const currentFov = fovFromZoom(newZoom)
|
||||||
|
store.updateZoomFactor(newZoom)
|
||||||
|
minimapViewer.camera.frustum.fov = currentFov
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMinimapExpand() {
|
||||||
|
isMinimapExpanded.value = !isMinimapExpanded.value
|
||||||
|
}
|
||||||
|
|
||||||
let viewer: Viewer
|
let viewer: Viewer
|
||||||
Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI4NDU1Zjk5ZS0xOWQzLTQ2ZDEtYTk5MC03NmFlMGIwYTE4YzIiLCJpZCI6MTMwMTIsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1NjIzMTc2OTh9.1gS4dOGsPgIB_Xd5ERV_LPBPrj12oVUX4ZtyQyoYw58'
|
Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI4NDU1Zjk5ZS0xOWQzLTQ2ZDEtYTk5MC03NmFlMGIwYTE4YzIiLCJpZCI6MTMwMTIsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1NjIzMTc2OTh9.1gS4dOGsPgIB_Xd5ERV_LPBPrj12oVUX4ZtyQyoYw58'
|
||||||
@@ -67,19 +203,86 @@ async function getTerrainPosition(cartographic: Cartographic) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="cesium-viewer" ref="viewerDivRef"></div>
|
<div id="cesium-viewer" ref="viewerDivRef"></div>
|
||||||
<div id="minimap">
|
<div id="minimap" :class="{ 'expanded': isMinimapExpanded }">
|
||||||
<div class="zoom-frame warning" style="width: 200px; height: 150px;">
|
<!-- 小框放大按钮 -->
|
||||||
<div class="top left corner"></div>
|
<button class="minimap-expand-btn" @click="toggleMinimapExpand" :title="isMinimapExpanded ? '缩小' : '放大'">
|
||||||
<div class="top right corner"></div>
|
<svg v-if="!isMinimapExpanded" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<div class="bottom left corner"></div>
|
<path d="M2 2H6V3H3V6H2V2ZM10 2H14V6H13V3H10V2ZM2 10V14H6V13H3V10H2ZM13 10V13H10V14H14V10H13Z" fill="white"/>
|
||||||
<div class="bottom right corner"></div><span class="frame-title map-text-shadow warning">
|
</svg>
|
||||||
{{ store.zoomFactor }}X
|
<svg v-else width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<span class="uranus-icon uranus-icon-cursor-pointer warning-tooltips warning-tooltips warning-tooltips">
|
<path d="M11 5H13V7H11V5ZM5 5H7V7H5V5ZM11 9H13V11H11V9ZM5 9H7V11H5V9Z" fill="white"/>
|
||||||
<svg class="svgfont svgfont-warning_outline" style="width: 1em; height: 1em;">
|
</svg>
|
||||||
<use xlink:href="#warning_outline"></use>
|
</button>
|
||||||
</svg>
|
|
||||||
|
<!-- Minimap HUD 覆盖层(按截图布局) -->
|
||||||
|
<div class="minimap-hud">
|
||||||
|
<!-- 中间:对焦框(zoom-frame) -->
|
||||||
|
<div class="zoom-frame warning">
|
||||||
|
<div class="top left corner"></div>
|
||||||
|
<div class="top right corner"></div>
|
||||||
|
<div class="bottom left corner"></div>
|
||||||
|
<div class="bottom right corner"></div>
|
||||||
|
<span class="frame-title map-text-shadow warning">
|
||||||
|
{{ store.zoomFactor }}X
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
|
|
||||||
|
<!-- 顶部居中:镜头切换 -->
|
||||||
|
<div class="minimap-lens-controls">
|
||||||
|
<button
|
||||||
|
:class="['minimap-lens-btn', { 'active': store.lensMode === 'wide' }]"
|
||||||
|
@click="store.setLensMode('wide')"
|
||||||
|
>
|
||||||
|
广角 {{ store.lensMode === 'wide' ? store.zoomFactor : 1 }}X
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:class="['minimap-lens-btn', { 'active': store.lensMode === 'zoom' }]"
|
||||||
|
@click="store.setLensMode('zoom')"
|
||||||
|
>
|
||||||
|
变焦 {{ store.lensMode === 'zoom' ? store.zoomFactor : 1 }}X
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 中上:偏航角 -->
|
||||||
|
<div class="minimap-yaw">
|
||||||
|
偏航角 {{ Math.round(store.yawAngle) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 左侧:云台 -->
|
||||||
|
<div class="minimap-gimbal">
|
||||||
|
<div class="minimap-gimbal-title">云台</div>
|
||||||
|
<div class="minimap-gimbal-value">{{ Math.round(store.gimbalPitch) }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部居中:保存 -->
|
||||||
|
<button class="minimap-save-btn" type="button">
|
||||||
|
保存
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="minimap-zoom-slider" :class="{ 'disabled': store.lensMode === 'wide' }">
|
||||||
|
<div class="minimap-zoom-marks">
|
||||||
|
<span class="minimap-zoom-mark">56x</span>
|
||||||
|
<span class="minimap-zoom-mark">14x</span>
|
||||||
|
<span class="minimap-zoom-mark">7x</span>
|
||||||
|
<span class="minimap-zoom-mark">2x</span>
|
||||||
|
<span class="minimap-zoom-mark">1x</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="minimap-zoom-track"
|
||||||
|
:class="{ 'disabled': store.lensMode === 'wide' }"
|
||||||
|
@mousedown="onSliderMouseDown"
|
||||||
|
@mousemove="onSliderMouseMove"
|
||||||
|
@mouseup="onSliderMouseUp"
|
||||||
|
@mouseleave="onSliderMouseUp"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="minimap-zoom-handle"
|
||||||
|
:class="{ 'disabled': store.lensMode === 'wide' }"
|
||||||
|
:style="{ bottom: getSliderPosition() + '%' }"
|
||||||
|
@mousedown.stop="onHandleMouseDown"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="position: fixed; bottom: 20px; left: 200px;">
|
<div style="position: fixed; bottom: 20px; left: 200px;">
|
||||||
@@ -87,6 +290,49 @@ async function getTerrainPosition(cartographic: Cartographic) {
|
|||||||
<img id="compass" src="/compass.svg" draggable="false">
|
<img id="compass" src="/compass.svg" draggable="false">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="point-list-container">
|
||||||
|
<div class="point-list-header">航点列表 ({{ store.pointList.length }})</div>
|
||||||
|
<div class="point-list-content">
|
||||||
|
<div
|
||||||
|
v-for="point in store.pointList"
|
||||||
|
:key="point.index"
|
||||||
|
:class="['point-item', { 'selected': point.selected }]"
|
||||||
|
>
|
||||||
|
<div class="point-index">#{{ point.index + 1 }}</div>
|
||||||
|
<div class="point-info">
|
||||||
|
<div class="point-row">
|
||||||
|
<span class="label">经度:</span>
|
||||||
|
<span class="value">{{ point.longitude.toFixed(6) }}°</span>
|
||||||
|
</div>
|
||||||
|
<div class="point-row">
|
||||||
|
<span class="label">纬度:</span>
|
||||||
|
<span class="value">{{ point.latitude.toFixed(6) }}°</span>
|
||||||
|
</div>
|
||||||
|
<div class="point-row">
|
||||||
|
<span class="label">ASL:</span>
|
||||||
|
<span class="value">{{ point.asl.toFixed(1) }} m</span>
|
||||||
|
</div>
|
||||||
|
<div class="point-row">
|
||||||
|
<span class="label">HAE:</span>
|
||||||
|
<span class="value">{{ point.alt.toFixed(1) }} m</span>
|
||||||
|
</div>
|
||||||
|
<div class="point-row">
|
||||||
|
<span class="label">航向:</span>
|
||||||
|
<span class="value">{{ point.heading.toFixed(1) }}°</span>
|
||||||
|
</div>
|
||||||
|
<div class="point-row">
|
||||||
|
<span class="label">俯仰:</span>
|
||||||
|
<span class="value">{{ point.pitch.toFixed(1) }}°</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="store.pointList.length === 0" class="empty-message">
|
||||||
|
暂无航点,按空格键创建航点
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -101,6 +347,33 @@ async function getTerrainPosition(cartographic: Cartographic) {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.65);
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#minimap::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.22);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cesium minimap canvas fill */
|
||||||
|
#minimap :deep(.cesium-viewer) {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#minimap.expanded {
|
||||||
|
width: 600px;
|
||||||
|
height: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
._dji_compass_p0y8b_78 {
|
._dji_compass_p0y8b_78 {
|
||||||
@@ -108,9 +381,6 @@ async function getTerrainPosition(cartographic: Cartographic) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.zoom-frame {
|
.zoom-frame {
|
||||||
position: fixed;
|
|
||||||
right: 20px;
|
|
||||||
bottom: 20px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -118,6 +388,8 @@ async function getTerrainPosition(cartographic: Cartographic) {
|
|||||||
z-index: 999;
|
z-index: 999;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
width: 200px;
|
||||||
|
height: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-frame .corner {
|
.zoom-frame .corner {
|
||||||
@@ -152,10 +424,12 @@ async function getTerrainPosition(cartographic: Cartographic) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.zoom-frame .frame-title {
|
.zoom-frame .frame-title {
|
||||||
color: #00ee8b;
|
color: #fff;
|
||||||
bottom: -8px;
|
top: -10px;
|
||||||
right: 0;
|
left: 50%;
|
||||||
transform: translateY(100%);
|
transform: translate(-50%, -100%);
|
||||||
|
font-weight: 700;
|
||||||
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.frame-title {
|
.frame-title {
|
||||||
@@ -180,4 +454,354 @@ async function getTerrainPosition(cartographic: Cartographic) {
|
|||||||
transform: translate(-50%, -100%);
|
transform: translate(-50%, -100%);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.point-list-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
width: 300px;
|
||||||
|
max-height: calc(100vh - 40px);
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
border: 2px solid #00D690;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-list-header {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: rgba(0, 214, 144, 0.2);
|
||||||
|
border-bottom: 1px solid #00D690;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-list-content {
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
max-height: calc(100vh - 100px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-item {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-color: rgba(0, 214, 144, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-item.selected {
|
||||||
|
background: rgba(0, 214, 144, 0.2);
|
||||||
|
border-color: #00D690;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 214, 144, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-index {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #00D690;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-info {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-row .label {
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-row .value {
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部中央:镜头模式切换 */
|
||||||
|
/* 左上角:放大/缩小按钮(按截图) */
|
||||||
|
.minimap-expand-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-expand-btn:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.75);
|
||||||
|
border-color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HUD 覆盖层容器 */
|
||||||
|
.minimap-hud {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部居中:镜头切换(按截图两段按钮) */
|
||||||
|
.minimap-lens-controls {
|
||||||
|
position: absolute;
|
||||||
|
top: 14px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 4px;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-lens-btn {
|
||||||
|
padding: 10px 22px;
|
||||||
|
background: rgba(255, 255, 255, 0.10);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: rgba(255, 255, 255, 0.70);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-lens-btn:first-child {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-lens-btn:last-child {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-lens-btn.active {
|
||||||
|
background: #2F80ED;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-lens-btn:not(.active):hover {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 中上:偏航角 */
|
||||||
|
.minimap-yaw {
|
||||||
|
position: absolute;
|
||||||
|
top: 72px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧:云台 */
|
||||||
|
.minimap-gimbal {
|
||||||
|
position: absolute;
|
||||||
|
top: 120px;
|
||||||
|
left: 48px;
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-gimbal-title {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-gimbal-value {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部:保存按钮 */
|
||||||
|
.minimap-save-btn {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 18px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 160px;
|
||||||
|
height: 44px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #2F80ED;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-save-btn:hover {
|
||||||
|
filter: brightness(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Minimap内的变焦滑块(右侧竖条) */
|
||||||
|
.minimap-zoom-slider {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 16px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
z-index: 3;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-zoom-marks {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 180px;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-zoom-mark {
|
||||||
|
text-align: right;
|
||||||
|
min-width: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-zoom-track {
|
||||||
|
position: relative;
|
||||||
|
width: 6px;
|
||||||
|
height: 180px;
|
||||||
|
background: rgba(255, 255, 255, 0.35);
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-zoom-handle {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: grab;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
||||||
|
transition: box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-zoom-handle:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-zoom-slider.disabled .minimap-zoom-track,
|
||||||
|
.minimap-zoom-track.disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-zoom-handle.disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 小框放大按钮 */
|
||||||
|
.minimap-expand-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
z-index: 1001;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimap-expand-btn:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
border-color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 云台和偏航角数据显示 */
|
||||||
|
.gimbal-data-display {
|
||||||
|
position: fixed;
|
||||||
|
top: 80px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
z-index: 1000;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-line {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
color: white;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-label {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-value {
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
+30
-1
@@ -1,12 +1,41 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export interface WayPointInfo {
|
||||||
|
index: number;
|
||||||
|
asl: number;
|
||||||
|
alt: number;
|
||||||
|
fov: number;
|
||||||
|
selected: boolean;
|
||||||
|
longitude: number;
|
||||||
|
latitude: number;
|
||||||
|
heading: number;
|
||||||
|
pitch: number;
|
||||||
|
roll: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LensMode = 'wide' | 'zoom';
|
||||||
|
|
||||||
export const useStore = defineStore('main', {
|
export const useStore = defineStore('main', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
zoomFactor: 0
|
zoomFactor: 1,
|
||||||
|
lensMode: 'wide' as LensMode,
|
||||||
|
pointList: [] as WayPointInfo[],
|
||||||
|
gimbalPitch: 0, // 云台俯仰角(度)
|
||||||
|
yawAngle: 0 // 偏航角(度)
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
updateZoomFactor(zoomFactor: number) {
|
updateZoomFactor(zoomFactor: number) {
|
||||||
this.zoomFactor = zoomFactor;
|
this.zoomFactor = zoomFactor;
|
||||||
|
},
|
||||||
|
updatePointList(pointList: WayPointInfo[]) {
|
||||||
|
this.pointList = pointList;
|
||||||
|
},
|
||||||
|
setLensMode(mode: LensMode) {
|
||||||
|
this.lensMode = mode;
|
||||||
|
},
|
||||||
|
updateGimbalData(pitch: number, yaw: number) {
|
||||||
|
this.gimbalPitch = pitch;
|
||||||
|
this.yawAngle = yaw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
+121
-17
@@ -81,6 +81,17 @@ export class Wayline {
|
|||||||
this.setupDragEvent()
|
this.setupDragEvent()
|
||||||
this.setupMovementEvent()
|
this.setupMovementEvent()
|
||||||
this.setupSelectEvent()
|
this.setupSelectEvent()
|
||||||
|
this.syncPointListToStore()
|
||||||
|
|
||||||
|
// 初始化云台数据
|
||||||
|
const store = useStore()
|
||||||
|
const initialPitch = CesiumMath.toDegrees(this.minimapViewer.camera.pitch)
|
||||||
|
const initialHeading = this.minimapViewer.camera.heading
|
||||||
|
let initialYaw = CesiumMath.toDegrees(initialHeading)
|
||||||
|
if (initialYaw > 180) {
|
||||||
|
initialYaw = initialYaw - 360
|
||||||
|
}
|
||||||
|
store.updateGimbalData(initialPitch, initialYaw)
|
||||||
}
|
}
|
||||||
|
|
||||||
createFrustum(): void {
|
createFrustum(): void {
|
||||||
@@ -104,6 +115,7 @@ export class Wayline {
|
|||||||
|
|
||||||
this.selectedWaypoint = wayPoint
|
this.selectedWaypoint = wayPoint
|
||||||
wayPoint.select()
|
wayPoint.select()
|
||||||
|
this.syncPointListToStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSelectEvent(): void {
|
setupSelectEvent(): void {
|
||||||
@@ -115,6 +127,7 @@ export class Wayline {
|
|||||||
this.selectedWaypoint?.unselect()
|
this.selectedWaypoint?.unselect()
|
||||||
this.selectedWaypoint = (object.id as any).wayPoint
|
this.selectedWaypoint = (object.id as any).wayPoint
|
||||||
this.selectedWaypoint.select()
|
this.selectedWaypoint.select()
|
||||||
|
this.syncPointListToStore()
|
||||||
}
|
}
|
||||||
}, ScreenSpaceEventType.LEFT_CLICK)
|
}, ScreenSpaceEventType.LEFT_CLICK)
|
||||||
}
|
}
|
||||||
@@ -124,16 +137,25 @@ export class Wayline {
|
|||||||
|
|
||||||
let isDragging = false
|
let isDragging = false
|
||||||
let isLifting = false
|
let isLifting = false
|
||||||
|
let isAdjustingHeight = false
|
||||||
let draggingWayPoint: WayPoint | null = null
|
let draggingWayPoint: WayPoint | null = null
|
||||||
|
|
||||||
const scene = viewer.scene
|
const scene = viewer.scene
|
||||||
handler.setInputAction((click: ScreenSpaceEventHandler.PositionedEvent) => {
|
handler.setInputAction((click: ScreenSpaceEventHandler.PositionedEvent) => {
|
||||||
const object = scene.pick(click.position)
|
const object = scene.pick(click.position)
|
||||||
if (object && object.id && object.id.wayPoint && object.id.wayPoint.selected) {
|
if (object && object.id) {
|
||||||
isDragging = true
|
// 检查是否点击了高度调整按钮
|
||||||
draggingWayPoint = object.id.wayPoint
|
if ((object.id as any).heightAdjustButton) {
|
||||||
|
isAdjustingHeight = true
|
||||||
scene.screenSpaceCameraController.enableRotate = false
|
draggingWayPoint = (object.id as any).heightAdjustButton
|
||||||
|
scene.screenSpaceCameraController.enableRotate = false
|
||||||
|
}
|
||||||
|
// 检查是否点击了航点三角形(billboard)
|
||||||
|
else if ((object.id as any).wayPoint && (object.id as any).wayPoint.selected) {
|
||||||
|
isDragging = true
|
||||||
|
draggingWayPoint = (object.id as any).wayPoint
|
||||||
|
scene.screenSpaceCameraController.enableRotate = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, ScreenSpaceEventType.LEFT_DOWN)
|
}, ScreenSpaceEventType.LEFT_DOWN)
|
||||||
|
|
||||||
@@ -146,10 +168,13 @@ export class Wayline {
|
|||||||
}, ScreenSpaceEventType.LEFT_DOWN, KeyboardEventModifier.ALT)
|
}, ScreenSpaceEventType.LEFT_DOWN, KeyboardEventModifier.ALT)
|
||||||
|
|
||||||
handler.setInputAction(() => {
|
handler.setInputAction(() => {
|
||||||
if (isDragging) {
|
if (isDragging || isAdjustingHeight) {
|
||||||
isDragging = false
|
isDragging = false
|
||||||
|
isAdjustingHeight = false
|
||||||
|
|
||||||
this.minimapViewer.camera.position = draggingWayPoint!.position.clone()
|
if (draggingWayPoint) {
|
||||||
|
this.minimapViewer.camera.position = draggingWayPoint.position.clone()
|
||||||
|
}
|
||||||
draggingWayPoint = null
|
draggingWayPoint = null
|
||||||
scene.screenSpaceCameraController.enableRotate = true
|
scene.screenSpaceCameraController.enableRotate = true
|
||||||
}
|
}
|
||||||
@@ -162,13 +187,32 @@ export class Wayline {
|
|||||||
|
|
||||||
handler.setInputAction((movement: ScreenSpaceEventHandler.MotionEvent) => {
|
handler.setInputAction((movement: ScreenSpaceEventHandler.MotionEvent) => {
|
||||||
const object = scene.pick(movement.endPosition)
|
const object = scene.pick(movement.endPosition)
|
||||||
if (object && object.id && (object.id as any).wayPoint && (object.id as any).wayPoint.selectd) {
|
if (object && object.id) {
|
||||||
scene.canvas.style.cursor = 'move'
|
if ((object.id as any).heightAdjustButton) {
|
||||||
|
scene.canvas.style.cursor = 'ns-resize' // 上下调整光标
|
||||||
|
} else if ((object.id as any).wayPoint && (object.id as any).wayPoint.selected) {
|
||||||
|
scene.canvas.style.cursor = 'move'
|
||||||
|
} else {
|
||||||
|
scene.canvas.style.cursor = 'default'
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
scene.canvas.style.cursor = 'default'
|
scene.canvas.style.cursor = 'default'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDragging && draggingWayPoint) {
|
// 高度调整:点击高度按钮后拖拽调整高度
|
||||||
|
if (isAdjustingHeight && draggingWayPoint) {
|
||||||
|
const deltaY = movement.endPosition.y - movement.startPosition.y
|
||||||
|
const deltaHeight = deltaY * getVerticalMetersPerPixel(viewer, draggingWayPoint.position)
|
||||||
|
|
||||||
|
const lngLat = Cartographic.fromCartesian(draggingWayPoint.position)
|
||||||
|
lngLat.height -= deltaHeight
|
||||||
|
|
||||||
|
draggingWayPoint.asl -= deltaHeight
|
||||||
|
draggingWayPoint.alt -= deltaHeight
|
||||||
|
draggingWayPoint.updatePosition(Cartesian3.fromRadians(lngLat.longitude, lngLat.latitude, lngLat.height))
|
||||||
|
}
|
||||||
|
// 左右平移:点击三角形后拖拽进行水平移动
|
||||||
|
else if (isDragging && draggingWayPoint) {
|
||||||
const endPosition = scene.globe.pick(scene.camera.getPickRay(movement.endPosition), scene)
|
const endPosition = scene.globe.pick(scene.camera.getPickRay(movement.endPosition), scene)
|
||||||
const startPosition = scene.globe.pick(scene.camera.getPickRay(movement.startPosition), scene)
|
const startPosition = scene.globe.pick(scene.camera.getPickRay(movement.startPosition), scene)
|
||||||
|
|
||||||
@@ -294,6 +338,11 @@ export class Wayline {
|
|||||||
heading = heading % (2 * Math.PI)
|
heading = heading % (2 * Math.PI)
|
||||||
if (heading < 0) heading += 2 * Math.PI
|
if (heading < 0) heading += 2 * Math.PI
|
||||||
|
|
||||||
|
// 限制pitch在-90度(正朝下)到+45度(朝上)之间
|
||||||
|
const minPitch = -Math.PI / 2 // -90度
|
||||||
|
const maxPitch = Math.PI / 4 // +45度
|
||||||
|
pitch = CesiumMath.clamp(pitch, minPitch, maxPitch)
|
||||||
|
|
||||||
camera.setView({
|
camera.setView({
|
||||||
destination: position,
|
destination: position,
|
||||||
orientation: {
|
orientation: {
|
||||||
@@ -303,6 +352,15 @@ export class Wayline {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 更新云台数据和偏航角到store
|
||||||
|
const store = useStore()
|
||||||
|
const pitchDegrees = CesiumMath.toDegrees(pitch)
|
||||||
|
let yawDegrees = CesiumMath.toDegrees(heading)
|
||||||
|
if (yawDegrees > 180) {
|
||||||
|
yawDegrees = yawDegrees - 360
|
||||||
|
}
|
||||||
|
store.updateGimbalData(pitchDegrees, yawDegrees)
|
||||||
|
|
||||||
requestAnimationFrame(updateCameraControl)
|
requestAnimationFrame(updateCameraControl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +412,11 @@ export class Wayline {
|
|||||||
heading = heading % (2 * Math.PI)
|
heading = heading % (2 * Math.PI)
|
||||||
if (heading < 0) heading += 2 * Math.PI
|
if (heading < 0) heading += 2 * Math.PI
|
||||||
|
|
||||||
pitch = CesiumMath.clamp(pitch, -Math.PI / 2, Math.PI / 2)
|
// 限制pitch在-90度(正朝下)到+45度(朝上)之间,水平是0
|
||||||
|
// Cesium中pitch: -90度 = -Math.PI/2, +45度 = Math.PI/4, 0度 = 0
|
||||||
|
const minPitch = -Math.PI / 2 // -90度
|
||||||
|
const maxPitch = Math.PI / 4 // +45度
|
||||||
|
pitch = CesiumMath.clamp(pitch, minPitch, maxPitch)
|
||||||
|
|
||||||
const position = Cartesian3.clone(minimapViewer.camera.position)
|
const position = Cartesian3.clone(minimapViewer.camera.position)
|
||||||
minimapViewer.camera.setView({
|
minimapViewer.camera.setView({
|
||||||
@@ -365,6 +427,14 @@ export class Wayline {
|
|||||||
roll: minimapViewer.camera.roll
|
roll: minimapViewer.camera.roll
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 更新云台数据和偏航角到store
|
||||||
|
const pitchDegrees = CesiumMath.toDegrees(pitch)
|
||||||
|
let yawDegrees = CesiumMath.toDegrees(heading)
|
||||||
|
if (yawDegrees > 180) {
|
||||||
|
yawDegrees = yawDegrees - 360
|
||||||
|
}
|
||||||
|
store.updateGimbalData(pitchDegrees, yawDegrees)
|
||||||
}, ScreenSpaceEventType.MOUSE_MOVE)
|
}, ScreenSpaceEventType.MOUSE_MOVE)
|
||||||
|
|
||||||
handler.setInputAction(() => {
|
handler.setInputAction(() => {
|
||||||
@@ -377,20 +447,34 @@ export class Wayline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let currentFov = minimapViewer.camera.frustum.fov
|
let currentFov = minimapViewer.camera.frustum.fov
|
||||||
let zoomFactor = 2
|
let zoomFactor = store.zoomFactor || 1
|
||||||
const minZoomFactor = 2
|
|
||||||
const maxZoomFactor = 200
|
|
||||||
|
|
||||||
handler.setInputAction(function (movement: any) {
|
handler.setInputAction(function (movement: any) {
|
||||||
const delta = movement
|
const delta = movement
|
||||||
|
const currentMode = store.lensMode
|
||||||
|
|
||||||
|
if (currentMode === 'wide') {
|
||||||
|
// 广角模式只能1x
|
||||||
|
zoomFactor = 1
|
||||||
|
} else {
|
||||||
|
// 变焦模式可以1-56x
|
||||||
|
const step = delta > 0 ? 1 : -1
|
||||||
|
zoomFactor += step
|
||||||
|
zoomFactor = CesiumMath.clamp(zoomFactor, 1, 56)
|
||||||
|
}
|
||||||
|
|
||||||
zoomFactor += delta > 0 ? 2 : -2
|
|
||||||
zoomFactor = CesiumMath.clamp(zoomFactor, minZoomFactor, maxZoomFactor)
|
|
||||||
currentFov = fovFromZoom(zoomFactor)
|
currentFov = fovFromZoom(zoomFactor)
|
||||||
|
|
||||||
store.updateZoomFactor(zoomFactor)
|
store.updateZoomFactor(zoomFactor)
|
||||||
minimapViewer.camera.frustum.fov = currentFov
|
minimapViewer.camera.frustum.fov = currentFov
|
||||||
}, ScreenSpaceEventType.WHEEL)
|
}, ScreenSpaceEventType.WHEEL)
|
||||||
|
|
||||||
|
// 初始化时根据模式设置变焦
|
||||||
|
if (store.lensMode === 'wide') {
|
||||||
|
zoomFactor = 1
|
||||||
|
currentFov = fovFromZoom(zoomFactor)
|
||||||
|
store.updateZoomFactor(zoomFactor)
|
||||||
|
minimapViewer.camera.frustum.fov = currentFov
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createWayline(): void {
|
createWayline(): void {
|
||||||
@@ -491,6 +575,26 @@ export class Wayline {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncPointListToStore(): void {
|
||||||
|
const store = useStore()
|
||||||
|
const pointListInfo = this.pointList.map(point => {
|
||||||
|
const cartographic = Cartographic.fromCartesian(point.position)
|
||||||
|
return {
|
||||||
|
index: point.index,
|
||||||
|
asl: point.asl,
|
||||||
|
alt: point.alt,
|
||||||
|
fov: point.fov,
|
||||||
|
selected: point.selected,
|
||||||
|
longitude: CesiumMath.toDegrees(cartographic.longitude),
|
||||||
|
latitude: CesiumMath.toDegrees(cartographic.latitude),
|
||||||
|
heading: CesiumMath.toDegrees(point.orientation.heading),
|
||||||
|
pitch: CesiumMath.toDegrees(point.orientation.pitch),
|
||||||
|
roll: CesiumMath.toDegrees(point.orientation.roll)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
store.updatePointList(pointListInfo)
|
||||||
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
viewer.entities.remove(this.wayLineEntity)
|
viewer.entities.remove(this.wayLineEntity)
|
||||||
viewer.entities.remove(this.takeOffPointEntity)
|
viewer.entities.remove(this.takeOffPointEntity)
|
||||||
|
|||||||
+76
-13
@@ -7,6 +7,7 @@ import {
|
|||||||
HeightReference,
|
HeightReference,
|
||||||
LabelStyle,
|
LabelStyle,
|
||||||
VerticalOrigin,
|
VerticalOrigin,
|
||||||
|
HorizontalOrigin,
|
||||||
PerspectiveFrustum,
|
PerspectiveFrustum,
|
||||||
DebugCameraPrimitive,
|
DebugCameraPrimitive,
|
||||||
Entity,
|
Entity,
|
||||||
@@ -28,6 +29,7 @@ export class WayPoint {
|
|||||||
selected!: boolean
|
selected!: boolean
|
||||||
heightLabel!: Entity
|
heightLabel!: Entity
|
||||||
wayPointEntity!: Entity
|
wayPointEntity!: Entity
|
||||||
|
heightAdjustButton!: Entity
|
||||||
orientation: { heading: number; pitch: number; roll: number }
|
orientation: { heading: number; pitch: number; roll: number }
|
||||||
debugCameraPrimitive: any
|
debugCameraPrimitive: any
|
||||||
distanceLabel1: any
|
distanceLabel1: any
|
||||||
@@ -91,11 +93,20 @@ export class WayPoint {
|
|||||||
const pointList = this.wayline.pointList
|
const pointList = this.wayline.pointList
|
||||||
this.position = position
|
this.position = position
|
||||||
this.wayPointEntity.position = position
|
this.wayPointEntity.position = position
|
||||||
this.heightLabel.position = position
|
if (this.heightLabel) {
|
||||||
this.debugCameraPrimitive._camera.position = position
|
this.heightLabel.position = position
|
||||||
|
}
|
||||||
|
if (this.heightAdjustButton) {
|
||||||
|
this.heightAdjustButton.position = position
|
||||||
|
}
|
||||||
|
if (this.debugCameraPrimitive) {
|
||||||
|
this.debugCameraPrimitive._camera.position = position
|
||||||
|
}
|
||||||
|
|
||||||
const text = `ASL: ${this.asl.toFixed(1)} m\nHAE: ${this.alt.toFixed(1)} m`
|
const text = `ASL: ${this.asl.toFixed(1)} m\nHAE: ${this.alt.toFixed(1)} m`
|
||||||
this.heightLabel.label.text = text
|
if (this.heightLabel) {
|
||||||
|
this.heightLabel.label.text = text
|
||||||
|
}
|
||||||
|
|
||||||
const index = this.index
|
const index = this.index
|
||||||
const prev = pointList[index - 1]
|
const prev = pointList[index - 1]
|
||||||
@@ -107,8 +118,10 @@ export class WayPoint {
|
|||||||
position
|
position
|
||||||
)
|
)
|
||||||
const midpoint = Cartesian3.lerp(prev.position, position, 0.5, new Cartesian3())
|
const midpoint = Cartesian3.lerp(prev.position, position, 0.5, new Cartesian3())
|
||||||
this.distanceLabel1.position = midpoint
|
if (this.distanceLabel1) {
|
||||||
this.distanceLabel1.label.text = distance.toFixed(1) + 'm'
|
this.distanceLabel1.position = midpoint
|
||||||
|
this.distanceLabel1.label.text = distance.toFixed(1) + 'm'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next) {
|
if (next) {
|
||||||
@@ -117,9 +130,13 @@ export class WayPoint {
|
|||||||
next.position
|
next.position
|
||||||
)
|
)
|
||||||
const midpoint = Cartesian3.lerp(position, next.position, 0.5, new Cartesian3())
|
const midpoint = Cartesian3.lerp(position, next.position, 0.5, new Cartesian3())
|
||||||
this.distanceLabel2.position = midpoint
|
if (this.distanceLabel2) {
|
||||||
this.distanceLabel2.label.text = distance.toFixed(1) + 'm'
|
this.distanceLabel2.position = midpoint
|
||||||
|
this.distanceLabel2.label.text = distance.toFixed(1) + 'm'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.wayline.syncPointListToStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
select(): void {
|
select(): void {
|
||||||
@@ -128,6 +145,7 @@ export class WayPoint {
|
|||||||
|
|
||||||
this.showDistanceLabels()
|
this.showDistanceLabels()
|
||||||
this.showHeightLabel()
|
this.showHeightLabel()
|
||||||
|
this.showHeightAdjustButton()
|
||||||
|
|
||||||
const camera = new Camera(viewer.scene)
|
const camera = new Camera(viewer.scene)
|
||||||
camera.frustum = new PerspectiveFrustum({
|
camera.frustum = new PerspectiveFrustum({
|
||||||
@@ -155,17 +173,20 @@ export class WayPoint {
|
|||||||
})
|
})
|
||||||
|
|
||||||
viewer.scene.primitives.add(this.debugCameraPrimitive)
|
viewer.scene.primitives.add(this.debugCameraPrimitive)
|
||||||
|
this.wayline.syncPointListToStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
unselect(): void {
|
unselect(): void {
|
||||||
this.selected = false
|
this.selected = false
|
||||||
this.wayPointEntity.billboard.image = generateNumberSvgDataUrl(this.index + 1)
|
this.wayPointEntity.billboard.image = generateNumberSvgDataUrl(this.index + 1)
|
||||||
|
|
||||||
viewer.entities.remove(this.distanceLabel0)
|
if (this.distanceLabel0) viewer.entities.remove(this.distanceLabel0)
|
||||||
viewer.entities.remove(this.distanceLabel1)
|
if (this.distanceLabel1) viewer.entities.remove(this.distanceLabel1)
|
||||||
viewer.entities.remove(this.distanceLabel2)
|
if (this.distanceLabel2) viewer.entities.remove(this.distanceLabel2)
|
||||||
viewer.entities.remove(this.heightLabel)
|
if (this.heightLabel) viewer.entities.remove(this.heightLabel)
|
||||||
viewer.scene.primitives.remove(this.debugCameraPrimitive)
|
if (this.heightAdjustButton) viewer.entities.remove(this.heightAdjustButton)
|
||||||
|
if (this.debugCameraPrimitive) viewer.scene.primitives.remove(this.debugCameraPrimitive)
|
||||||
|
this.wayline.syncPointListToStore()
|
||||||
}
|
}
|
||||||
distanceLabel0(distanceLabel0: any) {
|
distanceLabel0(distanceLabel0: any) {
|
||||||
throw new Error('Method not implemented.')
|
throw new Error('Method not implemented.')
|
||||||
@@ -175,13 +196,20 @@ export class WayPoint {
|
|||||||
const pointList = this.wayline.pointList
|
const pointList = this.wayline.pointList
|
||||||
|
|
||||||
viewer.entities.remove(this.wayPointEntity)
|
viewer.entities.remove(this.wayPointEntity)
|
||||||
viewer.scene.primitives.remove(this.debugCameraPrimitive)
|
if (this.heightAdjustButton) {
|
||||||
|
viewer.entities.remove(this.heightAdjustButton)
|
||||||
|
}
|
||||||
|
if (this.debugCameraPrimitive) {
|
||||||
|
viewer.scene.primitives.remove(this.debugCameraPrimitive)
|
||||||
|
}
|
||||||
|
|
||||||
pointList.splice(this.index, 1)
|
pointList.splice(this.index, 1)
|
||||||
for (let i = this.index; i < pointList.length; i++) {
|
for (let i = this.index; i < pointList.length; i++) {
|
||||||
pointList[i].wayPointEntity.billboard.image = generateNumberSvgDataUrl(i + 1)
|
pointList[i].wayPointEntity.billboard.image = generateNumberSvgDataUrl(i + 1)
|
||||||
pointList[i].index = i
|
pointList[i].index = i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.wayline.syncPointListToStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
private showHeightLabel(): void {
|
private showHeightLabel(): void {
|
||||||
@@ -206,6 +234,24 @@ export class WayPoint {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private showHeightAdjustButton(): void {
|
||||||
|
const { position } = this
|
||||||
|
const buttonUrl = generateHeightAdjustButtonSvgDataUrl()
|
||||||
|
|
||||||
|
this.heightAdjustButton = viewer.entities.add({
|
||||||
|
position,
|
||||||
|
heightAdjustButton: this,
|
||||||
|
billboard: {
|
||||||
|
image: buttonUrl,
|
||||||
|
scale: 0.8,
|
||||||
|
verticalOrigin: VerticalOrigin.CENTER,
|
||||||
|
horizontalOrigin: HorizontalOrigin.LEFT,
|
||||||
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||||
|
pixelOffset: new Cartesian2(25, 0) // 在航点右侧显示
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private showDistanceLabels(): void {
|
private showDistanceLabels(): void {
|
||||||
const index = this.index
|
const index = this.index
|
||||||
const pointList = this.wayline.pointList
|
const pointList = this.wayline.pointList
|
||||||
@@ -310,3 +356,20 @@ function generateHighlightedNumberSvgDataUrl(number: number): string {
|
|||||||
|
|
||||||
return `data:image/svg+xml,${encoded}`
|
return `data:image/svg+xml,${encoded}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateHeightAdjustButtonSvgDataUrl(): string {
|
||||||
|
const svg = `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 40" width="24" height="40">
|
||||||
|
<rect width="24" height="40" rx="4" fill="#00D690" fill-opacity="0.9" stroke="#FFF" stroke-width="1.5"/>
|
||||||
|
<path d="M12 8 L8 12 L12 16 M12 8 L16 12 L12 16" stroke="#FFF" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<line x1="12" y1="20" x2="12" y2="32" stroke="#FFF" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M12 32 L8 28 L12 24 M12 32 L16 28 L12 24" stroke="#FFF" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
`
|
||||||
|
|
||||||
|
const encoded = encodeURIComponent(svg)
|
||||||
|
.replace(/'/g, '%27')
|
||||||
|
.replace(/"/g, '%22')
|
||||||
|
|
||||||
|
return `data:image/svg+xml,${encoded}`
|
||||||
|
}
|
||||||
Reference in new issue
Block a user