Browse Source

空域图形和威力区持久化

master
cuitw 1 month ago
parent
commit
632b131e95
  1. 14
      ruoyi-system/src/main/java/com/ruoyi/system/domain/Rooms.java
  2. 6
      ruoyi-system/src/main/resources/mapper/system/RoomsMapper.xml
  3. 562
      ruoyi-ui/src/views/cesiumMap/index.vue
  4. 61
      ruoyi-ui/src/views/childRoom/index.vue

14
ruoyi-system/src/main/java/com/ruoyi/system/domain/Rooms.java

@ -37,6 +37,9 @@ public class Rooms extends BaseEntity
@Excel(name = "K0基准时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date kAnchorTime;
/** 房间内空域/威力区等前端图形JSON */
private String frontendDrawings;
public void setId(Long id)
{
this.id = id;
@ -87,6 +90,16 @@ public class Rooms extends BaseEntity
return kAnchorTime;
}
public void setFrontendDrawings(String frontendDrawings)
{
this.frontendDrawings = frontendDrawings;
}
public String getFrontendDrawings()
{
return frontendDrawings;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@ -95,6 +108,7 @@ public class Rooms extends BaseEntity
.append("ownerId", getOwnerId())
.append("name", getName())
.append("kAnchorTime", getkAnchorTime())
.append("frontendDrawings", getFrontendDrawings())
.toString();
}
}

6
ruoyi-system/src/main/resources/mapper/system/RoomsMapper.xml

@ -10,10 +10,11 @@
<result property="ownerId" column="owner_id" />
<result property="name" column="name" />
<result property="kAnchorTime" column="k_anchor_time" />
<result property="frontendDrawings" column="frontend_drawings" />
</resultMap>
<sql id="selectRoomsVo">
select id, parent_id, owner_id, name, k_anchor_time from ry.rooms
select id, parent_id, owner_id, name, k_anchor_time, frontend_drawings from ry.rooms
</sql>
<select id="selectRoomsList" parameterType="Rooms" resultMap="RoomsResult">
@ -38,12 +39,14 @@
<if test="ownerId != null">owner_id,</if>
<if test="name != null and name != ''">name,</if>
<if test="kAnchorTime != null">k_anchor_time,</if>
<if test="frontendDrawings != null">frontend_drawings,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="parentId != null">#{parentId},</if>
<if test="ownerId != null">#{ownerId},</if>
<if test="name != null and name != ''">#{name},</if>
<if test="kAnchorTime != null">#{kAnchorTime},</if>
<if test="frontendDrawings != null">#{frontendDrawings},</if>
</trim>
</insert>
@ -54,6 +57,7 @@
<if test="ownerId != null">owner_id = #{ownerId},</if>
<if test="name != null and name != ''">name = #{name},</if>
<if test="kAnchorTime != null">k_anchor_time = #{kAnchorTime},</if>
<if test="frontendDrawings != null">frontend_drawings = #{frontendDrawings},</if>
</trim>
where id = #{id}
</update>

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

@ -168,6 +168,7 @@ export default {
//
allEntities: [], //
entityCounter: 0,
loadingDrawingsFromRoom: false,
selectedEntity: null, //
//
measurementResult: null,
@ -198,7 +199,7 @@ export default {
circle: { color: '#800080', opacity: 0, width: 4 },
sector: { color: '#FF6347', opacity: 0, width: 3 },
arrow: { color: '#FF0000', width: 6 },
text: { color: '#000000', font: '48px Microsoft YaHei, PingFang SC, sans-serif', backgroundColor: 'rgba(255, 255, 255, 0.8)' },
text: { color: '#000000', font: '14px PingFang SC, Microsoft YaHei, Helvetica Neue, sans-serif', backgroundColor: 'rgba(255, 255, 255, 0.92)' },
image: { width: 150, height: 150 }
},
//
@ -2030,6 +2031,7 @@ export default {
this.initHoverHandler()
this.initMouseCoordinates()
console.log('Cesium离线二维地图已加载')
this.$emit('viewer-ready')
// 1.
this.viewer.scene.postProcessStages.fxaa.enabled = true
@ -2999,6 +3001,7 @@ export default {
this.viewer.entities.remove(this.tempEntity)
this.tempEntity = null
}
this.notifyDrawingEntitiesChanged()
return entity
},
//
@ -3146,6 +3149,7 @@ export default {
};
// 6.
this.drawingPoints = [];
this.notifyDrawingEntitiesChanged();
},
//
calculateRectangleArea(rectangle) {
@ -3377,6 +3381,7 @@ export default {
// 6.
this.drawingPoints = [];
this.activeCursorPosition = null;
this.notifyDrawingEntitiesChanged();
},
ellipseHeadingFromCenterAndMajor(center, majorEnd) {
const ellipsoid = this.viewer.scene.globe.ellipsoid;
@ -3407,11 +3412,12 @@ export default {
if (this.tempPreviewEntity) this.viewer.entities.remove(this.tempPreviewEntity);
this.tempEntity = null;
this.tempPreviewEntity = null;
// 2.
// 2. requestRender requestRenderMode
this.drawingHandler.setInputAction((movement) => {
const newPosition = this.getClickPosition(movement.endPosition);
if (newPosition) {
this.activeCursorPosition = newPosition;
if (this.viewer.scene.requestRenderMode) this.viewer.scene.requestRender();
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 3.
@ -3557,6 +3563,7 @@ export default {
this.allEntities.push(entityData);
// 5.
this.drawingPoints = [];
this.notifyDrawingEntitiesChanged();
},
// 线
generateCirclePositions(center, radius, numPoints = 1024) {
@ -3671,11 +3678,12 @@ export default {
if (this.tempPreviewEntity) this.viewer.entities.remove(this.tempPreviewEntity);
this.tempEntity = null;
this.tempPreviewEntity = null;
// 2.
// 2. requestRender requestRenderMode
this.drawingHandler.setInputAction((movement) => {
const newPosition = this.getClickPosition(movement.endPosition);
if (newPosition) {
this.activeCursorPosition = newPosition;
if (this.viewer.scene.requestRenderMode) this.viewer.scene.requestRender();
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 3.
@ -3737,6 +3745,7 @@ export default {
const entity = this.addArrowEntity([...this.drawingPoints]);
//
this.drawingPoints = [];
this.notifyDrawingEntitiesChanged();
return entity;
} else {
this.cancelDrawing();
@ -3815,28 +3824,17 @@ export default {
text: text,
font: this.defaultStyles.text.font,
fillColor: Cesium.Color.fromCssColorString(this.defaultStyles.text.color),
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
outlineColor: Cesium.Color.fromCssColorString('#e5e5e5'),
outlineWidth: 1,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
backgroundColor: Cesium.Color.fromCssColorString(this.defaultStyles.text.backgroundColor),
backgroundPadding: new Cesium.Cartesian2(10, 5),
backgroundPadding: new Cesium.Cartesian2(8, 5),
pixelOffset: new Cesium.Cartesian2(0, 0),
//
scaleByDistance: new Cesium.NearFarScalar(
1000, //
1.0, //
500000, //
0.1 // 0
),
//
translucencyByDistance: new Cesium.NearFarScalar(
1000, //
1.0, //
500000, //
0.3 //
)
// 仿
scaleByDistance: new Cesium.NearFarScalar(200, 1.12, 1200000, 0.72),
translucencyByDistance: new Cesium.NearFarScalar(300, 1.0, 600000, 0.88)
}
})
const entityData = {
@ -3857,6 +3855,7 @@ export default {
this.selectEntity(entityData)
e.stopPropagation()
}
this.notifyDrawingEntitiesChanged()
return entityData
},
//
@ -3954,20 +3953,8 @@ export default {
height: this.defaultStyles.image.height,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
//
scaleByDistance: new Cesium.NearFarScalar(
1000, //
1.0, //
500000, //
0.1 // 0
),
//
translucencyByDistance: new Cesium.NearFarScalar(
1000, //
1.0, //
500000, //
0.3 //
)
scaleByDistance: new Cesium.NearFarScalar(200, 1.12, 1200000, 0.72),
translucencyByDistance: new Cesium.NearFarScalar(300, 1.0, 600000, 0.88)
}
})
const entityData = {
@ -3987,6 +3974,7 @@ export default {
this.selectEntity(entityData)
e.stopPropagation()
}
this.notifyDrawingEntitiesChanged()
return entityData
},
// ================== ==================
@ -4103,6 +4091,7 @@ export default {
this.selectEntity(entityData)
e.stopPropagation()
}
this.notifyDrawingEntitiesChanged()
return entityData
},
addRectangleEntity(coordinates) {
@ -4509,6 +4498,10 @@ export default {
}
//
this.updateEntityStyle(entityData)
// /
if (this.getDrawingEntityTypes().includes(entityData.type)) {
this.notifyDrawingEntitiesChanged()
}
//
this.contextMenu.visible = false
}
@ -4611,11 +4604,13 @@ export default {
this.viewer.entities.remove(entity.centerEntity)
}
//
const wasDrawingType = this.getDrawingEntityTypes().includes(entity.type)
this.allEntities.splice(index, 1)
//
if (this.selectedEntity && (this.selectedEntity.id === id || (this.selectedEntity.entity && this.selectedEntity.entity.id === id))) {
this.selectedEntity = null
}
if (wasDrawingType) this.notifyDrawingEntitiesChanged()
}
},
/** 根据房间平台图标记录 ID(serverId)移除地图上对应的平台图标(用于该平台已创建航线后删除其“占位”图标) */
@ -4629,6 +4624,25 @@ export default {
this.allEntities = this.allEntities.filter(e => !(e.type === 'platformIcon' && e.serverId === serverId))
},
clearAll(showConfirm = true) {
// /线
if (this.toolMode === 'airspace') {
if (showConfirm) {
this.$confirm('是否清除所有空域图形?(平台与航线将保留)', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.clearDrawingEntities();
this.notifyDrawingEntitiesChanged();
this.$message({ type: 'success', message: '已清除空域图形' });
}).catch(() => {});
} else {
this.clearDrawingEntities();
this.notifyDrawingEntitiesChanged();
}
return;
}
//
if (showConfirm) {
this.$confirm('是否清除所有编辑内容?', '提示', {
confirmButtonText: '确定',
@ -4728,11 +4742,174 @@ export default {
this.measurementResult = null;
this.stopDrawing();
this.notifyDrawingEntitiesChanged();
this.$message({
type: 'success',
message: '清除成功!'
});
},
// ================== / ==================
/** 需要持久化到方案的空域图形类型(不含平台图标、航线、测距点线) */
getDrawingEntityTypes() {
return ['polygon', 'rectangle', 'circle', 'sector', 'arrow', 'text', 'image', 'powerZone']
},
/** 空域/威力区图形增删时通知父组件,用于自动保存到房间(从房间加载时不触发) */
notifyDrawingEntitiesChanged() {
if (this.loadingDrawingsFromRoom) return
this.$emit('drawing-entities-changed')
},
/** 将单个实体序列化为可存储的 JSON 结构 */
serializeEntityForSave(entity) {
const base = { id: entity.id, type: entity.type, label: entity.label || '', color: entity.color || '#008aff' }
let data = {}
switch (entity.type) {
case 'point':
data = { lat: entity.lat, lng: entity.lng }; break
case 'line':
data = { points: entity.points || [] }; break
case 'polygon':
data = {
points: entity.points || [],
opacity: entity.opacity != null ? entity.opacity : 0,
width: entity.width != null ? entity.width : 4,
borderColor: entity.borderColor || entity.color
}; break
case 'rectangle':
if (entity.points && entity.points.length >= 2) {
const lngs = entity.points.map(p => p.lng)
const lats = entity.points.map(p => p.lat)
data = {
coordinates: { west: Math.min(...lngs), south: Math.min(...lats), east: Math.max(...lngs), north: Math.max(...lats) },
points: entity.points,
opacity: entity.opacity != null ? entity.opacity : 0,
width: entity.width != null ? entity.width : 4,
borderColor: entity.borderColor || entity.color
}
} else {
data = {
coordinates: entity.coordinates || {},
points: entity.points || [],
opacity: entity.opacity != null ? entity.opacity : 0,
width: entity.width != null ? entity.width : 4,
borderColor: entity.borderColor || entity.color
}
}
break
case 'circle':
case 'hold_circle':
data = {
center: entity.points && entity.points[0] ? entity.points[0] : (entity.positions && entity.positions[0] ? this.cartesianToLatLng(entity.positions[0]) : (entity.center && typeof entity.center.lat === 'number' ? entity.center : null)),
radius: entity.radius,
opacity: entity.opacity != null ? entity.opacity : 0,
width: entity.width != null ? entity.width : 4,
borderColor: entity.borderColor || entity.color
}; break
case 'ellipse':
case 'hold_ellipse':
data = {
center: entity.points && entity.points[0] ? entity.points[0] : (entity.positions && entity.positions[0] ? this.cartesianToLatLng(entity.positions[0]) : null),
semiMajorAxis: entity.semiMajorAxis,
semiMinorAxis: entity.semiMinorAxis,
headingDeg: entity.headingDeg != null ? entity.headingDeg : 0
}; break
case 'sector':
data = {
center: entity.center,
radius: entity.radius,
startAngle: entity.startAngle,
endAngle: entity.endAngle,
opacity: entity.opacity != null ? entity.opacity : 0,
width: entity.width != null ? entity.width : 3,
borderColor: entity.borderColor || entity.color
}; break
case 'arrow':
data = { points: entity.points || [] }; break
case 'text':
data = {
lat: entity.lat,
lng: entity.lng,
text: entity.text,
font: entity.font,
backgroundColor: entity.backgroundColor
}; break
case 'image':
data = {
lat: entity.lat,
lng: entity.lng,
imageUrl: entity.imageUrl,
width: entity.width,
height: entity.height
}; break
case 'powerZone': {
const center = entity.center && typeof entity.center.lat === 'number' ? entity.center : (entity.entity && entity.entity.position ? this.cartesianToLatLng(entity.entity.position.getValue(Cesium.JulianDate.now())) : null)
data = {
center,
radius: entity.radius,
name: entity.name,
color: entity.color,
opacity: entity.opacity,
borderColor: entity.borderColor,
width: entity.width
}; break
}
default:
data = entity.center != null ? { center: entity.center, radius: entity.radius } : {}
}
return { ...base, data }
},
/** 供父组件/方案保存时调用:仅返回空域与威力区图形数据(不含平台、航线等) */
getFrontendDrawingsData() {
const types = this.getDrawingEntityTypes()
const entities = (this.allEntities || [])
.filter(e => e && types.includes(e.type))
.map(e => this.serializeEntityForSave(e))
return { version: '1.0', date: new Date().toISOString(), entities }
},
/** 仅清除空域/威力区图形(不删平台图标、航线) */
clearDrawingEntities() {
if (!this.allEntities || !this.viewer) return
const types = this.getDrawingEntityTypes()
const toRemove = this.allEntities.filter(item => types.includes(item.type))
toRemove.forEach(item => {
try {
let entity = item.entity || (item instanceof Cesium.Entity ? item : null)
if (!entity && item.id) entity = this.viewer.entities.getById(item.id)
if (entity) this.viewer.entities.remove(entity)
if (item.type === 'powerZone' && item.centerEntity) this.viewer.entities.remove(item.centerEntity)
} catch (e) { console.warn('clearDrawingEntities:', e) }
})
this.allEntities = this.allEntities.filter(item => !types.includes(item.type))
},
/** 从房间/方案加载的 frontend_drawings JSON 恢复空域图形(先清空当前图形再导入;加载期间不触发自动保存) */
loadFrontendDrawings(data) {
if (!this.viewer) return
let payload = data
if (typeof data === 'string') {
try {
payload = JSON.parse(data)
} catch (e) {
console.warn('loadFrontendDrawings parse error', e)
return
}
}
this.loadingDrawingsFromRoom = true
try {
if (!payload || !Array.isArray(payload.entities) || payload.entities.length === 0) {
this.clearDrawingEntities()
return
}
this.clearDrawingEntities()
payload.entities.forEach(entityData => {
try {
this.importEntity(entityData)
} catch (err) {
console.warn('loadFrontendDrawings import entity failed', entityData?.type, err)
}
})
} finally {
this.loadingDrawingsFromRoom = false
}
},
// ================== ==================
getTypeName(type) {
const names = {
@ -4747,7 +4924,8 @@ export default {
sector: '扇形',
arrow: '箭头',
text: '文本',
image: '图片'
image: '图片',
powerZone: '威力区'
}
return names[type] || type
},
@ -4755,31 +4933,7 @@ export default {
const data = {
version: '1.0',
date: new Date().toISOString(),
entities: this.allEntities.map(entity => ({
id: entity.id,
type: entity.type,
label: entity.label,
color: entity.color,
data: entity.type === 'point' ? {
lat: entity.lat,
lng: entity.lng
} : entity.type === 'line' || entity.type === 'polygon' ? {
points: entity.points
} : entity.type === 'rectangle' ? {
coordinates: entity.coordinates
} : entity.type === 'ellipse' || entity.type === 'hold_ellipse' ? {
center: entity.points && entity.points[0] ? entity.points[0] : (entity.positions && entity.positions[0] ? this.cartesianToLatLng(entity.positions[0]) : null),
semiMajorAxis: entity.semiMajorAxis,
semiMinorAxis: entity.semiMinorAxis,
headingDeg: entity.headingDeg
} : (entity.type === 'circle' || entity.type === 'hold_circle') ? {
center: entity.points && entity.points[0] ? entity.points[0] : (entity.positions && entity.positions[0] ? this.cartesianToLatLng(entity.positions[0]) : null),
radius: entity.radius
} : {
center: entity.center,
radius: entity.radius
}
}))
entities: (this.allEntities || []).map(entity => this.serializeEntityForSave(entity))
}
const blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json'})
const url = URL.createObjectURL(blob)
@ -4864,77 +5018,96 @@ export default {
}
})
break
case 'polygon':
case 'polygon': {
const polygonPositions = entityData.data.points.map(p => Cesium.Cartesian3.fromDegrees(p.lng, p.lat))
const polyOpacity = entityData.data.opacity != null ? entityData.data.opacity : 0
const polyWidth = entityData.data.width != null ? entityData.data.width : 4
const polyBorderColor = entityData.data.borderColor || color
entity = this.viewer.entities.add({
polygon: {
hierarchy: polygonPositions,
material: Cesium.Color.fromCssColorString(color).withAlpha(0.5),
outline: true,
outlineColor: Cesium.Color.fromCssColorString(color),
outlineWidth: 2
material: Cesium.Color.fromCssColorString(color).withAlpha(polyOpacity)
},
label: {
text: entityData.label || '面',
font: '14px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
position: polygonPositions[0]
polyline: {
positions: [...polygonPositions, polygonPositions[0]],
width: polyWidth,
material: Cesium.Color.fromCssColorString(polyBorderColor),
arcType: Cesium.ArcType.NONE
}
})
break
case 'rectangle':
const rectCoords = entityData.data.coordinates
}
case 'rectangle': {
let rectCoords = entityData.data.coordinates
if (!rectCoords && entityData.data.points && entityData.data.points.length >= 2) {
const lngs = entityData.data.points.map(p => p.lng)
const lats = entityData.data.points.map(p => p.lat)
rectCoords = { west: Math.min(...lngs), south: Math.min(...lats), east: Math.max(...lngs), north: Math.max(...lats) }
}
if (!rectCoords) break
const rectOpacity = entityData.data.opacity != null ? entityData.data.opacity : 0
const rectWidth = entityData.data.width != null ? entityData.data.width : 4
const rectBorderColor = entityData.data.borderColor || color
const rectColor = Cesium.Color.fromCssColorString(color).withAlpha(rectOpacity)
const southwest = Cesium.Cartesian3.fromDegrees(rectCoords.west, rectCoords.south)
const southeast = Cesium.Cartesian3.fromDegrees(rectCoords.east, rectCoords.south)
const northeast = Cesium.Cartesian3.fromDegrees(rectCoords.east, rectCoords.north)
const northwest = Cesium.Cartesian3.fromDegrees(rectCoords.west, rectCoords.north)
const borderPositions = [southwest, southeast, northeast, northwest, southwest]
entity = this.viewer.entities.add({
rectangle: {
coordinates: Cesium.Rectangle.fromDegrees(rectCoords.west, rectCoords.south, rectCoords.east, rectCoords.north),
material: Cesium.Color.fromCssColorString(color).withAlpha(0.5),
outline: true,
outlineColor: Cesium.Color.fromCssColorString(color),
outlineWidth: 2
material: rectColor
},
label: {
text: entityData.label || '矩形',
font: '14px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
position: Cesium.Cartesian3.fromDegrees((rectCoords.west + rectCoords.east) / 2, (rectCoords.south + rectCoords.north) / 2)
polyline: {
positions: borderPositions,
width: rectWidth,
material: Cesium.Color.fromCssColorString(rectBorderColor),
arcType: Cesium.ArcType.NONE
}
})
break
case 'circle':
//
this.allEntities.push({
id: entity.id,
type: 'rectangle',
points: entityData.data.points || [],
coordinates: rectCoords,
entity,
color,
borderColor: rectBorderColor,
opacity: rectOpacity,
width: rectWidth,
label: entityData.label || '矩形'
})
return
}
case 'circle': {
const radius = entityData.data.radius || 1000
if (radius <= 0) {
this.$message.error('圆形半径必须大于0')
return
}
const circleCenter = Cesium.Cartesian3.fromDegrees(entityData.data.center.lng, entityData.data.center.lat)
const circlePositions = this.generateCirclePositions(circleCenter, radius)
const circleOpacity = entityData.data.opacity != null ? entityData.data.opacity : 0
const circleWidth = entityData.data.width != null ? entityData.data.width : 4
const circleBorderColor = entityData.data.borderColor || color
entity = this.viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(entityData.data.center.lng, entityData.data.center.lat),
position: circleCenter,
ellipse: {
semiMinorAxis: radius,
semiMajorAxis: radius,
material: Cesium.Color.fromCssColorString(color).withAlpha(0.5),
outline: true,
outlineColor: Cesium.Color.fromCssColorString(color),
outlineWidth: 2
semiMinorAxis: radius,
material: Cesium.Color.fromCssColorString(color).withAlpha(circleOpacity),
arcType: Cesium.ArcType.NONE
},
label: {
text: entityData.label || '圆形',
font: '14px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -10)
polyline: {
positions: circlePositions,
width: circleWidth,
material: Cesium.Color.fromCssColorString(circleBorderColor),
arcType: Cesium.ArcType.NONE
}
})
break
}
case 'hold_circle': {
const hcRadius = entityData.data.radius || 1000
if (hcRadius <= 0) {
@ -4999,6 +5172,182 @@ export default {
})
break
}
case 'sector': {
const d = entityData.data
if (!d || !d.center || d.radius == null) break
const center = Cesium.Cartesian3.fromDegrees(d.center.lng, d.center.lat)
const startAngle = d.startAngle != null ? d.startAngle : 0
const endAngle = d.endAngle != null ? d.endAngle : Math.PI * 0.5
const sectorOpacity = d.opacity != null ? d.opacity : 0
const sectorWidth = d.width != null ? d.width : 3
const sectorBorderColor = d.borderColor || color
const positions = this.generateSectorPositions(center, d.radius, startAngle, endAngle)
entity = this.viewer.entities.add({
polygon: {
hierarchy: new Cesium.PolygonHierarchy(positions),
material: Cesium.Color.fromCssColorString(color).withAlpha(sectorOpacity)
},
polyline: {
positions: positions,
width: sectorWidth,
material: Cesium.Color.fromCssColorString(sectorBorderColor),
arcType: Cesium.ArcType.NONE
}
})
this.allEntities.push({
id: entity.id,
type: 'sector',
center: d.center,
radius: d.radius,
startAngle,
endAngle,
positions,
entity,
color,
borderColor: sectorBorderColor,
opacity: sectorOpacity,
width: sectorWidth,
label: entityData.label || '扇形'
})
this.notifyDrawingEntitiesChanged()
return
}
case 'arrow': {
const pts = entityData.data.points
if (!pts || pts.length < 2) break
const arrowPositions = pts.map(p => Cesium.Cartesian3.fromDegrees(p.lng, p.lat))
entity = this.viewer.entities.add({
polyline: {
positions: arrowPositions,
width: 12,
material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.fromCssColorString(color)),
arcType: Cesium.ArcType.NONE,
widthInMeters: false
}
})
this.allEntities.push({
id: entity.id,
type: 'arrow',
points: pts,
positions: arrowPositions,
entity,
color,
label: entityData.label || '箭头',
width: entityData.data.width
})
this.notifyDrawingEntitiesChanged()
return
}
case 'text': {
const td = entityData.data
if (!td || td.lat == null || td.lng == null) break
const textPos = Cesium.Cartesian3.fromDegrees(td.lng, td.lat)
entity = this.viewer.entities.add({
position: textPos,
label: {
text: td.text != null ? td.text : (entityData.label || '文本'),
font: td.font || this.defaultStyles.text.font,
fillColor: Cesium.Color.fromCssColorString(color),
outlineColor: Cesium.Color.fromCssColorString('#e5e5e5'),
outlineWidth: 1,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
backgroundColor: td.backgroundColor ? Cesium.Color.fromCssColorString(td.backgroundColor) : Cesium.Color.fromCssColorString(this.defaultStyles.text.backgroundColor),
backgroundPadding: new Cesium.Cartesian2(8, 5),
scaleByDistance: new Cesium.NearFarScalar(200, 1.12, 1200000, 0.72),
translucencyByDistance: new Cesium.NearFarScalar(300, 1.0, 600000, 0.88)
}
})
this.allEntities.push({
id: entity.id,
type: 'text',
lat: td.lat,
lng: td.lng,
text: td.text,
position: textPos,
entity,
color,
font: td.font,
backgroundColor: td.backgroundColor,
label: entityData.label || '文本'
})
this.notifyDrawingEntitiesChanged()
return
}
case 'image': {
const imgData = entityData.data
if (!imgData || imgData.lat == null || imgData.lng == null || !imgData.imageUrl) break
const imgPos = Cesium.Cartesian3.fromDegrees(imgData.lng, imgData.lat)
entity = this.viewer.entities.add({
position: imgPos,
billboard: {
image: imgData.imageUrl,
width: imgData.width != null ? imgData.width : this.defaultStyles.image.width,
height: imgData.height != null ? imgData.height : this.defaultStyles.image.height,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
scaleByDistance: new Cesium.NearFarScalar(200, 1.12, 1200000, 0.72),
translucencyByDistance: new Cesium.NearFarScalar(300, 1.0, 600000, 0.88)
}
})
this.allEntities.push({
id: entity.id,
type: 'image',
lat: imgData.lat,
lng: imgData.lng,
imageUrl: imgData.imageUrl,
position: imgPos,
entity,
width: imgData.width,
height: imgData.height,
label: entityData.label || '图片'
})
this.notifyDrawingEntitiesChanged()
return
}
case 'powerZone': {
const pd = entityData.data
if (!pd || !pd.center || pd.radius == null) break
const pzCenter = Cesium.Cartesian3.fromDegrees(pd.center.lng, pd.center.lat)
const pzRadius = Math.max(1, pd.radius)
const circlePositions = this.generateCirclePositions(pzCenter, pzRadius)
entity = this.viewer.entities.add({
polygon: {
hierarchy: new Cesium.PolygonHierarchy(circlePositions),
material: Cesium.Color.TRANSPARENT,
outline: true,
outlineColor: Cesium.Color.fromCssColorString(pd.color || '#FF0000'),
outlineWidth: (pd.width != null ? pd.width : 2)
},
polyline: {
positions: circlePositions,
width: 2,
material: Cesium.Color.RED,
arcType: Cesium.ArcType.NONE
}
})
const centerEntity = this.viewer.entities.add({
position: pzCenter,
point: { pixelSize: 8, color: Cesium.Color.RED, outlineColor: Cesium.Color.WHITE, outlineWidth: 2 },
label: { text: pd.name || '威力区', font: '14px sans-serif', fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.BLACK, outlineWidth: 2, style: Cesium.LabelStyle.FILL_AND_OUTLINE, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, pixelOffset: new Cesium.Cartesian2(0, -12) }
})
this.allEntities.push({
id: entity.id,
type: 'powerZone',
entity,
centerEntity,
center: pd.center,
radius: pzRadius,
name: pd.name,
color: pd.color || '#FF0000',
opacity: pd.opacity,
borderColor: pd.borderColor,
width: pd.width
})
this.notifyDrawingEntitiesChanged()
return
}
}
if (entity) {
this.allEntities.push({
@ -5006,8 +5355,10 @@ export default {
type: entityData.type,
label: entityData.label,
color: color,
entity,
...entityData.data
})
if (this.getDrawingEntityTypes().includes(entityData.type)) this.notifyDrawingEntitiesChanged()
}
},
handleLocate() {
@ -5515,6 +5866,7 @@ export default {
borderColor: '#FF0000',
width: 2
});
this.notifyDrawingEntitiesChanged();
if (this.powerZoneCenterEntity && this.powerZoneCenterEntity.label) {
this.powerZoneCenterEntity.label.text = radiusData.name;

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

@ -21,7 +21,9 @@
@open-route-dialog="handleOpenRouteEdit"
@scale-click="handleScaleClick"
@platform-icon-updated="onPlatformIconUpdated"
@platform-icon-removed="onPlatformIconRemoved" />
@platform-icon-removed="onPlatformIconRemoved"
@viewer-ready="onViewerReady"
@drawing-entities-changed="onDrawingEntitiesChanged" />
<div v-show="!screenshotMode" class="map-overlay-text">
<!-- <i class="el-icon-location-outline text-3xl mb-2 block"></i> -->
<!-- <p>二维GIS地图区域</p>
@ -498,6 +500,7 @@ export default {
roomDetail: null,
showKTimeSetDialog: false,
kTimeForm: { dateTime: null },
saveRoomDrawingsTimer: null,
//
isMenuHidden: true, //
@ -1034,6 +1037,7 @@ export default {
this.plans = scenarioRes.rows.map(s => ({
id: s.id,
name: s.name,
frontendDrawings: s.frontendDrawings || null,
routes: []
}));
}
@ -1327,9 +1331,43 @@ export default {
getRoomDetail() {
if (!this.currentRoomId) return;
getRooms(this.currentRoomId).then(res => {
if (res.code === 200 && res.data) this.roomDetail = res.data;
if (res.code === 200 && res.data) {
this.roomDetail = res.data;
this.$nextTick(() => this.loadRoomDrawings());
}
}).catch(() => {});
},
/** 加载当前房间的空域/威力区图形(与房间 ID 绑定,进入该房间即显示) */
loadRoomDrawings() {
if (!this.roomDetail || !this.$refs.cesiumMap || typeof this.$refs.cesiumMap.loadFrontendDrawings !== 'function') return;
if (this.roomDetail.frontendDrawings) {
this.$refs.cesiumMap.loadFrontendDrawings(this.roomDetail.frontendDrawings);
} else {
this.$refs.cesiumMap.clearDrawingEntities();
}
},
/** 地图 viewer 就绪时加载当前房间图形(可能 getRoomDetail 尚未返回,此处再试一次) */
onViewerReady() {
this.loadRoomDrawings();
},
/** 空域/威力区图形增删时防抖自动保存到当前房间 */
onDrawingEntitiesChanged() {
if (!this.currentRoomId) return;
if (this.saveRoomDrawingsTimer) clearTimeout(this.saveRoomDrawingsTimer);
this.saveRoomDrawingsTimer = setTimeout(() => {
this.saveRoomDrawingsTimer = null;
this.saveRoomDrawingsToServer();
}, 600);
},
/** 将当前地图上的空域图形写入当前房间(静默保存,不弹成功提示;失败时抛出供调用方处理) */
async saveRoomDrawingsToServer() {
if (!this.currentRoomId || !this.$refs.cesiumMap) return;
if (typeof this.$refs.cesiumMap.getFrontendDrawingsData !== 'function') return;
const drawingsData = this.$refs.cesiumMap.getFrontendDrawingsData();
const frontendDrawingsStr = JSON.stringify(drawingsData);
await updateRooms({ id: this.currentRoomId, frontendDrawings: frontendDrawingsStr });
if (this.roomDetail) this.roomDetail.frontendDrawings = frontendDrawingsStr;
},
/** 将任意日期字符串格式化为 yyyy-MM-dd HH:mm:ss,供日期选择器使用 */
formatKTimeForPicker(val) {
if (!val) return null;
@ -1402,9 +1440,18 @@ export default {
this.activeMenu = item.id;
},
//
savePlan() {
this.$message.success('保存计划');
// /
async savePlan() {
if (!this.currentRoomId) {
this.$message.warning('请先进入任务房间');
return;
}
try {
await this.saveRoomDrawingsToServer();
this.$message.success('房间空域图形已保存');
} catch (e) {
this.$message.error('保存失败,请检查网络');
}
},
importPlanFile() {
@ -2586,11 +2633,11 @@ export default {
this.selectedRouteDetails = null;
this.activeRouteIds = [];
//
// /线
if (this.$refs.cesiumMap && this.$refs.cesiumMap.clearAllWaypoints) {
this.$refs.cesiumMap.clearAllWaypoints();
}
console.log(`>>> [切换成功] 已进入方案: ${plan.name},地图已清空,列表已展开。`);
console.log(`>>> [切换成功] 已进入方案: ${plan && plan.name},地图已清空,列表已展开。`);
},
/** 切换航线:实现多选/开关逻辑 */
async selectRoute(route) {

Loading…
Cancel
Save