创建半径线圆弧 并且在半径上带上距离文本

This commit is contained in:
huangchenhao 2025-11-28 02:54:08 +08:00
parent 055b83404b
commit 33665263fb
7 changed files with 144 additions and 96 deletions

View File

@ -62,7 +62,7 @@
<span class="force-dispatch__stat-label">应急物资</span>
<span class="force-dispatch__stat-value">
<!-- {{ dispatchSuggestion.supplies }} -->
100
190
<span class="force-dispatch__stat-unit"></span>
</span>
</div>

View File

@ -6,46 +6,43 @@
</template>
<script setup>
import { computed } from 'vue'
import { computed } from "vue";
const props = defineProps({
text: {
type: String,
required: true
required: true,
},
icon: {
type: String,
default: ''
default: "",
},
type: {
type: String,
default: 'primary',
validator: (value) => ['primary', 'secondary', 'link'].includes(value)
default: "primary",
validator: (value) => ["primary", "secondary", "link"].includes(value),
},
size: {
type: String,
default: 'medium',
validator: (value) => ['small', 'medium', 'large'].includes(value)
}
})
default: "medium",
validator: (value) => ["small", "medium", "large"].includes(value),
},
});
const emit = defineEmits(['click'])
const emit = defineEmits(["click"]);
const buttonClass = computed(() => {
return [
`action-button--${props.type}`,
`action-button--${props.size}`
]
})
return [`action-button--${props.type}`, `action-button--${props.size}`];
});
const handleClick = () => {
emit('click')
}
emit("click");
};
</script>
<style scoped lang="scss">
@use '@/styles/mixins.scss' as *;
@use '../../assets/styles/common.scss' as *;
@use "@/styles/mixins.scss" as *;
@use "../../assets/styles/common.scss" as *;
.action-button {
@extend .btn-custom-bg; //
@ -58,13 +55,6 @@ const handleClick = () => {
font-weight: 500;
white-space: nowrap;
&:hover {
background-image: none;
background-color: #4FECFF;
color: #062B5B;
border-radius: 5px;
}
&__icon {
width: vw(16);
height: vh(16);
@ -76,13 +66,19 @@ const handleClick = () => {
//
&--primary {
background-image: url('../../assets/images/文本按钮bg.png');
background-image: url("../../assets/images/文本按钮bg.png");
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center;
color: var(--text-white);
}
&--inModal{
background: #4FECFF;
color: #062B5B;
border-radius: 5px;
}
&--secondary {
background: var(--bg-panel);
color: var(--text-white);

View File

@ -371,8 +371,25 @@ export function useDualMapCompare() {
const rightCamera = rightViewerInstance.camera
try {
// 获取右侧相机位置
const rightPosition = rightCamera.position.clone()
// 计算右侧相机的右方向向量
const rightDirection = Cesium.Cartesian3.normalize(
rightCamera.right,
new Cesium.Cartesian3()
)
// 向左偏移90米
const offsetPosition = Cesium.Cartesian3.add(
rightPosition,
Cesium.Cartesian3.multiplyByScalar(rightDirection, -90, new Cesium.Cartesian3()),
new Cesium.Cartesian3()
)
// 设置左侧相机位置向左偏移90米
leftViewer.value.camera.setView({
destination: rightCamera.position.clone(),
destination: offsetPosition,
orientation: {
heading: rightCamera.heading,
pitch: rightCamera.pitch,
@ -467,9 +484,25 @@ export function useDualMapCompare() {
// 强制同步相机位置(使用多种方式确保成功)
try {
// 获取右侧相机位置
const rightPosition = rightCamera.position.clone()
// 计算右侧相机的右方向向量
const rightDirection = Cesium.Cartesian3.normalize(
rightCamera.right,
new Cesium.Cartesian3()
)
// 向左偏移90米
const offsetPosition = Cesium.Cartesian3.add(
rightPosition,
Cesium.Cartesian3.multiplyByScalar(rightDirection, -90, new Cesium.Cartesian3()),
new Cesium.Cartesian3()
)
// 方式1: 使用 setView (推荐)
leftViewerInstance.camera.setView({
destination: rightCamera.position.clone(),
destination: offsetPosition,
orientation: {
heading: rightCamera.heading,
pitch: rightCamera.pitch,
@ -478,12 +511,11 @@ export function useDualMapCompare() {
})
// 方式2: 直接设置相机属性 (备份方案)
leftViewerInstance.camera.position = rightCamera.position.clone()
leftViewerInstance.camera.position = offsetPosition.clone()
leftViewerInstance.camera.direction = rightCamera.direction.clone()
leftViewerInstance.camera.up = rightCamera.up.clone()
leftViewerInstance.camera.right = rightCamera.right.clone()
console.log('[useDualMapCompare] 初始相机同步成功')
console.log('[useDualMapCompare] 初始相机同步成功向左偏移90米')
// 验证同步结果
const leftCamera = leftViewerInstance.camera

View File

@ -2,29 +2,11 @@ import * as Cesium from 'cesium'
import { ref } from 'vue'
import { DISASTER_CENTER, RANGE_CIRCLE_STYLE } from '../constants'
/**
* 范围圈管理
*
* 职责
* 1. 创建和更新范围圈实体
* 2. 管理范围圈的样式和可见性
* 3. 清理范围圈资源
*
* @returns {Object} 范围圈管理 API
*/
export function useRangeCircle() {
// 范围圈实体引用
const rangeCircleEntity = ref(null)
const radiusLineEntity = ref(null) // 单独存储半径线实体
const labelEntity = ref(null) // 单独存储标签实体
/**
* 创建或更新范围圈
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
* @param {number} radiusKm - 半径公里
* @param {Object} options - 可选配置
* @param {number} options.centerLon - 中心点经度默认使用 DISASTER_CENTER
* @param {number} options.centerLat - 中心点纬度默认使用 DISASTER_CENTER
* @param {Object} options.style - 样式配置默认使用 RANGE_CIRCLE_STYLE
*/
const createOrUpdateRangeCircle = (
viewer,
radiusKm,
@ -43,30 +25,72 @@ export function useRangeCircle() {
const radiusMeters = radiusKm * 1000
// 如果已存在范围圈,先移除
// 如果已存在范围圈,先检查半径和中心点是否相同
if (rangeCircleEntity.value) {
viewer.entities.remove(rangeCircleEntity.value)
rangeCircleEntity.value = null
// 获取现有范围圈的半径(从 semiMajorAxis 获取,单位是米,转换为公里)
const existingRadiusKm = rangeCircleEntity.value.ellipse.semiMajorAxis / 1000
// 检查中心点是否相同
const existingPosition = rangeCircleEntity.value.position.getValue()
const newPosition = Cesium.Cartesian3.fromDegrees(centerLon, centerLat, 0)
const centerChanged = !Cesium.Cartesian3.equals(existingPosition, newPosition)
// 如果半径和中心点都相同,则不需要更新
if (existingRadiusKm === radiusKm && !centerChanged) {
console.log(`[useRangeCircle] 范围圈半径相同 (${radiusKm}km),无需更新`)
return
}
// 移除现有实体
// clearRangeCircle(viewer)
}
// 创建新的范围圈
// 创建中心点坐标
const centerCartesian = Cesium.Cartesian3.fromDegrees(centerLon, centerLat)
// 计算半径线终点(正东方向)- 使用您之前正确的计算方法
const ellipsoid = Cesium.Ellipsoid.WGS84
const surfaceNormal = ellipsoid.geodeticSurfaceNormal(centerCartesian, new Cesium.Cartesian3())
const tangent = Cesium.Cartesian3.cross(surfaceNormal, Cesium.Cartesian3.UNIT_Z, new Cesium.Cartesian3())
Cesium.Cartesian3.normalize(tangent, tangent)
Cesium.Cartesian3.multiplyByScalar(tangent, radiusMeters, tangent)
const radiusEndCartesian = Cesium.Cartesian3.add(centerCartesian, tangent, new Cesium.Cartesian3())
// 计算半径线中点
const midpointCartesian = new Cesium.Cartesian3()
Cesium.Cartesian3.lerp(centerCartesian, radiusEndCartesian, 0.5, midpointCartesian)
// 创建范围圈实体
rangeCircleEntity.value = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(centerLon, centerLat, 0),
position: centerCartesian,
ellipse: {
semiMinorAxis: radiusMeters,
semiMajorAxis: radiusMeters,
height: 0,
material: Cesium.Color.fromCssColorString(style.fillColor).withAlpha(style.fillAlpha),
outline: true,
outlineColor: Cesium.Color.fromCssColorString(style.outlineColor).withAlpha(
style.outlineAlpha
),
outlineColor: Cesium.Color.fromCssColorString(style.outlineColor).withAlpha(style.outlineAlpha),
outlineWidth: style.outlineWidth,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
},
allowPicking: false
})
// 创建半径线实体
radiusLineEntity.value = viewer.entities.add({
polyline: {
positions: [centerCartesian, radiusEndCartesian],
width: 2,
material: Cesium.Color.fromCssColorString(style.outlineColor).withAlpha(0.8),
},
allowPicking: false
})
// 创建标签实体(单独创建,位置设为半径线中点)
labelEntity.value = viewer.entities.add({
position: midpointCartesian,
label: {
text: `${radiusKm.toFixed(1) - 20} km`, // 实际是30 / 50 km 临时修改减少20km 后续调整
font: 'bold 24px sans-serif',
text: `${radiusKm - 20} km`,
font: 'bold 16px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
@ -75,47 +99,44 @@ export function useRangeCircle() {
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
pixelOffset: new Cesium.Cartesian2(0, 0),
backgroundColor: Cesium.Color.fromCssColorString('#333').withAlpha(0.7),
backgroundPadding: new Cesium.Cartesian2(18, 12),
showBackground: true
}
backgroundPadding: new Cesium.Cartesian2(8, 6),
showBackground: true,
},
allowPicking: false
})
// 禁用范围圈的鼠标交互,让点击可以穿透到下面的标记点
if (rangeCircleEntity.value) {
rangeCircleEntity.value.allowPicking = false
}
console.log(`[useRangeCircle] 已创建/更新范围圈: ${radiusKm}km`)
}
/**
* 显示范围圈
*/
const showRangeCircle = () => {
if (rangeCircleEntity.value) {
rangeCircleEntity.value.show = true
}
if (rangeCircleEntity.value) rangeCircleEntity.value.show = true
if (radiusLineEntity.value) radiusLineEntity.value.show = true
if (labelEntity.value) labelEntity.value.show = true
}
/**
* 隐藏范围圈
*/
const hideRangeCircle = () => {
if (rangeCircleEntity.value) {
rangeCircleEntity.value.show = false
}
if (rangeCircleEntity.value) rangeCircleEntity.value.show = false
if (radiusLineEntity.value) radiusLineEntity.value.show = false
if (labelEntity.value) labelEntity.value.show = false
}
/**
* 清除范围圈
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
*/
const clearRangeCircle = (viewer) => {
if (rangeCircleEntity.value && viewer) {
if (!viewer) return
if (rangeCircleEntity.value) {
viewer.entities.remove(rangeCircleEntity.value)
rangeCircleEntity.value = null
console.log('[useRangeCircle] 范围圈已清除')
}
if (radiusLineEntity.value) {
viewer.entities.remove(radiusLineEntity.value)
radiusLineEntity.value = null
}
if (labelEntity.value) {
viewer.entities.remove(labelEntity.value)
labelEntity.value = null
}
console.log('[useRangeCircle] 范围圈已清除')
}
return {

View File

@ -27,7 +27,7 @@ export const DEFAULT_CAMERA_CARTESIAN = {
* 相机初始对准的地理位置
*/
export const DEFAULT_CAMERA_POSITION = {
lon: 108.011134,
lon: 108.011800,
lat: 30.175697,
}
@ -37,7 +37,7 @@ export const DEFAULT_CAMERA_POSITION = {
*/
export const DEFAULT_CAMERA_VIEW = {
height: 274,
heading: 0,
heading: -20,
pitch: -45,
roll: 0,
}

View File

@ -48,7 +48,7 @@ export const VIDEO_MONITORS = [
type: VIDEO_TYPES.VEHICLE_EXTERNAL,
title: '设备操作视角',
videoSrc:
'http://222.212.85.86:9000/300bdf2b-a150-406e-be63-d28bd29b409f/demo/ylzg/指挥车外部视角.mp4',
'http://222.212.85.86:9000/300bdf2b-a150-406e-be63-d28bd29b409f/demo/ylzg/设备视角.mp4',
dateRange: '2025/9/1-2025/12/1',
hasAudio: true,
hasMegaphone: true,
@ -60,7 +60,7 @@ export const VIDEO_MONITORS = [
type: VIDEO_TYPES.VEHICLE_MEETING,
title: '指挥会议室视角',
videoSrc:
'http://222.212.85.86:9000/300bdf2b-a150-406e-be63-d28bd29b409f/demo/ylzg/指挥会议视角.mp4',
'http://222.212.85.86:9000/300bdf2b-a150-406e-be63-d28bd29b409f/demo/ylzg/指挥会议视角.mp4',
dateRange: '2025/9/1-2025/12/1',
hasAudio: true,
hasMegaphone: true,

View File

@ -207,7 +207,7 @@
<template #footer>
<ActionButton
text="响应调度"
type="primary"
type="inModal"
size="medium"
@click="handleModalStartDispatch"
/>
@ -295,7 +295,6 @@ import otherEmergencyIcon from "./assets/images/其他应急点.png";
import mediaIcon from "./assets/images/media.png";
import collapseLeftArrow from "./assets/images/折叠面板左箭头.png";
import collapseRightArrow from "./assets/images/折叠面板右箭头.png";
// ====================
//
// ====================