Browse Source

拖拽航点后平台乱移bug修复

mh
menghao 5 days ago
parent
commit
8f1694ea51
  1. 8
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/RouteWaypointsMapper.java
  2. 4
      ruoyi-system/src/main/resources/mapper/system/RouteWaypointsMapper.xml
  3. 4
      ruoyi-ui/src/lang/zh.js
  4. 25
      ruoyi-ui/src/views/cesiumMap/index.vue
  5. 200
      ruoyi-ui/src/views/childRoom/index.vue
  6. 3
      ruoyi-ui/src/views/dialogs/RouteEditDialog.vue

8
ruoyi-system/src/main/java/com/ruoyi/system/mapper/RouteWaypointsMapper.java

@ -29,7 +29,7 @@ public interface RouteWaypointsMapper
public List<RouteWaypoints> selectRouteWaypointsList(RouteWaypoints routeWaypoints);
/** 查询指定航线下最大的序号 */
public Integer selectMaxSeqByRouteId(Long routeId);
public Integer selectMaxSeqByRouteId(@Param("routeId") Long routeId);
/** 将指定航线中 seq >= targetSeq 的航点序号均加 1,用于在指定位置插入新航点 */
int incrementSeqFrom(@Param("routeId") Long routeId, @Param("seq") Long targetSeq);
@ -56,7 +56,7 @@ public interface RouteWaypointsMapper
* @param id 航线具体航点明细主键
* @return 结果
*/
public int deleteRouteWaypointsById(Long id);
public int deleteRouteWaypointsById(@Param("id") Long id);
/**
* 删除航线具体航点明细
@ -64,7 +64,7 @@ public interface RouteWaypointsMapper
* @param routeId 航线主键
* @return 结果
*/
public int deleteRouteWaypointsByRouteId(Long routeId);
public int deleteRouteWaypointsByRouteId(@Param("routeId") Long routeId);
/**
* 批量删除航线具体航点明细
@ -72,5 +72,5 @@ public interface RouteWaypointsMapper
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteRouteWaypointsByIds(Long[] ids);
public int deleteRouteWaypointsByIds(@Param("ids") Long[] ids);
}

4
ruoyi-system/src/main/resources/mapper/system/RouteWaypointsMapper.xml

@ -114,9 +114,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
delete from route_waypoints where route_id = #{routeId}
</delete>
<delete id="deleteRouteWaypointsByIds" parameterType="String">
<delete id="deleteRouteWaypointsByIds">
delete from route_waypoints where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</delete>

4
ruoyi-ui/src/lang/zh.js

@ -186,7 +186,7 @@ export default {
color: '填充颜色',
borderWidth: '边线宽度',
vertices: '顶点坐标',
polygonPlaceholder: '至少 3 个顶点,十进制度。可每行一对「经度,纬度」;或一行写 (121.47,31.23)、(120.15,30.28) 用顿号分隔',
polygonPlaceholder: '至少3个顶点,可每行一对(经度,纬度)或用顿号分隔',
rectangleSwCorner: '西南角经纬度',
rectangleNeCorner: '东北角经纬度',
cornerLonLatPlaceholder: '(经度,纬度)例如 (116.39, 39.90)',
@ -198,7 +198,7 @@ export default {
cancel: '取消',
confirm: '生成',
defaultLabel: '空域',
errPolygonPoints: '多边形至少需要 3 个有效顶点(经度,纬度)',
errPolygonPoints: '多边形至少需要3个有效顶点(经度,纬度)',
errRectNumbers: '请按(经度,纬度)格式填写有效的西南角与东北角',
errCircle: '请按(经度,纬度)填写有效的圆心与半径(千米)',
errSector: '请按(经度,纬度)填写有效的圆心、半径(千米)',

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

@ -2542,8 +2542,9 @@ export default {
}
},
//线
renderRouteWaypoints(waypoints, routeId = 'default', platformId, platform, style) {
if (!waypoints || waypoints.length < 1) return;
renderRouteWaypoints(waypointsRaw, routeId = 'default', platformId, platform, style) {
if (!waypointsRaw || waypointsRaw.length < 1) return;
const waypoints = this.sortWaypointsBySeq(waypointsRaw);
this.waypointDragPreview = null;
this.unregisterWaypointMapDomLabelsForRoute(routeId);
// 线线 + id 便
@ -3232,6 +3233,21 @@ export default {
const t = (wp && wp.pointType) || (wp && wp.point_type) || 'normal';
return t === 'hold_circle' || t === 'hold_ellipse';
},
/** 航线几何与平台起点均以 seq 最小航点为「出发航点」;与数据库 seq 一致,不依赖接口返回数组下标顺序 */
sortWaypointsBySeq(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);
// seq
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);
});
},
parseHoldParams(wp) {
const raw = (wp && wp.holdParams) || (wp && wp.hold_params);
if (!raw) return null;
@ -4551,8 +4567,9 @@ export default {
* @param options - 可选 { holdRadiusByLegIndex: { [legIndex]: number } } 为指定盘旋段覆盖半径使落点精准在切点
* @returns {{ path, segmentEndIndices, holdArcRanges: { [legIndex]: { start, end } } }}
*/
getRoutePathWithSegmentIndices(waypoints, options) {
if (!waypoints || waypoints.length === 0) return { path: [], segmentEndIndices: [], holdArcRanges: {} };
getRoutePathWithSegmentIndices(waypointsRaw, options) {
if (!waypointsRaw || waypointsRaw.length === 0) return { path: [], segmentEndIndices: [], holdArcRanges: {} };
const waypoints = this.sortWaypointsBySeq(waypointsRaw);
const holdRadiusByLegIndex = (options && options.holdRadiusByLegIndex) || {};
const holdEllipseParamsByLegIndex = (options && options.holdEllipseParamsByLegIndex) || {};
const ellipsoid = this.viewer.scene.globe.ellipsoid;

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

@ -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,

3
ruoyi-ui/src/views/dialogs/RouteEditDialog.vue

@ -24,7 +24,8 @@
</template>
<template v-else>
<el-button size="mini" @click="cancelWaypointsEdit"> </el-button>
<el-button type="primary" size="mini" class="blue-btn" @click="confirmWaypointsEdit"> </el-button>
<!-- 航点表编辑确定应直接落库否则刷新后会丢失 -->
<el-button type="primary" size="mini" class="blue-btn" @click="handleSave"> </el-button>
</template>
</template>
</div>

Loading…
Cancel
Save