Browse Source

冲突1.1

mh
menghao 2 weeks ago
parent
commit
6bdf9f2530
  1. 3
      ruoyi-ui/package.json
  2. 4
      ruoyi-ui/src/utils/conflictDetection.js
  3. 425
      ruoyi-ui/src/views/childRoom/index.vue

3
ruoyi-ui/package.json

@ -71,7 +71,8 @@
"sass-loader": "10.1.1",
"script-ext-html-webpack-plugin": "2.1.5",
"svg-sprite-loader": "5.1.1",
"vue-template-compiler": "2.6.12"
"vue-template-compiler": "2.6.12",
"worker-loader": "^3.0.8"
},
"engines": {
"node": ">=8.9",

4
ruoyi-ui/src/utils/conflictDetection.js

@ -17,6 +17,10 @@ export const defaultConflictConfig = {
// 时间
timeWindowOverlapMinutes: 0, // 时间窗重叠判定:两航线时间窗重叠即报(0=任意重叠)
resourceBufferMinutes: 0, // 资源占用前后缓冲(分钟)
/** 到达时间容差(秒):早于或晚于计划在此秒数内不报冲突,超出才报 */
timeToleranceSeconds: 5,
/** 速度容差(km/h):与所需/建议速度差在此范围内不报冲突,超出才报 */
speedToleranceKmh: 20,
// 空间
minTrackSeparationMeters: 5000, // 推演中两平台航迹最小间隔(米)
minPlatformPlacementMeters: 3000, // 摆放平台图标最小间距(米)

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

@ -520,15 +520,10 @@ import GanttDrawer from './GanttDrawer.vue';
import {
CONFLICT_TYPE,
defaultConflictConfig,
detectTimeWindowOverlap,
detectTrackSeparation,
detectPlatformPlacementTooClose,
detectRestrictedZoneIntrusion,
parseRestrictedZonesFromDrawings,
detectSpectrumConflicts,
createSpectrumLedgerEntry,
normalizeConflictList
} from '@/utils/conflictDetection';
import ConflictCheckWorker from 'worker-loader!@/workers/conflictCheck.worker.js'
export default {
name: 'MissionPlanningView',
components: {
@ -747,6 +742,8 @@ export default {
conflictConfig: { ...defaultConflictConfig },
/** 频谱资源台账(用于频谱冲突检测),可后续从接口或界面维护 */
spectrumLedger: [],
/** 冲突检测 worker(非响应式对象,仅占位,实际实例挂在 this._conflictWorker) */
_conflictWorkerInited: false,
//
activePlatformTab: 'air',
@ -1036,6 +1033,11 @@ export default {
clearInterval(this.playbackInterval);
this.playbackInterval = null;
}
// worker
if (this._conflictWorker && this._conflictWorker.terminate) {
try { this._conflictWorker.terminate(); } catch (_) {}
this._conflictWorker = null;
}
},
created() {
this.currentRoomId = this.$route.query.roomId;
@ -1292,7 +1294,8 @@ export default {
if (segmentMode != null) addPayload.segmentMode = segmentMode;
if (segmentTargetMinutes != null) addPayload.segmentTargetMinutes = segmentTargetMinutes;
if (segmentTargetSpeed != null) addPayload.segmentTargetSpeed = segmentTargetSpeed;
const addRes = await addWaypoints(addPayload);
const roomIdParam = this.currentRoomId != null ? { roomId: this.currentRoomId } : {};
const addRes = await addWaypoints(addPayload, roomIdParam);
await this.getList();
let updated = this.routes.find(r => r.id === routeId);
if (!updated || !updated.waypoints || updated.waypoints.length !== count) {
@ -1351,7 +1354,7 @@ export default {
if (isNewlyInserted && segmentTargetMinutes != null) updatePayload.segmentTargetMinutes = segmentTargetMinutes;
if (isNewlyInserted && segmentTargetSpeed != null) updatePayload.segmentTargetSpeed = segmentTargetSpeed;
if (isNewlyInserted && segmentMode === 'fixed_speed') updatePayload.startTime = startTime;
await updateWaypoints(updatePayload);
await updateWaypoints(updatePayload, roomIdParam);
}
await this.getList();
updated = this.routes.find(r => r.id === routeId);
@ -1464,7 +1467,8 @@ export default {
} else {
payload.turnRadius = 0;
}
const response = await updateWaypoints(payload);
const roomIdParam = this.currentRoomId != null ? { roomId: this.currentRoomId } : {};
const response = await updateWaypoints(payload, roomIdParam);
if (response.code !== 200) throw new Error(response.msg || '更新失败');
const merged = { ...wp, ...payload };
const routeInList = this.routes.find(r => r.id === routeId);
@ -1571,7 +1575,8 @@ export default {
seq: wp.seq,
lat: Number(lat),
lng: Number(lng),
alt: Number(alt),
// 5000 -> 4999.999...
alt: wp.alt,
speed: wp.speed,
startTime: (wp.startTime != null && wp.startTime !== '') ? wp.startTime : 'K+00:00:00',
turnAngle: wp.turnAngle
@ -1589,8 +1594,9 @@ export default {
const prevWp = idx > 0 ? waypoints[idx - 1] : null;
payload.turnRadius = this.$refs.cesiumMap.getTurnRadiusFromPrevSpeed(prevWp, wp.turnAngle);
}
const roomIdParam = this.currentRoomId != null ? { roomId: this.currentRoomId } : {};
try {
const response = await updateWaypoints(payload);
const response = await updateWaypoints(payload, roomIdParam);
if (response.code === 200) {
const merged = { ...wp, ...payload };
if (idx !== -1) waypoints.splice(idx, 1, merged);
@ -1614,7 +1620,7 @@ export default {
const startPayload = { ...merged, startTime: newStartTime };
if (merged.segmentMode != null) startPayload.segmentMode = merged.segmentMode;
try {
const r2 = await updateWaypoints(startPayload);
const r2 = await updateWaypoints(startPayload, roomIdParam);
if (r2.code === 200) {
Object.assign(merged, { startTime: newStartTime });
if (idx !== -1) waypoints.splice(idx, 1, merged);
@ -1635,7 +1641,7 @@ export default {
const speedPayload = { ...prev, speed: speedVal };
if (prev.segmentMode != null) speedPayload.segmentMode = prev.segmentMode;
try {
const r2 = await updateWaypoints(speedPayload);
const r2 = await updateWaypoints(speedPayload, roomIdParam);
if (r2.code === 200) {
Object.assign(prev, { speed: speedVal });
const prevIdx = idx - 1;
@ -1673,7 +1679,7 @@ export default {
if (merged.pixelSize != null) currPayload.pixelSize = merged.pixelSize;
if (merged.outlineColor != null) currPayload.outlineColor = merged.outlineColor;
try {
const r2 = await updateWaypoints(currPayload);
const r2 = await updateWaypoints(currPayload, roomIdParam);
if (r2.code === 200) {
Object.assign(merged, { speed: speedVal });
waypoints.splice(idx, 1, merged);
@ -2260,7 +2266,7 @@ export default {
} else {
payload.turnRadius = 0;
}
await updateWaypoints(payload);
await updateWaypoints(payload, this.currentRoomId != null ? { roomId: this.currentRoomId } : {});
}
const mergedWaypoints = (newRouteData.waypoints || []).map((oldWp) => {
const fromDialog = updatedRoute.waypoints.find((w) => w.id === oldWp.id);
@ -2685,7 +2691,8 @@ export default {
if (updatedWaypoint.pixelSize != null) payload.pixelSize = updatedWaypoint.pixelSize;
if (updatedWaypoint.color != null) payload.color = updatedWaypoint.color;
if (updatedWaypoint.outlineColor != null) payload.outlineColor = updatedWaypoint.outlineColor;
const response = await updateWaypoints(payload);
const roomIdParam = this.currentRoomId != null ? { roomId: this.currentRoomId } : {};
const response = await updateWaypoints(payload, roomIdParam);
if (response.code === 200) {
const roomId = this.currentRoomId;
const index = sd.waypoints.findIndex(p => p.id === updatedWaypoint.id);
@ -2719,7 +2726,7 @@ export default {
if (prev.pixelSize != null) prevPayload.pixelSize = prev.pixelSize;
if (prev.outlineColor != null) prevPayload.outlineColor = prev.outlineColor;
try {
const r2 = await updateWaypoints(prevPayload);
const r2 = await updateWaypoints(prevPayload, roomIdParam);
if (r2.code === 200) {
Object.assign(prev, { speed: speedVal });
sd.waypoints.splice(index - 1, 1, prev);
@ -2755,7 +2762,7 @@ export default {
if (merged.color != null) currPayload.color = merged.color;
if (merged.outlineColor != null) currPayload.outlineColor = merged.outlineColor;
try {
const r2 = await updateWaypoints(currPayload);
const r2 = await updateWaypoints(currPayload, roomIdParam);
if (r2.code === 200) {
Object.assign(merged, { speed: speedVal });
sd.waypoints.splice(index, 1, merged);
@ -2804,7 +2811,7 @@ export default {
if (wp.pixelSize != null) cascadePayload.pixelSize = wp.pixelSize;
if (wp.outlineColor != null) cascadePayload.outlineColor = wp.outlineColor;
try {
const rCascade = await updateWaypoints(cascadePayload);
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);
@ -3969,15 +3976,9 @@ export default {
// /退
this.toggleWhiteboardMode();
} else if (item.id === 'start') {
// 线线
// Web Worker线线
this.showConflictDrawer = true;
this.conflictCheckRunning = true;
this.$nextTick(() => {
setTimeout(() => {
this.runConflictCheck();
this.conflictCheckRunning = false;
}, 0);
});
this.runConflictCheck();
} else if (item.id === 'insert') {
//
if (this.activeRightTab === 'platform' && !this.isRightPanelHidden) {
@ -4347,6 +4348,9 @@ export default {
const path = pathData && pathData.path;
const segmentEndIndices = pathData && pathData.segmentEndIndices;
const holdArcRanges = pathData && pathData.holdArcRanges || {};
const config = this.conflictConfig || defaultConflictConfig;
const timeTolMin = (config.timeToleranceSeconds != null ? config.timeToleranceSeconds : 5) / 60;
const speedTol = (config.speedToleranceKmh != null ? config.speedToleranceKmh : 20);
let skipNextLeg = false;
for (let i = 0; i < points.length - 1; i++) {
if (skipNextLeg) {
@ -4489,7 +4493,8 @@ export default {
const scheduled = points[i + 1].minutes;
if (travelMin > 0 && scheduled - points[i].minutes > 0) {
const requiredSpeedKmh = (dist / 1000) / ((scheduled - points[i].minutes) / 60);
if (actualArrival > scheduled) {
const needFasterBy = requiredSpeedKmh - speedKmh;
if (actualArrival > scheduled + timeTolMin && needFasterBy > speedTol) {
warnings.push(
`某航段:距离约 ${(dist / 1000).toFixed(1)}km,计划 ${(scheduled - points[i].minutes).toFixed(0)} 分钟,当前速度 ${speedKmh}km/h 无法按时到达,约需 ≥${Math.ceil(requiredSpeedKmh)}km/h,请调整相对K时或速度。`
);
@ -4498,9 +4503,11 @@ export default {
fromName: waypoints[i].name,
toName: waypoints[i + 1].name,
requiredSpeedKmh: Math.ceil(requiredSpeedKmh),
speedKmh
speedKmh,
actualArrival,
scheduled
});
} else if (actualArrival < scheduled - 0.5) {
} else if (actualArrival < scheduled - timeTolMin) {
warnings.push('存在航段将提前到达下一航点,平台将在该点等待至计划时间再飞往下一段。');
}
}
@ -4528,8 +4535,22 @@ export default {
const travelMin = (dist / 1000) * (60 / speedKmh);
const actualArrival = effectiveTime[i] + travelMin;
const scheduled = points[i + 1].minutes;
if (travelMin > 0 && scheduled - points[i].minutes > 0 && actualArrival < scheduled - 0.5) {
earlyArrivalLegs.push({ legIndex: i, scheduled, actualArrival, fromName: waypoints[i].name, toName: waypoints[i + 1].name });
const earlyMinutes = scheduled - actualArrival;
const planMinutes = scheduled - points[i].minutes;
const suggestedSpeedKmh = planMinutes > 0.01 ? Math.round((dist / 1000) * (60 / planMinutes) * 10) / 10 : null;
const tooFastBy = suggestedSpeedKmh != null ? speedKmh - suggestedSpeedKmh : 0;
if (travelMin > 0 && planMinutes > 0 && actualArrival < scheduled - timeTolMin && (suggestedSpeedKmh == null || tooFastBy > speedTol)) {
earlyArrivalLegs.push({
legIndex: i,
scheduled,
actualArrival,
fromName: waypoints[i].name,
toName: waypoints[i + 1].name,
dist,
speedKmh,
earlyMinutes,
suggestedSpeedKmh
});
}
}
return { segments, warnings, earlyArrivalLegs, lateArrivalLegs, holdDelayConflicts };
@ -4819,6 +4840,7 @@ export default {
const startTime = this.addHoldForm.startTimeMinutes !== '' && this.addHoldForm.startTimeMinutes != null && !Number.isNaN(Number(this.addHoldForm.startTimeMinutes))
? this.minutesToStartTime(Number(this.addHoldForm.startTimeMinutes))
: (nextWp.startTime || 'K+01:00');
const roomIdParam = this.currentRoomId != null ? { roomId: this.currentRoomId } : {};
try {
await addWaypoints({
routeId,
@ -4831,12 +4853,12 @@ export default {
startTime,
pointType: this.addHoldForm.holdType,
holdParams: JSON.stringify(holdParams)
});
await delWaypoints(nextWp.id);
}, roomIdParam);
await delWaypoints(nextWp.id, roomIdParam);
for (let i = legIndex + 2; i < waypoints.length; i++) {
const w = waypoints[i];
if (w.id) {
await updateWaypoints({ ...w, seq: baseSeq + (i - legIndex) });
await updateWaypoints({ ...w, seq: baseSeq + (i - legIndex) }, roomIdParam);
}
}
this.showAddHoldDialog = false;
@ -5221,128 +5243,235 @@ export default {
},
// 线
runConflictCheck() {
const routeIds = this.activeRouteIds && this.activeRouteIds.length > 0 ? this.activeRouteIds : this.routes.map(r => r.id);
const { minMinutes, maxMinutes } = this.getDeductionTimeRange();
const config = this.conflictConfig || defaultConflictConfig;
const waypointStartTimeToMinutes = (s) => this.waypointStartTimeToMinutes(s);
async runConflictCheck() {
// requestId
if (!this._conflictWorkerInited) {
this._initConflictWorkerOnce();
this._conflictWorkerInited = true;
}
const requestId = this._nextConflictRequestId();
this.conflictCheckRunning = true;
try {
const routeIdsAll = (this.activeRouteIds && this.activeRouteIds.length > 0) ? this.activeRouteIds : this.routes.map(r => r.id);
const { minMinutes, maxMinutes } = this.getDeductionTimeRange();
const config = this.conflictConfig || defaultConflictConfig;
const allRaw = [];
/** 按航线缓存 timeline(segments+path),供航迹间隔检测复用,避免每 (routeId,t) 重复 buildRouteTimeline 导致航线多时卡死 */
const routeIdToTimeline = {};
const allRaw = [];
const routeIdToTimeline = {};
const routeIdsWithTimeline = [];
// ---------- 线----------
routeIds.forEach(routeId => {
const route = this.routes.find(r => r.id === routeId);
if (!route || !route.waypoints || route.waypoints.length < 2) return;
let pathData = null;
if (this.$refs.cesiumMap && this.$refs.cesiumMap.getRoutePathWithSegmentIndices) {
const ret = this.$refs.cesiumMap.getRoutePathWithSegmentIndices(route.waypoints);
if (ret.path && ret.path.length > 0 && ret.segmentEndIndices) {
pathData = { path: ret.path, segmentEndIndices: ret.segmentEndIndices, holdArcRanges: ret.holdArcRanges || {} };
// ---------- 线----------
// timelinesegments+path worker
for (let idx = 0; idx < routeIdsAll.length; idx++) {
const routeId = routeIdsAll[idx];
const route = this.routes.find(r => r.id === routeId);
if (!route || !route.waypoints || route.waypoints.length < 2) continue;
const cache = this._getConflictTimelineCache(routeId, route.waypoints, minMinutes, maxMinutes);
if (cache) {
routeIdToTimeline[routeId] = cache;
routeIdsWithTimeline.push(routeId);
} else {
let pathData = null;
if (this.$refs.cesiumMap && this.$refs.cesiumMap.getRoutePathWithSegmentIndices) {
const ret = this.$refs.cesiumMap.getRoutePathWithSegmentIndices(route.waypoints);
if (ret.path && ret.path.length > 0 && ret.segmentEndIndices) {
pathData = { path: ret.path, segmentEndIndices: ret.segmentEndIndices, holdArcRanges: ret.holdArcRanges || {} };
}
}
const timeline = this.buildRouteTimeline(route.waypoints, minMinutes, maxMinutes, pathData);
routeIdToTimeline[routeId] = {
segments: timeline.segments,
path: pathData && pathData.path ? pathData.path : null,
segmentEndIndices: pathData && pathData.segmentEndIndices ? pathData.segmentEndIndices : null
};
this._setConflictTimelineCache(routeId, route.waypoints, minMinutes, maxMinutes, routeIdToTimeline[routeId]);
routeIdsWithTimeline.push(routeId);
const { earlyArrivalLegs, lateArrivalLegs, holdDelayConflicts } = timeline;
const routeName = route.name || `航线${route.id}`;
(earlyArrivalLegs || []).forEach(leg => {
const earlyMin = leg.earlyMinutes != null ? Math.round(leg.earlyMinutes * 10) / 10 : 0;
const earlyStr = earlyMin >= 0.1 ? `${earlyMin} 分钟` : `${Math.round(earlyMin * 60)}`;
const speedStr = leg.suggestedSpeedKmh != null && Number.isFinite(leg.suggestedSpeedKmh) ? `${leg.suggestedSpeedKmh} km/h` : '(按计划时间反算)';
const kTimeStr = this.minutesToStartTime(leg.scheduled);
allRaw.push({
type: CONFLICT_TYPE.TIME,
subType: 'early_arrival',
title: '提前到达',
routeName,
routeIds: [routeId],
fromWaypoint: leg.fromName,
toWaypoint: leg.toName,
time: this.minutesToStartTime(leg.actualArrival),
suggestion: `① 将本段速度降至 ${speedStr} ② 若下一航点为盘旋点,可盘旋等待 ${earlyStr} ③ 将下一航点相对K时调至 ${kTimeStr} 或更晚`,
severity: 'high'
});
});
(lateArrivalLegs || []).forEach(leg => {
const kTimeStr = leg.actualArrival != null && Number.isFinite(leg.actualArrival) ? this.minutesToStartTime(leg.actualArrival) : '';
const part2 = kTimeStr ? ` ② 或将下一航点相对K时调至 ${kTimeStr} 或更晚` : ' ② 或将下一航点相对K时调晚';
allRaw.push({
type: CONFLICT_TYPE.TIME,
subType: 'late_arrival',
title: '无法按时到达',
routeName,
routeIds: [routeId],
fromWaypoint: leg.fromName,
toWaypoint: leg.toName,
suggestion: `① 将本段速度提升至 ≥${leg.requiredSpeedKmh} km/h${part2} ③ 调整上游航段速度或时间`,
severity: 'high'
});
});
(holdDelayConflicts || []).forEach(conf => {
allRaw.push({
type: CONFLICT_TYPE.TIME,
subType: 'hold_delay',
title: '盘旋时间不足',
routeName,
routeIds: [routeId],
fromWaypoint: conf.fromName,
toWaypoint: conf.toName,
time: this.minutesToStartTime(conf.setExitTime),
position: conf.holdCenter ? `经度 ${conf.holdCenter.lng.toFixed(5)}°, 纬度 ${conf.holdCenter.lat.toFixed(5)}°` : undefined,
suggestion: `实际切出将延迟 ${conf.delaySeconds} 秒。① 延长该盘旋点相对K时 ② 定时盘旋调转弯半径,非定时调上一航点速度或本点相对K时 ③ 微调上下游航点相对K时`,
severity: 'high',
holdCenter: conf.holdCenter,
positionLng: conf.holdCenter && conf.holdCenter.lng,
positionLat: conf.holdCenter && conf.holdCenter.lat,
positionAlt: conf.holdCenter && conf.holdCenter.alt
});
});
}
// 线线worker
if (idx % 8 === 7) await new Promise(r => setTimeout(r, 0));
}
const timeline = this.buildRouteTimeline(route.waypoints, minMinutes, maxMinutes, pathData);
const { earlyArrivalLegs, lateArrivalLegs, holdDelayConflicts } = timeline;
routeIdToTimeline[routeId] = {
segments: timeline.segments,
path: pathData && pathData.path ? pathData.path : null,
segmentEndIndices: pathData && pathData.segmentEndIndices ? pathData.segmentEndIndices : null
};
const routeName = route.name || `航线${route.id}`;
(earlyArrivalLegs || []).forEach(leg => {
// K=//K/
allRaw.push({
type: CONFLICT_TYPE.TIME,
subType: 'early_arrival',
title: '提前到达',
routeName,
routeIds: [routeId],
fromWaypoint: leg.fromName,
toWaypoint: leg.toName,
time: this.minutesToStartTime(leg.actualArrival),
suggestion: '① 降低本段速度 ② 下一航点为盘旋点时依靠盘旋等待 ③ 将下一航点相对K时调晚',
severity: 'high'
});
});
(lateArrivalLegs || []).forEach(leg => {
// KK/
allRaw.push({
type: CONFLICT_TYPE.TIME,
subType: 'late_arrival',
title: '无法按时到达',
routeName,
routeIds: [routeId],
fromWaypoint: leg.fromName,
toWaypoint: leg.toName,
suggestion: `① 将本段速度提升至 ≥${leg.requiredSpeedKmh} km/h ② 将下一航点相对K时调早 ③ 调整上游航段速度或时间`,
severity: 'high'
});
});
(holdDelayConflicts || []).forEach(conf => {
// =K-K/
allRaw.push({
type: CONFLICT_TYPE.TIME,
subType: 'hold_delay',
title: '盘旋时间不足',
routeName,
routeIds: [routeId],
fromWaypoint: conf.fromName,
toWaypoint: conf.toName,
time: this.minutesToStartTime(conf.setExitTime),
position: conf.holdCenter ? `经度 ${conf.holdCenter.lng.toFixed(5)}°, 纬度 ${conf.holdCenter.lat.toFixed(5)}°` : undefined,
suggestion: `实际切出将延迟 ${conf.delaySeconds} 秒。① 延长该盘旋点相对K时 ② 定时盘旋调转弯半径,非定时调上一航点速度或本点相对K时 ③ 微调上下游航点相对K时`,
severity: 'high',
holdCenter: conf.holdCenter,
positionLng: conf.holdCenter && conf.holdCenter.lng,
positionLat: conf.holdCenter && conf.holdCenter.lat,
positionAlt: conf.holdCenter && conf.holdCenter.alt
});
// ---------- // worker ----------
const platformIcons = (this.$refs.cesiumMap && this.$refs.cesiumMap.getPlatformIconPositions)
? this.$refs.cesiumMap.getPlatformIconPositions()
: [];
const drawingsEntities = (this.$refs.cesiumMap && this.$refs.cesiumMap.getFrontendDrawingsData)
? (((this.$refs.cesiumMap.getFrontendDrawingsData() || {}).entities) || [])
: [];
const workerRaw = await this._runConflictWorkerOnce({
requestId,
routeIds: routeIdsWithTimeline,
minMinutes,
maxMinutes,
config,
routeTimelines: routeIdToTimeline,
platformIcons,
drawingsEntities,
spectrumLedger: this.spectrumLedger || []
});
});
// /
// detectTimeWindowOverlap
if (workerRaw && Array.isArray(workerRaw)) {
workerRaw.forEach(c => allRaw.push(c));
}
// UI
this.conflicts = normalizeConflictList(allRaw, 1);
this.conflictCount = this.conflicts.length;
if (this.conflicts.length > 0) {
this.$message.warning(`检测到 ${this.conflicts.length} 处冲突`);
} else {
this.$message.success('未发现冲突');
}
} catch (e) {
console.warn('runConflictCheck failed', e);
this.$message.error('冲突检测失败,请稍后重试');
} finally {
// request loading
if (this._isLatestConflictRequestId(requestId)) {
this.conflictCheckRunning = false;
}
}
},
_initConflictWorkerOnce() {
// worker this Vue
this._conflictWorker = new ConflictCheckWorker();
this._conflictWorkerCallbacks = {};
this._conflictRequestSeq = 0;
this._conflictLatestRequestId = null;
this._conflictTimelineCache = {}; // routeId -> { key, data }
// ---------- 使 timeline (routeId,t) buildRouteTimeline----------
const getPositionAtMinutesForConflict = (routeId, minutesFromK) => {
const c = routeIdToTimeline[routeId];
if (!c || !c.segments || c.segments.length === 0) return null;
const pos = this.getPositionFromTimeline(c.segments, minutesFromK, c.path, c.segmentEndIndices);
return pos || null;
this._conflictWorker.onmessage = (evt) => {
const data = evt && evt.data ? evt.data : {};
const { requestId } = data;
const cb = this._conflictWorkerCallbacks && requestId ? this._conflictWorkerCallbacks[requestId] : null;
if (!cb) return;
delete this._conflictWorkerCallbacks[requestId];
cb(data);
};
const trackConflicts = detectTrackSeparation(routeIds, minMinutes, maxMinutes, getPositionAtMinutesForConflict, config);
trackConflicts.forEach(c => allRaw.push(c));
// ---------- ----------
const platformIcons = this.$refs.cesiumMap && this.$refs.cesiumMap.getPlatformIconPositions ? this.$refs.cesiumMap.getPlatformIconPositions() : [];
const placementConflicts = detectPlatformPlacementTooClose(platformIcons, config);
placementConflicts.forEach(c => allRaw.push(c));
// ---------- ----------
let restrictedZones = [];
if (this.$refs.cesiumMap && this.$refs.cesiumMap.getFrontendDrawingsData) {
const drawings = this.$refs.cesiumMap.getFrontendDrawingsData();
const entities = (drawings && drawings.entities) || [];
restrictedZones = parseRestrictedZonesFromDrawings(entities, config.restrictedZoneNameKeywords || defaultConflictConfig.restrictedZoneNameKeywords);
restrictedZones = restrictedZones.map(z => ({ ...z, points: z.points || (z.data && z.data.points) || [] })).filter(z => z.points && z.points.length >= 3);
}
const restrictedConflicts = detectRestrictedZoneIntrusion(routeIds, minMinutes, maxMinutes, getPositionAtMinutesForConflict, restrictedZones, config);
restrictedConflicts.forEach(c => allRaw.push(c));
// ---------- ----------
if (this.spectrumLedger && this.spectrumLedger.length >= 2) {
const spectrumConflicts = detectSpectrumConflicts(this.spectrumLedger, config);
spectrumConflicts.forEach(c => allRaw.push(c));
}
this.conflicts = normalizeConflictList(allRaw, 1);
this.conflictCount = this.conflicts.length;
if (this.conflicts.length > 0) {
this.$message.warning(`检测到 ${this.conflicts.length} 处冲突`);
} else {
this.$message.success('未发现冲突');
this._conflictWorker.onerror = (err) => {
console.warn('conflict worker error', err);
};
},
_nextConflictRequestId() {
this._conflictRequestSeq = (this._conflictRequestSeq || 0) + 1;
const id = `conflict_${Date.now()}_${this._conflictRequestSeq}`;
this._conflictLatestRequestId = id;
return id;
},
_isLatestConflictRequestId(requestId) {
return requestId && this._conflictLatestRequestId && requestId === this._conflictLatestRequestId;
},
_runConflictWorkerOnce(payload) {
return new Promise((resolve, reject) => {
if (!this._conflictWorker) return reject(new Error('worker not ready'));
const requestId = payload && payload.requestId;
if (!requestId) return reject(new Error('missing requestId'));
this._conflictWorkerCallbacks[requestId] = (msg) => {
if (!this._isLatestConflictRequestId(requestId)) return resolve(null); //
if (msg && msg.ok) return resolve(msg.conflicts || []);
reject(new Error((msg && msg.error) ? msg.error : 'worker failed'));
};
this._conflictWorker.postMessage(payload);
});
},
_hashWaypointsForConflict(waypoints) {
// hash stringify
// startTimespeed
const wps = Array.isArray(waypoints) ? waypoints : [];
let h = 5381;
const take = (v) => (v == null ? '' : String(v));
const n = wps.length;
const step = n > 60 ? Math.ceil(n / 60) : 1; // 60
for (let i = 0; i < n; i += step) {
const w = wps[i] || {};
const s = `${take(w.lng)},${take(w.lat)},${take(w.alt)},${take(w.startTime)},${take(w.speed)},${take(w.pointType || w.point_type)},${take(w.segmentMode)},${take(w.segmentTargetMinutes || (w.displayStyle && w.displayStyle.segmentTargetMinutes))}`
for (let k = 0; k < s.length; k++) {
h = ((h << 5) + h) ^ s.charCodeAt(k);
}
}
return (h >>> 0).toString(16) + '_' + n;
},
_makeConflictTimelineCacheKey(waypoints, minMinutes, maxMinutes) {
return `${minMinutes}|${maxMinutes}|${this._hashWaypointsForConflict(waypoints)}`;
},
_getConflictTimelineCache(routeId, waypoints, minMinutes, maxMinutes) {
const cache = this._conflictTimelineCache && this._conflictTimelineCache[routeId];
if (!cache) return null;
const key = this._makeConflictTimelineCacheKey(waypoints, minMinutes, maxMinutes);
if (cache.key !== key) return null;
return cache.data || null;
},
_setConflictTimelineCache(routeId, waypoints, minMinutes, maxMinutes, data) {
if (!this._conflictTimelineCache) this._conflictTimelineCache = {};
const key = this._makeConflictTimelineCacheKey(waypoints, minMinutes, maxMinutes);
this._conflictTimelineCache[routeId] = { key, data };
},
/** 查看冲突:展开问题航线、显示右侧方案树、定位到冲突位置并跳转时间轴 */

Loading…
Cancel
Save