From 2227d367fdb5acf45cbd0d4fa9aca90225c370b5 Mon Sep 17 00:00:00 2001 From: sd <1504629600@qq.com> Date: Wed, 11 Feb 2026 15:27:43 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E5=9D=90=E6=A0=87=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/cesiumMap/LocateDialog.vue | 95 ++++++++++++++++++++++----- ruoyi-ui/src/views/cesiumMap/index.vue | 49 ++++++++++++-- ruoyi-ui/src/views/childRoom/index.vue | 8 ++- 3 files changed, 128 insertions(+), 24 deletions(-) diff --git a/ruoyi-ui/src/views/cesiumMap/LocateDialog.vue b/ruoyi-ui/src/views/cesiumMap/LocateDialog.vue index 750db97..9a9861e 100644 --- a/ruoyi-ui/src/views/cesiumMap/LocateDialog.vue +++ b/ruoyi-ui/src/views/cesiumMap/LocateDialog.vue @@ -61,7 +61,7 @@ @@ -70,7 +70,7 @@ @@ -78,7 +78,7 @@ @@ -107,6 +107,10 @@ export default { visible: { type: Boolean, default: false + }, + coordinateFormat: { + type: String, + default: 'dms' // 'decimal' 或 'dms' } }, data() { @@ -137,9 +141,44 @@ export default { this.loadScenarios() } this.$emit('update:visible', newVal) + }, + coordinateFormat: { + handler(newVal) { + this.updateCoordinateFormat(newVal) + } } - }, + }, methods: { + updateCoordinateFormat(newFormat) { + if (!this.formData.lng || !this.formData.lat) return + + let lngDegrees, latDegrees + + if (this.coordinateFormat === 'dms') { + lngDegrees = this.dmsToDegrees(this.formData.lng) + latDegrees = this.dmsToDegrees(this.formData.lat) + } else { + lngDegrees = parseFloat(this.formData.lng) + latDegrees = parseFloat(this.formData.lat) + } + + if (lngDegrees !== null && latDegrees !== null && !isNaN(lngDegrees) && !isNaN(latDegrees)) { + if (newFormat === 'dms') { + this.formData.lng = this.degreesToDMS(lngDegrees) + this.formData.lat = this.degreesToDMS(latDegrees) + } else { + this.formData.lng = lngDegrees.toFixed(6) + this.formData.lat = latDegrees.toFixed(6) + } + } + }, + formatCoordinate(value) { + if (this.coordinateFormat === 'dms') { + return this.degreesToDMS(value) + } else { + return value.toFixed(6) + } + }, degreesToDMS(decimalDegrees) { const degrees = Math.floor(decimalDegrees) const minutesDecimal = (decimalDegrees - degrees) * 60 @@ -157,12 +196,22 @@ export default { return sign * (Math.abs(degrees) + minutes / 60 + seconds / 3600) }, resetForm() { - this.formData = { - scenarioId: null, - routeId: null, - waypointId: null, - lng: '116°23\'48.64"', - lat: '39°54\'33.48"' + if (this.coordinateFormat === 'dms') { + this.formData = { + scenarioId: null, + routeId: null, + waypointId: null, + lng: '116°23\'48.64"', + lat: '39°54\'33.48"' + } + } else { + this.formData = { + scenarioId: null, + routeId: null, + waypointId: null, + lng: '116.396844', + lat: '39.909289' + } } this.routeList = [] this.waypointList = [] @@ -214,8 +263,8 @@ export default { if (value) { const waypoint = this.waypointList.find(w => w.id === value) if (waypoint) { - this.formData.lng = this.degreesToDMS(waypoint.lng) - this.formData.lat = this.degreesToDMS(waypoint.lat) + this.formData.lng = this.formatCoordinate(waypoint.lng) + this.formData.lat = this.formatCoordinate(waypoint.lat) } } }, @@ -233,12 +282,24 @@ export default { return } - const lngDegrees = this.dmsToDegrees(lng) - const latDegrees = this.dmsToDegrees(lat) + let lngDegrees, latDegrees - if (lngDegrees === null || latDegrees === null || isNaN(lngDegrees) || isNaN(latDegrees)) { - this.$message.error('请输入有效的度分秒格式!格式:116°23\'48.64"') - return + if (this.coordinateFormat === 'dms') { + lngDegrees = this.dmsToDegrees(lng) + latDegrees = this.dmsToDegrees(lat) + + if (lngDegrees === null || latDegrees === null || isNaN(lngDegrees) || isNaN(latDegrees)) { + this.$message.error('请输入有效的度分秒格式!格式:116°23\'48.64"') + return + } + } else { + lngDegrees = parseFloat(lng) + latDegrees = parseFloat(lat) + + if (isNaN(lngDegrees) || isNaN(latDegrees)) { + this.$message.error('请输入有效的十进制格式!') + return + } } if (lngDegrees < -180 || lngDegrees > 180 || latDegrees < -90 || latDegrees > 90) { diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 5802ace..6c8c1df 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -43,6 +43,7 @@ Date: Wed, 11 Feb 2026 16:39:42 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E7=A9=BA=E5=9F=9F=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/cesiumMap/ContextMenu.vue | 12 ++++++++++++ ruoyi-ui/src/views/cesiumMap/index.vue | 19 +++++++++++++++++++ ruoyi-ui/src/views/dialogs/RadiusDialog.vue | 13 +++++++++++++ 3 files changed, 44 insertions(+) diff --git a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue index c2b2114..0f3a1fd 100644 --- a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue +++ b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue @@ -117,6 +117,11 @@ 填充属性 + + 📝 + 名称 + {{ entityData.name || '' }} + 🎨 填充色 @@ -367,6 +372,13 @@ export default { this.$emit('toggle-route-label') }, + editName() { + const newName = prompt('请输入威力区名称:', this.entityData.name || '') + if (newName !== null && newName.trim() !== '') { + this.$emit('update-property', 'name', newName.trim()) + } + }, + toggleColorPicker(property) { if (this.showColorPickerFor === property) { this.showColorPickerFor = null diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 6c8c1df..9985c6c 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -4101,6 +4101,9 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true entity.polyline.material = Cesium.Color.fromCssColorString(data.borderColor || data.color) entity.polyline.width = data.width } + if (data.name && data.centerEntity && data.centerEntity.label) { + data.centerEntity.label.text = data.name + } break case 'sector': if (entity.polygon) { @@ -5104,6 +5107,17 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true outlineColor: Cesium.Color.WHITE, outlineWidth: 2, disableDepthTestDistance: Number.POSITIVE_INFINITY + }, + label: { + text: '', + font: '14px Microsoft YaHei', + fillColor: Cesium.Color.WHITE, + outlineColor: Cesium.Color.BLACK, + outlineWidth: 2, + style: Cesium.LabelStyle.FILL_AND_OUTLINE, + verticalOrigin: Cesium.VerticalOrigin.TOP, + pixelOffset: new Cesium.Cartesian2(0, -20), + disableDepthTestDistance: Number.POSITIVE_INFINITY } }); @@ -5144,12 +5158,17 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true centerEntity: this.powerZoneCenterEntity, center: this.powerZoneCenter, radius: radiusInMeters, + name: radiusData.name, color: '#FF0000', opacity: 0, borderColor: '#FF0000', width: 2 }); + if (this.powerZoneCenterEntity && this.powerZoneCenterEntity.label) { + this.powerZoneCenterEntity.label.text = radiusData.name; + } + this.isDrawing = false; this.viewer.canvas.style.cursor = 'default'; diff --git a/ruoyi-ui/src/views/dialogs/RadiusDialog.vue b/ruoyi-ui/src/views/dialogs/RadiusDialog.vue index 7e33dcc..63d2914 100644 --- a/ruoyi-ui/src/views/dialogs/RadiusDialog.vue +++ b/ruoyi-ui/src/views/dialogs/RadiusDialog.vue @@ -8,6 +8,14 @@ + + + + { if (valid) { this.$emit('confirm', { + name: this.formData.name, radius: this.formData.radius, unit: this.formData.unit }) From 3a449dabbe50379ee5765ba829249f01f76d79a7 Mon Sep 17 00:00:00 2001 From: cuitw <1051735452@qq.com> Date: Tue, 24 Feb 2026 14:10:51 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E9=80=9A=E8=BF=87=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E7=BC=96=E8=BE=91=E8=88=AA=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/cesiumMap/ContextMenu.vue | 17 + ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue | 2 +- ruoyi-ui/src/views/cesiumMap/index.vue | 815 +++++++++++++++++------- ruoyi-ui/src/views/childRoom/index.vue | 81 ++- 4 files changed, 661 insertions(+), 254 deletions(-) diff --git a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue index 377d39b..0aed7ea 100644 --- a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue +++ b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue @@ -312,6 +312,15 @@ 修改朝向 {{ entityData.heading != null ? entityData.heading + '°' : '0°' }} + 航线 + + ⬅️ + 在此之前插入航线 + + + ➡️ + 在此之后插入航线 + @@ -385,6 +394,14 @@ export default { this.$emit('edit-platform-heading') }, + handleStartRouteBeforePlatform() { + this.$emit('start-route-before-platform', this.entityData) + }, + + handleStartRouteAfterPlatform() { + this.$emit('start-route-after-platform', this.entityData) + }, + handleToggleRouteLabel() { this.$emit('toggle-route-label') }, diff --git a/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue b/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue index d6c880d..02c054f 100644 --- a/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue +++ b/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue @@ -41,7 +41,7 @@ export default { return { // 完整工具列表(空域模式,移除点和线) allToolbarItems: [ - { id: 'mouse', name: '鼠标', icon: 'el-icon-position' }, + { id: 'mouse', name: '鼠标', icon: 'cursor' }, { id: 'polygon', name: '多边形空域', icon: 'el-icon-house' }, { id: 'rectangle', name: '矩形空域', icon: 'jx' }, { id: 'circle', name: '圆形空域', icon: 'circle' }, diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 7d6b376..4550a75 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -40,6 +40,8 @@ @show-transform-box="showPlatformIconTransformBox" @toggle-route-label="toggleRouteLabelVisibility" @toggle-route-lock="toggleRouteLock" + @start-route-before-platform="handleStartRouteBeforePlatform" + @start-route-after-platform="handleStartRouteAfterPlatform" /> @@ -185,6 +187,8 @@ export default { routeLabelVisible: {}, // 航线上锁状态:routeId -> true 上锁(不可编辑)/ false 或未设 可编辑 routeLocked: {}, + // 从平台右键进入的航线绘制:{ platformInfo: { platformId, platform }, mode: 'before'|'after' } + platformRouteDrawing: null, // 默认样式 defaultStyles: { point: { color: '#FF0000', size: 12 }, @@ -388,7 +392,7 @@ export default { // 检查实体是否与航线相关 const isRouteRelated = item.type === 'route' || item.routeId || (item.entity && item.entity.properties && - (item.entity.properties.isMissionWaypoint || item.entity.properties.isMissionRouteLine)); + (item.entity.properties.isMissionWaypoint || item.entity.properties.isMissionRouteLine)); // 如果是航线相关实体,从地图中移除 if (isRouteRelated && item.entity) { @@ -436,22 +440,22 @@ export default { console.warn(`>>> [地图同步失败] 尝试匹配 ID: ${dbId}, 但地图现有 ID 列表为:`, availableIds); } }, - // 新建航线绘制 + // 新建航线绘制(与测距一致:已确定段实线 + 最后一段到鼠标的虚线实时预览) startMissionRouteDrawing() { this.stopDrawing(); // 停止其他可能存在的绘制 this.drawingPoints = []; this.missionHoldParamsByIndex = {}; this.missionPendingHold = null; - let activeCursorPosition = null; this.isDrawing = true; this.viewer.canvas.style.cursor = 'crosshair'; this.drawingHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas); window.addEventListener('contextmenu', this.preventContextMenu, true); - // 鼠标移动预览逻辑 + // 鼠标移动:更新实时光标位置并请求重绘,实现虚线预览跟随 this.drawingHandler.setInputAction((movement) => { const newPosition = this.getClickPosition(movement.endPosition); if (newPosition) { - activeCursorPosition = newPosition; + this.activeCursorPosition = newPosition; + if (this.viewer.scene.requestRender) this.viewer.scene.requestRender(); } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); // 左键点击逻辑 @@ -467,8 +471,8 @@ export default { name: `WP${wpIndex}`, position: position, properties: { - isMissionWaypoint: true, // 这是一个永久的业务标记 - originalIndex: wpIndex, // 存下它是第几个点 + isMissionWaypoint: true, + originalIndex: wpIndex, temp: true }, point: { @@ -476,7 +480,7 @@ export default { color: Cesium.Color.WHITE, outlineColor: Cesium.Color.fromCssColorString('#0078FF'), outlineWidth: 3, - disableDepthTestDistance: Number.POSITIVE_INFINITY // 保证不被地形遮挡 + disableDepthTestDistance: Number.POSITIVE_INFINITY }, label: { text: `WP${wpIndex}`, @@ -488,29 +492,46 @@ export default { style: Cesium.LabelStyle.FILL_AND_OUTLINE } }); - // 第一次点击后,创建动态黑白斑马线(若有盘旋则在连线中插入盘旋点) - if (this.drawingPoints.length === 1) { - this.tempPreviewEntity = this.viewer.entities.add({ - polyline: { - positions: new Cesium.CallbackProperty(() => { - let positions = this.drawingPoints; - if (this.missionPendingHold && this.drawingPoints.length > 0) { - const idx = this.missionPendingHold.beforeIndex; - positions = [ - ...this.drawingPoints.slice(0, idx + 1), - this.missionPendingHold.center, - ...this.drawingPoints.slice(idx + 1) - ]; - } - if (positions.length > 0 && activeCursorPosition) return [...positions, activeCursorPosition]; - return positions; - }, false), - width: 3, - material: this.getPolylineSolidMaterial('#800080'), - clampToGround: true - } - }); + // 已确定段:实线(≥2 点时更新) + if (this.drawingPoints.length >= 2) { + if (this.tempEntity) { + this.viewer.entities.remove(this.tempEntity); + this.tempEntity = null; + } + const solidPositions = this.getMissionRouteSolidPositions(); + if (solidPositions.length >= 2) { + this.tempEntity = this.viewer.entities.add({ + polyline: { + positions: solidPositions, + width: 3, + material: Cesium.Color.fromCssColorString('#800080'), + arcType: Cesium.ArcType.NONE + } + }); + } + } + // 实时预览段:虚线(从最后一逻辑点到鼠标),与测距一致 + if (this.tempPreviewEntity) { + this.viewer.entities.remove(this.tempPreviewEntity); + this.tempPreviewEntity = null; } + this.tempPreviewEntity = this.viewer.entities.add({ + polyline: { + positions: new Cesium.CallbackProperty(() => { + const last = this.getMissionRouteLastPosition(); + if (this.activeCursorPosition && last) { + return [last, this.activeCursorPosition]; + } + return last ? [last, last] : []; + }, false), + width: 3, + material: new Cesium.PolylineDashMaterialProperty({ + color: Cesium.Color.fromCssColorString('#800080'), + dashLength: 16 + }), + arcType: Cesium.ArcType.NONE + } + }); }, Cesium.ScreenSpaceEventType.LEFT_CLICK); // 右键点击逻辑(结束绘制、抛出数据、恢复右键) this.drawingHandler.setInputAction(() => { @@ -557,6 +578,251 @@ export default { }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); }, + /** 航线绘制中“已确定”段的位置数组(含盘旋插入点),用于实线预览 */ + getMissionRouteSolidPositions() { + if (!this.drawingPoints || this.drawingPoints.length === 0) return []; + if (!this.missionPendingHold || this.drawingPoints.length < 2) return [...this.drawingPoints]; + const idx = this.missionPendingHold.beforeIndex; + return [ + ...this.drawingPoints.slice(0, idx + 1), + this.missionPendingHold.center, + ...this.drawingPoints.slice(idx + 1) + ]; + }, + /** 航线绘制中虚线预览的起点(最后一逻辑点:最后一个航点或待插入盘旋中心) */ + getMissionRouteLastPosition() { + if (!this.drawingPoints || this.drawingPoints.length === 0) return null; + if (this.missionPendingHold) return this.missionPendingHold.center; + return this.drawingPoints[this.drawingPoints.length - 1]; + }, + + /** + * 从平台右键进入的航线绘制:在此之前插入(先画前段,平台为最后一站)/ 在此之后插入(平台为起点,再画后段)。 + * @param {Object} entityData - 平台图标 entityData,含 lng, lat, platformId, platform, name + * @param {'before'|'after'} mode - 'before' 先画平台前航点,右键结束时平台作为最后一站;'after' 平台为起点再画后续 + */ + startMissionRouteDrawingFromPlatform(entityData, mode) { + this.stopDrawing(); + const platformCartesian = Cesium.Cartesian3.fromDegrees(entityData.lng, entityData.lat); + const platformInfo = { + platformId: entityData.platformId, + platform: entityData.platform, + serverId: entityData.serverId, + mapEntityId: entityData.id + }; + this.platformRouteDrawing = { platformInfo, mode, platformName: entityData.name || entityData.label || '平台' }; + + this.drawingPoints = []; + this.missionHoldParamsByIndex = {}; + this.missionPendingHold = null; + this.activeCursorPosition = null; + + if (mode === 'after') { + this.drawingPoints.push(platformCartesian); + this.viewer.entities.add({ + id: 'temp_wp_1', + name: this.platformRouteDrawing.platformName, + position: platformCartesian, + properties: { isMissionWaypoint: true, originalIndex: 1, temp: true }, + point: { + pixelSize: 10, + color: Cesium.Color.WHITE, + outlineColor: Cesium.Color.fromCssColorString('#0078FF'), + outlineWidth: 3, + disableDepthTestDistance: Number.POSITIVE_INFINITY + }, + label: { + text: this.platformRouteDrawing.platformName, + font: '14px Microsoft YaHei', + pixelOffset: new Cesium.Cartesian2(0, -20), + fillColor: Cesium.Color.fromCssColorString('#333333'), + outlineColor: Cesium.Color.BLACK, + outlineWidth: 1, + style: Cesium.LabelStyle.FILL_AND_OUTLINE + } + }); + this.$emit('drawing-points-update', 1); + // 平台→第一航点的虚线预览延后到首次 MOUSE_MOVE 创建,确保 activeCursorPosition 有效(否则 [last,last] 零长度不显示) + } + + this.isDrawing = true; + this.$emit('platform-route-drawing-started'); + this.viewer.canvas.style.cursor = 'crosshair'; + this.drawingHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas); + window.addEventListener('contextmenu', this.preventContextMenu, true); + + this.drawingHandler.setInputAction((movement) => { + const newPosition = this.getClickPosition(movement.endPosition); + if (newPosition) { + this.activeCursorPosition = newPosition; + // 「在此之后」仅 1 点时延后创建虚线;「在此之前」首点已在左键创建预览 + const platformRoute = this.platformRouteDrawing; + const onePoint = platformRoute && this.drawingPoints.length === 1 && !this.tempPreviewEntity; + if (onePoint && platformRoute.mode === 'after') { + this.tempPreviewEntity = this.viewer.entities.add({ + polyline: { + positions: new Cesium.CallbackProperty(() => { + const last = this.drawingPoints.length > 0 ? this.drawingPoints[this.drawingPoints.length - 1] : null; + if (last && this.activeCursorPosition) return [last, this.activeCursorPosition]; + return last ? [last, last] : []; + }, false), + width: 3, + material: new Cesium.PolylineDashMaterialProperty({ + color: Cesium.Color.fromCssColorString('#800080'), + dashLength: 16 + }), + arcType: Cesium.ArcType.NONE + } + }); + } + if (this.viewer.scene.requestRender) this.viewer.scene.requestRender(); + } + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + + this.drawingHandler.setInputAction((click) => { + const position = this.getClickPosition(click.position); + if (!position) return; + const platformRoute = this.platformRouteDrawing; + this.drawingPoints.push(position); + const wpIndex = this.drawingPoints.length; + this.$emit('drawing-points-update', this.drawingPoints.length); + this.viewer.entities.add({ + id: `temp_wp_${wpIndex}`, + name: `WP${wpIndex}`, + position: position, + properties: { isMissionWaypoint: true, originalIndex: wpIndex, temp: true }, + point: { + pixelSize: 10, + color: Cesium.Color.WHITE, + outlineColor: Cesium.Color.fromCssColorString('#0078FF'), + outlineWidth: 3, + disableDepthTestDistance: Number.POSITIVE_INFINITY + }, + label: { + text: `WP${wpIndex}`, + font: '14px Microsoft YaHei', + pixelOffset: new Cesium.Cartesian2(0, -20), + fillColor: Cesium.Color.fromCssColorString('#333333'), + outlineColor: Cesium.Color.BLACK, + outlineWidth: 1, + style: Cesium.LabelStyle.FILL_AND_OUTLINE + } + }); + // 「在此之前」首点:用点击位置初始化光标,这样立刻创建预览就有有效线段(一开始即显示预览) + if (platformRoute && platformRoute.mode === 'before' && this.drawingPoints.length === 1) { + this.activeCursorPosition = position; + } + // 与普通航线一致:已确定段实线,最后一段到鼠标虚线实时预览 + if (this.tempPreviewEntity) { + this.viewer.entities.remove(this.tempPreviewEntity); + this.tempPreviewEntity = null; + } + if (this.drawingPoints.length >= 2) { + if (this.tempEntity) { + this.viewer.entities.remove(this.tempEntity); + this.tempEntity = null; + } + // 「在此之前」:先点的为倒数第二、最后点的为起点,实线显示为 最后点→…→第一点(与最终航线顺序一致) + const solidPositions = platformRoute && platformRoute.mode === 'before' ? [...this.drawingPoints].reverse() : [...this.drawingPoints]; + this.tempEntity = this.viewer.entities.add({ + polyline: { + positions: solidPositions, + width: 3, + material: Cesium.Color.fromCssColorString('#800080'), + arcType: Cesium.ArcType.NONE + } + }); + } + this.tempPreviewEntity = this.viewer.entities.add({ + polyline: { + positions: new Cesium.CallbackProperty(() => { + const last = this.drawingPoints.length > 0 ? this.drawingPoints[this.drawingPoints.length - 1] : null; + if (last && this.activeCursorPosition) return [last, this.activeCursorPosition]; + return last ? [last, last] : []; + }, false), + width: 3, + material: new Cesium.PolylineDashMaterialProperty({ + color: Cesium.Color.fromCssColorString('#800080'), + dashLength: 16 + }), + arcType: Cesium.ArcType.NONE + } + }); + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + + this.drawingHandler.setInputAction(() => { + const pr = this.platformRouteDrawing; + if (!pr) { + this.stopDrawing(); + this.$emit('drawing-cancelled'); + setTimeout(() => window.removeEventListener('contextmenu', this.preventContextMenu, true), 200); + return; + } + let pointsToEmit = this.drawingPoints; + if (pr.mode === 'before') { + if (this.drawingPoints.length < 1) { + this.$message && this.$message.info('已取消'); + this.platformRouteDrawing = null; + this.stopDrawing(); + this.$emit('drawing-cancelled'); + setTimeout(() => window.removeEventListener('contextmenu', this.preventContextMenu, true), 200); + return; + } + // 顺序反转:先点的为倒数第二个点,最后点的为起点;平台为最后一个点 + pointsToEmit = [...this.drawingPoints].reverse(); + pointsToEmit.push(platformCartesian); + const lastId = `temp_wp_${pointsToEmit.length}`; + this.viewer.entities.add({ + id: lastId, + name: pr.platformName, + position: platformCartesian, + properties: { isMissionWaypoint: true, temp: true }, + point: { + pixelSize: 10, + color: Cesium.Color.WHITE, + outlineColor: Cesium.Color.fromCssColorString('#0078FF'), + outlineWidth: 3, + disableDepthTestDistance: Number.POSITIVE_INFINITY + }, + label: { + text: pr.platformName, + font: '14px Microsoft YaHei', + pixelOffset: new Cesium.Cartesian2(0, -20), + fillColor: Cesium.Color.fromCssColorString('#333333'), + outlineColor: Cesium.Color.BLACK, + outlineWidth: 1, + style: Cesium.LabelStyle.FILL_AND_OUTLINE + } + }); + } + if (pointsToEmit.length < 2) { + this.$message && this.$message.info('点数不足,航线已取消'); + this.platformRouteDrawing = null; + this.stopDrawing(); + this.$emit('drawing-cancelled'); + setTimeout(() => window.removeEventListener('contextmenu', this.preventContextMenu, true), 200); + return; + } + const latLngPoints = []; + pointsToEmit.forEach((pos, i) => { + const coords = this.cartesianToLatLng(pos); + const name = (pr.mode === 'after' && i === 0) || (pr.mode === 'before' && i === pointsToEmit.length - 1) ? pr.platformName : `WP${i + 1}`; + latLngPoints.push({ + id: i + 1, + name, + lat: coords.lat, + lng: coords.lng, + alt: 5000, + speed: 800 + }); + }); + this.$emit('draw-complete', latLngPoints, pr.platformInfo); + this.platformRouteDrawing = null; + this.stopDrawing(); + setTimeout(() => window.removeEventListener('contextmenu', this.preventContextMenu, true), 200); + }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); + }, + /** 航线绘制过程中:在最后两航点之间插入盘旋(由父组件在弹窗确认后调用)。插入后“最后一个点”会被移除,下一点击即为盘旋后的下一航点。 */ insertHoldBetweenLastTwo(holdParams) { if (!this.drawingPoints || this.drawingPoints.length < 2) return; @@ -581,6 +847,22 @@ export default { point: { pixelSize: 10, color: Cesium.Color.ORANGE, outlineColor: Cesium.Color.WHITE, outlineWidth: 2, disableDepthTestDistance: Number.POSITIVE_INFINITY }, label: { text: 'HOLD', font: '14px Microsoft YaHei', fillColor: Cesium.Color.ORANGE, outlineColor: Cesium.Color.BLACK, outlineWidth: 1 } }); + // 更新实线预览(含盘旋点) + if (this.tempEntity) { + this.viewer.entities.remove(this.tempEntity); + this.tempEntity = null; + } + const solidPositions = this.getMissionRouteSolidPositions(); + if (solidPositions.length >= 2) { + this.tempEntity = this.viewer.entities.add({ + polyline: { + positions: solidPositions, + width: 3, + material: Cesium.Color.fromCssColorString('#800080'), + arcType: Cesium.ArcType.NONE + } + }); + } this.$emit('drawing-points-update', this.drawingPoints.length); }, @@ -714,8 +996,8 @@ export default { polyline: { positions: linePositions, width: 3, - material: this.getPolylineSolidMaterial('#008aff'), - clampToGround: true, + material: Cesium.Color.fromCssColorString('#008aff'), + arcType: Cesium.ArcType.NONE, disableDepthTestDistance: Number.POSITIVE_INFINITY } }); @@ -950,10 +1232,19 @@ export default { const wpColor = wpStyle.color || '#ffffff'; const wpOutline = wpStyle.outlineColor || '#0078FF'; const wpOutlineW = wpStyle.outlineWidth != null ? wpStyle.outlineWidth : 2; - // 航线默认紫色、线宽 3;与测距线一致使用轮廓材质,平滑无锯齿 + // 航线默认紫色、线宽 3;与主航线一致的线型用于主线和转弯半径弧 const lineWidth = lineStyle.width != null ? lineStyle.width : 3; const lineColor = lineStyle.color || '#800080'; - const lineMaterial = this.getPolylineSolidMaterial(lineColor); + const gapColor = lineStyle.gapColor != null ? lineStyle.gapColor : '#000000'; + const dashLen = lineStyle.dashLength != null ? lineStyle.dashLength : 20; + const useDash = (lineStyle.style || 'solid') === 'dash'; + const lineMaterial = useDash + ? new Cesium.PolylineDashMaterialProperty({ + color: Cesium.Color.fromCssColorString(lineColor), + gapColor: Cesium.Color.fromCssColorString(gapColor), + dashLength: dashLen + }) + : Cesium.Color.fromCssColorString(lineColor); // 先收集所有航点的世界坐标 const originalPositions = []; waypoints.forEach((wp) => { @@ -1116,7 +1407,7 @@ export default { finalPathPositions.push(exit); this.viewer.entities.add({ id: `hold-line-${routeId}-${i}`, - polyline: { positions: [entry, ...arcPoints.slice(1), exit], width: 8, material: this.getPolylineSolidMaterial('#FFA500'), clampToGround: true, zIndex: 20 }, + polyline: { positions: [entry, ...arcPoints.slice(1), exit], width: 8, material: Cesium.Color.ORANGE, arcType: Cesium.ArcType.NONE, zIndex: 20 }, properties: { routeId: routeId } }); lastPos = exit; @@ -1133,7 +1424,7 @@ export default { const arcPoints = this.computeArcPositions(lastPos, currPos, nextLogical, radius); this.viewer.entities.add({ id: `arc-line-${routeId}-${i}`, - polyline: { positions: arcPoints, width: lineWidth, material: lineMaterial, clampToGround: true, zIndex: 20 }, + polyline: { positions: arcPoints, width: lineWidth, material: lineMaterial, arcType: Cesium.ArcType.NONE, zIndex: 20 }, properties: { routeId: routeId } }); // 转弯半径两侧航点与航线整体航点风格一致,点击均打开原航点编辑(dbId 为当前 wp.id) @@ -1184,7 +1475,7 @@ export default { positions: finalPathPositions, width: lineWidth, material: lineMaterial, - clampToGround: true, + arcType: Cesium.ArcType.NONE, zIndex: 1 }, properties: {isMissionRouteLine: true, routeId: routeId} @@ -1694,10 +1985,8 @@ export default { imageryProvider: false, terrainProvider: new Cesium.EllipsoidTerrainProvider(), baseLayer: false, - // 按需渲染:仅在场景变化时渲染,降低空闲时 CPU/GPU 占用(若某处界面不刷新可主动调用 viewer.scene.requestRender()) requestRenderMode: true, maximumRenderTimeChange: Infinity, - // 允许截图时读取 canvas 内容(若截图仅用 readPixels 可改为 false 以提升性能) contextOptions: { preserveDrawingBuffer: true, webgl: { @@ -1708,9 +1997,6 @@ export default { } }) this.viewer.cesiumWidget.creditContainer.style.display = "none" - // 根据浏览器缩放比例提高渲染分辨率,减轻 80% 等缩放下线条锯齿 - this.applyResolutionScale() - this._resolutionScaleCleanup = this.setupResolutionScaleListener() // 按需渲染时:实体增删改后主动请求一帧,避免界面不刷新 if (this.viewer.scene.requestRenderMode) { this.viewer.entities.collectionChanged.addEventListener(() => { @@ -1746,7 +2032,7 @@ export default { console.log('Cesium离线二维地图已加载') // 1. 定义全局拾取处理器(含防抖,避免双击误触导致相机高度剧烈变化) -this.viewer.scene.postProcessStages.fxaa.enabled = true + this.viewer.scene.postProcessStages.fxaa.enabled = true this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas); this.handler.setInputAction((click) => { // 隐藏右键菜单 @@ -2545,21 +2831,21 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true } this.tempEntity = this.viewer.entities.add({ polyline: { - positions: this.getPolylinePositionsStraightIn2D([...this.drawingPoints], false), + positions: this.drawingPoints, width: this.defaultStyles.line.width, - material: this.getPolylineSolidMaterial(this.defaultStyles.line.color), - clampToGround: true + material: Cesium.Color.fromCssColorString(this.defaultStyles.line.color), + arcType: Cesium.ArcType.NONE } }); } - // 创建新的预览线(与测距线一致:轮廓材质,2D 下直线) + // 创建新的预览虚线(使用 CallbackProperty 实现实时更新) this.tempPreviewEntity = this.viewer.entities.add({ polyline: { positions: new Cesium.CallbackProperty(() => { - if (this.activeCursorPosition && this.drawingPoints.length > 0) { - return this.getPolylinePositionsStraightIn2D([this.drawingPoints[this.drawingPoints.length - 1], this.activeCursorPosition], false); + if (this.activeCursorPosition) { + return [this.drawingPoints[this.drawingPoints.length - 1], this.activeCursorPosition]; } - return this.drawingPoints.length > 0 ? [this.drawingPoints[this.drawingPoints.length - 1]] : []; + return [this.drawingPoints[this.drawingPoints.length - 1]]; }, false), width: this.defaultStyles.line.width, material: new Cesium.PolylineDashMaterialProperty({ @@ -2672,15 +2958,16 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true polyline: { // positions 使用 CallbackProperty 实现动态闭合线 positions: new Cesium.CallbackProperty(() => { - if (this.activeCursorPosition && this.drawingPoints.length >= 2) { - return this.getPolylinePositionsStraightIn2D([...this.drawingPoints, this.activeCursorPosition], true); + if (this.activeCursorPosition) { + // 闭合回路:[所有点, 鼠标位置, 回到起点] + return [...this.drawingPoints, this.activeCursorPosition, this.drawingPoints[0]]; } return this.drawingPoints; }, false), width: this.defaultStyles.polygon.width, - // 边框与测距线一致:轮廓材质,2D 下直线 - material: this.getPolylineSolidMaterial(this.defaultStyles.polygon.color), - clampToGround: true + // 边框使用实线,表示正在编辑中 + material: Cesium.Color.fromCssColorString(this.defaultStyles.polygon.color), + arcType: Cesium.ArcType.NONE } }); } @@ -2700,6 +2987,12 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true finishPolygonDrawing() { const positions = [...this.drawingPoints] const entity = this.addPolygonEntity(positions) + // 计算面积 + const area = this.calculatePolygonArea(positions) + this.measurementResult = { + area: area, + type: 'polygon' + } // 重置绘制点数组,保持绘制状态以继续绘制 this.drawingPoints = [] if (this.tempEntity) { @@ -2742,23 +3035,34 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true } return Cesium.Rectangle.fromDegrees(0, 0, 0, 0); }, false), - // 设置填充颜色 - material: Cesium.Color.fromCssColorString(this.defaultStyles.rectangle.color).withAlpha(this.defaultStyles.rectangle.opacity), - clampToGround: true // 贴地 + material: Cesium.Color.fromCssColorString(this.defaultStyles.rectangle.color).withAlpha(this.defaultStyles.rectangle.opacity) }, // 边框部分 polyline: { // 关键:使用 CallbackProperty 动态计算矩形边框线 positions: new Cesium.CallbackProperty(() => { if (this.drawingPoints.length > 0 && this.activeCursorPosition) { + // 计算矩形的四个角点 const rect = Cesium.Rectangle.fromCartesianArray([this.drawingPoints[0], this.activeCursorPosition]); - return this.getRectangleBorderPositions(rect); + const west = rect.west; + const south = rect.south; + const east = rect.east; + const north = rect.north; + + // 创建四个角点的笛卡尔坐标 + const southwest = Cesium.Cartesian3.fromRadians(west, south); + const southeast = Cesium.Cartesian3.fromRadians(east, south); + const northeast = Cesium.Cartesian3.fromRadians(east, north); + const northwest = Cesium.Cartesian3.fromRadians(west, north); + + // 返回闭合的边框线位置数组 + return [southwest, southeast, northeast, northwest, southwest]; } return []; }, false), width: this.defaultStyles.rectangle.width, - material: this.getPolylineSolidMaterial(this.defaultStyles.rectangle.color), - clampToGround: true // 贴地 + material: Cesium.Color.fromCssColorString(this.defaultStyles.rectangle.color), + arcType: Cesium.ArcType.NONE } }); } @@ -2789,8 +3093,20 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true this.entityCounter++; const id = `rectangle_${this.entityCounter}`; - // 创建边框线位置(按等经度/等纬度采样,2D 缩小时仍为直线) - const borderPositions = this.getRectangleBorderPositions(rect); + // 计算矩形的四个角点 + const west = rect.west; + const south = rect.south; + const east = rect.east; + const north = rect.north; + + // 创建四个角点的笛卡尔坐标 + const southwest = Cesium.Cartesian3.fromRadians(west, south); + const southeast = Cesium.Cartesian3.fromRadians(east, south); + const northeast = Cesium.Cartesian3.fromRadians(east, north); + const northwest = Cesium.Cartesian3.fromRadians(west, north); + + // 创建边框线的位置数组(闭合) + const borderPositions = [southwest, southeast, northeast, northwest, southwest]; // 创建一个复合实体,包含填充和边框 const finalEntity = this.viewer.entities.add({ @@ -2798,15 +3114,14 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true // 填充部分 rectangle: { coordinates: rect, - material: Cesium.Color.fromCssColorString(this.defaultStyles.rectangle.color).withAlpha(this.defaultStyles.rectangle.opacity), - clampToGround: true + material: Cesium.Color.fromCssColorString(this.defaultStyles.rectangle.color).withAlpha(this.defaultStyles.rectangle.opacity) }, // 边框部分 polyline: { positions: borderPositions, width: this.defaultStyles.rectangle.width, - material: this.getPolylineSolidMaterial(this.defaultStyles.rectangle.color), - clampToGround: true + material: Cesium.Color.fromCssColorString(this.defaultStyles.rectangle.color), + arcType: Cesium.ArcType.NONE } }); // 4. 记录到实体列表 @@ -2820,10 +3135,16 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true borderColor: this.defaultStyles.rectangle.color, // 边框颜色 opacity: this.defaultStyles.rectangle.opacity, width: this.defaultStyles.rectangle.width, - label: `矩形空域 ${this.entityCounter}` + label: `矩形 ${this.entityCounter}` }; this.allEntities.push(entityData); - // 5. 重置状态,保持绘制状态以继续绘制 + // 5. 计算并显示面积 + const area = this.calculateRectangleArea(rect); + this.measurementResult = { + area: area, + type: 'rectangle' + }; + // 6. 重置状态,保持绘制状态以继续绘制 this.drawingPoints = []; }, // 计算矩形面积(辅助方法) @@ -2947,10 +3268,9 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true }, false), // 设置填充颜色 material: Cesium.Color.fromCssColorString(this.defaultStyles.circle.color).withAlpha(this.defaultStyles.circle.opacity), - // 移除 outline 属性,改用 polyline 绘制边框 - clampToGround: true + arcType: Cesium.ArcType.NONE }, - // 边框部分 + // 边框部分 - 使用 polyline polyline: { // 关键:使用 CallbackProperty 动态计算圆形边框线 positions: new Cesium.CallbackProperty(() => { @@ -2970,8 +3290,8 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true } }, false), width: this.defaultStyles.circle.width, - material: this.getPolylineSolidMaterial(this.defaultStyles.circle.color), - clampToGround: true + material: Cesium.Color.fromCssColorString(this.defaultStyles.circle.color), + arcType: Cesium.ArcType.NONE } }); } @@ -3014,24 +3334,21 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true // 生成圆形的点,用于绘制边框线 const circlePositions = this.generateCirclePositions(centerPoint, radius); - // 创建一个复合实体,包含填充和边框 const finalEntity = this.viewer.entities.add({ id: id, position: centerPoint, - // 填充部分 ellipse: { semiMajorAxis: radius, semiMinorAxis: radius, - // 设置填充颜色 material: Cesium.Color.fromCssColorString(this.defaultStyles.circle.color).withAlpha(this.defaultStyles.circle.opacity), - clampToGround: true + arcType: Cesium.ArcType.NONE }, // 边框部分 polyline: { positions: circlePositions, width: this.defaultStyles.circle.width, - material: this.getPolylineSolidMaterial(this.defaultStyles.circle.color), - clampToGround: true + material: Cesium.Color.fromCssColorString(this.defaultStyles.circle.color), + arcType: Cesium.ArcType.NONE } }); // 4. 记录实体 @@ -3046,10 +3363,18 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true opacity: this.defaultStyles.circle.opacity, width: this.defaultStyles.circle.width, radius: radius, - label: `圆形空域 ${this.entityCounter}` + label: `圆形 ${this.entityCounter}` }; this.allEntities.push(entityData); - // 5. 重置状态,保持绘制状态以继续绘制 + // 5. 计算面积 (π * r²) 并显示 + // 半径单位是米,面积单位是平方米 + const area = Math.PI * Math.pow(radius, 2); + this.measurementResult = { + radius: radius, // 也可以额外显示半径 + area: area, + type: 'circle' + }; + // 6. 重置状态,保持绘制状态以继续绘制 this.drawingPoints = []; this.activeCursorPosition = null; }, @@ -3076,8 +3401,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true }, // 绘制扇形 startSectorDrawing() { - // 重置绘制状态 - this.drawingPoints = []; // 存储圆心、半径端点、角度端点 + this.drawingPoints = []; // 1. 清理旧实体 if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); if (this.tempPreviewEntity) this.viewer.entities.remove(this.tempPreviewEntity); @@ -3108,15 +3432,18 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true return []; }, false), width: this.defaultStyles.sector.width, - material: this.getPolylineSolidMaterial(this.defaultStyles.sector.color), - clampToGround: true + material: new Cesium.PolylineDashMaterialProperty({ + color: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color), + dashLength: 16 + }), + arcType: Cesium.ArcType.NONE } }); } // --- 情况B:第二次点击(确定半径) --- else if (this.drawingPoints.length === 1) { this.drawingPoints.push(position); - this.activeCursorPosition = position; // 更新 activeCursorPosition 为实际点击位置 + this.activeCursorPosition = position; const centerPoint = this.drawingPoints[0]; const radiusPoint = this.drawingPoints[1]; const fixedRadius = Cesium.Cartesian3.distance(centerPoint, radiusPoint); @@ -3143,9 +3470,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true } return new Cesium.PolygonHierarchy([]); }, false), - // 设置填充颜色 - material: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color).withAlpha(this.defaultStyles.sector.opacity), - clampToGround: true + material: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color).withAlpha(this.defaultStyles.sector.opacity) }, // 边框部分 polyline: { @@ -3164,15 +3489,15 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true return []; }, false), width: this.defaultStyles.sector.width, - material: this.getPolylineSolidMaterial(this.defaultStyles.sector.color), - clampToGround: true + material: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color), + arcType: Cesium.ArcType.NONE } }); } // --- 情况C:第三次点击(确定角度) --- else if (this.drawingPoints.length === 2) { this.drawingPoints.push(position); - this.activeCursorPosition = null; // 停止动态更新 + this.activeCursorPosition = null; // 传递角度点位置去结束绘制 this.finishSectorDrawing(this.drawingPoints[0], this.drawingPoints[1], this.drawingPoints[2]); } @@ -3198,20 +3523,18 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true // 3. 创建最终显示的静态实体 const finalEntity = this.viewer.entities.add({ id: 'sector-' + new Date().getTime(), - name: `扇形空域 ${this.entityCounter}`, + name: `扇形 ${this.entityCounter}`, // 填充部分 polygon: { hierarchy: new Cesium.PolygonHierarchy(positions), - // 设置填充颜色 - material: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color).withAlpha(this.defaultStyles.sector.opacity), - clampToGround: true + material: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color).withAlpha(this.defaultStyles.sector.opacity) }, // 边框部分 polyline: { positions: positions, width: this.defaultStyles.sector.width, - material: this.getPolylineSolidMaterial(this.defaultStyles.sector.color), - clampToGround: true + material: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color), + arcType: Cesium.ArcType.NONE } }); // 4. 记录实体 @@ -3225,18 +3548,18 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true endAngle: endAngle, positions: positions, entity: finalEntity, - color: this.defaultStyles.sector.color, // 填充颜色 - borderColor: this.defaultStyles.sector.color, // 边框颜色 + color: this.defaultStyles.sector.color, + borderColor: this.defaultStyles.sector.color, opacity: 0.5, width: this.defaultStyles.sector.width, - label: `扇形空域 ${this.entityCounter}` + label: `扇形 ${this.entityCounter}` }; this.allEntities.push(entityData); // 5. 重置状态,保持绘制状态以继续绘制 this.drawingPoints = []; }, // 生成圆形的点,用于绘制边框线 - generateCirclePositions(center, radius, numPoints = 192) { + generateCirclePositions(center, radius, numPoints = 1024) { const positions = []; const ellipsoid = this.viewer.scene.globe.ellipsoid; @@ -3313,7 +3636,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true // 确保角度差不为零 angleDiff = Math.max(0.01, angleDiff); // 计算扇形的顶点数(根据角度差确定,确保平滑) - const numPoints = Math.max(16, Math.ceil(angleDiff * 180 / Math.PI / 3)); + const numPoints = Math.max(200, Math.ceil(angleDiff * 180 / Math.PI)); const angleStep = angleDiff / (numPoints - 1); // 生成扇形的顶点(顺时针方向) for (let i = 0; i < numPoints; i++) { @@ -3328,6 +3651,14 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true positions.push(center); return positions; }, + // 生成扇形边缘点 + generateSectorEdgePoint(center, radius, angle) { + const centerLL = Cesium.Cartographic.fromCartesian(center); + const distance = radius / 6378137; + const lat = centerLL.latitude + Math.sin(angle) * distance; + const lng = centerLL.longitude + Math.cos(angle) * distance / Math.cos(centerLL.latitude); + return Cesium.Cartesian3.fromRadians(lng, lat); + }, // 计算两点之间的距离(米) calculateDistance(point1, point2) { return Cesium.Cartesian3.distance(point1, point2); @@ -3369,12 +3700,12 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true } return []; }, false), - width: 8, // 增加宽度以获得更大的箭头头部 - // 使用箭头材质以显示箭头方向 + width: 12, // 增加宽度以获得更大的箭头头部 + // 使用箭头材质 material: new Cesium.PolylineArrowMaterialProperty( Cesium.Color.fromCssColorString(this.defaultStyles.arrow.color) ), - clampToGround: true, // 贴地 + arcType: Cesium.ArcType.NONE, widthInMeters: false // 使用像素宽度模式 } }); @@ -3422,11 +3753,11 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true name: `箭头 ${this.entityCounter}`, polyline: { positions: positions, - width: 8, // 增加宽度以获得更大的箭头头部 + width: 12, // 增加宽度以获得更大的箭头头部 material: new Cesium.PolylineArrowMaterialProperty( Cesium.Color.fromCssColorString(this.defaultStyles.arrow.color) ), - clampToGround: true, + arcType: Cesium.ArcType.NONE, // 使用像素宽度模式,确保箭头在缩放时保持比例 widthInMeters: false } @@ -3704,15 +4035,14 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true addLineEntity(positions, pointEntities = []) { this.entityCounter++ const id = `line_${this.entityCounter}` - const straightPositions = this.getPolylinePositionsStraightIn2D(positions, false) const entity = this.viewer.entities.add({ id: id, name: `线 ${this.entityCounter}`, polyline: { - positions: straightPositions, + positions: positions, width: this.defaultStyles.line.width, - material: this.getPolylineSolidMaterial(this.defaultStyles.line.color), - clampToGround: true + material: Cesium.Color.fromCssColorString(this.defaultStyles.line.color), + arcType: Cesium.ArcType.NONE } }) const entityData = { @@ -3737,21 +4067,23 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true addPolygonEntity(positions) { this.entityCounter++ const id = `polygon_${this.entityCounter}` + // 闭合多边形 const polygonPositions = [...positions, positions[0]] - const straightBorder = this.getPolylinePositionsStraightIn2D(polygonPositions, false) + // 创建一个复合实体,包含填充和边框 const entity = this.viewer.entities.add({ id: id, name: `面 ${this.entityCounter}`, + // 填充部分 polygon: { hierarchy: new Cesium.PolygonHierarchy(polygonPositions), - material: Cesium.Color.TRANSPARENT, - clampToGround: true + material: Cesium.Color.TRANSPARENT }, + // 边框部分 polyline: { - positions: straightBorder, + positions: polygonPositions, width: this.defaultStyles.polygon.width, - material: this.getPolylineSolidMaterial(this.defaultStyles.polygon.color), - clampToGround: true + material: Cesium.Color.fromCssColorString(this.defaultStyles.polygon.color), + arcType: Cesium.ArcType.NONE } }) const entityData = { @@ -3764,7 +4096,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true borderColor: this.defaultStyles.polygon.color, opacity: 0, width: this.defaultStyles.polygon.width, - label: `多边形空域 ${this.entityCounter}` + label: `面 ${this.entityCounter}` } this.allEntities.push(entityData) entity.clickHandler = (e) => { @@ -3778,7 +4110,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true const id = `rectangle_${this.entityCounter}` const entity = this.viewer.entities.add({ id: id, - name: `矩形空域 ${this.entityCounter}`, + name: `矩形 ${this.entityCounter}`, rectangle: { coordinates: coordinates, // 移除填充颜色,只显示边框 @@ -3799,17 +4131,17 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true const id = `circle_${this.entityCounter}` const entity = this.viewer.entities.add({ id: id, - name: `圆形空域 ${this.entityCounter}`, + name: `圆形 ${this.entityCounter}`, position: center, // 圆心位置 // 【优化】使用 ellipse (椭圆) 绘制圆形 ellipse: { - semiMinorAxis: validRadius, // 半短轴 = 半径 - semiMajorAxis: validRadius, // 半长轴 = 半径 - // 移除填充颜色,只显示边框 + semiMinorAxis: validRadius, + semiMajorAxis: validRadius, material: Cesium.Color.TRANSPARENT, outline: true, outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.circle.color), - outlineWidth: this.defaultStyles.circle.width + outlineWidth: this.defaultStyles.circle.width, + arcType: Cesium.ArcType.NONE } }) // 【重要修改】直接把 entity 推入数组 @@ -4024,7 +4356,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true break case 'line': if (entity.polyline) { - entity.polyline.material = this.getPolylineSolidMaterial(data.color) + entity.polyline.material = Cesium.Color.fromCssColorString(data.color) entity.polyline.width = data.width } break @@ -4033,7 +4365,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true entity.polygon.material = Cesium.Color.fromCssColorString(data.color).withAlpha(data.opacity) } if (entity.polyline) { - entity.polyline.material = this.getPolylineSolidMaterial(data.borderColor || data.color) + entity.polyline.material = Cesium.Color.fromCssColorString(data.borderColor || data.color) entity.polyline.width = data.width } break @@ -4042,7 +4374,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true entity.rectangle.material = Cesium.Color.fromCssColorString(data.color).withAlpha(data.opacity) } if (entity.polyline) { - entity.polyline.material = this.getPolylineSolidMaterial(data.borderColor || data.color) + entity.polyline.material = Cesium.Color.fromCssColorString(data.borderColor || data.color) entity.polyline.width = data.width } break @@ -4051,7 +4383,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true entity.ellipse.material = Cesium.Color.fromCssColorString(data.color).withAlpha(data.opacity) } if (entity.polyline) { - entity.polyline.material = this.getPolylineSolidMaterial(data.borderColor || data.color) + entity.polyline.material = Cesium.Color.fromCssColorString(data.borderColor || data.color) entity.polyline.width = data.width } break @@ -4075,7 +4407,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true entity.ellipse.material = Cesium.Color.fromCssColorString(data.color).withAlpha(data.opacity) } if (entity.polyline) { - entity.polyline.material = this.getPolylineSolidMaterial(data.borderColor || data.color) + entity.polyline.material = Cesium.Color.fromCssColorString(data.borderColor || data.color) entity.polyline.width = data.width } if (data.name && data.centerEntity && data.centerEntity.label) { @@ -4087,7 +4419,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true entity.polygon.material = Cesium.Color.fromCssColorString(data.color).withAlpha(data.opacity) } if (entity.polyline) { - entity.polyline.material = this.getPolylineSolidMaterial(data.borderColor || data.color) + entity.polyline.material = Cesium.Color.fromCssColorString(data.borderColor || data.color) entity.polyline.width = data.width } break @@ -4211,6 +4543,23 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true this.$message && this.$message.success('已显示伸缩框') }, + /** 右键「在此之前插入航线」:先画平台前的航点,右键结束时将平台作为最后一站 */ + handleStartRouteBeforePlatform() { + const entityData = this.contextMenu.entityData + this.contextMenu.visible = false + if (entityData && entityData.type === 'platformIcon' && entityData.lat != null && entityData.lng != null) { + this.startMissionRouteDrawingFromPlatform(entityData, 'before') + } + }, + /** 右键「在此之后插入航线」:以平台为起点,再画后续航点 */ + handleStartRouteAfterPlatform() { + const entityData = this.contextMenu.entityData + this.contextMenu.visible = false + if (entityData && entityData.type === 'platformIcon' && entityData.lat != null && entityData.lng != null) { + this.startMissionRouteDrawingFromPlatform(entityData, 'after') + } + }, + /** 位置改为图形化:直接拖动图标即可,无需弹窗 */ openPlatformIconPositionDialog() { this.contextMenu.visible = false @@ -4269,6 +4618,16 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true } } }, + /** 根据房间平台图标记录 ID(serverId)移除地图上对应的平台图标(用于该平台已创建航线后删除其“占位”图标) */ + removePlatformIconByServerId(serverId) { + if (!serverId || !this.allEntities) return + const ed = this.allEntities.find(e => e.type === 'platformIcon' && e.serverId === serverId) + if (!ed) return + this.removeTransformHandles(ed) + if (this.selectedPlatformIcon === ed) this.selectedPlatformIcon = null + if (ed.entity) this.viewer.entities.remove(ed.entity) + this.allEntities = this.allEntities.filter(e => !(e.type === 'platformIcon' && e.serverId === serverId)) + }, clearAll(showConfirm = true) { if (showConfirm) { this.$confirm('是否清除所有编辑内容?', '提示', { @@ -4285,107 +4644,107 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true } }, executeClearAll() { - // 1. 检查数组是否有内容 - if (this.allEntities && this.allEntities.length > 0) { - // 2. 遍历每一个对象进行删除 - this.allEntities.forEach(item => { - try { - let entity = null; - - // 情况 A: 数组里存的是原生的 Cesium Entity (点、线通常是这种情况) - if (item instanceof Cesium.Entity) { - entity = item; - } - // 情况 B: 数组里存的是包装对象 (你的矩形、圆可能是这种 { entity: ... }) - else if (item.entity && item.entity instanceof Cesium.Entity) { - entity = item.entity; - } - // 情况 C: 兜底方案,尝试通过 ID 删除 - else if (item.id) { - entity = this.viewer.entities.getById(item.id); - } + // 1. 检查数组是否有内容 + if (this.allEntities && this.allEntities.length > 0) { + // 2. 遍历每一个对象进行删除 + this.allEntities.forEach(item => { + try { + let entity = null; - // 检查是否是航线实体,如果是则跳过删除 - if (entity && entity.properties) { - const isMissionWaypoint = entity.properties.isMissionWaypoint; - const isMissionRouteLine = entity.properties.isMissionRouteLine; - if (isMissionWaypoint || isMissionRouteLine) { - return; // 跳过航线相关实体 - } - } + // 情况 A: 数组里存的是原生的 Cesium Entity (点、线通常是这种情况) + if (item instanceof Cesium.Entity) { + entity = item; + } + // 情况 B: 数组里存的是包装对象 (你的矩形、圆可能是这种 { entity: ... }) + else if (item.entity && item.entity instanceof Cesium.Entity) { + entity = item.entity; + } + // 情况 C: 兜底方案,尝试通过 ID 删除 + else if (item.id) { + entity = this.viewer.entities.getById(item.id); + } - // 删除非航线实体 - if (entity) { - this.viewer.entities.remove(entity); + // 检查是否是航线实体,如果是则跳过删除 + if (entity && entity.properties) { + const isMissionWaypoint = entity.properties.isMissionWaypoint; + const isMissionRouteLine = entity.properties.isMissionRouteLine; + if (isMissionWaypoint || isMissionRouteLine) { + return; // 跳过航线相关实体 } + } - // 移除线实体相关的点实体 - if (item.type === 'line' && item.pointEntities) { - item.pointEntities.forEach(pointEntity => { - this.viewer.entities.remove(pointEntity); - }); - } - if (item.type === 'platformIcon') { - this.removeTransformHandles(item); - if (this.selectedPlatformIcon === item) this.selectedPlatformIcon = null; - } - // 移除威力区的圆心实体 - if (item.type === 'powerZone' && item.centerEntity) { - this.viewer.entities.remove(item.centerEntity); - } - } catch (e) { - console.warn('删除实体失败:', e); + // 删除非航线实体 + if (entity) { + this.viewer.entities.remove(entity); } - }); - } - // 3. 清空数组(只保留航线实体) - this.allEntities = this.allEntities.filter(item => { - let entity = null; - if (item instanceof Cesium.Entity) { - entity = item; - } else if (item.entity && item.entity instanceof Cesium.Entity) { - entity = item.entity; - } else if (item.id) { - entity = this.viewer.entities.getById(item.id); - } - if (entity && entity.properties) { - const isMissionWaypoint = entity.properties.isMissionWaypoint; - const isMissionRouteLine = entity.properties.isMissionRouteLine; - return isMissionWaypoint || isMissionRouteLine; + // 移除线实体相关的点实体 + if (item.type === 'line' && item.pointEntities) { + item.pointEntities.forEach(pointEntity => { + this.viewer.entities.remove(pointEntity); + }); + } + if (item.type === 'platformIcon') { + this.removeTransformHandles(item); + if (this.selectedPlatformIcon === item) this.selectedPlatformIcon = null; + } + // 移除威力区的圆心实体 + if (item.type === 'powerZone' && item.centerEntity) { + this.viewer.entities.remove(item.centerEntity); + } + } catch (e) { + console.warn('删除实体失败:', e); } - return false; }); - // 4. 清理可能残留的绘制过程中的临时图形 - if (this.tempEntity) { - this.viewer.entities.remove(this.tempEntity); - this.tempEntity = null; + } + // 3. 清空数组(只保留航线实体) + this.allEntities = this.allEntities.filter(item => { + let entity = null; + if (item instanceof Cesium.Entity) { + entity = item; + } else if (item.entity && item.entity instanceof Cesium.Entity) { + entity = item.entity; + } else if (item.id) { + entity = this.viewer.entities.getById(item.id); } - if (this.tempPreviewEntity) { - this.viewer.entities.remove(this.tempPreviewEntity); - this.tempPreviewEntity = null; + + if (entity && entity.properties) { + const isMissionWaypoint = entity.properties.isMissionWaypoint; + const isMissionRouteLine = entity.properties.isMissionRouteLine; + return isMissionWaypoint || isMissionRouteLine; } - // 5. 重置其他状态(如测量面板、绘制状态) - this.measurementResult = null; - this.stopDrawing(); + return false; + }); + // 4. 清理可能残留的绘制过程中的临时图形 + if (this.tempEntity) { + this.viewer.entities.remove(this.tempEntity); + this.tempEntity = null; + } + if (this.tempPreviewEntity) { + this.viewer.entities.remove(this.tempPreviewEntity); + this.tempPreviewEntity = null; + } + // 5. 重置其他状态(如测量面板、绘制状态) + this.measurementResult = null; + this.stopDrawing(); - this.$message({ - type: 'success', - message: '清除成功!' - }); + this.$message({ + type: 'success', + message: '清除成功!' + }); }, // ================== 其他方法 ================== getTypeName(type) { const names = { point: '点', line: '线', - polygon: '多边形空域', - rectangle: '矩形空域', - circle: '圆形空域', + polygon: '面', + rectangle: '矩形', + circle: '圆形', ellipse: '椭圆', hold_circle: '圆形盘旋', hold_ellipse: '椭圆盘旋', - sector: '扇形空域', + sector: '扇形', arrow: '箭头', text: '文本', image: '图片' @@ -4487,13 +4846,12 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true break case 'line': const linePositions = entityData.data.points.map(p => Cesium.Cartesian3.fromDegrees(p.lng, p.lat)) - const straightLinePositions = this.getPolylinePositionsStraightIn2D(linePositions, false) entity = this.viewer.entities.add({ polyline: { - positions: straightLinePositions, + positions: linePositions, width: 3, - material: this.getPolylineSolidMaterial(color), - clampToGround: true + material: Cesium.Color.fromCssColorString(color), + arcType: Cesium.ArcType.NONE }, label: { text: entityData.label || '线', @@ -4517,7 +4875,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true outlineWidth: 2 }, label: { - text: entityData.label || '多边形空域', + text: entityData.label || '面', font: '14px sans-serif', fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.BLACK, @@ -4538,7 +4896,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true outlineWidth: 2 }, label: { - text: entityData.label || '矩形空域', + text: entityData.label || '矩形', font: '14px sans-serif', fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.BLACK, @@ -4566,7 +4924,7 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true outlineWidth: 2 }, label: { - text: entityData.label || '圆形空域', + text: entityData.label || '圆形', font: '14px sans-serif', fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.BLACK, @@ -4899,12 +5257,13 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true id: selectedLineEntity.id, name: selectedLineEntity.label, polyline: { - positions: this.getPolylinePositionsStraightIn2D(newPositions, false), + positions: newPositions, width: selectedLineEntity.width, - material: this.getPolylineSolidMaterial(selectedLineEntity.color), - clampToGround: true + material: Cesium.Color.fromCssColorString(selectedLineEntity.color), + arcType: Cesium.ArcType.NONE } }) + // 更新线实体的引用和位置数组 selectedLineEntity.entity = newEntity selectedLineEntity.positions = newPositions // 更新点数据 @@ -5007,10 +5366,6 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true this.scaleBarCleanup() this.scaleBarCleanup = null } - if (typeof this._resolutionScaleCleanup === 'function') { - this._resolutionScaleCleanup() - this._resolutionScaleCleanup = null - } if (this.viewer) { this.viewer.destroy() @@ -5137,13 +5492,13 @@ this.viewer.scene.postProcessStages.fxaa.enabled = true semiMinorAxis: radiusInMeters, semiMajorAxis: radiusInMeters, material: Cesium.Color.RED.withAlpha(0), - clampToGround: true + arcType: Cesium.ArcType.NONE }, polyline: { positions: circlePositions, width: 2, - material: this.getPolylineSolidMaterial('#FF0000'), - clampToGround: true + material: Cesium.Color.RED, + arcType: Cesium.ArcType.NONE } }); diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 418e33e..85b2764 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -15,6 +15,8 @@ :coordinateFormat="coordinateFormat" @draw-complete="handleMapDrawComplete" @drawing-points-update="missionDrawingPointsCount = $event" + @platform-route-drawing-started="missionDrawingActive = true" + @drawing-cancelled="missionDrawingActive = false" @open-waypoint-dialog="handleOpenWaypointEdit" @open-route-dialog="handleOpenRouteEdit" @scale-click="handleScaleClick" @@ -41,11 +43,17 @@ - + + + + + + 暂无方案,请先点击地图左侧红点展开菜单,选择「方案」并新建方案后再保存。 +