Browse Source

威力区,经纬度改变

lbj
sd 2 months ago
parent
commit
497e2fb7d8
  1. 2
      ruoyi-ui/src/views/cesiumMap/ContextMenu.vue
  2. 54
      ruoyi-ui/src/views/cesiumMap/LocateDialog.vue
  3. 128
      ruoyi-ui/src/views/cesiumMap/index.vue
  4. 14
      ruoyi-ui/src/views/childRoom/TopHeader.vue
  5. 26
      ruoyi-ui/src/views/childRoom/index.vue
  6. 32
      ruoyi-ui/src/views/dialogs/PowerZoneDialog.vue
  7. 153
      ruoyi-ui/src/views/dialogs/RadiusDialog.vue
  8. 54
      ruoyi-ui/src/views/system/waypoints/index.vue

2
ruoyi-ui/src/views/cesiumMap/ContextMenu.vue

@ -106,7 +106,7 @@
</div>
<!-- 多边形特有选项 -->
<div class="menu-section" v-if="entityData.type === 'polygon' || entityData.type === 'rectangle' || entityData.type === 'circle' || entityData.type === 'sector'">
<div class="menu-section" v-if="entityData.type === 'polygon' || entityData.type === 'rectangle' || entityData.type === 'circle' || entityData.type === 'sector' || entityData.type === 'powerZone'">
<div class="menu-title">填充属性</div>
<div class="menu-item" @click="toggleColorPicker('color')">
<span class="menu-icon">🎨</span>

54
ruoyi-ui/src/views/cesiumMap/LocateDialog.vue

@ -61,7 +61,7 @@
<el-option
v-for="item in waypointList"
:key="item.id"
:label="`${item.name} (${item.lng}, ${item.lat})`"
:label="`${item.name} (${degreesToDMS(item.lng)}, ${degreesToDMS(item.lat)})`"
:value="item.id"
/>
</el-select>
@ -70,9 +70,7 @@
<el-form-item label="经度:">
<el-input
v-model="formData.lng"
type="number"
placeholder="例如 116.40"
step="0.000001"
placeholder="例如 116°23'48.64""
clearable
/>
</el-form-item>
@ -80,9 +78,7 @@
<el-form-item label="纬度:">
<el-input
v-model="formData.lat"
type="number"
placeholder="例如 39.90"
step="0.000001"
placeholder="例如 39°54'33.48""
clearable
/>
</el-form-item>
@ -120,8 +116,8 @@ export default {
scenarioId: null,
routeId: null,
waypointId: null,
lng: '116.3974',
lat: '39.9093'
lng: '116°23\'48.64"',
lat: '39°54\'33.48"'
},
scenarioList: [],
routeList: [],
@ -144,13 +140,29 @@ export default {
}
},
methods: {
degreesToDMS(decimalDegrees) {
const degrees = Math.floor(decimalDegrees)
const minutesDecimal = (decimalDegrees - degrees) * 60
const minutes = Math.floor(minutesDecimal)
const seconds = ((minutesDecimal - minutes) * 60).toFixed(2)
return `${degrees}°${minutes}'${seconds}"`
},
dmsToDegrees(dms) {
const match = dms.match(/^(-?\d+)°(\d+)'([\d.]+)"$/)
if (!match) return null
const degrees = parseFloat(match[1])
const minutes = parseFloat(match[2])
const seconds = parseFloat(match[3])
const sign = degrees < 0 ? -1 : 1
return sign * (Math.abs(degrees) + minutes / 60 + seconds / 3600)
},
resetForm() {
this.formData = {
scenarioId: null,
routeId: null,
waypointId: null,
lng: '116.3974',
lat: '39.9093'
lng: '116°23\'48.64"',
lat: '39°54\'33.48"'
}
this.routeList = []
this.waypointList = []
@ -202,8 +214,8 @@ export default {
if (value) {
const waypoint = this.waypointList.find(w => w.id === value)
if (waypoint) {
this.formData.lng = waypoint.lng
this.formData.lat = waypoint.lat
this.formData.lng = this.degreesToDMS(waypoint.lng)
this.formData.lat = this.degreesToDMS(waypoint.lat)
}
}
},
@ -216,19 +228,27 @@ export default {
handleConfirm() {
const { lng, lat } = this.formData
if (!lng || !lat || isNaN(parseFloat(lng)) || isNaN(parseFloat(lat))) {
if (!lng || !lat) {
this.$message.error('请输入有效的经度和纬度!')
return
}
if (lng < -180 || lng > 180 || lat < -90 || lat > 90) {
const lngDegrees = this.dmsToDegrees(lng)
const latDegrees = this.dmsToDegrees(lat)
if (lngDegrees === null || latDegrees === null || isNaN(lngDegrees) || isNaN(latDegrees)) {
this.$message.error('请输入有效的度分秒格式!格式:116°23\'48.64"')
return
}
if (lngDegrees < -180 || lngDegrees > 180 || latDegrees < -90 || latDegrees > 90) {
this.$message.error('经纬度超出有效范围!')
return
}
this.$emit('confirm', {
lng: parseFloat(lng),
lat: parseFloat(lat)
lng: lngDegrees,
lat: latDegrees
})
this.$emit('update:visible', false)
}

128
ruoyi-ui/src/views/cesiumMap/index.vue

@ -44,6 +44,13 @@
@cancel="handleLocateCancel"
/>
<!-- 半径输入弹窗 -->
<radius-dialog
:visible="radiusDialogVisible"
@update:visible="radiusDialogVisible = $event"
@confirm="handleRadiusConfirm"
/>
<!-- 地图右下角比例尺 + 经纬度 -->
<div class="map-info-panel">
<div class="scale-bar" @click="handleScaleClick">
@ -67,6 +74,7 @@ import MeasurementPanel from './MeasurementPanel.vue'
import HoverTooltip from './HoverTooltip.vue'
import ContextMenu from './ContextMenu.vue'
import LocateDialog from './LocateDialog.vue'
import RadiusDialog from '../dialogs/RadiusDialog.vue'
import axios from 'axios'
import request from '@/utils/request'
import { getToken } from '@/utils/auth'
@ -171,7 +179,12 @@ export default {
currentScaleUnit: 'm',
isApplyingScale: false,
//
locateDialogVisible: false
locateDialogVisible: false,
//
radiusDialogVisible: false,
powerZoneCenter: null,
powerZoneCircleEntity: null,
powerZoneCenterEntity: null
}
},
components: {
@ -179,7 +192,8 @@ export default {
MeasurementPanel,
HoverTooltip,
ContextMenu,
LocateDialog
LocateDialog,
RadiusDialog
},
mounted() {
console.log(this.drawDomClick,999999)
@ -904,6 +918,15 @@ export default {
}
}
}
//
if (!entityData) {
for (const powerZoneEntity of this.allEntities) {
if (powerZoneEntity.type === 'powerZone' && powerZoneEntity.centerEntity === pickedEntity) {
entityData = powerZoneEntity
break
}
}
}
if (entityData && entityData.type !== 'route') {
//
this.contextMenu = {
@ -1090,7 +1113,7 @@ export default {
this.viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(lon, lat),
label: {
text: `${lat}°N\n${lon}°E`,
text: `${this.degreesToDMS(lat)}N\n${this.degreesToDMS(lon)}E`,
font: '12px sans-serif',
fillColor: Cesium.Color.BLACK,
outlineColor: Cesium.Color.WHITE,
@ -2865,6 +2888,15 @@ export default {
entity.polyline.width = data.width
}
break
case 'powerZone':
if (entity.ellipse) {
entity.ellipse.material = Cesium.Color.fromCssColorString(data.color).withAlpha(data.opacity)
}
if (entity.polyline) {
entity.polyline.material = Cesium.Color.fromCssColorString(data.borderColor || data.color)
entity.polyline.width = data.width
}
break
case 'sector':
if (entity.polygon) {
entity.polygon.material = Cesium.Color.fromCssColorString(data.color).withAlpha(data.opacity)
@ -2923,7 +2955,7 @@ export default {
//
entityData[property] = value
// 20%
if (property === 'color' && (entityData.type === 'polygon' || entityData.type === 'rectangle' || entityData.type === 'circle' || entityData.type === 'sector')) {
if (property === 'color' && (entityData.type === 'polygon' || entityData.type === 'rectangle' || entityData.type === 'circle' || entityData.type === 'sector' || entityData.type === 'powerZone')) {
entityData.opacity = 0.2
}
//
@ -2955,6 +2987,10 @@ export default {
this.viewer.entities.remove(pointEntity)
})
}
//
if (entity.type === 'powerZone' && entity.centerEntity) {
this.viewer.entities.remove(entity.centerEntity)
}
//
this.allEntities.splice(index, 1)
//
@ -3019,6 +3055,10 @@ export default {
this.viewer.entities.remove(pointEntity);
});
}
//
if (item.type === 'powerZone' && item.centerEntity) {
this.viewer.entities.remove(item.centerEntity);
}
} catch (e) {
console.warn('删除实体失败:', e);
}
@ -3645,6 +3685,86 @@ export default {
//
const distanceVec = [mousePosition.x - projection[0], mousePosition.y - projection[1]];
return Math.sqrt(distanceVec[0] * distanceVec[0] + distanceVec[1] * distanceVec[1]);
},
//
startPowerZoneDrawing() {
this.stopDrawing();
this.powerZoneCenter = null;
this.powerZoneCircleEntity = null;
this.powerZoneCenterEntity = null;
this.isDrawing = true;
this.viewer.canvas.style.cursor = 'crosshair';
this.drawingHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
this.drawingHandler.setInputAction((click) => {
const position = this.getClickPosition(click.position);
if (!position) return;
this.powerZoneCenter = position;
this.powerZoneCenterEntity = this.viewer.entities.add({
position: position,
point: {
pixelSize: 10,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
disableDepthTestDistance: Number.POSITIVE_INFINITY
}
});
this.drawingHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
this.drawingHandler.destroy();
this.drawingHandler = null;
this.radiusDialogVisible = true;
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
},
handleRadiusConfirm(radiusData) {
if (!this.powerZoneCenter) return;
const radiusInMeters = radiusData.unit === 'km' ? radiusData.radius * 1000 : radiusData.radius;
const circlePositions = this.generateCirclePositions(this.powerZoneCenter, radiusInMeters);
this.powerZoneCircleEntity = this.viewer.entities.add({
position: this.powerZoneCenter,
ellipse: {
semiMinorAxis: radiusInMeters,
semiMajorAxis: radiusInMeters,
material: Cesium.Color.RED.withAlpha(0),
clampToGround: true
},
polyline: {
positions: circlePositions,
width: 2,
material: Cesium.Color.RED,
clampToGround: true
}
});
this.allEntities.push({
id: ++this.entityCounter,
type: 'powerZone',
entity: this.powerZoneCircleEntity,
centerEntity: this.powerZoneCenterEntity,
center: this.powerZoneCenter,
radius: radiusInMeters,
color: '#FF0000',
opacity: 0,
borderColor: '#FF0000',
width: 2
});
this.isDrawing = false;
this.viewer.canvas.style.cursor = 'default';
this.$message.success(`威力区创建成功,半径:${radiusData.radius}${radiusData.unit}`);
this.powerZoneCenter = null;
this.powerZoneCircleEntity = null;
this.powerZoneCenterEntity = null;
}
}
}

14
ruoyi-ui/src/views/childRoom/TopHeader.vue

@ -270,12 +270,7 @@
</div>
</div>
<!-- 威力区弹窗 -->
<power-zone-dialog
v-model="powerZoneDialogVisible"
:power-zone="currentPowerZone"
@save="savePowerZone"
/>
<!-- 比例尺弹窗 -->
<scale-dialog
@ -297,14 +292,12 @@
</template>
<script>
import PowerZoneDialog from '../dialogs/PowerZoneDialog'
import ScaleDialog from '../dialogs/ScaleDialog'
import ExternalParamsDialog from '../dialogs/ExternalParamsDialog'
export default {
name: 'TopHeader',
components: {
PowerZoneDialog,
ScaleDialog,
ExternalParamsDialog
},
@ -358,8 +351,6 @@ export default {
data() {
return {
activeTopNav: 'file',
powerZoneDialogVisible: false,
currentPowerZone: {},
scaleDialogVisible: false,
currentScale: {},
externalParamsDialogVisible: false,
@ -511,8 +502,7 @@ export default {
//
powerZone() {
this.powerZoneDialogVisible = true
this.currentPowerZone = {}
this.$emit('start-power-zone-drawing')
},
threatZone() {

26
ruoyi-ui/src/views/childRoom/index.vue

@ -96,7 +96,7 @@
@load-terrain="loadTerrain"
@change-projection="changeProjection"
@load-aero-chart="loadAeroChart"
@save-power-zone="savePowerZone"
@start-power-zone-drawing="startPowerZoneDrawing"
@threat-zone="threatZone"
@route-calculation="routeCalculation"
@conflict-display="conflictDisplay"
@ -261,12 +261,7 @@
@save="updateWaypoint"
/>
<!-- 威力区弹窗 -->
<power-zone-dialog
v-model="showPowerZoneDialog"
:power-zone="currentPowerZone"
@save="savePowerZone"
/>
<!-- 比例尺弹窗 -->
<scale-dialog
@ -323,7 +318,6 @@ import OnlineMembersDialog from '@/views/dialogs/OnlineMembersDialog'
import PlatformEditDialog from '@/views/dialogs/PlatformEditDialog'
import RouteEditDialog from '@/views/dialogs/RouteEditDialog'
import WaypointEditDialog from '@/views/dialogs/WaypointEditDialog'
import PowerZoneDialog from '@/views/dialogs/PowerZoneDialog'
import ScaleDialog from '@/views/dialogs/ScaleDialog'
import ExternalParamsDialog from '@/views/dialogs/ExternalParamsDialog'
import PageLayoutDialog from '@/views/dialogs/PageLayoutDialog'
@ -347,7 +341,6 @@ export default {
PlatformEditDialog,
RouteEditDialog,
WaypointEditDialog,
PowerZoneDialog,
ScaleDialog,
ExternalParamsDialog,
PageLayoutDialog,
@ -369,9 +362,7 @@ export default {
selectedRoute: null,
showWaypointDialog: false,
selectedWaypoint: null,
//
showPowerZoneDialog: false,
currentPowerZone: {},
//
showScaleDialog: false,
currentScale: {
scaleNumerator: 1,
@ -1324,10 +1315,7 @@ export default {
'loadTerrain': () => this.loadTerrain(),
'changeProjection': () => this.changeProjection(),
'loadAeroChart': () => this.loadAeroChart(),
'powerZone': () => {
this.showPowerZoneDialog = true
this.currentPowerZone = {}
},
'powerZone': () => this.startPowerZoneDrawing(),
'threatZone': () => this.threatZone(),
'routeCalculation': () => this.routeCalculation(),
'conflictDisplay': () => this.conflictDisplay(),
@ -1485,9 +1473,9 @@ export default {
},
//
savePowerZone(powerZone) {
console.log('保存威力区:', powerZone)
this.$message.success(`威力区 "${powerZone.name}" 保存成功`);
startPowerZoneDrawing() {
this.airspaceDrawDom = true
this.$refs.cesiumMap.startPowerZoneDrawing()
},
threatZone() {

32
ruoyi-ui/src/views/dialogs/PowerZoneDialog.vue

@ -207,14 +207,30 @@ export default {
}
},
methods: {
degreesToDMS(decimalDegrees) {
const degrees = Math.floor(decimalDegrees)
const minutesDecimal = (decimalDegrees - degrees) * 60
const minutes = Math.floor(minutesDecimal)
const seconds = ((minutesDecimal - minutes) * 60).toFixed(2)
return `${degrees}°${minutes}'${seconds}"`
},
dmsToDegrees(dms) {
const match = dms.match(/^(-?\d+)°(\d+)'([\d.]+)"$/)
if (!match) return null
const degrees = parseFloat(match[1])
const minutes = parseFloat(match[2])
const seconds = parseFloat(match[3])
const sign = degrees < 0 ? -1 : 1
return sign * (Math.abs(degrees) + minutes / 60 + seconds / 3600)
},
initFormData() {
this.formData = {
name: this.powerZone.name || '',
font: this.powerZone.font || 'Microsoft YaHei',
shapeType: this.powerZone.shapeType || 'circle',
location: {
lat: this.powerZone.lat || '',
lng: this.powerZone.lng || ''
lat: this.powerZone.lat ? this.degreesToDMS(this.powerZone.lat) : '',
lng: this.powerZone.lng ? this.degreesToDMS(this.powerZone.lng) : ''
},
radius: this.powerZone.radius || 0,
angle: this.powerZone.angle || 0,
@ -236,11 +252,19 @@ export default {
savePowerZone() {
this.$refs.formRef.validate((valid) => {
if (valid) {
const latDegrees = this.dmsToDegrees(this.formData.location.lat);
const lngDegrees = this.dmsToDegrees(this.formData.location.lng);
if (latDegrees === null || lngDegrees === null) {
this.$message.error('请输入有效的度分秒格式!格式:39°54\'33.48"');
return;
}
this.$emit('save', {
...this.powerZone,
...this.formData,
lat: this.formData.location.lat,
lng: this.formData.location.lng
lat: latDegrees,
lng: lngDegrees
});
this.closeDialog();
}

153
ruoyi-ui/src/views/dialogs/RadiusDialog.vue

@ -0,0 +1,153 @@
<template>
<div v-if="visible" class="radius-dialog">
<div class="dialog-content">
<div class="dialog-header">
<h3>输入半径</h3>
<div class="close-btn" @click="closeDialog">×</div>
</div>
<div class="dialog-body">
<el-form :model="formData" :rules="rules" ref="formRef" label-width="80px" size="small">
<el-form-item label="半径" prop="radius">
<el-input-number
v-model="formData.radius"
:min="0.1"
:precision="2"
placeholder="请输入半径"
style="width: 100%;"
></el-input-number>
</el-form-item>
<el-form-item label="单位" prop="unit">
<el-radio-group v-model="formData.unit">
<el-radio-button label="km">千米</el-radio-button>
<el-radio-button label="m"></el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
</div>
<div class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="confirmRadius">确定</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'RadiusDialog',
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
formData: {
radius: 50,
unit: 'km'
},
rules: {
radius: [
{ required: true, message: '请输入半径', trigger: 'blur' }
]
}
}
},
methods: {
closeDialog() {
this.$emit('update:visible', false)
},
confirmRadius() {
this.$refs.formRef.validate((valid) => {
if (valid) {
this.$emit('confirm', {
radius: this.formData.radius,
unit: this.formData.unit
})
this.closeDialog()
}
})
}
}
}
</script>
<style scoped>
.radius-dialog {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.dialog-content {
position: relative;
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
width: 90%;
max-width: 400px;
animation: dialog-fade-in 0.3s ease;
pointer-events: auto;
}
@keyframes dialog-fade-in {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #e8e8e8;
}
.dialog-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.close-btn {
font-size: 20px;
color: #999;
cursor: pointer;
transition: color 0.3s;
}
.close-btn:hover {
color: #666;
}
.dialog-body {
padding: 20px;
}
.dialog-footer {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 16px 20px;
border-top: 1px solid #e8e8e8;
gap: 10px;
}
</style>

54
ruoyi-ui/src/views/system/waypoints/index.vue

@ -131,8 +131,16 @@
<el-table-column label="所属航线ID (关联 routes.id)" align="center" prop="routeId" />
<el-table-column label="航点名称 (如: WP1)" align="center" prop="name" />
<el-table-column label="航点顺序 (从1开始递增)" align="center" prop="seq" />
<el-table-column label="纬度" align="center" prop="lat" />
<el-table-column label="经度" align="center" prop="lng" />
<el-table-column label="纬度" align="center" prop="lat">
<template slot-scope="scope">
{{ degreesToDMS(scope.row.lat) }}
</template>
</el-table-column>
<el-table-column label="经度" align="center" prop="lng">
<template slot-scope="scope">
{{ degreesToDMS(scope.row.lng) }}
</template>
</el-table-column>
<el-table-column label="高度 (米)" align="center" prop="alt" />
<el-table-column label="速度 (km/h)" align="center" prop="speed" />
<el-table-column label="起始时间 (如: K+00:40:00)" align="center" prop="startTime" />
@ -178,10 +186,10 @@
<el-input v-model="form.seq" placeholder="请输入航点顺序 (从1开始递增)" />
</el-form-item>
<el-form-item label="纬度" prop="lat">
<el-input v-model="form.lat" placeholder="请输入纬度" />
<el-input v-model="form.lat" placeholder="请输入纬度(度分秒格式,如:39°54'33.48"" />
</el-form-item>
<el-form-item label="经度" prop="lng">
<el-input v-model="form.lng" placeholder="请输入经度" />
<el-input v-model="form.lng" placeholder="请输入经度(度分秒格式,如:116°23'48.64"" />
</el-form-item>
<el-form-item label="高度 (米)" prop="alt">
<el-input v-model="form.alt" placeholder="请输入高度 (米)" />
@ -278,6 +286,27 @@ export default {
this.getList()
},
methods: {
degreesToDMS(decimalDegrees) {
if (!decimalDegrees) return ''
const degrees = Math.floor(decimalDegrees)
const minutesDecimal = (decimalDegrees - degrees) * 60
const minutes = Math.floor(minutesDecimal)
const seconds = ((minutesDecimal - minutes) * 60).toFixed(2)
return `${degrees}°${minutes}'${seconds}"`
},
dmsToDegrees(dms) {
if (!dms) return null
const match = dms.match(/^(-?\d+)°(\d+)'([\d.]+)"$/)
if (!match) {
const num = parseFloat(dms)
return isNaN(num) ? null : num
}
const degrees = parseFloat(match[1])
const minutes = parseFloat(match[2])
const seconds = parseFloat(match[3])
const sign = degrees < 0 ? -1 : 1
return sign * (Math.abs(degrees) + minutes / 60 + seconds / 3600)
},
/** 查询航线具体航点明细列表 */
getList() {
this.loading = true
@ -336,6 +365,8 @@ export default {
const id = row.id || this.ids
getWaypoints(id).then(response => {
this.form = response.data
this.form.lat = this.degreesToDMS(this.form.lat)
this.form.lng = this.degreesToDMS(this.form.lng)
this.open = true
this.title = "修改航线具体航点明细"
})
@ -344,14 +375,23 @@ export default {
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateWaypoints(this.form).then(response => {
const formData = { ...this.form }
formData.lat = this.dmsToDegrees(formData.lat)
formData.lng = this.dmsToDegrees(formData.lng)
if (formData.lat === null || formData.lng === null) {
this.$modal.msgError("请输入有效的度分秒格式!格式:39°54'33.48\"")
return
}
if (formData.id != null) {
updateWaypoints(formData).then(response => {
this.$modal.msgSuccess("修改成功")
this.open = false
this.getList()
})
} else {
addWaypoints(this.form).then(response => {
addWaypoints(formData).then(response => {
this.$modal.msgSuccess("新增成功")
this.open = false
this.getList()

Loading…
Cancel
Save