Browse Source

飞机朝向优化

wxp
ctw 2 months ago
parent
commit
3687d4ff0a
  1. 83
      ruoyi-ui/src/views/cesiumMap/index.vue
  2. 81
      ruoyi-ui/src/views/childRoom/index.vue

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

@ -422,11 +422,17 @@ export default {
}
});
});
// 线
// 线线
const iconUrl = (platform && platform.imageUrl) || (platform && platform.iconUrl);
if (iconUrl && originalPositions.length > 0) {
const platformBillboardId = `route-platform-${routeId}`;
const fullUrl = this.formatPlatformIconUrl(iconUrl);
let initialRotation;
const pathData = this.getRoutePathWithSegmentIndices(waypoints);
if (pathData.path && pathData.path.length >= 2) {
const heading = this.computeHeadingFromPositions(pathData.path[0], pathData.path[1]);
if (heading !== undefined) initialRotation = Math.PI / 2 - heading;
}
this.viewer.entities.add({
id: platformBillboardId,
name: (platform && platform.name) || '平台',
@ -439,7 +445,8 @@ export default {
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
scaleByDistance: new Cesium.NearFarScalar(500, 2.0, 200000, 0.4),
translucencyByDistance: new Cesium.NearFarScalar(1000, 1.0, 500000, 0.6)
translucencyByDistance: new Cesium.NearFarScalar(1000, 1.0, 500000, 0.6),
...(initialRotation !== undefined && { rotation: initialRotation })
}
});
}
@ -545,6 +552,44 @@ export default {
}
return arc;
},
/**
* 获取与地图绘制一致的带转弯弧的路径用于推演时图标沿弧线运动
* @param {Array} waypoints - 航点列表需含 lng, lat, alt, speed, turnAngle
* @returns {{ path: Array<{lng,lat,alt}>, segmentEndIndices: number[] }} path 为路径点segmentEndIndices[i] 为第 i 航点 i -> i+1 path 中的结束下标
*/
getRoutePathWithSegmentIndices(waypoints) {
if (!waypoints || waypoints.length === 0) return { path: [], segmentEndIndices: [] };
const ellipsoid = this.viewer.scene.globe.ellipsoid;
const toLngLatAlt = (cartesian) => {
const carto = Cesium.Cartographic.fromCartesian(cartesian, ellipsoid);
return {
lng: Cesium.Math.toDegrees(carto.longitude),
lat: Cesium.Math.toDegrees(carto.latitude),
alt: carto.height
};
};
const originalPositions = waypoints.map(wp =>
Cesium.Cartesian3.fromDegrees(parseFloat(wp.lng), parseFloat(wp.lat), Number(wp.alt) || 0)
);
const path = [];
const segmentEndIndices = [];
for (let i = 0; i < waypoints.length; i++) {
const currPos = originalPositions[i];
const radius = this.getWaypointRadius(waypoints[i]);
if (i === 0 || i === waypoints.length - 1 || radius <= 0) {
path.push(toLngLatAlt(currPos));
} else {
const prevPos = originalPositions[i - 1];
const nextPos = originalPositions[i + 1];
const arcPoints = this.computeArcPositions(prevPos, currPos, nextPos, radius);
arcPoints.forEach(p => path.push(toLngLatAlt(p)));
}
if (i >= 1) segmentEndIndices[i - 1] = path.length - 1;
}
return { path, segmentEndIndices };
},
removeRouteById(routeId) {
// routeId
const entityList = this.viewer.entities.values;
@ -562,8 +607,31 @@ export default {
}
this.allEntities = this.allEntities.filter(item => item.id !== routeId && item.id !== `route-platform-${routeId}`);
},
/** 动态推演:更新某条航线的平台图标位置(position: { lng, lat, alt } 或 Cesium.Cartesian3) */
updatePlatformPosition(routeId, position) {
/**
* 根据当前点与另一点计算航向角弧度用于飞机图标朝向
* 航向北为 0顺时针为正Cesium billboard rotation 为自上而下看逆时针故设置 rotation = -heading
*/
computeHeadingFromPositions(current, other) {
if (!current || !other) return undefined;
const cartesian1 = current.x !== undefined && current.y !== undefined && current.z !== undefined
? current
: Cesium.Cartesian3.fromDegrees(Number(current.lng), Number(current.lat), Number(current.alt) || 0);
const cartesian2 = other.x !== undefined && other.y !== undefined && other.z !== undefined
? other
: Cesium.Cartesian3.fromDegrees(Number(other.lng), Number(other.lat), Number(other.alt) || 0);
const enu = Cesium.Transforms.eastNorthUpToFixedFrame(cartesian1);
const east = Cesium.Matrix4.getColumn(enu, 0, new Cesium.Cartesian3());
const north = Cesium.Matrix4.getColumn(enu, 1, new Cesium.Cartesian3());
const toOther = Cesium.Cartesian3.subtract(cartesian2, cartesian1, new Cesium.Cartesian3());
const e = Cesium.Cartesian3.dot(toOther, east);
const n = Cesium.Cartesian3.dot(toOther, north);
if (Math.abs(e) < 1e-10 && Math.abs(n) < 1e-10) return undefined;
const heading = Math.atan2(e, n);
return heading;
},
/** 动态推演:更新某条航线的平台图标位置与朝向(position: { lng, lat, alt } 或 Cesium.Cartesian3;directionPoint 为用于计算机头朝向的另一点,如下一位置或上一位置) */
updatePlatformPosition(routeId, position, directionPoint) {
if (!this.viewer) return;
const entity = this.viewer.entities.getById(`route-platform-${routeId}`);
if (!entity || !entity.position) return;
@ -577,6 +645,13 @@ export default {
return;
}
entity.position = cartesian;
if (entity.billboard && directionPoint) {
const heading = this.computeHeadingFromPositions(position, directionPoint);
if (heading !== undefined) {
// 90° 使 rotation = π/2 - heading
entity.billboard.rotation = Math.PI / 2 - heading;
}
}
},
checkCesiumLoaded() {
if (typeof Cesium === 'undefined') {

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

@ -1716,25 +1716,70 @@ 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' });
segments.push({ startTime: effectiveTime[i], endTime: actualArrival, startPos: posCur, endPos: posNext, type: 'fly', legIndex: i });
if (actualArrival < effectiveTime[i + 1]) {
segments.push({ startTime: actualArrival, endTime: effectiveTime[i + 1], startPos: posNext, endPos: posNext, type: 'wait' });
segments.push({ startTime: actualArrival, endTime: effectiveTime[i + 1], startPos: posNext, endPos: posNext, type: 'wait', legIndex: i });
}
}
return { segments, warnings };
},
/** 从时间轴中取当前推演时间对应的位置 */
getPositionFromTimeline(segments, minutesFromK) {
/** 从路径片段中按比例 t(0~1)取点:按弧长比例插值 */
getPositionAlongPathSlice(pathSlice, t) {
if (!pathSlice || pathSlice.length === 0) return null;
if (pathSlice.length === 1 || t <= 0) return pathSlice[0];
if (t >= 1) return pathSlice[pathSlice.length - 1];
let totalLen = 0;
const lengths = [0];
for (let i = 1; i < pathSlice.length; i++) {
totalLen += this.segmentDistance(pathSlice[i - 1], pathSlice[i]);
lengths.push(totalLen);
}
const targetDist = t * totalLen;
let idx = 0;
while (idx < lengths.length - 1 && lengths[idx + 1] < targetDist) idx++;
const a = pathSlice[idx];
const b = pathSlice[idx + 1];
const segLen = lengths[idx + 1] - lengths[idx];
const segT = segLen > 0 ? (targetDist - lengths[idx]) / segLen : 0;
return {
lng: a.lng + (b.lng - a.lng) * segT,
lat: a.lat + (b.lat - a.lat) * segT,
alt: a.alt + (b.alt - a.alt) * segT
};
},
/** 从时间轴中取当前推演时间对应的位置;若有 path/segmentEndIndices 则沿带转弯弧的路径插值 */
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];
if (minutesFromK >= last.endTime) return last.endPos;
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]];
}
return last.endPos;
}
for (let i = 0; i < segments.length; i++) {
const s = segments[i];
if (minutesFromK < s.endTime) {
const t = (minutesFromK - s.startTime) / (s.endTime - s.startTime);
if (s.type === 'wait') return s.startPos;
if (s.type === 'wait') {
// 线线
if (path && segmentEndIndices && s.legIndex != null && s.legIndex < segmentEndIndices.length) {
const endIdx = segmentEndIndices[s.legIndex];
if (path[endIdx]) return path[endIdx];
}
return s.startPos;
}
// 沿线线
if (path && segmentEndIndices && s.legIndex != null && s.legIndex < segmentEndIndices.length) {
const startIdx = s.legIndex === 0 ? 0 : segmentEndIndices[s.legIndex - 1];
const endIdx = segmentEndIndices[s.legIndex];
const pathSlice = path.slice(startIdx, endIdx + 1);
if (pathSlice.length > 0) return this.getPositionAlongPathSlice(pathSlice, t);
}
return {
lng: s.startPos.lng + (s.endPos.lng - s.startPos.lng) * t,
lat: s.startPos.lat + (s.endPos.lat - s.startPos.lat) * t,
@ -1745,12 +1790,24 @@ export default {
return last.endPos;
},
/** 根据当前推演时间(相对 K 的分钟)得到平台位置,使用速度与等待逻辑;返回 { position, warnings } */
/** 根据当前推演时间(相对 K 的分钟)得到平台位置,使用速度与等待逻辑;若有地图 ref 则沿转弯弧路径运动;返回 { position, nextPosition, previousPosition, warnings },用于计算机头朝向 */
getPositionAtMinutesFromK(waypoints, minutesFromK, globalMin, globalMax) {
if (!waypoints || waypoints.length === 0) return { position: null, warnings: [] };
if (!waypoints || waypoints.length === 0) return { position: null, nextPosition: null, previousPosition: null, warnings: [] };
const { segments, warnings } = this.buildRouteTimeline(waypoints, globalMin, globalMax);
const position = this.getPositionFromTimeline(segments, minutesFromK);
return { position, warnings };
let path = null;
let segmentEndIndices = null;
if (this.$refs.cesiumMap && this.$refs.cesiumMap.getRoutePathWithSegmentIndices) {
const ret = this.$refs.cesiumMap.getRoutePathWithSegmentIndices(waypoints);
if (ret.path && ret.path.length > 0 && ret.segmentEndIndices && ret.segmentEndIndices.length > 0) {
path = ret.path;
segmentEndIndices = ret.segmentEndIndices;
}
}
const position = this.getPositionFromTimeline(segments, minutesFromK, path, segmentEndIndices);
const stepMin = 1 / 60;
const nextPosition = this.getPositionFromTimeline(segments, minutesFromK + stepMin, path, segmentEndIndices);
const previousPosition = this.getPositionFromTimeline(segments, minutesFromK - stepMin, path, segmentEndIndices);
return { position, nextPosition, previousPosition, warnings };
},
/** 仅根据当前展示的航线(activeRouteIds)更新平台图标位置,并汇总航段提示 */
@ -1762,9 +1819,9 @@ export default {
this.activeRouteIds.forEach(routeId => {
const route = this.routes.find(r => r.id === routeId);
if (!route || !route.waypoints || route.waypoints.length === 0) return;
const { position, warnings } = this.getPositionAtMinutesFromK(route.waypoints, minutesFromK, minMinutes, maxMinutes);
const { position, nextPosition, previousPosition, warnings } = this.getPositionAtMinutesFromK(route.waypoints, minutesFromK, minMinutes, maxMinutes);
if (warnings && warnings.length) allWarnings.push(...warnings);
if (position) this.$refs.cesiumMap.updatePlatformPosition(routeId, position);
if (position) this.$refs.cesiumMap.updatePlatformPosition(routeId, position, nextPosition || previousPosition);
});
this.deductionWarnings = [...new Set(allWarnings)];
},

Loading…
Cancel
Save