|
|
|
@ -4113,9 +4113,9 @@ export default { |
|
|
|
/** |
|
|
|
* 按速度与计划时间构建航线时间轴:含飞行段、盘旋段与“提前到达则等待”的等待段。 |
|
|
|
* pathData 可选:{ path, segmentEndIndices, holdArcRanges },由 getRoutePathWithSegmentIndices 提供,用于输出 hold 段。 |
|
|
|
* holdRadiusByLegIndex 可选:{ [legIndex]: number },为盘旋段指定半径(用于推演时落点精准在切点)。 |
|
|
|
* 圆形盘旋半径由速度+坡度公式固定计算,盘旋时间靠多转圈数解决,不反算半径。 |
|
|
|
*/ |
|
|
|
buildRouteTimeline(waypoints, globalMin, globalMax, pathData, holdRadiusByLegIndex) { |
|
|
|
buildRouteTimeline(waypoints, globalMin, globalMax, pathData) { |
|
|
|
const warnings = []; |
|
|
|
if (!waypoints || waypoints.length === 0) return { segments: [], warnings }; |
|
|
|
const points = waypoints.map((wp, idx) => ({ |
|
|
|
@ -4153,7 +4153,12 @@ export default { |
|
|
|
const path = pathData && pathData.path; |
|
|
|
const segmentEndIndices = pathData && pathData.segmentEndIndices; |
|
|
|
const holdArcRanges = pathData && pathData.holdArcRanges || {}; |
|
|
|
let skipNextLeg = false; |
|
|
|
for (let i = 0; i < points.length - 1; i++) { |
|
|
|
if (skipNextLeg) { |
|
|
|
skipNextLeg = false; |
|
|
|
continue; |
|
|
|
} |
|
|
|
if (this.isHoldWaypoint(waypoints[i + 1]) && path && segmentEndIndices && holdArcRanges[i]) { |
|
|
|
const range = holdArcRanges[i]; |
|
|
|
const startIdx = i === 0 ? 0 : segmentEndIndices[i - 1] + 1; |
|
|
|
@ -4183,25 +4188,35 @@ export default { |
|
|
|
} |
|
|
|
const holdEndTime = points[i + 1].minutes; // 用户设定的切出时间(如 K+10) |
|
|
|
const exitPos = holdPathSlice.length ? holdPathSlice[holdPathSlice.length - 1] : (toEntrySlice.length ? toEntrySlice[toEntrySlice.length - 1] : { lng: points[i].lng, lat: points[i].lat, alt: points[i].alt }); |
|
|
|
let loopEndIdx = 1; |
|
|
|
for (let k = 1; k < Math.min(holdPathSlice.length, 200); k++) { |
|
|
|
if (this.segmentDistance(holdPathSlice[0], holdPathSlice[k]) < 80) { loopEndIdx = k; break; } |
|
|
|
let loopEndIdx; |
|
|
|
if (range.loopEndIndex != null) { |
|
|
|
loopEndIdx = range.loopEndIndex - range.start; |
|
|
|
} else { |
|
|
|
const minSearchIdx = Math.max(2, Math.floor(holdPathSlice.length * 0.33)); |
|
|
|
loopEndIdx = holdPathSlice.length - 1; |
|
|
|
for (let k = minSearchIdx; k < holdPathSlice.length; k++) { |
|
|
|
if (this.segmentDistance(holdPathSlice[0], holdPathSlice[k]) < 80) { loopEndIdx = k; break; } |
|
|
|
} |
|
|
|
} |
|
|
|
const holdClosedLoopPath = holdPathSlice.slice(0, loopEndIdx + 1); |
|
|
|
if (loopEndIdx < 1) loopEndIdx = 1; |
|
|
|
if (loopEndIdx >= holdPathSlice.length) loopEndIdx = holdPathSlice.length - 1; |
|
|
|
const holdClosedLoopRaw = holdPathSlice.slice(0, loopEndIdx + 1); |
|
|
|
const holdClosedLoopPath = holdClosedLoopRaw.length >= 2 |
|
|
|
? [...holdClosedLoopRaw.slice(0, -1), { ...holdClosedLoopRaw[0] }] |
|
|
|
: holdClosedLoopRaw; |
|
|
|
const holdLoopLength = this.pathSliceDistance(holdClosedLoopPath) || 1; |
|
|
|
// 出口必须在整条盘旋路径上找,不能只在“整圈”段内找,否则会误把圈上某点当出口导致飞半圈就停或折回 |
|
|
|
let exitIdxOnLoop = holdPathSlice.length - 1; |
|
|
|
let minD = 1e9; |
|
|
|
for (let k = 0; k < holdPathSlice.length; k++) { |
|
|
|
const d = this.segmentDistance(holdPathSlice[k], exitPos); |
|
|
|
if (d < minD) { minD = d; exitIdxOnLoop = k; } |
|
|
|
} |
|
|
|
const holdExitDistanceOnLoop = this.pathSliceDistance(holdPathSlice.slice(0, exitIdxOnLoop + 1)); |
|
|
|
const holdEntryToExitRaw = holdPathSlice.slice(loopEndIdx); |
|
|
|
const holdEntryToExitSlice = holdEntryToExitRaw.length >= 2 |
|
|
|
? [{ ...holdClosedLoopPath[0] }, ...holdEntryToExitRaw.slice(1)] |
|
|
|
: holdEntryToExitRaw; |
|
|
|
const holdExitDistanceOnLoop = this.pathSliceDistance(holdEntryToExitSlice); |
|
|
|
const holdSpeedKmh = points[i + 1].speed || 800; |
|
|
|
const HOLD_SPEED_KMH = 800; |
|
|
|
const speedMpMin = (HOLD_SPEED_KMH * 1000) / 60; |
|
|
|
const requiredDistAtK10 = (holdEndTime - arrivalEntry) * speedMpMin; |
|
|
|
let n = Math.ceil((requiredDistAtK10 - holdExitDistanceOnLoop) / holdLoopLength); |
|
|
|
const rawLoops = (requiredDistAtK10 - holdExitDistanceOnLoop) / holdLoopLength; |
|
|
|
let n = Math.ceil(rawLoops - 1e-9); |
|
|
|
if (n < 0 || !Number.isFinite(n)) n = 0; |
|
|
|
const segmentEndTime = arrivalEntry + (holdExitDistanceOnLoop + n * holdLoopLength) / speedMpMin; |
|
|
|
if (segmentEndTime > holdEndTime) { |
|
|
|
@ -4222,22 +4237,22 @@ export default { |
|
|
|
const distExitToNext = this.pathSliceDistance(toNextSlice); |
|
|
|
const travelExitMin = (distExitToNext / 1000) * (60 / holdSpeedKmh); |
|
|
|
const arrivalNext = segmentEndTime + travelExitMin; |
|
|
|
effectiveTime[i + 1] = holdEndTime; |
|
|
|
effectiveTime[i + 1] = segmentEndTime; |
|
|
|
if (i + 2 < points.length) effectiveTime[i + 2] = arrivalNext; |
|
|
|
const posCur = { lng: points[i].lng, lat: points[i].lat, alt: points[i].alt }; |
|
|
|
const entryPos = toEntrySlice.length ? toEntrySlice[toEntrySlice.length - 1] : posCur; |
|
|
|
const holdWp = waypoints[i + 1]; |
|
|
|
const holdParams = this.parseHoldParams(holdWp); |
|
|
|
const holdCenter = holdWp ? { lng: parseFloat(holdWp.lng), lat: parseFloat(holdWp.lat), alt: Number(holdWp.alt) || 0 } : null; |
|
|
|
const overrideR = holdRadiusByLegIndex && holdRadiusByLegIndex[i] != null ? holdRadiusByLegIndex[i] : null; |
|
|
|
const holdRadius = (overrideR != null && Number.isFinite(overrideR)) ? overrideR : (holdParams && holdParams.radius != null ? holdParams.radius : null); |
|
|
|
const computedR = this.$refs.cesiumMap ? this.$refs.cesiumMap.getWaypointRadius(holdWp) : null; |
|
|
|
const holdRadius = (computedR != null && computedR > 0) ? computedR : 500; |
|
|
|
const holdClockwise = holdParams && holdParams.clockwise !== false; |
|
|
|
const holdCircumference = holdRadius != null ? 2 * Math.PI * holdRadius : null; |
|
|
|
const holdEntryAngle = holdCenter && entryPos && holdRadius != null |
|
|
|
? this.angleFromCenterToPoint(holdCenter.lng, holdCenter.lat, entryPos.lng, entryPos.lat) |
|
|
|
: null; |
|
|
|
segments.push({ startTime: effectiveTime[i], endTime: arrivalEntry, startPos: posCur, endPos: entryPos, type: 'fly', legIndex: i, pathSlice: toEntrySlice, speedKmh: speedKmhForLeg }); |
|
|
|
const holdEntryToExitPath = holdClosedLoopPath.slice(0, exitIdxOnLoop + 1); |
|
|
|
const holdEntryToExitPath = holdEntryToExitSlice; |
|
|
|
segments.push({ |
|
|
|
startTime: arrivalEntry, |
|
|
|
endTime: segmentEndTime, |
|
|
|
@ -4259,8 +4274,18 @@ export default { |
|
|
|
holdClockwise, |
|
|
|
holdEntryAngle |
|
|
|
}); |
|
|
|
segments.push({ startTime: segmentEndTime, endTime: arrivalNext, startPos: exitPos, endPos: toNextSlice.length ? toNextSlice[toNextSlice.length - 1] : exitPos, type: 'fly', legIndex: i, pathSlice: toNextSlice, speedKmh: holdSpeedKmh }); |
|
|
|
continue; // 不 i++,让下次迭代处理下一航段(含连续盘旋点如 WP2→WP3 均为盘旋) |
|
|
|
// 出口→下一航点的 fly 段 |
|
|
|
const exitEndPos = toNextSlice.length ? toNextSlice[toNextSlice.length - 1] : exitPos; |
|
|
|
// 如果下一个航点(WP_{i+2})也是盘旋,不创建 fly 段(让下一次循环处理), |
|
|
|
// 只更新 effectiveTime 使下一次循环的起始时间正确 |
|
|
|
if (i + 2 < points.length && this.isHoldWaypoint(waypoints[i + 2])) { |
|
|
|
segments.push({ startTime: segmentEndTime, endTime: arrivalNext, startPos: exitPos, endPos: exitEndPos, type: 'fly', legIndex: i + 1, pathSlice: toNextSlice, speedKmh: holdSpeedKmh }); |
|
|
|
} else { |
|
|
|
segments.push({ startTime: segmentEndTime, endTime: arrivalNext, startPos: exitPos, endPos: exitEndPos, type: 'fly', legIndex: i + 1, pathSlice: toNextSlice, speedKmh: holdSpeedKmh }); |
|
|
|
// 下一航点不是盘旋,fly 段已覆盖 leg i+1,跳过 |
|
|
|
skipNextLeg = true; |
|
|
|
} |
|
|
|
continue; |
|
|
|
} |
|
|
|
const dist = this.segmentDistance(points[i], points[i + 1]); |
|
|
|
const speedKmh = points[i].speed || 800; |
|
|
|
@ -4287,7 +4312,15 @@ export default { |
|
|
|
effectiveTime[i + 1] = Math.max(actualArrival, scheduled); |
|
|
|
const posCur = { lng: points[i].lng, lat: points[i].lat, alt: points[i].alt }; |
|
|
|
const posNext = { lng: points[i + 1].lng, lat: points[i + 1].lat, alt: points[i + 1].alt }; |
|
|
|
segments.push({ startTime: effectiveTime[i], endTime: actualArrival, startPos: posCur, endPos: posNext, type: 'fly', legIndex: i, speedKmh: speedKmh }); |
|
|
|
let flyPathSlice = null; |
|
|
|
if (path && segmentEndIndices) { |
|
|
|
const startIdx = i === 0 ? 0 : (segmentEndIndices[i - 1] != null ? segmentEndIndices[i - 1] : 0); |
|
|
|
const endIdx = segmentEndIndices[i]; |
|
|
|
if (endIdx != null && endIdx >= startIdx) { |
|
|
|
flyPathSlice = path.slice(startIdx, endIdx + 1); |
|
|
|
} |
|
|
|
} |
|
|
|
segments.push({ startTime: effectiveTime[i], endTime: actualArrival, startPos: posCur, endPos: posNext, type: 'fly', legIndex: i, speedKmh: speedKmh, pathSlice: flyPathSlice }); |
|
|
|
if (actualArrival < effectiveTime[i + 1]) { |
|
|
|
segments.push({ startTime: actualArrival, endTime: effectiveTime[i + 1], startPos: posNext, endPos: posNext, type: 'wait', legIndex: i }); |
|
|
|
} |
|
|
|
@ -4402,24 +4435,27 @@ export default { |
|
|
|
const cesiumMap = this.$refs.cesiumMap; |
|
|
|
let pathData = null; |
|
|
|
if (cesiumMap && cesiumMap.getRoutePathWithSegmentIndices) { |
|
|
|
const cachedRadii = (routeId != null && cesiumMap._routeHoldRadiiByRoute && cesiumMap._routeHoldRadiiByRoute[routeId]) ? cesiumMap._routeHoldRadiiByRoute[routeId] : {}; |
|
|
|
const cachedEllipse = (routeId != null && cesiumMap._routeHoldEllipseParamsByRoute && cesiumMap._routeHoldEllipseParamsByRoute[routeId]) ? cesiumMap._routeHoldEllipseParamsByRoute[routeId] : {}; |
|
|
|
const opts = (Object.keys(cachedRadii).length > 0 || Object.keys(cachedEllipse).length > 0) ? { holdRadiusByLegIndex: cachedRadii, holdEllipseParamsByLegIndex: cachedEllipse } : {}; |
|
|
|
const opts = Object.keys(cachedEllipse).length > 0 ? { holdEllipseParamsByLegIndex: cachedEllipse } : {}; |
|
|
|
const ret = cesiumMap.getRoutePathWithSegmentIndices(waypoints, opts); |
|
|
|
if (ret.path && ret.path.length > 0 && ret.segmentEndIndices && ret.segmentEndIndices.length > 0) { |
|
|
|
pathData = { path: ret.path, segmentEndIndices: ret.segmentEndIndices, holdArcRanges: ret.holdArcRanges || {} }; |
|
|
|
} |
|
|
|
} |
|
|
|
let { segments, warnings, earlyArrivalLegs } = this.buildRouteTimeline(waypoints, globalMin, globalMax, pathData); |
|
|
|
const holdRadiusByLegIndex = {}; |
|
|
|
// 圆形盘旋:半径固定由速度+坡度公式计算,盘旋时间靠多转圈数解决,不反算半径。 |
|
|
|
// 椭圆/跑道形盘旋:通过反算椭圆参数(semiMajor/semiMinor)来匹配盘旋时间。 |
|
|
|
const holdEllipseParamsByLegIndex = {}; |
|
|
|
if (cesiumMap && segments && pathData) { |
|
|
|
for (let idx = 0; idx < segments.length; idx++) { |
|
|
|
const s = segments[idx]; |
|
|
|
if (s.type !== 'hold' || s.holdCenter == null) continue; |
|
|
|
const i = s.legIndex; |
|
|
|
const holdEndTime = s.holdEndTime != null ? s.holdEndTime : this.waypointStartTimeToMinutesDecimal(waypoints[i + 1]?.startTime); |
|
|
|
const holdWp = waypoints[i + 1]; |
|
|
|
if (!holdWp) continue; |
|
|
|
const isHoldEllipse = (holdWp.pointType || holdWp.point_type) === 'hold_ellipse'; |
|
|
|
if (!isHoldEllipse || !cesiumMap.computeEllipseParamsForDuration) continue; |
|
|
|
const holdEndTime = s.holdEndTime != null ? s.holdEndTime : this.waypointStartTimeToMinutesDecimal(holdWp?.startTime); |
|
|
|
const segTarget = holdWp && (holdWp.segmentTargetMinutes ?? holdWp.displayStyle?.segmentTargetMinutes); |
|
|
|
const arrivalAtHold = (holdWp && holdWp.segmentMode === 'fixed_time' && segTarget != null && segTarget !== '') |
|
|
|
? Number(segTarget) : s.startTime; |
|
|
|
@ -4428,123 +4464,36 @@ export default { |
|
|
|
const totalHoldDistM = speedKmh * (holdDurationMin / 60) * 1000; |
|
|
|
const prevWp = waypoints[i]; |
|
|
|
const nextWp = (i + 2) < waypoints.length ? waypoints[i + 2] : holdWp; |
|
|
|
if (!prevWp || !holdWp) continue; |
|
|
|
if (!prevWp) continue; |
|
|
|
const centerCartesian = Cesium.Cartesian3.fromDegrees(parseFloat(holdWp.lng), parseFloat(holdWp.lat), Number(holdWp.alt) || 0); |
|
|
|
const prevCartesian = Cesium.Cartesian3.fromDegrees(parseFloat(prevWp.lng), parseFloat(prevWp.lat), Number(prevWp.alt) || 0); |
|
|
|
const nextCartesian = nextWp ? Cesium.Cartesian3.fromDegrees(parseFloat(nextWp.lng), parseFloat(nextWp.lat), Number(nextWp.alt) || 0) : centerCartesian; |
|
|
|
const clockwise = s.holdClockwise !== false; |
|
|
|
const isHoldEllipse = waypoints[i + 1] && (waypoints[i + 1].pointType || waypoints[i + 1].point_type) === 'hold_ellipse'; |
|
|
|
const isEllipse = isHoldEllipse || s.holdRadius == null; |
|
|
|
if (isEllipse && !isHoldEllipse && cesiumMap.computeEllipseParamsForDuration) { |
|
|
|
const holdParams = this.parseHoldParams(holdWp); |
|
|
|
const headingDeg = holdParams && holdParams.headingDeg != null ? holdParams.headingDeg : 0; |
|
|
|
const a0 = holdParams && (holdParams.semiMajor != null || holdParams.semiMajorAxis != null) ? (holdParams.semiMajor ?? holdParams.semiMajorAxis) : 500; |
|
|
|
const b0 = holdParams && (holdParams.semiMinor != null || holdParams.semiMinorAxis != null) ? (holdParams.semiMinor ?? holdParams.semiMinorAxis) : 300; |
|
|
|
const out = cesiumMap.computeEllipseParamsForDuration(centerCartesian, prevCartesian, nextCartesian, clockwise, totalHoldDistM, headingDeg, a0, b0); |
|
|
|
if (out && out.semiMajor != null && out.semiMinor != null) { |
|
|
|
holdEllipseParamsByLegIndex[i] = { |
|
|
|
semiMajor: out.semiMajor, |
|
|
|
semiMinor: out.semiMinor, |
|
|
|
headingDeg |
|
|
|
}; |
|
|
|
} |
|
|
|
} else if (!isEllipse && cesiumMap.computeHoldRadiusForDuration) { |
|
|
|
let R = cesiumMap.computeHoldRadiusForDuration(centerCartesian, prevCartesian, nextCartesian, clockwise, totalHoldDistM); |
|
|
|
if (R == null || !Number.isFinite(R)) { |
|
|
|
R = totalHoldDistM / (2 * Math.PI); |
|
|
|
} |
|
|
|
if (R != null && Number.isFinite(R) && R > 0) { |
|
|
|
holdRadiusByLegIndex[i] = R; |
|
|
|
} |
|
|
|
const holdParams = this.parseHoldParams(holdWp); |
|
|
|
const headingDeg = holdParams && holdParams.headingDeg != null ? holdParams.headingDeg : 0; |
|
|
|
const a0 = holdParams && (holdParams.semiMajor != null || holdParams.semiMajorAxis != null) ? (holdParams.semiMajor ?? holdParams.semiMajorAxis) : 500; |
|
|
|
const b0 = holdParams && (holdParams.semiMinor != null || holdParams.semiMinorAxis != null) ? (holdParams.semiMinor ?? holdParams.semiMinorAxis) : 300; |
|
|
|
const out = cesiumMap.computeEllipseParamsForDuration(centerCartesian, prevCartesian, nextCartesian, s.holdClockwise !== false, totalHoldDistM, headingDeg, a0, b0); |
|
|
|
if (out && out.semiMajor != null && out.semiMinor != null) { |
|
|
|
holdEllipseParamsByLegIndex[i] = { semiMajor: out.semiMajor, semiMinor: out.semiMinor, headingDeg }; |
|
|
|
} |
|
|
|
} |
|
|
|
const hasCircle = Object.keys(holdRadiusByLegIndex).length > 0; |
|
|
|
const hasEllipse = Object.keys(holdEllipseParamsByLegIndex).length > 0; |
|
|
|
if (hasCircle || hasEllipse) { |
|
|
|
let pathData2 = null; |
|
|
|
let segments2 = null; |
|
|
|
for (let iter = 0; iter < 4; iter++) { |
|
|
|
const ret2 = cesiumMap.getRoutePathWithSegmentIndices(waypoints, { holdRadiusByLegIndex, holdEllipseParamsByLegIndex }); |
|
|
|
if (!ret2.path || ret2.path.length === 0 || !ret2.segmentEndIndices || ret2.segmentEndIndices.length === 0) break; |
|
|
|
pathData2 = { path: ret2.path, segmentEndIndices: ret2.segmentEndIndices, holdArcRanges: ret2.holdArcRanges || {} }; |
|
|
|
const out = this.buildRouteTimeline(waypoints, globalMin, globalMax, pathData2, holdRadiusByLegIndex); |
|
|
|
segments2 = out.segments; |
|
|
|
let changed = false; |
|
|
|
if (hasCircle) { |
|
|
|
const nextRadii = {}; |
|
|
|
for (let idx = 0; idx < segments2.length; idx++) { |
|
|
|
const s = segments2[idx]; |
|
|
|
if (s.type !== 'hold' || s.holdRadius == null || s.holdCenter == null) continue; |
|
|
|
const i = s.legIndex; |
|
|
|
const holdWpCircle = waypoints[i + 1]; |
|
|
|
const holdEndTimeCircle = s.holdEndTime != null ? s.holdEndTime : this.waypointStartTimeToMinutesDecimal(holdWpCircle?.startTime); |
|
|
|
const segTargetCircle = holdWpCircle && (holdWpCircle.segmentTargetMinutes ?? holdWpCircle.displayStyle?.segmentTargetMinutes); |
|
|
|
const arrivalAtHoldCircle = (holdWpCircle && holdWpCircle.segmentMode === 'fixed_time' && segTargetCircle != null && segTargetCircle !== '') |
|
|
|
? Number(segTargetCircle) : s.startTime; |
|
|
|
const holdDurationMin = Math.max(0, holdEndTimeCircle - arrivalAtHoldCircle); |
|
|
|
const speedKmh = s.speedKmh != null ? s.speedKmh : (Number(holdWpCircle?.speed) || 800); |
|
|
|
const totalHoldDistM = speedKmh * (holdDurationMin / 60) * 1000; |
|
|
|
const prevWp = waypoints[i]; |
|
|
|
const holdWp = holdWpCircle; |
|
|
|
const nextWp = (i + 2) < waypoints.length ? waypoints[i + 2] : holdWp; |
|
|
|
if (!prevWp || !holdWp) continue; |
|
|
|
const centerCartesian = Cesium.Cartesian3.fromDegrees(parseFloat(holdWp.lng), parseFloat(holdWp.lat), Number(holdWp.alt) || 0); |
|
|
|
const prevCartesian = Cesium.Cartesian3.fromDegrees(parseFloat(prevWp.lng), parseFloat(prevWp.lat), Number(prevWp.alt) || 0); |
|
|
|
const nextCartesian = nextWp ? Cesium.Cartesian3.fromDegrees(parseFloat(nextWp.lng), parseFloat(nextWp.lat), Number(nextWp.alt) || 0) : centerCartesian; |
|
|
|
let Rnew = cesiumMap.computeHoldRadiusForDuration(centerCartesian, prevCartesian, nextCartesian, s.holdClockwise !== false, totalHoldDistM); |
|
|
|
if (Rnew == null || !Number.isFinite(Rnew)) Rnew = totalHoldDistM / (2 * Math.PI); |
|
|
|
if (Rnew != null && Number.isFinite(Rnew) && Rnew > 0) { |
|
|
|
nextRadii[i] = Rnew; |
|
|
|
if (holdRadiusByLegIndex[i] == null || Math.abs(nextRadii[i] - holdRadiusByLegIndex[i]) > 1) changed = true; |
|
|
|
} |
|
|
|
} |
|
|
|
Object.assign(holdRadiusByLegIndex, nextRadii); |
|
|
|
} |
|
|
|
if (hasEllipse) { |
|
|
|
for (let idx = 0; idx < segments2.length; idx++) { |
|
|
|
const s = segments2[idx]; |
|
|
|
if (s.type !== 'hold' || s.holdRadius != null || s.holdCenter == null) continue; |
|
|
|
const i = s.legIndex; |
|
|
|
const holdWp = waypoints[i + 1]; |
|
|
|
if ((holdWp && (holdWp.pointType || holdWp.point_type) === 'hold_ellipse')) continue; |
|
|
|
const holdParams = this.parseHoldParams(holdWp); |
|
|
|
const holdEndTime = s.holdEndTime != null ? s.holdEndTime : this.waypointStartTimeToMinutesDecimal(holdWp?.startTime); |
|
|
|
const segTargetEllipse = holdWp && (holdWp.segmentTargetMinutes ?? holdWp.displayStyle?.segmentTargetMinutes); |
|
|
|
const arrivalAtHold = (holdWp && holdWp.segmentMode === 'fixed_time' && segTargetEllipse != null && segTargetEllipse !== '') |
|
|
|
? Number(segTargetEllipse) : s.startTime; |
|
|
|
const holdDurationMin = Math.max(0, holdEndTime - arrivalAtHold); |
|
|
|
const speedKmh = s.speedKmh != null ? s.speedKmh : (Number(holdWp?.speed) || 800); |
|
|
|
const totalHoldDistM = speedKmh * (holdDurationMin / 60) * 1000; |
|
|
|
const prevWp = waypoints[i]; |
|
|
|
const nextWp = (i + 2) < waypoints.length ? waypoints[i + 2] : holdWp; |
|
|
|
if (!prevWp || !holdWp || !cesiumMap.computeEllipseParamsForDuration) continue; |
|
|
|
const centerCartesian = Cesium.Cartesian3.fromDegrees(parseFloat(holdWp.lng), parseFloat(holdWp.lat), Number(holdWp.alt) || 0); |
|
|
|
const prevCartesian = Cesium.Cartesian3.fromDegrees(parseFloat(prevWp.lng), parseFloat(prevWp.lat), Number(prevWp.alt) || 0); |
|
|
|
const nextCartesian = nextWp ? Cesium.Cartesian3.fromDegrees(parseFloat(nextWp.lng), parseFloat(nextWp.lat), Number(nextWp.alt) || 0) : centerCartesian; |
|
|
|
const headingDeg = holdParams && holdParams.headingDeg != null ? holdParams.headingDeg : 0; |
|
|
|
const a0 = holdParams && (holdParams.semiMajor != null || holdParams.semiMajorAxis != null) ? (holdParams.semiMajor ?? holdParams.semiMajorAxis) : 500; |
|
|
|
const b0 = holdParams && (holdParams.semiMinor != null || holdParams.semiMinorAxis != null) ? (holdParams.semiMinor ?? holdParams.semiMinorAxis) : 300; |
|
|
|
const out = cesiumMap.computeEllipseParamsForDuration(centerCartesian, prevCartesian, nextCartesian, s.holdClockwise !== false, totalHoldDistM, headingDeg, a0, b0); |
|
|
|
if (out && out.semiMajor != null) { |
|
|
|
const smj = out.semiMajor; |
|
|
|
const smn = out.semiMinor; |
|
|
|
const old = holdEllipseParamsByLegIndex[i]; |
|
|
|
if (!old || Math.abs(smj - old.semiMajor) > 1) changed = true; |
|
|
|
holdEllipseParamsByLegIndex[i] = { semiMajor: smj, semiMinor: smn, headingDeg }; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (!changed || iter === 3) break; |
|
|
|
if (hasEllipse) { |
|
|
|
const ret2 = cesiumMap.getRoutePathWithSegmentIndices(waypoints, { holdEllipseParamsByLegIndex }); |
|
|
|
if (ret2.path && ret2.path.length > 0 && ret2.segmentEndIndices && ret2.segmentEndIndices.length > 0) { |
|
|
|
pathData = { path: ret2.path, segmentEndIndices: ret2.segmentEndIndices, holdArcRanges: ret2.holdArcRanges || {} }; |
|
|
|
const out2 = this.buildRouteTimeline(waypoints, globalMin, globalMax, pathData); |
|
|
|
segments = out2.segments; |
|
|
|
} |
|
|
|
if (pathData2) pathData = pathData2; |
|
|
|
if (segments2) segments = segments2; |
|
|
|
if (routeId != null) { |
|
|
|
if (cesiumMap.setRouteHoldRadii) cesiumMap.setRouteHoldRadii(routeId, holdRadiusByLegIndex); |
|
|
|
if (cesiumMap.setRouteHoldEllipseParams) cesiumMap.setRouteHoldEllipseParams(routeId, holdEllipseParamsByLegIndex); |
|
|
|
if (routeId != null && cesiumMap.setRouteHoldEllipseParams) { |
|
|
|
cesiumMap.setRouteHoldEllipseParams(routeId, holdEllipseParamsByLegIndex); |
|
|
|
} |
|
|
|
} else if (routeId != null) { |
|
|
|
if (cesiumMap.setRouteHoldRadii) cesiumMap.setRouteHoldRadii(routeId, {}); |
|
|
|
if (cesiumMap.setRouteHoldEllipseParams) cesiumMap.setRouteHoldEllipseParams(routeId, {}); |
|
|
|
} else if (routeId != null && cesiumMap.setRouteHoldEllipseParams) { |
|
|
|
cesiumMap.setRouteHoldEllipseParams(routeId, {}); |
|
|
|
} |
|
|
|
// 圆形盘旋不再使用反算半径,清空旧缓存 |
|
|
|
if (routeId != null && cesiumMap.setRouteHoldRadii) { |
|
|
|
cesiumMap.setRouteHoldRadii(routeId, {}); |
|
|
|
} |
|
|
|
} |
|
|
|
const path = pathData ? pathData.path : null; |
|
|
|
|