|
|
|
@ -34,10 +34,13 @@ |
|
|
|
:visible="contextMenu.visible" |
|
|
|
:position="contextMenu.position" |
|
|
|
:entity-data="contextMenu.entityData" |
|
|
|
:pick-list="contextMenu.pickList" |
|
|
|
:pick-index="contextMenu.pickIndex" |
|
|
|
:route-locked="routeLocked" |
|
|
|
:detection-zone-visible="contextMenuZoneDetectionVisible" |
|
|
|
:power-zone-visible="contextMenuZonePowerVisible" |
|
|
|
@delete="deleteEntityFromContextMenu" |
|
|
|
@switch-pick="handleContextMenuSwitchPick" |
|
|
|
@update-property="updateEntityProperty" |
|
|
|
@edit-platform-position="openPlatformIconPositionDialog" |
|
|
|
@edit-platform-heading="openPlatformIconHeadingDialog" |
|
|
|
@ -47,6 +50,7 @@ |
|
|
|
@start-route-before-platform="handleStartRouteBeforePlatform" |
|
|
|
@start-route-after-platform="handleStartRouteAfterPlatform" |
|
|
|
@copy-route="handleCopyRouteFromMenu" |
|
|
|
@single-route-deduction="handleSingleRouteDeductionFromMenu" |
|
|
|
@edit-platform="openEditPlatformDialog" |
|
|
|
@detection-zone="openDetectionZoneDialog" |
|
|
|
@power-zone="openPowerZoneDialog" |
|
|
|
@ -55,6 +59,7 @@ |
|
|
|
@open-waypoint-dialog="handleContextMenuOpenWaypointDialog" |
|
|
|
@add-waypoint-at="handleAddWaypointAt" |
|
|
|
@toggle-waypoint-hold="handleToggleWaypointHold" |
|
|
|
@edit-hold-speed="handleEditHoldSpeed" |
|
|
|
@launch-missile="openLaunchMissileDialog" |
|
|
|
@adjust-airspace-position="startAirspacePositionEdit" |
|
|
|
/> |
|
|
|
@ -442,6 +447,10 @@ export default { |
|
|
|
position: { x: 0, y: 0 }, |
|
|
|
entityData: null |
|
|
|
}, |
|
|
|
/** 右键菜单高亮的空域实体(用于关闭时恢复样式) */ |
|
|
|
_contextMenuHighlightedEntityData: null, |
|
|
|
/** 高亮闪烁定时器 */ |
|
|
|
_contextMenuHighlightTimer: null, |
|
|
|
editPlatformLabelDialogVisible: false, |
|
|
|
editPlatformLabelForm: { |
|
|
|
routeId: null, |
|
|
|
@ -606,6 +615,7 @@ export default { |
|
|
|
copyPreviewMouseCartesian: null, |
|
|
|
// 在航点前/后增加航点:{ routeId, waypointIndex, mode: 'before'|'after', waypoints },预览折线实体 |
|
|
|
addWaypointContext: null, |
|
|
|
addWaypointSolidEntity: null, |
|
|
|
addWaypointPreviewEntity: null, |
|
|
|
// 空域位置调整:{ entityData },预览实体用 CallbackProperty 实时跟随鼠标(与测距绘制一致) |
|
|
|
airspacePositionEditContext: null, |
|
|
|
@ -701,10 +711,17 @@ export default { |
|
|
|
deep: true, |
|
|
|
immediate: true, |
|
|
|
handler(entities) { |
|
|
|
if (this.whiteboardMode && entities && entities.length > 0) { |
|
|
|
this.renderWhiteboardEntities(entities) |
|
|
|
if (this.whiteboardMode) { |
|
|
|
if (entities && entities.length > 0) { |
|
|
|
this.renderWhiteboardEntities(entities) |
|
|
|
} else { |
|
|
|
this.clearWhiteboardEntities() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
'contextMenu.visible'(val) { |
|
|
|
if (!val) this.clearAirspaceHighlight() |
|
|
|
} |
|
|
|
}, |
|
|
|
computed: { |
|
|
|
@ -2543,20 +2560,21 @@ export default { |
|
|
|
const nextPosCloned = nextPos ? Cesium.Cartesian3.clone(nextPos) : null; |
|
|
|
const routeIdHold = routeId; |
|
|
|
const that = this; |
|
|
|
const buildHoldPositions = (radiusOrEllipse) => { |
|
|
|
const buildHoldPositions = (radiusOrEllipse, centerOverride) => { |
|
|
|
const isCircleArg = typeof radiusOrEllipse === 'number'; |
|
|
|
const R = isCircleArg ? radiusOrEllipse : 0; |
|
|
|
const smj = isCircleArg ? defaultSemiMajor : (radiusOrEllipse.semiMajor ?? defaultSemiMajor); |
|
|
|
const smn = isCircleArg ? defaultSemiMinor : (radiusOrEllipse.semiMinor ?? defaultSemiMinor); |
|
|
|
const hd = isCircleArg ? defaultHeadingRad : ((radiusOrEllipse.headingDeg != null ? radiusOrEllipse.headingDeg * Math.PI / 180 : defaultHeadingRad)); |
|
|
|
const centerPos = centerOverride || currPosCloned; |
|
|
|
let entry; let exit; let centerForCircle; |
|
|
|
if (useCircle) { |
|
|
|
centerForCircle = that.getHoldCenterFromPrevNext(lastPosCloned, currPosCloned, R, clockwise); |
|
|
|
centerForCircle = that.getHoldCenterFromPrevNext(lastPosCloned, centerPos, R, clockwise); |
|
|
|
entry = that.getCircleTangentEntryPoint(centerForCircle, lastPosCloned, R, clockwise); |
|
|
|
exit = that.getCircleTangentExitPoint(centerForCircle, nextPosCloned || currPosCloned, R, clockwise); |
|
|
|
exit = that.getCircleTangentExitPoint(centerForCircle, nextPosCloned || centerPos, R, clockwise); |
|
|
|
} else { |
|
|
|
entry = that.getEllipseTangentEntryPoint(currPosCloned, lastPosCloned, smj, smn, hd, clockwise); |
|
|
|
exit = that.getEllipseTangentExitPoint(currPosCloned, nextPosCloned || currPosCloned, smj, smn, hd, clockwise); |
|
|
|
entry = that.getEllipseTangentEntryPoint(centerPos, lastPosCloned, smj, smn, hd, clockwise); |
|
|
|
exit = that.getEllipseTangentExitPoint(centerPos, nextPosCloned || centerPos, smj, smn, hd, clockwise); |
|
|
|
} |
|
|
|
let arcPoints; |
|
|
|
if (useCircle) { |
|
|
|
@ -2573,10 +2591,10 @@ export default { |
|
|
|
if (!arcPoints || arcPoints.length < 2) arcPoints = [Cesium.Cartesian3.clone(entry), Cesium.Cartesian3.clone(exit)]; |
|
|
|
return arcPoints; |
|
|
|
} else { |
|
|
|
const tEntry = that.cartesianToEllipseParam(currPosCloned, smj, smn, hd, entry); |
|
|
|
const tEntry = that.cartesianToEllipseParam(centerPos, smj, smn, hd, entry); |
|
|
|
const entryLocalAngle = Math.atan2(smn * Math.sin(tEntry), smj * Math.cos(tEntry)); |
|
|
|
const fullCirclePoints = that.getEllipseFullCircle(currPosCloned, smj, smn, hd, entryLocalAngle, clockwise, 128); |
|
|
|
arcPoints = that.buildEllipseHoldArc(currPosCloned, smj, smn, hd, entry, exit, clockwise, 120); |
|
|
|
const fullCirclePoints = that.getEllipseFullCircle(centerPos, smj, smn, hd, entryLocalAngle, clockwise, 128); |
|
|
|
arcPoints = that.buildEllipseHoldArc(centerPos, smj, smn, hd, entry, exit, clockwise, 120); |
|
|
|
return [entry, ...(fullCirclePoints || []).slice(1), ...(arcPoints || []).slice(1)]; |
|
|
|
} |
|
|
|
}; |
|
|
|
@ -2586,17 +2604,26 @@ export default { |
|
|
|
nextPosCloned, |
|
|
|
params && params.headingDeg != null ? params.headingDeg : 0 |
|
|
|
); |
|
|
|
const buildRaceTrackPositions = () => that.buildRaceTrackWithEntryExit(currPosCloned, lastPosCloned, nextPosCloned, raceTrackDirectionRad, edgeLengthM, arcRadiusM, clockwise, 24); |
|
|
|
const buildRaceTrackPositions = (centerOverride) => that.buildRaceTrackWithEntryExit(centerOverride || currPosCloned, lastPosCloned, nextPosCloned, raceTrackDirectionRad, edgeLengthM, arcRadiusM, clockwise, 24); |
|
|
|
const holdPositions = useCircle ? buildHoldPositions(radius) : buildRaceTrackPositions(); |
|
|
|
for (let k = 0; k < holdPositions.length; k++) finalPathPositions.push(holdPositions[k]); |
|
|
|
const wpIdHold = wp.id; |
|
|
|
const getHoldPositions = () => { |
|
|
|
let centerOverride = null; |
|
|
|
if (that.waypointDragging && that.waypointDragging.routeId === routeIdHold && that.waypointDragging.dbId === wpIdHold) { |
|
|
|
const wpEnt = that.viewer.entities.getById(`wp_${routeIdHold}_${wpIdHold}`); |
|
|
|
if (wpEnt && wpEnt.position) { |
|
|
|
const p = wpEnt.position.getValue(Cesium.JulianDate.now()); |
|
|
|
if (p) centerOverride = p; |
|
|
|
} |
|
|
|
} |
|
|
|
if (useCircle) { |
|
|
|
const R = (that._routeHoldRadiiByRoute && that._routeHoldRadiiByRoute[routeIdHold] && that._routeHoldRadiiByRoute[routeIdHold][legIndexHold] != null) |
|
|
|
? that._routeHoldRadiiByRoute[routeIdHold][legIndexHold] |
|
|
|
: turnRadiusForHold; |
|
|
|
return buildHoldPositions(R); |
|
|
|
return buildHoldPositions(R, centerOverride); |
|
|
|
} |
|
|
|
return buildRaceTrackPositions(); |
|
|
|
return buildRaceTrackPositions(centerOverride); |
|
|
|
}; |
|
|
|
this.viewer.entities.add({ |
|
|
|
id: `hold-line-${routeId}-${i}`, |
|
|
|
@ -2643,10 +2670,20 @@ export default { |
|
|
|
const currPosClonedForArc = Cesium.Cartesian3.clone(currPos); |
|
|
|
if (radius > 0) { |
|
|
|
const currPosCloned = Cesium.Cartesian3.clone(currPos); |
|
|
|
const routeIdCloned = routeId; |
|
|
|
const dbIdCloned = wp.id; |
|
|
|
const routeIdArc = routeId; |
|
|
|
const dbIdArc = wp.id; |
|
|
|
const that = this; |
|
|
|
const getArcPoints = () => that.computeArcPositions(lastPosCloned, currPosCloned, nextLogicalCloned, radius); |
|
|
|
const getArcPoints = () => { |
|
|
|
let centerPos = currPosCloned; |
|
|
|
if (that.waypointDragging && that.waypointDragging.routeId === routeIdArc && that.waypointDragging.dbId === dbIdArc) { |
|
|
|
const wpEnt = that.viewer.entities.getById(`wp_${routeIdArc}_${dbIdArc}`); |
|
|
|
if (wpEnt && wpEnt.position) { |
|
|
|
const p = wpEnt.position.getValue(Cesium.JulianDate.now()); |
|
|
|
if (p) centerPos = p; |
|
|
|
} |
|
|
|
} |
|
|
|
return that.computeArcPositions(lastPosCloned, centerPos, nextLogicalCloned, radius); |
|
|
|
}; |
|
|
|
this.viewer.entities.add({ |
|
|
|
id: `arc-line-${routeId}-${i}`, |
|
|
|
show: false, |
|
|
|
@ -2665,9 +2702,16 @@ export default { |
|
|
|
const arcWpOutline = wp.outlineColor != null ? wp.outlineColor : defaultWpOutline; |
|
|
|
[0, 1].forEach((idx) => { |
|
|
|
const suffix = idx === 0 ? '_entry' : '_exit'; |
|
|
|
const getPos = () => { const pts = getArcPoints(); return idx === 0 ? pts[0] : pts[pts.length - 1]; }; |
|
|
|
const entId = `wp_${routeId}_${wp.id}${suffix}`; |
|
|
|
const getPos = () => { |
|
|
|
if (that.waypointDragging && that.waypointDragging.entity && (that.waypointDragging.entity.id === entId) && that.waypointDragPreview && that.waypointDragPreview.routeId === routeIdArc && that.waypointDragPreview.dbId === dbIdArc) { |
|
|
|
return that.waypointDragPreview.position; |
|
|
|
} |
|
|
|
const pts = getArcPoints(); |
|
|
|
return idx === 0 ? pts[0] : pts[pts.length - 1]; |
|
|
|
}; |
|
|
|
this.viewer.entities.add({ |
|
|
|
id: `wp_${routeId}_${wp.id}${suffix}`, |
|
|
|
id: entId, |
|
|
|
name: wpName, |
|
|
|
position: new Cesium.CallbackProperty(getPos, false), |
|
|
|
properties: { isMissionWaypoint: true, routeId: routeId, dbId: wp.id }, |
|
|
|
@ -2693,6 +2737,11 @@ export default { |
|
|
|
const hitLineId = lineId + '-hit'; |
|
|
|
const hasHold = waypoints.some((wp) => that.isHoldWaypoint(wp)); |
|
|
|
const routePositionsCallback = new Cesium.CallbackProperty(function () { |
|
|
|
// 拖拽航点时优先使用实时实体位置,确保航线实时更新 |
|
|
|
if (that.waypointDragging && that.waypointDragging.routeId === routeId) { |
|
|
|
const livePos = that.getRouteLinePositionsFromWaypointEntities(routeId); |
|
|
|
if (livePos && livePos.length > 0) return livePos; |
|
|
|
} |
|
|
|
if (hasHold) { |
|
|
|
const pathPos = that.getRoutePathPositionsForLine(routeId); |
|
|
|
if (pathPos && pathPos.length > 0) return pathPos; |
|
|
|
@ -4689,8 +4738,10 @@ export default { |
|
|
|
const endLat = Cesium.Math.toDegrees(lat2) |
|
|
|
const endLng = Cesium.Math.toDegrees(lon2) |
|
|
|
|
|
|
|
const durationSec = d / 1000 |
|
|
|
const durationMinutes = durationSec / 60 |
|
|
|
// 与甘特图 GanttDrawer.loadMissiles 一致:按固定巡航速度推算飞行时长(勿用 d/1000 当秒数,会与甘特/标签严重偏差) |
|
|
|
const distanceKm = Number(distance) || 1000 |
|
|
|
const missileSpeedKmh = 1400 |
|
|
|
const durationMinutes = (distanceKm / missileSpeedKmh) * 60 |
|
|
|
const endK = launchK + durationMinutes |
|
|
|
|
|
|
|
const startPoint = Cesium.Cartesian3.fromDegrees(Number(entityData.lng), Number(entityData.lat), 1000) |
|
|
|
@ -4781,8 +4832,9 @@ export default { |
|
|
|
const lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(d / R) * Math.cos(lat1), Math.cos(d / R) - Math.sin(lat1) * Math.sin(lat2)) |
|
|
|
const endLng = Cesium.Math.toDegrees(lon2) |
|
|
|
const endLat = Cesium.Math.toDegrees(lat2) |
|
|
|
const durationSec = d / 1000 |
|
|
|
const durationMinutes = durationSec / 60 |
|
|
|
const distanceKm = Number(distance) || 1000 |
|
|
|
const missileSpeedKmh = 1400 |
|
|
|
const durationMinutes = (distanceKm / missileSpeedKmh) * 60 |
|
|
|
const endK = launchK + durationMinutes |
|
|
|
const startPoint = Cesium.Cartesian3.fromDegrees(startLng, startLat, 1000) |
|
|
|
const endPoint = Cesium.Cartesian3.fromDegrees(endLng, endLat, 0) |
|
|
|
@ -5154,10 +5206,13 @@ export default { |
|
|
|
} |
|
|
|
} |
|
|
|
if (this.addWaypointContext) { |
|
|
|
const cartesian = this.getClickPosition(movement.endPosition); |
|
|
|
const ctx = this.addWaypointContext; |
|
|
|
const refAlt = ctx.mode === 'before' |
|
|
|
? (ctx.waypoints[ctx.waypointIndex - 1] && Number(ctx.waypoints[ctx.waypointIndex - 1].alt)) || (ctx.waypoints[ctx.waypointIndex] && Number(ctx.waypoints[ctx.waypointIndex].alt)) || 5000 |
|
|
|
: (ctx.waypoints[ctx.waypointIndex] && Number(ctx.waypoints[ctx.waypointIndex].alt)) || (ctx.waypoints[ctx.waypointIndex + 1] && Number(ctx.waypoints[ctx.waypointIndex + 1].alt)) || 5000; |
|
|
|
const cartesian = this.getClickPositionWithHeight(movement.endPosition, refAlt); |
|
|
|
if (cartesian) { |
|
|
|
this.addWaypointContext.mouseCartesian = cartesian; |
|
|
|
this.updateAddWaypointPreview(); |
|
|
|
ctx.mouseCartesian = cartesian; |
|
|
|
if (this.viewer.scene.requestRender) this.viewer.scene.requestRender(); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -5256,6 +5311,11 @@ export default { |
|
|
|
// 隐藏之前可能显示的菜单 |
|
|
|
this.contextMenu.visible = false; |
|
|
|
|
|
|
|
// 使用 drillPick 获取重叠/接近位置的所有实体(11x11 像素拾取区域,便于选中重叠或接近的图形) |
|
|
|
const drillPicks = this.viewer.scene.drillPick(click.position, 20, 11, 11); |
|
|
|
const pickList = []; |
|
|
|
const seenEntities = new Set(); |
|
|
|
|
|
|
|
const pickedObject = this.viewer.scene.pick(click.position) |
|
|
|
if (Cesium.defined(pickedObject) && pickedObject.id) { |
|
|
|
const pickedEntity = pickedObject.id |
|
|
|
@ -5269,14 +5329,14 @@ export default { |
|
|
|
let platformId = 0 |
|
|
|
let platformName = '平台' |
|
|
|
if (pickedEntity.properties) { |
|
|
|
const now = Cesium.JulianDate.now(); |
|
|
|
// 尝试直接获取 |
|
|
|
if (pickedEntity.properties.platformId) { |
|
|
|
platformId = pickedEntity.properties.platformId.getValue ? pickedEntity.properties.platformId.getValue(now) : pickedEntity.properties.platformId |
|
|
|
} |
|
|
|
if (pickedEntity.properties.platformName) { |
|
|
|
platformName = pickedEntity.properties.platformName.getValue ? pickedEntity.properties.platformName.getValue(now) : pickedEntity.properties.platformName |
|
|
|
} |
|
|
|
const now = Cesium.JulianDate.now(); |
|
|
|
// 尝试直接获取 |
|
|
|
if (pickedEntity.properties.platformId) { |
|
|
|
platformId = pickedEntity.properties.platformId.getValue ? pickedEntity.properties.platformId.getValue(now) : pickedEntity.properties.platformId |
|
|
|
} |
|
|
|
if (pickedEntity.properties.platformName) { |
|
|
|
platformName = pickedEntity.properties.platformName.getValue ? pickedEntity.properties.platformName.getValue(now) : pickedEntity.properties.platformName |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
entityData = { |
|
|
|
@ -5431,11 +5491,23 @@ export default { |
|
|
|
if (dbId && dbId.getValue) dbId = dbId.getValue(); |
|
|
|
const ids = this._routeWaypointIdsByRoute && this._routeWaypointIdsByRoute[rId]; |
|
|
|
const waypointIndex = ids && dbId != null ? ids.indexOf(dbId) : -1; |
|
|
|
if (rId != null) entityData = { type: 'routeWaypoint', routeId: rId, dbId, waypointIndex }; |
|
|
|
const routeWps = this._routeWaypointsByRoute && this._routeWaypointsByRoute[rId]; |
|
|
|
const wp = routeWps && waypointIndex >= 0 ? routeWps[waypointIndex] : null; |
|
|
|
const hp = wp ? this.parseHoldParams(wp) : null; |
|
|
|
const holdSpeed = hp && hp.holdSpeed != null ? Number(hp.holdSpeed) : null; |
|
|
|
if (rId != null) entityData = { |
|
|
|
type: 'routeWaypoint', |
|
|
|
routeId: rId, |
|
|
|
dbId, |
|
|
|
waypointIndex, |
|
|
|
speed: wp && wp.speed != null ? Number(wp.speed) : null, |
|
|
|
holdSpeed, |
|
|
|
pointType: wp && (wp.pointType || wp.point_type) |
|
|
|
}; |
|
|
|
} else if (isLine) { |
|
|
|
let rId = props.routeId; |
|
|
|
if (rId && rId.getValue) rId = rId.getValue(); |
|
|
|
if (rId) entityData = { type: 'route', routeId: rId }; |
|
|
|
if (rId) entityData = {type: 'route', routeId: rId}; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -5445,26 +5517,69 @@ export default { |
|
|
|
if (parts.length >= 4) { |
|
|
|
const routeId = parts[2]; |
|
|
|
const segIdx = parseInt(parts[3], 10); |
|
|
|
if (!isNaN(segIdx)) entityData = { type: 'routeWaypoint', routeId, waypointIndex: segIdx, fromHold: true }; |
|
|
|
if (!isNaN(segIdx)) { |
|
|
|
const routeWps = this._routeWaypointsByRoute && this._routeWaypointsByRoute[routeId]; |
|
|
|
const holdWp = routeWps && routeWps[segIdx] ? routeWps[segIdx] : null; |
|
|
|
const hp = holdWp ? this.parseHoldParams(holdWp) : null; |
|
|
|
const holdSpeed = hp && hp.holdSpeed != null ? Number(hp.holdSpeed) : null; |
|
|
|
entityData = { |
|
|
|
type: 'routeWaypoint', |
|
|
|
routeId, |
|
|
|
waypointIndex: segIdx, |
|
|
|
dbId: holdWp && holdWp.id != null ? holdWp.id : null, |
|
|
|
speed: holdWp && holdWp.speed != null ? Number(holdWp.speed) : 800, |
|
|
|
holdSpeed, |
|
|
|
pointType: holdWp && (holdWp.pointType || holdWp.point_type), |
|
|
|
fromHold: true |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// 航线实体在 allEntities 中可能没有 routeId,从 id 解析 |
|
|
|
if (entityData && entityData.type === 'route' && entityData.id && !entityData.routeId) { |
|
|
|
entityData = { ...entityData, routeId: entityData.id.replace('route-line-', '') }; |
|
|
|
} |
|
|
|
if (entityData) { |
|
|
|
this.contextMenu = { |
|
|
|
visible: true, |
|
|
|
position: { |
|
|
|
x: click.position.x, |
|
|
|
y: click.position.y |
|
|
|
}, |
|
|
|
entityData: entityData |
|
|
|
}; |
|
|
|
entityData = {...entityData, routeId: entityData.id.replace('route-line-', '')}; |
|
|
|
} |
|
|
|
for (const pick of drillPicks) { |
|
|
|
const pickedEntity = pick.id || pick.object; |
|
|
|
if (!pickedEntity || seenEntities.has(pickedEntity)) continue; |
|
|
|
const entityData = this.resolveEntityDataFromPick(pickedEntity); |
|
|
|
if (entityData) { |
|
|
|
seenEntities.add(pickedEntity); |
|
|
|
pickList.push({entityData, pickedEntity}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (pickList.length > 0) { |
|
|
|
const { entityData, pickedEntity } = pickList[0]; |
|
|
|
const anchorCartesian = this.getContextMenuAnchorCartesian(entityData, pickedEntity, click.position); |
|
|
|
this.contextMenu = { |
|
|
|
visible: true, |
|
|
|
position: { x: click.position.x, y: click.position.y }, |
|
|
|
entityData, |
|
|
|
anchorCartesian, |
|
|
|
pickList, |
|
|
|
pickIndex: 0 |
|
|
|
}; |
|
|
|
if (['polygon', 'rectangle', 'circle', 'sector', 'auxiliaryLine', 'arrow'].includes(entityData.type)) { |
|
|
|
this.applyAirspaceHighlight(entityData); |
|
|
|
} |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK) |
|
|
|
|
|
|
|
// 监听场景渲染,使右键菜单随地图/图形移动(每帧更新,小范围移动也能跟随) |
|
|
|
const that = this; |
|
|
|
const updateContextMenuPosition = () => { |
|
|
|
if (!that.contextMenu.visible || !that.contextMenu.anchorCartesian || !that.viewer || !that.viewer.scene) return; |
|
|
|
try { |
|
|
|
const screenPos = Cesium.SceneTransforms.worldToWindowCoordinates(that.viewer.scene, that.contextMenu.anchorCartesian); |
|
|
|
if (Cesium.defined(screenPos)) { |
|
|
|
that.contextMenu.position = { x: screenPos.x, y: screenPos.y }; |
|
|
|
} |
|
|
|
} catch (e) {} |
|
|
|
}; |
|
|
|
this.viewer.scene.preRender.addEventListener(updateContextMenuPosition); |
|
|
|
this._contextMenuCameraListener = updateContextMenuPosition; |
|
|
|
}, |
|
|
|
|
|
|
|
openEditPlatformLabelDialog() { |
|
|
|
@ -5610,7 +5725,7 @@ export default { |
|
|
|
if (!entityData && idStr.startsWith('wb_')) { |
|
|
|
entityData = this.whiteboardEntityDataMap && this.whiteboardEntityDataMap[idStr]; |
|
|
|
} |
|
|
|
if (entityData) { |
|
|
|
if (entityData && entityData.type === 'platformIcon') { |
|
|
|
this.pendingDragIcon = entityData; |
|
|
|
this.dragStartScreenPos = { x: click.position.x, y: click.position.y }; |
|
|
|
return; |
|
|
|
@ -6109,6 +6224,12 @@ export default { |
|
|
|
this.addPointEntity(lat, lng) |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK) |
|
|
|
// 右键退出绘制状态 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
this.stopDrawing(); |
|
|
|
this.drawingMode = null; |
|
|
|
this.$message && this.$message.info('已退出绘制'); |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
// 绘制线 |
|
|
|
startLineDrawing() { |
|
|
|
@ -6234,7 +6355,7 @@ export default { |
|
|
|
}); |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|
|
|
// 3. 右键完成绘制 |
|
|
|
// 3. 右键完成绘制并退出绘制状态 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
// 移除临时实体 |
|
|
|
if (this.tempPreviewEntity) { |
|
|
|
@ -6257,6 +6378,9 @@ export default { |
|
|
|
} |
|
|
|
// 重置鼠标位置 |
|
|
|
this.activeCursorPosition = null; |
|
|
|
// 右键退出绘制状态 |
|
|
|
this.stopDrawing(); |
|
|
|
this.drawingMode = null; |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
finishLineDrawing() { |
|
|
|
@ -6362,7 +6486,7 @@ export default { |
|
|
|
} |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|
|
|
// 4. 右键完成绘制 |
|
|
|
// 4. 右键完成绘制并退出绘制状态 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
if (this.drawingPoints.length >= 3) { |
|
|
|
this.finishPolygonDrawing(); // 调用原有的完成逻辑 |
|
|
|
@ -6371,6 +6495,9 @@ export default { |
|
|
|
} |
|
|
|
// 重置状态 |
|
|
|
this.activeCursorPosition = null; |
|
|
|
// 右键退出绘制状态 |
|
|
|
this.stopDrawing(); |
|
|
|
this.drawingMode = null; |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
finishPolygonDrawing() { |
|
|
|
@ -6480,9 +6607,11 @@ export default { |
|
|
|
} |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|
|
|
// 4. 右键取消 |
|
|
|
// 4. 右键取消并退出绘制状态 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
this.cancelDrawing(); |
|
|
|
this.stopDrawing(); |
|
|
|
this.drawingMode = null; |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
finishRectangleDrawing() { |
|
|
|
@ -6737,10 +6866,12 @@ export default { |
|
|
|
console.warn('Mouse click error:', e); |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|
|
|
// 5. 右键取消 |
|
|
|
// 5. 右键取消并退出绘制状态 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
try { |
|
|
|
this.cancelDrawing(); |
|
|
|
this.stopDrawing(); |
|
|
|
this.drawingMode = null; |
|
|
|
} catch (e) { |
|
|
|
console.warn('Right click error:', e); |
|
|
|
} |
|
|
|
@ -6945,9 +7076,11 @@ export default { |
|
|
|
} |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|
|
|
// 4. 右键取消 |
|
|
|
// 4. 右键取消并退出绘制状态 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
this.cancelDrawing(); |
|
|
|
this.stopDrawing(); |
|
|
|
this.drawingMode = null; |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
// 完成扇形绘制 |
|
|
|
@ -7181,6 +7314,8 @@ export default { |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK) |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
this.cancelDrawing() |
|
|
|
this.stopDrawing(); |
|
|
|
this.drawingMode = null; |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK) |
|
|
|
}, |
|
|
|
finishAuxiliaryLineDrawing() { |
|
|
|
@ -7301,9 +7436,11 @@ export default { |
|
|
|
} |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|
|
|
// 4. 右键取消 |
|
|
|
// 4. 右键取消并退出绘制状态 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
this.cancelDrawing(); |
|
|
|
this.stopDrawing(); |
|
|
|
this.drawingMode = null; |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
// 完成箭头绘制 |
|
|
|
@ -7391,9 +7528,11 @@ export default { |
|
|
|
} |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|
|
|
// 3. 右键取消 |
|
|
|
// 3. 右键取消并退出绘制状态 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
this.cancelDrawing(); |
|
|
|
this.stopDrawing(); |
|
|
|
this.drawingMode = null; |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
// 添加文本实体 |
|
|
|
@ -7482,9 +7621,11 @@ export default { |
|
|
|
fileInput.click(); |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|
|
|
// 3. 右键取消 |
|
|
|
// 3. 右键取消并退出绘制状态 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
this.cancelDrawing(); |
|
|
|
this.stopDrawing(); |
|
|
|
this.drawingMode = null; |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
// 上传图片 |
|
|
|
@ -8156,6 +8297,18 @@ export default { |
|
|
|
this.contextMenu.visible = false; |
|
|
|
this.$emit('copy-route', ed.routeId); |
|
|
|
}, |
|
|
|
|
|
|
|
/** 右键「单条航线推演」:弹出时间轴,仅推演该航线 */ |
|
|
|
handleSingleRouteDeductionFromMenu(routeId) { |
|
|
|
const ed = this.contextMenu.entityData; |
|
|
|
const rid = routeId != null ? routeId : (ed && ed.routeId); |
|
|
|
if (rid == null) { |
|
|
|
this.contextMenu.visible = false; |
|
|
|
return; |
|
|
|
} |
|
|
|
this.contextMenu.visible = false; |
|
|
|
this.$emit('single-route-deduction', rid); |
|
|
|
}, |
|
|
|
/** 开始航线复制预览:整条航线跟随鼠标,左键放置后父组件弹窗保存 */ |
|
|
|
startRouteCopyPreview(waypoints) { |
|
|
|
if (!waypoints || waypoints.length < 2) return; |
|
|
|
@ -8219,6 +8372,10 @@ export default { |
|
|
|
this.contextMenu.visible = false; |
|
|
|
this.$emit('toggle-waypoint-hold', payload); |
|
|
|
}, |
|
|
|
handleEditHoldSpeed(payload) { |
|
|
|
this.contextMenu.visible = false; |
|
|
|
this.$emit('edit-hold-speed', payload); |
|
|
|
}, |
|
|
|
/** 开始“在航点前/后增加航点”模式:显示预览折线,左键放置、右键取消。segmentMode 可选:null(默认)、fixed_time(定时点)、fixed_speed(定速点)。segmentTargetMinutes/segmentTargetSpeed 为用户填写的固定值。 */ |
|
|
|
startAddWaypointAt(routeId, waypointIndex, mode, waypoints, segmentMode, segmentTargetMinutes, segmentTargetSpeed) { |
|
|
|
if (!waypoints || waypoints.length === 0) return; |
|
|
|
@ -8240,7 +8397,7 @@ export default { |
|
|
|
this.$message && this.$message.info(`${mode === 'before' ? '在当前位置前' : '在当前位置后'}插入${modeLabel},点击地图放置,右键取消`); |
|
|
|
this.updateAddWaypointPreview(); |
|
|
|
}, |
|
|
|
/** 更新“增加航点”预览折线:上一/当前/下一与鼠标位置连线,含盘旋段视觉效果 */ |
|
|
|
/** 更新“增加航点”预览折线:与新建航线一致,已确定段实线 + 最后一段到鼠标的虚线实时预览 */ |
|
|
|
updateAddWaypointPreview() { |
|
|
|
const ctx = this.addWaypointContext; |
|
|
|
if (!ctx || !this.viewer || !ctx.waypoints || ctx.waypoints.length === 0) return; |
|
|
|
@ -8252,30 +8409,48 @@ export default { |
|
|
|
const alt = Number(wp.alt) || 5000; |
|
|
|
return Cesium.Cartesian3.fromDegrees(lon, lat, alt); |
|
|
|
}; |
|
|
|
let positions = []; |
|
|
|
const color = Cesium.Color.fromCssColorString('#64748b'); |
|
|
|
const that = this; |
|
|
|
// 1. 已确定段:实线(未被修改的航段) |
|
|
|
if (this.addWaypointSolidEntity) { |
|
|
|
this.viewer.entities.remove(this.addWaypointSolidEntity); |
|
|
|
this.addWaypointSolidEntity = null; |
|
|
|
} |
|
|
|
let solidPositions = []; |
|
|
|
if (ctx.mode === 'before') { |
|
|
|
const prev = idx > 0 ? toCartesian(waypoints[idx - 1]) : null; |
|
|
|
const curr = toCartesian(waypoints[idx]); |
|
|
|
if (ctx.mouseCartesian) { |
|
|
|
if (prev) positions = [prev, ctx.mouseCartesian, curr]; |
|
|
|
else positions = [ctx.mouseCartesian, curr]; |
|
|
|
} |
|
|
|
solidPositions = waypoints.slice(0, idx).map(wp => toCartesian(wp)); |
|
|
|
} else { |
|
|
|
const curr = toCartesian(waypoints[idx]); |
|
|
|
const next = idx + 1 < waypoints.length ? toCartesian(waypoints[idx + 1]) : null; |
|
|
|
if (ctx.mouseCartesian) { |
|
|
|
if (next) positions = [curr, ctx.mouseCartesian, next]; |
|
|
|
else positions = [curr, ctx.mouseCartesian]; |
|
|
|
} |
|
|
|
solidPositions = waypoints.slice(0, idx + 1).map(wp => toCartesian(wp)); |
|
|
|
} |
|
|
|
if (positions.length < 2) return; |
|
|
|
if (solidPositions.length >= 2) { |
|
|
|
this.addWaypointSolidEntity = this.viewer.entities.add({ |
|
|
|
polyline: { |
|
|
|
positions: solidPositions, |
|
|
|
width: 2, |
|
|
|
material: color, |
|
|
|
arcType: Cesium.ArcType.NONE |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
// 2. 实时预览段:虚线(从最后一逻辑点到鼠标),使用 CallbackProperty 跟随鼠标 |
|
|
|
if (this.addWaypointPreviewEntity) { |
|
|
|
this.viewer.entities.remove(this.addWaypointPreviewEntity); |
|
|
|
this.addWaypointPreviewEntity = null; |
|
|
|
} |
|
|
|
const getAnchor = () => (ctx.mode === 'before' ? (idx > 0 ? toCartesian(waypoints[idx - 1]) : null) : toCartesian(waypoints[idx])); |
|
|
|
const getNext = () => (ctx.mode === 'before' ? toCartesian(waypoints[idx]) : (idx + 1 < waypoints.length ? toCartesian(waypoints[idx + 1]) : null)); |
|
|
|
this.addWaypointPreviewEntity = this.viewer.entities.add({ |
|
|
|
polyline: { |
|
|
|
positions: positions, |
|
|
|
positions: new Cesium.CallbackProperty(() => { |
|
|
|
const anchor = getAnchor(); |
|
|
|
const next = getNext(); |
|
|
|
const mouse = that.addWaypointContext && that.addWaypointContext.mouseCartesian; |
|
|
|
if (!mouse) return anchor && next ? [anchor, next] : (anchor ? [anchor, anchor] : (next ? [next, next] : [])); |
|
|
|
if (anchor && next) return [anchor, mouse, next]; |
|
|
|
if (anchor) return [anchor, mouse]; |
|
|
|
if (next) return [mouse, next]; |
|
|
|
return [mouse, mouse]; |
|
|
|
}, false), |
|
|
|
width: 2, |
|
|
|
material: new Cesium.PolylineDashMaterialProperty({ |
|
|
|
color: Cesium.Color.fromCssColorString('#64748b'), |
|
|
|
@ -8287,6 +8462,10 @@ export default { |
|
|
|
}, |
|
|
|
/** 清除“增加航点”模式及预览折线 */ |
|
|
|
clearAddWaypointContext() { |
|
|
|
if (this.addWaypointSolidEntity) { |
|
|
|
this.viewer.entities.remove(this.addWaypointSolidEntity); |
|
|
|
this.addWaypointSolidEntity = null; |
|
|
|
} |
|
|
|
if (this.addWaypointPreviewEntity) { |
|
|
|
this.viewer.entities.remove(this.addWaypointPreviewEntity); |
|
|
|
this.addWaypointPreviewEntity = null; |
|
|
|
@ -8603,6 +8782,195 @@ export default { |
|
|
|
return null; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 从拾取的实体解析出 entityData(供 drillPick 等多选场景复用) */ |
|
|
|
resolveEntityDataFromPick(pickedEntity) { |
|
|
|
if (!pickedEntity) return null; |
|
|
|
const idStr = typeof pickedEntity.id === 'string' ? pickedEntity.id : (pickedEntity.id || ''); |
|
|
|
let entityData = null; |
|
|
|
if (idStr.startsWith('route-platform-')) { |
|
|
|
const routeId = idStr.replace('route-platform-', '').replace('route-platform-label-', ''); |
|
|
|
let platformId = 0, platformName = '平台'; |
|
|
|
if (pickedEntity.properties) { |
|
|
|
const now = Cesium.JulianDate.now(); |
|
|
|
if (pickedEntity.properties.platformId) platformId = pickedEntity.properties.platformId.getValue ? pickedEntity.properties.platformId.getValue(now) : pickedEntity.properties.platformId; |
|
|
|
if (pickedEntity.properties.platformName) platformName = pickedEntity.properties.platformName.getValue ? pickedEntity.properties.platformName.getValue(now) : pickedEntity.properties.platformName; |
|
|
|
} |
|
|
|
entityData = { type: 'routePlatform', routeId, entity: pickedEntity, platformId, platformName, labelVisible: this.routeLabelVisible[routeId] !== false }; |
|
|
|
const now = Cesium.JulianDate.now(); |
|
|
|
if (pickedEntity.position) { |
|
|
|
const pos = pickedEntity.position.getValue ? pickedEntity.position.getValue(now) : pickedEntity.position; |
|
|
|
if (pos) { |
|
|
|
const ll = this.cartesianToLatLng(pos); |
|
|
|
if (ll) { entityData.lat = ll.lat; entityData.lng = ll.lng; } |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (!entityData && idStr.startsWith('wb_')) entityData = this.whiteboardEntityDataMap && this.whiteboardEntityDataMap[idStr]; |
|
|
|
if (!entityData) { |
|
|
|
entityData = this.allEntities.find(e => e.entity === pickedEntity || e === pickedEntity); |
|
|
|
if (!entityData) { |
|
|
|
for (const lineEntity of this.allEntities) { |
|
|
|
if (lineEntity.type === 'line' && lineEntity.pointEntities && lineEntity.pointEntities.includes(pickedEntity)) { |
|
|
|
entityData = lineEntity; break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (!entityData) { |
|
|
|
for (const powerZoneEntity of this.allEntities) { |
|
|
|
if (powerZoneEntity.type === 'powerZone' && powerZoneEntity.centerEntity === pickedEntity) { |
|
|
|
entityData = powerZoneEntity; break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (!entityData && pickedEntity.properties) { |
|
|
|
const now = Cesium.JulianDate.now(); |
|
|
|
const props = pickedEntity.properties.getValue ? pickedEntity.properties.getValue(now) : null; |
|
|
|
if (props) { |
|
|
|
const isWp = props.isMissionWaypoint && props.isMissionWaypoint.getValue ? props.isMissionWaypoint.getValue() : props.isMissionWaypoint; |
|
|
|
const isLine = props.isMissionRouteLine && props.isMissionRouteLine.getValue ? props.isMissionRouteLine.getValue() : props.isMissionRouteLine; |
|
|
|
if (isWp) { |
|
|
|
let rId = props.routeId; if (rId && rId.getValue) rId = rId.getValue(); |
|
|
|
let dbId = props.dbId; if (dbId && dbId.getValue) dbId = dbId.getValue(); |
|
|
|
const ids = this._routeWaypointIdsByRoute && this._routeWaypointIdsByRoute[rId]; |
|
|
|
const waypointIndex = ids && dbId != null ? ids.indexOf(dbId) : -1; |
|
|
|
const routeWps = this._routeWaypointsByRoute && this._routeWaypointsByRoute[rId]; |
|
|
|
const wp = routeWps && waypointIndex >= 0 ? routeWps[waypointIndex] : null; |
|
|
|
const hp = wp ? this.parseHoldParams(wp) : null; |
|
|
|
const holdSpeed = hp && hp.holdSpeed != null ? Number(hp.holdSpeed) : null; |
|
|
|
if (rId != null) entityData = { |
|
|
|
type: 'routeWaypoint', |
|
|
|
routeId: rId, |
|
|
|
dbId, |
|
|
|
waypointIndex, |
|
|
|
speed: wp && wp.speed != null ? Number(wp.speed) : null, |
|
|
|
holdSpeed, |
|
|
|
pointType: wp && (wp.pointType || wp.point_type) |
|
|
|
}; |
|
|
|
} else if (isLine) { |
|
|
|
let rId = props.routeId; if (rId && rId.getValue) rId = rId.getValue(); |
|
|
|
if (rId) entityData = { type: 'route', routeId: rId }; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (!entityData && idStr.startsWith('hold-line-')) { |
|
|
|
const parts = idStr.split('-'); |
|
|
|
if (parts.length >= 4) { |
|
|
|
const routeId = parts[2]; |
|
|
|
const segIdx = parseInt(parts[3], 10); |
|
|
|
if (!isNaN(segIdx)) { |
|
|
|
const routeWps = this._routeWaypointsByRoute && this._routeWaypointsByRoute[routeId]; |
|
|
|
const holdWp = routeWps && routeWps[segIdx] ? routeWps[segIdx] : null; |
|
|
|
const hp = holdWp ? this.parseHoldParams(holdWp) : null; |
|
|
|
const holdSpeed = hp && hp.holdSpeed != null ? Number(hp.holdSpeed) : null; |
|
|
|
entityData = { |
|
|
|
type: 'routeWaypoint', |
|
|
|
routeId, |
|
|
|
waypointIndex: segIdx, |
|
|
|
dbId: holdWp && holdWp.id != null ? holdWp.id : null, |
|
|
|
speed: holdWp && holdWp.speed != null ? Number(holdWp.speed) : 800, |
|
|
|
holdSpeed, |
|
|
|
pointType: holdWp && (holdWp.pointType || holdWp.point_type), |
|
|
|
fromHold: true |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (entityData && entityData.type === 'route' && entityData.id && !entityData.routeId) { |
|
|
|
entityData = { ...entityData, routeId: entityData.id.replace('route-line-', '') }; |
|
|
|
} |
|
|
|
return entityData; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 为空域/辅助线/箭头实体应用高亮闪烁(右键选中时,保持原色,出现与消失闪烁) */ |
|
|
|
applyAirspaceHighlight(entityData) { |
|
|
|
if (!entityData || !entityData.entity) return; |
|
|
|
const types = ['polygon', 'rectangle', 'circle', 'sector', 'auxiliaryLine', 'arrow']; |
|
|
|
if (!types.includes(entityData.type)) return; |
|
|
|
this.clearAirspaceHighlight(); |
|
|
|
const entity = entityData.entity; |
|
|
|
this._contextMenuHighlightedEntityData = entityData; |
|
|
|
let visible = true; |
|
|
|
this._contextMenuHighlightTimer = setInterval(() => { |
|
|
|
if (!this._contextMenuHighlightedEntityData || this._contextMenuHighlightedEntityData !== entityData) return; |
|
|
|
visible = !visible; |
|
|
|
try { |
|
|
|
entity.show = visible; |
|
|
|
if (this.viewer && this.viewer.scene && this.viewer.scene.requestRender) { |
|
|
|
this.viewer.scene.requestRender(); |
|
|
|
} |
|
|
|
} catch (e) {} |
|
|
|
}, 350); |
|
|
|
}, |
|
|
|
|
|
|
|
/** 清除空域高亮,恢复显示 */ |
|
|
|
clearAirspaceHighlight() { |
|
|
|
if (this._contextMenuHighlightTimer) { |
|
|
|
clearInterval(this._contextMenuHighlightTimer); |
|
|
|
this._contextMenuHighlightTimer = null; |
|
|
|
} |
|
|
|
const entityData = this._contextMenuHighlightedEntityData; |
|
|
|
if (!entityData || !entityData.entity) { |
|
|
|
this._contextMenuHighlightedEntityData = null; |
|
|
|
return; |
|
|
|
} |
|
|
|
try { |
|
|
|
entityData.entity.show = true; |
|
|
|
} catch (e) { console.warn('clearAirspaceHighlight:', e); } |
|
|
|
this._contextMenuHighlightedEntityData = null; |
|
|
|
if (this.viewer && this.viewer.scene && this.viewer.scene.requestRender) { |
|
|
|
this.viewer.scene.requestRender(); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
/** 获取右键菜单的锚点(笛卡尔坐标),用于随地图移动时更新菜单位置 */ |
|
|
|
getContextMenuAnchorCartesian(entityData, pickedEntity, clickPosition) { |
|
|
|
if (!entityData || !this.viewer || !this.viewer.scene) return null; |
|
|
|
const now = Cesium.JulianDate.now(); |
|
|
|
// 空域图形:使用中心点 |
|
|
|
const airspaceCenter = this.getAirspaceCenter(entityData); |
|
|
|
if (airspaceCenter) return airspaceCenter; |
|
|
|
// 有 entity 且有 position 的实体 |
|
|
|
const ent = entityData.entity || pickedEntity; |
|
|
|
if (ent && ent.position) { |
|
|
|
try { |
|
|
|
const pos = ent.position.getValue ? ent.position.getValue(now) : ent.position; |
|
|
|
if (pos) return Cesium.Cartesian3.clone(pos); |
|
|
|
} catch (e) {} |
|
|
|
} |
|
|
|
// line:使用线段中心 |
|
|
|
if (entityData.type === 'line' && entityData.positions && entityData.positions.length > 0) { |
|
|
|
const sum = entityData.positions.reduce((acc, p) => Cesium.Cartesian3.add(acc, p, new Cesium.Cartesian3()), new Cesium.Cartesian3()); |
|
|
|
return Cesium.Cartesian3.divideByScalar(sum, entityData.positions.length, new Cesium.Cartesian3()); |
|
|
|
} |
|
|
|
// point:有 position |
|
|
|
if (entityData.type === 'point' && entityData.position) { |
|
|
|
return Cesium.Cartesian3.clone(entityData.position); |
|
|
|
} |
|
|
|
if (entityData.type === 'point' && (entityData.lat != null || entityData.lng != null)) { |
|
|
|
return Cesium.Cartesian3.fromDegrees(entityData.lng || 0, entityData.lat || 0); |
|
|
|
} |
|
|
|
// powerZone:圆心 |
|
|
|
if (entityData.type === 'powerZone' && entityData.centerEntity && entityData.centerEntity.position) { |
|
|
|
try { |
|
|
|
const pos = entityData.centerEntity.position.getValue ? entityData.centerEntity.position.getValue(now) : entityData.centerEntity.position; |
|
|
|
if (pos) return Cesium.Cartesian3.clone(pos); |
|
|
|
} catch (e) {} |
|
|
|
} |
|
|
|
if (entityData.type === 'powerZone' && entityData.center) { |
|
|
|
const c = entityData.center; |
|
|
|
if (typeof c.lng === 'number' && typeof c.lat === 'number') { |
|
|
|
return Cesium.Cartesian3.fromDegrees(c.lng, c.lat); |
|
|
|
} |
|
|
|
} |
|
|
|
// 兜底:使用点击位置对应的地表坐标 |
|
|
|
if (clickPosition) { |
|
|
|
const cartesian = this.viewer.camera.pickEllipsoid(clickPosition, this.viewer.scene.globe.ellipsoid); |
|
|
|
if (cartesian) return cartesian; |
|
|
|
} |
|
|
|
return null; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 应用空域位置调整并退出模式 */ |
|
|
|
applyAirspacePositionEdit(newCenter) { |
|
|
|
const ctx = this.airspacePositionEditContext; |
|
|
|
@ -10237,6 +10605,21 @@ export default { |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
/** 右键菜单:切换选择重叠/接近的图形 */ |
|
|
|
handleContextMenuSwitchPick() { |
|
|
|
const pickList = this.contextMenu.pickList; |
|
|
|
if (!pickList || pickList.length <= 1) return; |
|
|
|
const nextIndex = ((this.contextMenu.pickIndex || 0) + 1) % pickList.length; |
|
|
|
const { entityData, pickedEntity } = pickList[nextIndex]; |
|
|
|
const anchorCartesian = this.getContextMenuAnchorCartesian(entityData, pickedEntity, null); |
|
|
|
this.contextMenu.entityData = entityData; |
|
|
|
this.contextMenu.pickIndex = nextIndex; |
|
|
|
if (anchorCartesian) this.contextMenu.anchorCartesian = anchorCartesian; |
|
|
|
if (['polygon', 'rectangle', 'circle', 'sector', 'auxiliaryLine', 'arrow'].includes(entityData.type)) { |
|
|
|
this.applyAirspaceHighlight(entityData); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 从右键菜单删除实体 |
|
|
|
deleteEntityFromContextMenu() { |
|
|
|
if (this.contextMenu.entityData) { |
|
|
|
@ -12453,6 +12836,7 @@ export default { |
|
|
|
} |
|
|
|
}, |
|
|
|
destroyViewer() { |
|
|
|
this.clearAirspaceHighlight(); |
|
|
|
this.stopDrawing() |
|
|
|
this.clearAll(false) |
|
|
|
if (this.entityClickDebounceTimer) { |
|
|
|
@ -12484,6 +12868,10 @@ export default { |
|
|
|
this.rightClickHandler.destroy() |
|
|
|
this.rightClickHandler = null |
|
|
|
} |
|
|
|
if (this._contextMenuCameraListener && this.viewer && this.viewer.scene) { |
|
|
|
this.viewer.scene.preRender.removeEventListener(this._contextMenuCameraListener) |
|
|
|
this._contextMenuCameraListener = null |
|
|
|
} |
|
|
|
if (this._boundPreventContextMenuWindow) { |
|
|
|
window.removeEventListener('contextmenu', this._boundPreventContextMenuWindow, true) |
|
|
|
this._boundPreventContextMenuWindow = null |
|
|
|
@ -12675,6 +13063,11 @@ export default { |
|
|
|
width: 100vw; |
|
|
|
height: 100vh; |
|
|
|
position: relative; |
|
|
|
/* 防止拖拽地图/航点时误触导致整页文字被选中 */ |
|
|
|
user-select: none; |
|
|
|
-webkit-user-select: none; |
|
|
|
-moz-user-select: none; |
|
|
|
-ms-user-select: none; |
|
|
|
} |
|
|
|
|
|
|
|
#cesiumViewer { |
|
|
|
|