|
|
|
@ -1420,7 +1420,7 @@ export default { |
|
|
|
const isNewlyInserted = wp.id === newWp.id; |
|
|
|
const isPrevToNew = prevWp && wp.id === prevWp.id; |
|
|
|
const nameToUse = isNewlyInserted ? (isHold(wp) ? `HOLD${newSeq}` : `WP${newSeq}`) : (wp.name || (isHold(wp) ? `HOLD${newSeq}` : `WP${newSeq}`)); |
|
|
|
// 插入任意航点后:上一航点之后有航点了,应把上一航点默认转弯坡度设为45度(首点保持0) |
|
|
|
// 插入任意航点后:上一航点变为中间航点时,将上一航点转弯坡度设为 45° |
|
|
|
const prevTurnAngle = (isPrevToNew && insertIndex > 1) ? 45 : wp.turnAngle; |
|
|
|
const updatePayload = { |
|
|
|
id: wp.id, |
|
|
|
@ -1466,24 +1466,27 @@ export default { |
|
|
|
updated.waypoints = sortedWaypoints; |
|
|
|
const routeInList = this.routes.find(r => r.id === routeId); |
|
|
|
if (routeInList) routeInList.waypoints = sortedWaypoints; |
|
|
|
if (this.selectedRouteId === routeId) { |
|
|
|
this.selectedRouteDetails = { ...this.selectedRouteDetails, waypoints: sortedWaypoints }; |
|
|
|
} |
|
|
|
await this.recalculateAndPersistRouteKTimes(routeId); |
|
|
|
const routeAfterK = this.routes.find(r => r.id === routeId) || updated; |
|
|
|
const wptsForMap = routeAfterK.waypoints || sortedWaypoints; |
|
|
|
if (this.activeRouteIds.includes(routeId) && this.$refs.cesiumMap) { |
|
|
|
const roomId = this.currentRoomId; |
|
|
|
if (roomId && updated.platformId) { |
|
|
|
if (roomId && routeAfterK.platformId) { |
|
|
|
try { |
|
|
|
const styleRes = await getPlatformStyle({ roomId, routeId, platformId: updated.platformId }); |
|
|
|
const styleRes = await getPlatformStyle({ roomId, routeId, platformId: routeAfterK.platformId }); |
|
|
|
if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(routeId, styleRes.data); |
|
|
|
} catch (_) {} |
|
|
|
} |
|
|
|
this.$refs.cesiumMap.removeRouteById(routeId); |
|
|
|
this.$refs.cesiumMap.renderRouteWaypoints(sortedWaypoints, routeId, updated.platformId, updated.platform, this.parseRouteStyle(updated.attributes)); |
|
|
|
this.$refs.cesiumMap.renderRouteWaypoints(wptsForMap, routeId, routeAfterK.platformId, routeAfterK.platform, this.parseRouteStyle(routeAfterK.attributes || updated.attributes)); |
|
|
|
this.$nextTick(() => this.updateDeductionPositions()); |
|
|
|
if (roomId && sortedWaypoints.length > 0) { |
|
|
|
this.updateMissilePositionsAfterRouteEdit(roomId, routeId, updated.platformId != null ? updated.platformId : 0, sortedWaypoints); |
|
|
|
if (roomId && wptsForMap.length > 0) { |
|
|
|
this.updateMissilePositionsAfterRouteEdit(roomId, routeId, routeAfterK.platformId != null ? routeAfterK.platformId : 0, wptsForMap); |
|
|
|
} |
|
|
|
} |
|
|
|
if (this.selectedRouteId === routeId) { |
|
|
|
this.selectedRouteDetails = { ...this.selectedRouteDetails, waypoints: sortedWaypoints }; |
|
|
|
} |
|
|
|
const modeLabel = segmentMode === 'fixed_time' ? '定时点' : (segmentMode === 'fixed_speed' ? '定速点' : '航点'); |
|
|
|
this.$message.success(`已添加${modeLabel},该航段已按所选方式规划`); |
|
|
|
this.wsConnection?.sendSyncWaypoints?.(routeId); |
|
|
|
@ -1528,12 +1531,11 @@ export default { |
|
|
|
} |
|
|
|
const index = waypoints.indexOf(wp); |
|
|
|
const total = waypoints.length; |
|
|
|
const isFirstOrLast = index === 0 || index === total - 1; |
|
|
|
const isHold = this.isHoldWaypoint(wp); |
|
|
|
let pointType; |
|
|
|
let holdParams; |
|
|
|
let turnAngle; |
|
|
|
const preserveTurnAngle = () => isFirstOrLast ? 0 : (wp.turnAngle != null && wp.turnAngle !== '' ? Number(wp.turnAngle) : 0); |
|
|
|
const preserveTurnAngle = () => (wp.turnAngle != null && wp.turnAngle !== '' ? Number(wp.turnAngle) : 45); |
|
|
|
if (isHold) { |
|
|
|
pointType = 'normal'; |
|
|
|
holdParams = null; |
|
|
|
@ -1581,6 +1583,7 @@ export default { |
|
|
|
const idxS = this.selectedRouteDetails.waypoints.findIndex(p => p.id === wp.id); |
|
|
|
if (idxS !== -1) this.selectedRouteDetails.waypoints.splice(idxS, 1, merged); |
|
|
|
} |
|
|
|
await this.recalculateAndPersistRouteKTimes(routeId); |
|
|
|
if (this.activeRouteIds.includes(routeId) && this.$refs.cesiumMap) { |
|
|
|
const r = this.routes.find(rr => rr.id === routeId); |
|
|
|
if (r && r.waypoints) { |
|
|
|
@ -1592,7 +1595,7 @@ export default { |
|
|
|
} catch (_) {} |
|
|
|
} |
|
|
|
const { minMinutes, maxMinutes } = this.getDeductionTimeRange(); |
|
|
|
if (r.waypoints.some(wp => this.isHoldWaypoint(wp))) { |
|
|
|
if (r.waypoints.some(wp2 => this.isHoldWaypoint(wp2))) { |
|
|
|
this.getPositionAtMinutesFromK(r.waypoints, minMinutes, minMinutes, maxMinutes, routeId); |
|
|
|
} |
|
|
|
this.$refs.cesiumMap.removeRouteById(routeId); |
|
|
|
@ -1668,7 +1671,7 @@ export default { |
|
|
|
lng: wp.lng, |
|
|
|
alt: wp.alt, |
|
|
|
speed: wp.speed != null ? wp.speed : 800, |
|
|
|
turnAngle: wp.turnAngle != null && wp.turnAngle !== '' ? Number(wp.turnAngle) : 0, |
|
|
|
turnAngle: wp.turnAngle != null && wp.turnAngle !== '' ? Number(wp.turnAngle) : 45, |
|
|
|
pointType: (wp.pointType || wp.point_type || 'hold_circle'), |
|
|
|
...(wp.startTime != null && wp.startTime !== '' ? { startTime: wp.startTime } : {}) |
|
|
|
}; |
|
|
|
@ -1692,6 +1695,7 @@ export default { |
|
|
|
const idxS = this.selectedRouteDetails.waypoints.findIndex(p => p.id === wp.id); |
|
|
|
if (idxS !== -1) this.selectedRouteDetails.waypoints.splice(idxS, 1, merged); |
|
|
|
} |
|
|
|
await this.recalculateAndPersistRouteKTimes(routeId); |
|
|
|
if (this.activeRouteIds.includes(routeId) && this.$refs.cesiumMap) { |
|
|
|
const r = this.routes.find(rr => rr.id === routeId); |
|
|
|
if (r && r.waypoints) { |
|
|
|
@ -1925,109 +1929,12 @@ export default { |
|
|
|
const i = this.selectedRouteDetails.waypoints.findIndex(p => p.id === dbId); |
|
|
|
if (i !== -1) this.selectedRouteDetails.waypoints.splice(i, 1, merged); |
|
|
|
} |
|
|
|
// 定速/定时:根据拖拽后的新位置重算相对K时或上一航点速度 |
|
|
|
const routeForPlatform = this.routes.find(r => r.id === routeId) || route; |
|
|
|
if (idx > 0) { |
|
|
|
const prev = waypoints[idx - 1]; |
|
|
|
const distM = this.segmentDistance( |
|
|
|
{ lat: prev.lat, lng: prev.lng, alt: prev.alt }, |
|
|
|
{ lat: merged.lat, lng: merged.lng, alt: merged.alt } |
|
|
|
); |
|
|
|
const prevMinutes = this.waypointStartTimeToMinutesDecimal(prev.startTime); |
|
|
|
const prevSegMode = getSegMode(prev); |
|
|
|
const prevSegTargetSpeed = getSegTargetSpeed(prev); |
|
|
|
const mergedSegMode = getSegMode(merged); |
|
|
|
const mergedSegTargetMinutes = getSegTargetMinutes(merged); |
|
|
|
if (prevSegMode === 'fixed_speed') { |
|
|
|
const speedKmh = Number(prevSegTargetSpeed ?? prev.speed) || 800; |
|
|
|
const newMinutesFromK = prevMinutes + (distM / 1000) / speedKmh * 60; |
|
|
|
const newStartTime = this.minutesToStartTimeWithSeconds(newMinutesFromK); |
|
|
|
const startPayload = { ...merged, startTime: newStartTime }; |
|
|
|
if (mergedSegMode != null) startPayload.segmentMode = mergedSegMode; |
|
|
|
try { |
|
|
|
const r2 = await updateWaypoints(startPayload, roomIdParam); |
|
|
|
if (r2.code === 200) { |
|
|
|
Object.assign(merged, { startTime: newStartTime }); |
|
|
|
if (idx !== -1) waypoints.splice(idx, 1, merged); |
|
|
|
if (this.selectedRouteDetails && this.selectedRouteDetails.id === routeId) { |
|
|
|
const i = this.selectedRouteDetails.waypoints.findIndex(p => p.id === dbId); |
|
|
|
if (i !== -1) this.selectedRouteDetails.waypoints.splice(i, 1, merged); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
console.warn('定速重算相对K时失败', e); |
|
|
|
} |
|
|
|
} else if (mergedSegMode === 'fixed_time') { |
|
|
|
const currMinutes = (mergedSegTargetMinutes != null && mergedSegTargetMinutes !== '') |
|
|
|
? Number(mergedSegTargetMinutes) |
|
|
|
: this.waypointStartTimeToMinutesDecimal(merged.startTime); |
|
|
|
const deltaMin = currMinutes - prevMinutes; |
|
|
|
if (deltaMin > 0.001) { |
|
|
|
const newSpeedKmh = (distM / 1000) / (deltaMin / 60); |
|
|
|
const speedVal = Math.round(newSpeedKmh * 10) / 10; |
|
|
|
const speedPayload = { ...prev, speed: speedVal }; |
|
|
|
if (prevSegMode != null) speedPayload.segmentMode = prevSegMode; |
|
|
|
try { |
|
|
|
const r2 = await updateWaypoints(speedPayload, roomIdParam); |
|
|
|
if (r2.code === 200) { |
|
|
|
Object.assign(prev, { speed: speedVal }); |
|
|
|
const prevIdx = idx - 1; |
|
|
|
waypoints.splice(prevIdx, 1, prev); |
|
|
|
if (this.selectedRouteDetails && this.selectedRouteDetails.id === routeId) { |
|
|
|
const i = this.selectedRouteDetails.waypoints.findIndex(p => p.id === prev.id); |
|
|
|
if (i !== -1) this.selectedRouteDetails.waypoints.splice(i, 1, prev); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
console.warn('定时重算上一航点速度失败', e); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// 下一航点是定时点时:重算本航点(被拖动的)速度,使平台能在下一航点的 K 时到达 |
|
|
|
if (idx >= 0 && idx < waypoints.length - 1) { |
|
|
|
const next = waypoints[idx + 1]; |
|
|
|
const nextSegMode = getSegMode(next); |
|
|
|
const nextSegTargetMinutes = getSegTargetMinutes(next); |
|
|
|
if (nextSegMode === 'fixed_time') { |
|
|
|
const distToNextM = this.segmentDistance( |
|
|
|
{ lat: merged.lat, lng: merged.lng, alt: merged.alt }, |
|
|
|
{ lat: next.lat, lng: next.lng, alt: next.alt } |
|
|
|
); |
|
|
|
const mergedSegTargetMinutes = getSegTargetMinutes(merged); |
|
|
|
const currMinutes = (mergedSegTargetMinutes != null && mergedSegTargetMinutes !== '') |
|
|
|
? Number(mergedSegTargetMinutes) |
|
|
|
: this.waypointStartTimeToMinutesDecimal(merged.startTime); |
|
|
|
const nextMinutes = (nextSegTargetMinutes != null && nextSegTargetMinutes !== '') |
|
|
|
? Number(nextSegTargetMinutes) |
|
|
|
: this.waypointStartTimeToMinutesDecimal(next.startTime); |
|
|
|
const deltaMin = nextMinutes - currMinutes; |
|
|
|
if (deltaMin > 0.001) { |
|
|
|
const newSpeedKmh = (distToNextM / 1000) / (deltaMin / 60); |
|
|
|
const speedVal = Math.round(newSpeedKmh * 10) / 10; |
|
|
|
const currPayload = { ...merged, speed: speedVal }; |
|
|
|
if (wpSegMode != null) currPayload.segmentMode = wpSegMode; |
|
|
|
if (merged.labelFontSize != null) currPayload.labelFontSize = merged.labelFontSize; |
|
|
|
if (merged.labelColor != null) currPayload.labelColor = merged.labelColor; |
|
|
|
if (merged.color != null) currPayload.color = merged.color; |
|
|
|
if (merged.pixelSize != null) currPayload.pixelSize = merged.pixelSize; |
|
|
|
if (merged.outlineColor != null) currPayload.outlineColor = merged.outlineColor; |
|
|
|
try { |
|
|
|
const r2 = await updateWaypoints(currPayload, roomIdParam); |
|
|
|
if (r2.code === 200) { |
|
|
|
Object.assign(merged, { speed: speedVal }); |
|
|
|
waypoints.splice(idx, 1, merged); |
|
|
|
if (this.selectedRouteDetails && this.selectedRouteDetails.id === routeId) { |
|
|
|
const i = this.selectedRouteDetails.waypoints.findIndex(p => p.id === dbId); |
|
|
|
if (i !== -1) this.selectedRouteDetails.waypoints.splice(i, 1, merged); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
console.warn('下一航点为定时点重算本航点速度失败', e); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
const routeInListSync = this.routes.find(r => r.id === routeId); |
|
|
|
if (routeInListSync && routeInListSync.waypoints !== waypoints) { |
|
|
|
this.$set(routeInListSync, 'waypoints', waypoints); |
|
|
|
} |
|
|
|
await this.recalculateAndPersistRouteKTimes(routeId); |
|
|
|
if (this.$refs.cesiumMap) { |
|
|
|
const roomId = this.currentRoomId; |
|
|
|
if (roomId && routeForPlatform.platformId) { |
|
|
|
@ -2762,6 +2669,9 @@ export default { |
|
|
|
this.selectedRouteDetails.platform = updatedRoute.platform; |
|
|
|
this.selectedRouteDetails.attributes = updatedRoute.attributes; |
|
|
|
} |
|
|
|
if (updatedRoute.waypoints && updatedRoute.waypoints.length > 0) { |
|
|
|
await this.recalculateAndPersistRouteKTimes(updatedRoute.id); |
|
|
|
} |
|
|
|
this.$message.success(updatedRoute.waypoints && updatedRoute.waypoints.length > 0 ? '航线与航点已保存' : '航线更新成功'); |
|
|
|
this.wsConnection?.sendSyncWaypoints?.(updatedRoute.id); |
|
|
|
const routeStyle = updatedRoute.routeStyle || this.parseRouteStyle(updatedRoute.attributes); |
|
|
|
@ -3044,10 +2954,8 @@ export default { |
|
|
|
} |
|
|
|
return { ...p, startTime }; |
|
|
|
}); |
|
|
|
// 新建航线时:首尾航点转弯坡度固定为 0,中间可编辑航点默认 45° |
|
|
|
// 新建航线时:各航点默认转弯坡度 45°(未拖点单独指定时) |
|
|
|
const finalWaypoints = pointsWithStartTime.map((p, index) => { |
|
|
|
const isFirstOrLast = index === 0 || index === wpCount - 1; |
|
|
|
const defaultTurnAngle = isFirstOrLast ? 0.0 : 45.0; |
|
|
|
return { |
|
|
|
name: p.name || `WP${index + 1}`, |
|
|
|
lat: p.lat, |
|
|
|
@ -3055,7 +2963,7 @@ export default { |
|
|
|
alt: p.alt != null ? p.alt : 5000.0, |
|
|
|
speed: p.speed != null ? p.speed : 800.0, |
|
|
|
startTime: p.startTime, |
|
|
|
turnAngle: p.turnAngle != null ? p.turnAngle : defaultTurnAngle, |
|
|
|
turnAngle: p.turnAngle != null ? p.turnAngle : 45.0, |
|
|
|
labelFontSize: p.labelFontSize != null ? p.labelFontSize : 14, |
|
|
|
labelColor: p.labelColor || '#333333', |
|
|
|
...(p.pointType && { pointType: p.pointType }), |
|
|
|
@ -3163,7 +3071,7 @@ export default { |
|
|
|
this.selectedWaypoint = data; |
|
|
|
this.showWaypointDialog = true; |
|
|
|
}, |
|
|
|
/** 航点编辑保存:更新数据库并同步地图显示。定时/定速点变更会级联重算整条航线后续航点的 K 时。 */ |
|
|
|
/** 航点编辑保存:更新数据库并同步地图显示;保存后按航段类型整条重算 K 时并写库。 */ |
|
|
|
async updateWaypoint(updatedWaypoint) { |
|
|
|
if (!this.selectedRouteDetails || !this.selectedRouteDetails.waypoints) return; |
|
|
|
const routeId = updatedWaypoint.routeId != null ? updatedWaypoint.routeId : this.selectedRouteDetails.id; |
|
|
|
@ -3172,7 +3080,6 @@ export default { |
|
|
|
return; |
|
|
|
} |
|
|
|
const sd = this.selectedRouteDetails; |
|
|
|
const waypointsBefore = JSON.parse(JSON.stringify(sd.waypoints)); |
|
|
|
try { |
|
|
|
const idxForRadius = sd.waypoints.findIndex(p => p.id === updatedWaypoint.id); |
|
|
|
if (this.$refs.cesiumMap && updatedWaypoint.turnAngle > 0 && idxForRadius >= 0) { |
|
|
|
@ -3181,23 +3088,10 @@ export default { |
|
|
|
} else { |
|
|
|
updatedWaypoint.turnRadius = 0; |
|
|
|
} |
|
|
|
// 明确构造后端需要的字段,确保 startTime(相对K时)一定会被提交并更新到数据库 |
|
|
|
let startTimeToUse = (updatedWaypoint.startTime != null && updatedWaypoint.startTime !== '') |
|
|
|
// 明确构造后端需要的字段;整条航线 K 时在保存后由 recalculateAndPersistRouteKTimes 统一重算 |
|
|
|
const startTimeToUse = (updatedWaypoint.startTime != null && updatedWaypoint.startTime !== '') |
|
|
|
? updatedWaypoint.startTime |
|
|
|
: 'K+00:00:00'; |
|
|
|
// 定速点:根据上一航点K时、路程和定速重算本航点相对K时 |
|
|
|
const indexForSpeed = sd.waypoints.findIndex(p => p.id === updatedWaypoint.id); |
|
|
|
if (updatedWaypoint.segmentMode === 'fixed_speed' && (updatedWaypoint.segmentTargetSpeed != null && updatedWaypoint.segmentTargetSpeed !== '') && indexForSpeed > 0) { |
|
|
|
const prev = sd.waypoints[indexForSpeed - 1]; |
|
|
|
const distM = this.segmentDistance( |
|
|
|
{ lat: prev.lat, lng: prev.lng, alt: prev.alt }, |
|
|
|
{ lat: updatedWaypoint.lat, lng: updatedWaypoint.lng, alt: updatedWaypoint.alt } |
|
|
|
); |
|
|
|
const prevMinutes = this.waypointStartTimeToMinutesDecimal(prev.startTime); |
|
|
|
const speedKmh = Number(updatedWaypoint.segmentTargetSpeed) || 800; |
|
|
|
const newMinutesFromK = prevMinutes + (distM / 1000) / speedKmh * 60; |
|
|
|
startTimeToUse = this.minutesToStartTimeWithSeconds(newMinutesFromK); |
|
|
|
} |
|
|
|
const payload = { |
|
|
|
id: updatedWaypoint.id, |
|
|
|
routeId: updatedWaypoint.routeId, |
|
|
|
@ -3234,126 +3128,7 @@ export default { |
|
|
|
const idxInList = routeInList.waypoints.findIndex(p => p.id === updatedWaypoint.id); |
|
|
|
if (idxInList !== -1) routeInList.waypoints.splice(idxInList, 1, merged); |
|
|
|
} |
|
|
|
// 定时点:根据“本航点相对K时”反算上一航点速度,使平台能在 K+本点时间 到达本点并出发(或提前到达后盘旋至该时刻) |
|
|
|
if (merged.segmentMode === 'fixed_time' && index > 0) { |
|
|
|
const prev = sd.waypoints[index - 1]; |
|
|
|
const distM = this.segmentDistance( |
|
|
|
{ lat: prev.lat, lng: prev.lng, alt: prev.alt }, |
|
|
|
{ lat: merged.lat, lng: merged.lng, alt: merged.alt } |
|
|
|
); |
|
|
|
const prevMinutes = this.waypointStartTimeToMinutesDecimal(prev.startTime); |
|
|
|
const currMinutes = (merged.segmentTargetMinutes != null && merged.segmentTargetMinutes !== '') ? Number(merged.segmentTargetMinutes) : this.waypointStartTimeToMinutesDecimal(merged.startTime); |
|
|
|
const deltaMin = currMinutes - prevMinutes; |
|
|
|
if (deltaMin > 0.001) { |
|
|
|
const newSpeedKmh = (distM / 1000) / (deltaMin / 60); |
|
|
|
const speedVal = Math.round(newSpeedKmh * 10) / 10; |
|
|
|
const prevPayload = { ...prev, speed: speedVal }; |
|
|
|
if (prev.segmentMode != null) prevPayload.segmentMode = prev.segmentMode; |
|
|
|
if (prev.labelFontSize != null) prevPayload.labelFontSize = prev.labelFontSize; |
|
|
|
if (prev.labelColor != null) prevPayload.labelColor = prev.labelColor; |
|
|
|
if (prev.color != null) prevPayload.color = prev.color; |
|
|
|
if (prev.pixelSize != null) prevPayload.pixelSize = prev.pixelSize; |
|
|
|
if (prev.outlineColor != null) prevPayload.outlineColor = prev.outlineColor; |
|
|
|
try { |
|
|
|
const r2 = await updateWaypoints(prevPayload, roomIdParam); |
|
|
|
if (r2.code === 200) { |
|
|
|
Object.assign(prev, { speed: speedVal }); |
|
|
|
sd.waypoints.splice(index - 1, 1, prev); |
|
|
|
if (routeInList && routeInList.waypoints) { |
|
|
|
const prevIdxInList = routeInList.waypoints.findIndex(p => p.id === prev.id); |
|
|
|
if (prevIdxInList !== -1) routeInList.waypoints.splice(prevIdxInList, 1, prev); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
console.warn('定时点重算上一航点速度失败', e); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// 下一航点是定时点时:重算本航点速度,使平台能在下一航点的 K 时到达 |
|
|
|
if (index < sd.waypoints.length - 1) { |
|
|
|
const next = sd.waypoints[index + 1]; |
|
|
|
if (next.segmentMode === 'fixed_time') { |
|
|
|
const distToNextM = this.segmentDistance( |
|
|
|
{ lat: merged.lat, lng: merged.lng, alt: merged.alt }, |
|
|
|
{ lat: next.lat, lng: next.lng, alt: next.alt } |
|
|
|
); |
|
|
|
const currMinutes = this.waypointStartTimeToMinutesDecimal(merged.startTime); |
|
|
|
const nextMinutes = (next.segmentTargetMinutes != null && next.segmentTargetMinutes !== '') ? Number(next.segmentTargetMinutes) : this.waypointStartTimeToMinutesDecimal(next.startTime); |
|
|
|
const deltaMin = nextMinutes - currMinutes; |
|
|
|
if (deltaMin > 0.001) { |
|
|
|
const newSpeedKmh = (distToNextM / 1000) / (deltaMin / 60); |
|
|
|
const speedVal = Math.round(newSpeedKmh * 10) / 10; |
|
|
|
const currPayload = { ...merged, speed: speedVal }; |
|
|
|
if (merged.segmentMode != null) currPayload.segmentMode = merged.segmentMode; |
|
|
|
if (merged.labelFontSize != null) currPayload.labelFontSize = merged.labelFontSize; |
|
|
|
if (merged.labelColor != null) currPayload.labelColor = merged.labelColor; |
|
|
|
if (merged.pixelSize != null) currPayload.pixelSize = merged.pixelSize; |
|
|
|
if (merged.color != null) currPayload.color = merged.color; |
|
|
|
if (merged.outlineColor != null) currPayload.outlineColor = merged.outlineColor; |
|
|
|
try { |
|
|
|
const r2 = await updateWaypoints(currPayload, roomIdParam); |
|
|
|
if (r2.code === 200) { |
|
|
|
Object.assign(merged, { speed: speedVal }); |
|
|
|
sd.waypoints.splice(index, 1, merged); |
|
|
|
if (routeInList && routeInList.waypoints) { |
|
|
|
const idxInList = routeInList.waypoints.findIndex(p => p.id === merged.id); |
|
|
|
if (idxInList !== -1) routeInList.waypoints.splice(idxInList, 1, merged); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
console.warn('下一航点为定时点重算本航点速度失败', e); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// 定时/定速点变更后:级联重算后续航点的 K 时,使整条航线时间轴一致 |
|
|
|
const oldMinutesAtIdx = this.waypointStartTimeToMinutesDecimal(waypointsBefore[index]?.startTime); |
|
|
|
const newMinutesAtIdx = this.waypointStartTimeToMinutesDecimal(sd.waypoints[index]?.startTime); |
|
|
|
const deltaMin = newMinutesAtIdx - oldMinutesAtIdx; |
|
|
|
if (Math.abs(deltaMin) > 0.001 && index < sd.waypoints.length - 1) { |
|
|
|
for (let i = index + 1; i < sd.waypoints.length; i++) { |
|
|
|
const wp = sd.waypoints[i]; |
|
|
|
const oldArrival = (wp.segmentTargetMinutes != null && wp.segmentTargetMinutes !== '') ? Number(wp.segmentTargetMinutes) : this.waypointStartTimeToMinutesDecimal(wp.startTime); |
|
|
|
const newArrival = oldArrival + deltaMin; |
|
|
|
const newStartTime = this.minutesToStartTimeWithSeconds(newArrival); |
|
|
|
const cascadePayload = { |
|
|
|
id: wp.id, |
|
|
|
routeId, |
|
|
|
name: wp.name, |
|
|
|
seq: wp.seq, |
|
|
|
lat: wp.lat, |
|
|
|
lng: wp.lng, |
|
|
|
alt: wp.alt, |
|
|
|
speed: wp.speed, |
|
|
|
startTime: newStartTime, |
|
|
|
turnAngle: wp.turnAngle |
|
|
|
}; |
|
|
|
if (wp.pointType != null) cascadePayload.pointType = wp.pointType; |
|
|
|
if (wp.holdParams != null) cascadePayload.holdParams = wp.holdParams; |
|
|
|
if (wp.segmentMode != null) cascadePayload.segmentMode = wp.segmentMode; |
|
|
|
if (wp.segmentMode === 'fixed_time') cascadePayload.segmentTargetMinutes = newArrival; |
|
|
|
else if (wp.segmentTargetMinutes != null) cascadePayload.segmentTargetMinutes = wp.segmentTargetMinutes; |
|
|
|
if (wp.segmentTargetSpeed != null) cascadePayload.segmentTargetSpeed = wp.segmentTargetSpeed; |
|
|
|
if (wp.labelFontSize != null) cascadePayload.labelFontSize = wp.labelFontSize; |
|
|
|
if (wp.labelColor != null) cascadePayload.labelColor = wp.labelColor; |
|
|
|
if (wp.color != null) cascadePayload.color = wp.color; |
|
|
|
if (wp.pixelSize != null) cascadePayload.pixelSize = wp.pixelSize; |
|
|
|
if (wp.outlineColor != null) cascadePayload.outlineColor = wp.outlineColor; |
|
|
|
try { |
|
|
|
const rCascade = await updateWaypoints(cascadePayload, roomIdParam); |
|
|
|
if (rCascade.code === 200) { |
|
|
|
Object.assign(wp, { startTime: newStartTime, ...(wp.segmentMode === 'fixed_time' && { segmentTargetMinutes: newArrival }) }); |
|
|
|
sd.waypoints.splice(i, 1, wp); |
|
|
|
if (routeInList && routeInList.waypoints) { |
|
|
|
const idxInList = routeInList.waypoints.findIndex(p => p.id === wp.id); |
|
|
|
if (idxInList !== -1) routeInList.waypoints.splice(idxInList, 1, wp); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
console.warn(`级联更新航点 ${i + 1} K时失败`, e); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
await this.recalculateAndPersistRouteKTimes(routeId); |
|
|
|
if (this.$refs.cesiumMap) { |
|
|
|
if (roomId && sd.platformId) { |
|
|
|
try { |
|
|
|
@ -5613,6 +5388,265 @@ export default { |
|
|
|
return `K${sign}${String(h).padStart(2, '0')}:${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}`; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 与推演一致:按 seq 排序航点 */ |
|
|
|
sortWaypointsBySeqForK(waypoints) { |
|
|
|
if (!waypoints || !waypoints.length) return []; |
|
|
|
return waypoints.slice().sort((a, b) => { |
|
|
|
const saRaw = a.seq != null ? a.seq : a.Seq; |
|
|
|
const sbRaw = b.seq != null ? b.seq : b.Seq; |
|
|
|
const saNum = Number(saRaw); |
|
|
|
const sbNum = Number(sbRaw); |
|
|
|
const sa = Number.isFinite(saNum) ? saNum : Number.POSITIVE_INFINITY; |
|
|
|
const sb = Number.isFinite(sbNum) ? sbNum : Number.POSITIVE_INFINITY; |
|
|
|
if (sa !== sb) return sa - sb; |
|
|
|
return (Number(a.id) || 0) - (Number(b.id) || 0); |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 按几何与航段类型(默认/定速/定时、盘旋)正向推算整条航线各航点相对 K 时。 |
|
|
|
* 首点锚定在 K+0;定时点保持 segmentTargetMinutes 为计划到达(盘旋点为该时刻为进入盘旋),必要时将进入时刻与几何到达取 max。 |
|
|
|
*/ |
|
|
|
computeRecalculatedKTimeMap(sorted) { |
|
|
|
const result = new Map(); |
|
|
|
const getMode = (w) => (w?.segmentMode ?? w?.displayStyle?.segmentMode ?? null); |
|
|
|
const getTargetMin = (w) => { |
|
|
|
const v = w?.segmentTargetMinutes ?? w?.displayStyle?.segmentTargetMinutes; |
|
|
|
if (v == null || v === '') return null; |
|
|
|
const n = Number(v); |
|
|
|
return Number.isFinite(n) ? n : null; |
|
|
|
}; |
|
|
|
const getTargetSpeed = (w) => { |
|
|
|
const v = w?.segmentTargetSpeed ?? w?.displayStyle?.segmentTargetSpeed; |
|
|
|
if (v == null || v === '') return null; |
|
|
|
const n = Number(v); |
|
|
|
return Number.isFinite(n) && n > 0 ? n : null; |
|
|
|
}; |
|
|
|
const legSpeedKmh = (A, B) => { |
|
|
|
if (getMode(B) === 'fixed_speed') { |
|
|
|
const sp = getTargetSpeed(B); |
|
|
|
if (sp != null) return sp; |
|
|
|
} |
|
|
|
return Number(A.speed) || 800; |
|
|
|
}; |
|
|
|
const snapshotHoldDurMin = (B) => { |
|
|
|
if (!this.isHoldWaypoint(B)) return 0; |
|
|
|
const exitM = this.waypointStartTimeToMinutesDecimal(B.startTime); |
|
|
|
const mode = getMode(B); |
|
|
|
const entryStored = getTargetMin(B); |
|
|
|
if (mode === 'fixed_time' && entryStored != null && exitM > entryStored + 1e-6) { |
|
|
|
return exitM - entryStored; |
|
|
|
} |
|
|
|
return 5; |
|
|
|
}; |
|
|
|
|
|
|
|
const n = sorted.length; |
|
|
|
if (n === 0) return result; |
|
|
|
const t0 = this.minutesToStartTimeWithSeconds(0); |
|
|
|
result.set(sorted[0].id, { startTime: t0 }); |
|
|
|
let tCurrent = 0; |
|
|
|
if (n === 1) return result; |
|
|
|
|
|
|
|
for (let i = 0; i < n - 1; i++) { |
|
|
|
const A = sorted[i]; |
|
|
|
const B = sorted[i + 1]; |
|
|
|
const distM = this.segmentDistance( |
|
|
|
{ lat: A.lat, lng: A.lng, alt: A.alt }, |
|
|
|
{ lat: B.lat, lng: B.lng, alt: B.alt } |
|
|
|
); |
|
|
|
const tDepart = tCurrent; |
|
|
|
const speedKmh = legSpeedKmh(A, B); |
|
|
|
const flyMin = distM > 0 && speedKmh > 0 ? (distM / 1000) / speedKmh * 60 : 0; |
|
|
|
const tFly = tDepart + flyMin; |
|
|
|
const modeB = getMode(B); |
|
|
|
const targetB = getTargetMin(B); |
|
|
|
|
|
|
|
if (modeB === 'fixed_time' && targetB != null) { |
|
|
|
if (this.isHoldWaypoint(B)) { |
|
|
|
const hd = snapshotHoldDurMin(B); |
|
|
|
const tEntry = Math.max(tFly, targetB); |
|
|
|
const tExit = tEntry + hd; |
|
|
|
result.set(B.id, { |
|
|
|
startTime: this.minutesToStartTimeWithSeconds(tExit), |
|
|
|
segmentTargetMinutes: tEntry |
|
|
|
}); |
|
|
|
tCurrent = tExit; |
|
|
|
} else { |
|
|
|
result.set(B.id, { |
|
|
|
startTime: this.minutesToStartTimeWithSeconds(targetB), |
|
|
|
segmentTargetMinutes: targetB |
|
|
|
}); |
|
|
|
tCurrent = targetB; |
|
|
|
} |
|
|
|
} else if (this.isHoldWaypoint(B)) { |
|
|
|
const hd = snapshotHoldDurMin(B); |
|
|
|
const tExit = tFly + hd; |
|
|
|
result.set(B.id, { startTime: this.minutesToStartTimeWithSeconds(tExit) }); |
|
|
|
tCurrent = tExit; |
|
|
|
} else { |
|
|
|
result.set(B.id, { startTime: this.minutesToStartTimeWithSeconds(tFly) }); |
|
|
|
tCurrent = tFly; |
|
|
|
} |
|
|
|
} |
|
|
|
return result; |
|
|
|
}, |
|
|
|
|
|
|
|
getWaypointSegTargetMinutesForCompare(wp) { |
|
|
|
const v = wp?.segmentTargetMinutes ?? wp?.displayStyle?.segmentTargetMinutes; |
|
|
|
if (v == null || v === '') return null; |
|
|
|
const n = Number(v); |
|
|
|
return Number.isFinite(n) ? n : null; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 根据当前几何重算并持久化整条航线 K 时(不写甘特图拉伸等纯时间变换场景) */ |
|
|
|
async recalculateAndPersistRouteKTimes(routeId) { |
|
|
|
if (routeId == null || this.isRouteLockedByOther(routeId)) return; |
|
|
|
const routeInList = this.routes.find(r => r.id === routeId); |
|
|
|
if (!routeInList || !routeInList.waypoints || routeInList.waypoints.length === 0) return; |
|
|
|
const sorted = this.sortWaypointsBySeqForK(routeInList.waypoints); |
|
|
|
const plan = this.computeRecalculatedKTimeMap(sorted); |
|
|
|
const roomIdParam = this.currentRoomId != null ? { roomId: this.currentRoomId } : {}; |
|
|
|
|
|
|
|
for (const wp of sorted) { |
|
|
|
const p = plan.get(wp.id); |
|
|
|
if (!p) continue; |
|
|
|
const oldM = this.waypointStartTimeToMinutesDecimal(wp.startTime); |
|
|
|
const newM = this.waypointStartTimeToMinutesDecimal(p.startTime); |
|
|
|
const oldTm = this.getWaypointSegTargetMinutesForCompare(wp); |
|
|
|
const newTm = p.segmentTargetMinutes != null && Number.isFinite(p.segmentTargetMinutes) ? p.segmentTargetMinutes : null; |
|
|
|
const mode = wp.segmentMode ?? wp.displayStyle?.segmentMode ?? null; |
|
|
|
let tmChanged = false; |
|
|
|
if (mode === 'fixed_time' && newTm != null) { |
|
|
|
tmChanged = oldTm == null || Math.abs(oldTm - newTm) > 1e-4; |
|
|
|
} |
|
|
|
if (Math.abs(oldM - newM) < 1e-4 && !tmChanged) continue; |
|
|
|
|
|
|
|
const nextSegTarget = mode === 'fixed_time' && newTm != null ? newTm : undefined; |
|
|
|
const payload = this.buildWaypointTimeUpdatePayload(wp, routeId, p.startTime, nextSegTarget); |
|
|
|
const idx = sorted.findIndex(x => x.id === wp.id); |
|
|
|
const prevWp = idx > 0 ? sorted[idx - 1] : null; |
|
|
|
if (wp.turnAngle > 0 && this.$refs.cesiumMap) { |
|
|
|
payload.turnRadius = this.$refs.cesiumMap.getTurnRadiusFromPrevSpeed(prevWp, wp.turnAngle); |
|
|
|
} else { |
|
|
|
payload.turnRadius = 0; |
|
|
|
} |
|
|
|
try { |
|
|
|
const res = await updateWaypoints(payload, roomIdParam); |
|
|
|
if (res.code === 200) { |
|
|
|
wp.startTime = p.startTime; |
|
|
|
if (nextSegTarget != null && Number.isFinite(nextSegTarget)) { |
|
|
|
wp.segmentTargetMinutes = Number(nextSegTarget.toFixed(6)); |
|
|
|
wp.displayStyle = { ...(wp.displayStyle || {}), segmentMode: wp.segmentMode, segmentTargetMinutes: wp.segmentTargetMinutes }; |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
console.warn('重算航线 K 时持久化失败', wp.id, e); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
await this.persistLegSpeedsForFixedTimeAnchors(routeId, sorted); |
|
|
|
|
|
|
|
if (this.selectedRouteDetails && this.selectedRouteDetails.id === routeId) { |
|
|
|
if (this.selectedRouteDetails.waypoints === routeInList.waypoints) { |
|
|
|
/* 同一引用,已在上面原地更新 */ |
|
|
|
} else { |
|
|
|
this.selectedRouteDetails.waypoints.forEach((w) => { |
|
|
|
const u = routeInList.waypoints.find(x => x.id === w.id); |
|
|
|
if (u) { |
|
|
|
Object.assign(w, { |
|
|
|
startTime: u.startTime, |
|
|
|
segmentTargetMinutes: u.segmentTargetMinutes, |
|
|
|
displayStyle: u.displayStyle, |
|
|
|
speed: u.speed |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 航段终点为定时点时:按「上一航点(或盘旋切出)K 时 → 本点计划到达/盘旋进入」与距离反算该段应飞的地速,并写回上一航点的 speed(与原先航点编辑保存行为一致)。 |
|
|
|
* 须在 K 时已重算并写回 startTime/segmentTargetMinutes 之后调用。 |
|
|
|
*/ |
|
|
|
async persistLegSpeedsForFixedTimeAnchors(routeId, sorted) { |
|
|
|
if (routeId == null || !sorted || sorted.length < 2) return; |
|
|
|
const roomIdParam = this.currentRoomId != null ? { roomId: this.currentRoomId } : {}; |
|
|
|
const getMode = (w) => (w?.segmentMode ?? w?.displayStyle?.segmentMode ?? null); |
|
|
|
const getTargetMin = (w) => { |
|
|
|
const v = w?.segmentTargetMinutes ?? w?.displayStyle?.segmentTargetMinutes; |
|
|
|
if (v == null || v === '') return null; |
|
|
|
const n = Number(v); |
|
|
|
return Number.isFinite(n) ? n : null; |
|
|
|
}; |
|
|
|
|
|
|
|
for (let i = 0; i < sorted.length - 1; i++) { |
|
|
|
const A = sorted[i]; |
|
|
|
const B = sorted[i + 1]; |
|
|
|
if (getMode(B) !== 'fixed_time') continue; |
|
|
|
|
|
|
|
const distM = this.segmentDistance( |
|
|
|
{ lat: A.lat, lng: A.lng, alt: A.alt }, |
|
|
|
{ lat: B.lat, lng: B.lng, alt: B.alt } |
|
|
|
); |
|
|
|
const tDepart = this.waypointStartTimeToMinutesDecimal(A.startTime); |
|
|
|
let tArr; |
|
|
|
if (this.isHoldWaypoint(B)) { |
|
|
|
tArr = getTargetMin(B); |
|
|
|
if (tArr == null) continue; |
|
|
|
} else { |
|
|
|
tArr = getTargetMin(B); |
|
|
|
if (tArr == null) { |
|
|
|
tArr = this.waypointStartTimeToMinutesDecimal(B.startTime); |
|
|
|
} |
|
|
|
} |
|
|
|
const deltaMin = tArr - tDepart; |
|
|
|
if (deltaMin <= 0.001 || distM <= 0) continue; |
|
|
|
|
|
|
|
const newSpeedKmh = Math.round(((distM / 1000) / (deltaMin / 60)) * 10) / 10; |
|
|
|
if (!Number.isFinite(newSpeedKmh) || newSpeedKmh <= 0) continue; |
|
|
|
if (Math.abs(Number(A.speed) - newSpeedKmh) < 0.05) continue; |
|
|
|
|
|
|
|
const idxA = sorted.findIndex(x => x.id === A.id); |
|
|
|
const prevWpForA = idxA > 0 ? sorted[idxA - 1] : null; |
|
|
|
const payload = { |
|
|
|
id: A.id, |
|
|
|
routeId: A.routeId != null ? A.routeId : routeId, |
|
|
|
name: A.name, |
|
|
|
seq: A.seq, |
|
|
|
lat: A.lat, |
|
|
|
lng: A.lng, |
|
|
|
alt: A.alt, |
|
|
|
speed: newSpeedKmh, |
|
|
|
startTime: A.startTime != null && A.startTime !== '' ? A.startTime : 'K+00:00:00', |
|
|
|
turnAngle: A.turnAngle |
|
|
|
}; |
|
|
|
if (A.pointType != null) payload.pointType = A.pointType; |
|
|
|
if (A.holdParams != null) payload.holdParams = A.holdParams; |
|
|
|
if (A.labelFontSize != null) payload.labelFontSize = A.labelFontSize; |
|
|
|
if (A.labelColor != null) payload.labelColor = A.labelColor; |
|
|
|
if (A.segmentMode != null) payload.segmentMode = A.segmentMode; |
|
|
|
if (A.segmentTargetMinutes != null && A.segmentTargetMinutes !== '') payload.segmentTargetMinutes = A.segmentTargetMinutes; |
|
|
|
if (A.segmentTargetSpeed != null && A.segmentTargetSpeed !== '') payload.segmentTargetSpeed = A.segmentTargetSpeed; |
|
|
|
if (A.pixelSize != null) payload.pixelSize = A.pixelSize; |
|
|
|
if (A.color != null) payload.color = A.color; |
|
|
|
if (A.outlineColor != null) payload.outlineColor = A.outlineColor; |
|
|
|
if (A.turnAngle > 0 && this.$refs.cesiumMap) { |
|
|
|
payload.turnRadius = this.$refs.cesiumMap.getTurnRadiusFromPrevSpeed(prevWpForA, A.turnAngle); |
|
|
|
} else { |
|
|
|
payload.turnRadius = 0; |
|
|
|
} |
|
|
|
try { |
|
|
|
const res = await updateWaypoints(payload, roomIdParam); |
|
|
|
if (res.code === 200) { |
|
|
|
A.speed = newSpeedKmh; |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
console.warn('定时航段反算上一航点速度失败', A.id, e); |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
isHoldWaypoint(wp) { |
|
|
|
const t = (wp && wp.pointType) || (wp && wp.point_type) || 'normal'; |
|
|
|
return t === 'hold_circle' || t === 'hold_ellipse'; |
|
|
|
@ -6408,6 +6442,7 @@ export default { |
|
|
|
this.showAddHoldDialog = false; |
|
|
|
this.addHoldContext = null; |
|
|
|
await this.getList(); |
|
|
|
await this.recalculateAndPersistRouteKTimes(routeId); |
|
|
|
const updated = this.routes.find(r => r.id === routeId); |
|
|
|
if (updated && updated.waypoints && this.activeRouteIds.includes(routeId) && this.$refs.cesiumMap) { |
|
|
|
const roomId = this.currentRoomId; |
|
|
|
|