|
|
|
@ -1374,7 +1374,16 @@ export default { |
|
|
|
return; |
|
|
|
} |
|
|
|
const others = list.filter(w => w.id !== newWp.id); |
|
|
|
others.sort((a, b) => (Number(a.seq) || 0) - (Number(b.seq) || 0)); |
|
|
|
others.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); |
|
|
|
}); |
|
|
|
const reordered = [...others.slice(0, insertIndex), newWp, ...others.slice(insertIndex)]; |
|
|
|
const routeInListFirst = this.routes.find(r => r.id === routeId); |
|
|
|
if (routeInListFirst) routeInListFirst.waypoints = reordered; |
|
|
|
@ -1424,7 +1433,16 @@ export default { |
|
|
|
this.$message.warning('刷新后未拿到航线航点'); |
|
|
|
return; |
|
|
|
} |
|
|
|
const sortedWaypoints = updated.waypoints.slice().sort((a, b) => (Number(a.seq) || 0) - (Number(b.seq) || 0)); |
|
|
|
const sortedWaypoints = updated.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); |
|
|
|
}); |
|
|
|
updated.waypoints = sortedWaypoints; |
|
|
|
const routeInList = this.routes.find(r => r.id === routeId); |
|
|
|
if (routeInList) routeInList.waypoints = sortedWaypoints; |
|
|
|
@ -1506,6 +1524,7 @@ export default { |
|
|
|
turnAngle = preserveTurnAngle(); |
|
|
|
} |
|
|
|
try { |
|
|
|
// 不要在 startTime 为空时强行写入默认值(例如 K+00:00:00),否则会改变推演时间轴 min(K) ,导致平台从错误航点开始。 |
|
|
|
const payload = { |
|
|
|
id: wp.id, |
|
|
|
routeId, |
|
|
|
@ -1515,9 +1534,9 @@ export default { |
|
|
|
lng: wp.lng, |
|
|
|
alt: wp.alt, |
|
|
|
speed: wp.speed, |
|
|
|
startTime: wp.startTime != null && wp.startTime !== '' ? wp.startTime : 'K+00:00:00', |
|
|
|
turnAngle, |
|
|
|
pointType |
|
|
|
pointType, |
|
|
|
...(wp.startTime != null && wp.startTime !== '' ? { startTime: wp.startTime } : {}) |
|
|
|
}; |
|
|
|
if (holdParams != null) payload.holdParams = holdParams; |
|
|
|
else payload.holdParams = ''; |
|
|
|
@ -1558,7 +1577,8 @@ export default { |
|
|
|
} |
|
|
|
this.$refs.cesiumMap.removeRouteById(routeId); |
|
|
|
this.$refs.cesiumMap.renderRouteWaypoints(r.waypoints, routeId, r.platformId, r.platform, this.parseRouteStyle(r.attributes)); |
|
|
|
this.$nextTick(() => this.updateDeductionPositions()); |
|
|
|
// 切换盘旋会影响推演所用的数据(尤其 startTime/hold 几何),需同步最新 min/max 下的 deductionMinutesFromK。 |
|
|
|
this.$nextTick(() => this.updateTimeFromProgress()); |
|
|
|
if (roomId && r.waypoints && r.waypoints.length > 0) { |
|
|
|
this.updateMissilePositionsAfterRouteEdit(roomId, routeId, r.platformId != null ? r.platformId : 0, r.waypoints); |
|
|
|
} |
|
|
|
@ -1628,9 +1648,9 @@ export default { |
|
|
|
lng: wp.lng, |
|
|
|
alt: wp.alt, |
|
|
|
speed: wp.speed != null ? wp.speed : 800, |
|
|
|
startTime: wp.startTime != null && wp.startTime !== '' ? wp.startTime : 'K+00:00:00', |
|
|
|
turnAngle: wp.turnAngle != null && wp.turnAngle !== '' ? Number(wp.turnAngle) : 0, |
|
|
|
pointType: (wp.pointType || wp.point_type || 'hold_circle') |
|
|
|
pointType: (wp.pointType || wp.point_type || 'hold_circle'), |
|
|
|
...(wp.startTime != null && wp.startTime !== '' ? { startTime: wp.startTime } : {}) |
|
|
|
}; |
|
|
|
payload.holdParams = JSON.stringify(nextHoldParamsObj); |
|
|
|
if (wp.segmentMode != null) payload.segmentMode = wp.segmentMode; |
|
|
|
@ -1668,7 +1688,7 @@ export default { |
|
|
|
} |
|
|
|
this.$refs.cesiumMap.removeRouteById(routeId); |
|
|
|
this.$refs.cesiumMap.renderRouteWaypoints(r.waypoints, routeId, r.platformId, r.platform, this.parseRouteStyle(r.attributes)); |
|
|
|
this.$nextTick(() => this.updateDeductionPositions()); |
|
|
|
this.$nextTick(() => this.updateTimeFromProgress()); |
|
|
|
} |
|
|
|
} |
|
|
|
this.$message.success(`盘旋速度已更新为 ${Math.round(targetSpeed * 10) / 10} km/h`); |
|
|
|
@ -1829,6 +1849,21 @@ export default { |
|
|
|
this.$message.error('未找到对应航点'); |
|
|
|
return; |
|
|
|
} |
|
|
|
// 兼容:分段模式/目标时间/目标速度可能在顶层,也可能在 displayStyle 内 |
|
|
|
// 若拖拽后取不到,就会导致定速/定时重算逻辑不触发,平台与时间轴不同步。 |
|
|
|
const getSegMode = (w) => (w?.segmentMode ?? w?.displayStyle?.segmentMode ?? null); |
|
|
|
const getSegTargetMinutes = (w) => (w?.segmentTargetMinutes ?? w?.displayStyle?.segmentTargetMinutes ?? null); |
|
|
|
const getSegTargetSpeed = (w) => (w?.segmentTargetSpeed ?? w?.displayStyle?.segmentTargetSpeed ?? null); |
|
|
|
const wpSegMode = getSegMode(wp); |
|
|
|
const wpSegTargetMinutes = getSegTargetMinutes(wp); |
|
|
|
const wpSegTargetSpeed = getSegTargetSpeed(wp); |
|
|
|
const segTargetMinutesNum = wpSegTargetMinutes != null ? Number(wpSegTargetMinutes) : null; |
|
|
|
const fixedTimeStartTime = (wpSegMode === 'fixed_time' |
|
|
|
&& wpSegTargetMinutes != null |
|
|
|
&& wpSegTargetMinutes !== '' |
|
|
|
&& Number.isFinite(segTargetMinutesNum)) |
|
|
|
? this.minutesToStartTimeWithSeconds(segTargetMinutesNum) |
|
|
|
: null; |
|
|
|
const payload = { |
|
|
|
id: wp.id, |
|
|
|
routeId: wp.routeId != null ? wp.routeId : routeId, |
|
|
|
@ -1839,14 +1874,19 @@ export default { |
|
|
|
// 拖拽只允许修改经纬度,高度保持原值(避免 5000 -> 4999.999... 的数值抖动) |
|
|
|
alt: wp.alt, |
|
|
|
speed: wp.speed, |
|
|
|
startTime: (wp.startTime != null && wp.startTime !== '') ? wp.startTime : 'K+00:00:00', |
|
|
|
// fixed_time:始终用 segmentTargetMinutes 生成 startTime,避免 startTime 与目标时间不一致导致推演段错位(如 wait 段提前/滞后) |
|
|
|
startTime: fixedTimeStartTime != null |
|
|
|
? fixedTimeStartTime |
|
|
|
: (wp.startTime != null && wp.startTime !== '') ? wp.startTime : 'K+00:00:00', |
|
|
|
turnAngle: wp.turnAngle |
|
|
|
}; |
|
|
|
if (wp.pointType != null) payload.pointType = wp.pointType; |
|
|
|
if (wp.holdParams != null) payload.holdParams = wp.holdParams; |
|
|
|
if (wp.labelFontSize != null) payload.labelFontSize = wp.labelFontSize; |
|
|
|
if (wp.labelColor != null) payload.labelColor = wp.labelColor; |
|
|
|
if (wp.segmentMode != null) payload.segmentMode = wp.segmentMode; |
|
|
|
if (wpSegMode != null) payload.segmentMode = wpSegMode; |
|
|
|
if (wpSegTargetMinutes != null && wpSegTargetMinutes !== '') payload.segmentTargetMinutes = wpSegTargetMinutes; |
|
|
|
if (wpSegTargetSpeed != null && wpSegTargetSpeed !== '') payload.segmentTargetSpeed = wpSegTargetSpeed; |
|
|
|
if (wp.color != null) payload.color = wp.color; |
|
|
|
if (wp.pixelSize != null) payload.pixelSize = wp.pixelSize; |
|
|
|
if (wp.outlineColor != null) payload.outlineColor = wp.outlineColor; |
|
|
|
@ -1874,12 +1914,16 @@ export default { |
|
|
|
{ lat: merged.lat, lng: merged.lng, alt: merged.alt } |
|
|
|
); |
|
|
|
const prevMinutes = this.waypointStartTimeToMinutesDecimal(prev.startTime); |
|
|
|
if (prev.segmentMode === 'fixed_speed') { |
|
|
|
const speedKmh = Number(prev.segmentTargetSpeed ?? prev.speed) || 800; |
|
|
|
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 (merged.segmentMode != null) startPayload.segmentMode = merged.segmentMode; |
|
|
|
if (mergedSegMode != null) startPayload.segmentMode = mergedSegMode; |
|
|
|
try { |
|
|
|
const r2 = await updateWaypoints(startPayload, roomIdParam); |
|
|
|
if (r2.code === 200) { |
|
|
|
@ -1893,14 +1937,16 @@ export default { |
|
|
|
} catch (e) { |
|
|
|
console.warn('定速重算相对K时失败', e); |
|
|
|
} |
|
|
|
} else if (merged.segmentMode === 'fixed_time') { |
|
|
|
const currMinutes = (merged.segmentTargetMinutes != null && merged.segmentTargetMinutes !== '') ? Number(merged.segmentTargetMinutes) : this.waypointStartTimeToMinutesDecimal(merged.startTime); |
|
|
|
} 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 (prev.segmentMode != null) speedPayload.segmentMode = prev.segmentMode; |
|
|
|
if (prevSegMode != null) speedPayload.segmentMode = prevSegMode; |
|
|
|
try { |
|
|
|
const r2 = await updateWaypoints(speedPayload, roomIdParam); |
|
|
|
if (r2.code === 200) { |
|
|
|
@ -1921,19 +1967,26 @@ export default { |
|
|
|
// 下一航点是定时点时:重算本航点(被拖动的)速度,使平台能在下一航点的 K 时到达 |
|
|
|
if (idx >= 0 && idx < waypoints.length - 1) { |
|
|
|
const next = waypoints[idx + 1]; |
|
|
|
if (next.segmentMode === 'fixed_time') { |
|
|
|
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 currMinutes = this.waypointStartTimeToMinutesDecimal(merged.startTime); |
|
|
|
const nextMinutes = (next.segmentTargetMinutes != null && next.segmentTargetMinutes !== '') ? Number(next.segmentTargetMinutes) : this.waypointStartTimeToMinutesDecimal(next.startTime); |
|
|
|
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 (merged.segmentMode != null) currPayload.segmentMode = merged.segmentMode; |
|
|
|
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; |
|
|
|
@ -1963,6 +2016,11 @@ export default { |
|
|
|
if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(routeId, styleRes.data); |
|
|
|
} catch (_) {} |
|
|
|
} |
|
|
|
// 与列表数据一致:避免仅选中详情为深拷贝时 routes 仍为旧引用,推演用 routes 与地图渲染脱节 |
|
|
|
const routeInList = this.routes.find(r => r.id === routeId); |
|
|
|
if (routeInList && routeInList.waypoints !== waypoints) { |
|
|
|
this.$set(routeInList, 'waypoints', waypoints); |
|
|
|
} |
|
|
|
this.$refs.cesiumMap.renderRouteWaypoints( |
|
|
|
waypoints, |
|
|
|
routeId, |
|
|
|
@ -2746,7 +2804,16 @@ export default { |
|
|
|
platform: item.platform, |
|
|
|
attributes: item.attributes, |
|
|
|
points: item.waypoints ? item.waypoints.length : 0, |
|
|
|
waypoints: item.waypoints || [], |
|
|
|
waypoints: (item.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); |
|
|
|
}), |
|
|
|
conflict: false, |
|
|
|
scenarioId: item.scenarioId |
|
|
|
})); |
|
|
|
@ -5074,7 +5141,16 @@ export default { |
|
|
|
if (![oldStart, oldEnd, newStart, newEnd].every(Number.isFinite) || newEnd <= newStart) return; |
|
|
|
const roomId = this.getRouteOperationRoomId(route); |
|
|
|
const roomIdParam = roomId != null ? { roomId } : {}; |
|
|
|
const oldWpSorted = (route.waypoints || []).slice().sort((a, b) => (Number(a.seq) || 0) - (Number(b.seq) || 0)); |
|
|
|
const oldWpSorted = (route.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); |
|
|
|
}); |
|
|
|
const oldToNewById = {}; |
|
|
|
let prevNewMinutes = null; |
|
|
|
oldWpSorted.forEach(wp => { |
|
|
|
@ -5433,7 +5509,8 @@ export default { |
|
|
|
/** 将航点 startTime 字符串转为相对 K 的分钟数 */ |
|
|
|
waypointStartTimeToMinutes(s) { |
|
|
|
if (!s || typeof s !== 'string') return 0; |
|
|
|
const m = s.match(/K([+-])(\d{2}):(\d{2})/); |
|
|
|
// 兼容小时为 1~2 位(例如 K+0:18:00 或 K+00:18:00) |
|
|
|
const m = s.match(/K([+-])(\d{1,2}):(\d{2})(?::(\d{2}))?/); |
|
|
|
if (!m) return 0; |
|
|
|
const sign = m[1] === '+' ? 1 : -1; |
|
|
|
const h = parseInt(m[2], 10); |
|
|
|
@ -5443,7 +5520,8 @@ export default { |
|
|
|
/** 将 startTime(如 K+00:19:30)转为相对 K 的分钟数(含秒,保留小数) */ |
|
|
|
waypointStartTimeToMinutesDecimal(s) { |
|
|
|
if (!s || typeof s !== 'string') return 0; |
|
|
|
const m = s.match(/K([+-])(\d{2}):(\d{2})(?::(\d{2}))?/); |
|
|
|
// 兼容小时为 1~2 位(例如 K+0:18:00 或 K+00:18:00) |
|
|
|
const m = s.match(/K([+-])(\d{1,2}):(\d{2})(?::(\d{2}))?/); |
|
|
|
if (!m) return 0; |
|
|
|
const sign = m[1] === '+' ? 1 : -1; |
|
|
|
const h = parseInt(m[2], 10); |
|
|
|
@ -5549,6 +5627,18 @@ export default { |
|
|
|
buildRouteTimeline(waypoints, globalMin, globalMax, pathData) { |
|
|
|
const warnings = []; |
|
|
|
if (!waypoints || waypoints.length === 0) return { segments: [], warnings }; |
|
|
|
// 含盘旋时不按分钟排序 points,故航点顺序必须与航线几何/数据库 seq 一致。 |
|
|
|
// 注意:seq 缺失/不可解析时不要当成 0 抢到最前,否则平台起点会错。 |
|
|
|
waypoints = 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); |
|
|
|
}); |
|
|
|
const points = waypoints.map((wp, idx) => ({ |
|
|
|
lng: parseFloat(wp.lng), |
|
|
|
lat: parseFloat(wp.lat), |
|
|
|
@ -5559,6 +5649,11 @@ export default { |
|
|
|
})); |
|
|
|
const hasHold = points.some(p => p.isHold); |
|
|
|
const allSame = points.every(p => p.minutes === points[0].minutes); |
|
|
|
// 若所有航点 minutes 完全相同,通常表示 startTime 还没正确初始化/解析。 |
|
|
|
// 这种情况下无论是否存在盘旋点,都应先把时间展开成递增,否则 segments 的 start/endTime 会塌缩, |
|
|
|
// getPositionFromTimeline 可能会落到错误的段,从而导致平台位置“跳错/错乱”。 |
|
|
|
// 但:盘旋点切换不应影响 K+0 的起始段几何/位置,因此只有在“无盘旋”时才展开 minutes。 |
|
|
|
// 否则切到有盘旋后会触发展开并重建 segments,导致当前位置跳到后续航段/等待段。 |
|
|
|
if (allSame && points.length > 1 && !hasHold) { |
|
|
|
const span = Math.max(globalMax - globalMin, 1); |
|
|
|
points.forEach((p, i) => { |
|
|
|
@ -5889,8 +5984,15 @@ export default { |
|
|
|
/** 从时间轴中取当前推演时间对应的位置;支持 fly/wait/hold,hold 沿 holdPath 弧长插值 */ |
|
|
|
getPositionFromTimeline(segments, minutesFromK, path, segmentEndIndices) { |
|
|
|
if (!segments || segments.length === 0) return null; |
|
|
|
if (minutesFromK <= segments[0].startTime) return segments[0].startPos; |
|
|
|
const last = segments[segments.length - 1]; |
|
|
|
// buildRouteTimeline 在盘旋分支中会先 push wait 段再 push 后续 fly/hold 段, |
|
|
|
// 因此这里必须按时间排序并且只在 [startTime, endTime) 内命中,避免提前“命中”wait段导致图标停在错误航点。 |
|
|
|
const sortedSegments = segments.slice().sort((a, b) => { |
|
|
|
const ds = Number(a.startTime) - Number(b.startTime); |
|
|
|
if (ds !== 0) return ds; |
|
|
|
return Number(a.endTime) - Number(b.endTime); |
|
|
|
}); |
|
|
|
if (minutesFromK <= sortedSegments[0].startTime) return sortedSegments[0].startPos; |
|
|
|
const last = sortedSegments[sortedSegments.length - 1]; |
|
|
|
if (minutesFromK >= last.endTime) { |
|
|
|
if (last.type === 'wait' && path && segmentEndIndices && last.legIndex != null && last.legIndex < segmentEndIndices.length && path[segmentEndIndices[last.legIndex]]) { |
|
|
|
return path[segmentEndIndices[last.legIndex]]; |
|
|
|
@ -5898,10 +6000,13 @@ export default { |
|
|
|
if (last.type === 'hold' && last.holdPath && last.holdPath.length) return last.holdPath[last.holdPath.length - 1]; |
|
|
|
return last.endPos; |
|
|
|
} |
|
|
|
for (let i = 0; i < segments.length; i++) { |
|
|
|
const s = segments[i]; |
|
|
|
if (minutesFromK < s.endTime) { |
|
|
|
const t = Math.max(0, Math.min(1, (minutesFromK - s.startTime) / (s.endTime - s.startTime))); |
|
|
|
for (let i = 0; i < sortedSegments.length; i++) { |
|
|
|
const s = sortedSegments[i]; |
|
|
|
if (minutesFromK >= s.startTime && minutesFromK < s.endTime) { |
|
|
|
const duration = s.endTime - s.startTime; |
|
|
|
const t = duration !== 0 |
|
|
|
? Math.max(0, Math.min(1, (minutesFromK - s.startTime) / duration)) |
|
|
|
: 0; |
|
|
|
if (s.type === 'wait') { |
|
|
|
if (path && segmentEndIndices && s.legIndex != null && s.legIndex < segmentEndIndices.length) { |
|
|
|
const endIdx = segmentEndIndices[s.legIndex]; |
|
|
|
@ -5966,6 +6071,17 @@ export default { |
|
|
|
buildPathDataForRouteTimeline(waypoints, routeId) { |
|
|
|
const cesiumMap = this.$refs.cesiumMap; |
|
|
|
if (!waypoints || waypoints.length === 0 || !cesiumMap || !cesiumMap.getRoutePathWithSegmentIndices) return null; |
|
|
|
// 与 buildRouteTimeline 一致:必须按 seq 排序,否则含盘旋时不会按 K 时重排,推演首段可能与时间轴 min(K) 不一致(飞机卡在错误航点) |
|
|
|
waypoints = 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); |
|
|
|
}); |
|
|
|
const cachedEllipse = (routeId != null && cesiumMap._routeHoldEllipseParamsByRoute && cesiumMap._routeHoldEllipseParamsByRoute[routeId]) |
|
|
|
? cesiumMap._routeHoldEllipseParamsByRoute[routeId] |
|
|
|
: {}; |
|
|
|
@ -5980,6 +6096,16 @@ export default { |
|
|
|
/** 根据当前推演时间(相对 K 的分钟)得到平台位置,使用速度与等待逻辑;若有地图 ref 则沿转弯弧/盘旋弧路径运动;盘旋半径由系统根据 k+10 落点反算,使平滑落在切点。routeId 可选,传入时会把计算半径同步给地图以实时渲染盘旋轨迹与切点进入。返回 { position, nextPosition, previousPosition, warnings, earlyArrivalLegs, currentSegment } */ |
|
|
|
getPositionAtMinutesFromK(waypoints, minutesFromK, globalMin, globalMax, routeId) { |
|
|
|
if (!waypoints || waypoints.length === 0) return { position: null, nextPosition: null, previousPosition: null, warnings: [], earlyArrivalLegs: [], currentSegment: null }; |
|
|
|
waypoints = 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); |
|
|
|
}); |
|
|
|
const cesiumMap = this.$refs.cesiumMap; |
|
|
|
let pathData = this.buildPathDataForRouteTimeline(waypoints, routeId); |
|
|
|
let { segments, warnings, earlyArrivalLegs } = this.buildRouteTimeline(waypoints, globalMin, globalMax, pathData); |
|
|
|
@ -6117,7 +6243,19 @@ export default { |
|
|
|
const { position, nextPosition, previousPosition, warnings, earlyArrivalLegs, currentSegment } = this.getPositionAtMinutesFromK(route.waypoints, minutesFromK, minMinutes, maxMinutes, routeId); |
|
|
|
if (warnings && warnings.length) allWarnings.push(...warnings); |
|
|
|
if (position) { |
|
|
|
const directionPoint = nextPosition || previousPosition; |
|
|
|
// 方向点优先取 nextPosition,但避免在边界/等待段 nextPosition 与 position 重合导致 heading=0 |
|
|
|
const isSamePos = (p1, p2) => { |
|
|
|
if (!p1 || !p2) return false; |
|
|
|
const dLng = Number(p1.lng) - Number(p2.lng); |
|
|
|
const dLat = Number(p1.lat) - Number(p2.lat); |
|
|
|
const dAlt = (Number(p1.alt != null ? p1.alt : 0) - Number(p2.alt != null ? p2.alt : 0)); |
|
|
|
// 经度/纬度阈值用极小值,alt 用米级阈值 |
|
|
|
return Math.abs(dLng) < 1e-8 && Math.abs(dLat) < 1e-8 && Math.abs(dAlt) < 2; |
|
|
|
}; |
|
|
|
let directionPoint = nextPosition; |
|
|
|
if (nextPosition && isSamePos(position, nextPosition)) directionPoint = null; |
|
|
|
if (!directionPoint && previousPosition && !isSamePos(position, previousPosition)) directionPoint = previousPosition; |
|
|
|
if (!directionPoint) directionPoint = nextPosition || previousPosition; |
|
|
|
const labelData = { |
|
|
|
name: (route.platform && route.platform.name) ? route.platform.name : '平台', |
|
|
|
altitude: position.alt != null ? Number(position.alt) : 0, |
|
|
|
|