Browse Source

Merge branch 'ctw' of http://124.70.32.114:3100/woka/cesium-map-object into mh

# Conflicts:
#	ruoyi-ui/src/views/childRoom/index.vue
master
menghao 2 months ago
parent
commit
04a79af078
  1. 83
      ruoyi-ui/src/views/cesiumMap/index.vue
  2. 140
      ruoyi-ui/src/views/childRoom/index.vue

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

@ -428,11 +428,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) || '平台',
@ -445,7 +451,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 })
}
});
}
@ -551,6 +558,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;
@ -568,8 +613,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;
@ -583,6 +651,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') {

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

@ -331,6 +331,7 @@ import { listRoutes, getRoutes, addRoutes, updateRoutes, delRoutes } from "@/api
import { updateWaypoints } from "@/api/system/waypoints";
import { listLib,addLib,delLib} from "@/api/system/lib";
import { getRooms, updateRooms } from "@/api/system/rooms";
import { getMenuConfig, saveMenuConfig } from "@/api/system/userMenuConfig";
import PlatformImportDialog from "@/views/dialogs/PlatformImportDialog.vue";
export default {
name: 'MissionPlanningView',
@ -544,8 +545,9 @@ export default {
this.isMenuHidden = true;
//
this.isRightPanelHidden = true;
//
//
this.menuItems = [...this.defaultMenuItems];
this.loadUserMenuConfig();
//
this.updateTime();
@ -1111,9 +1113,7 @@ export default {
},
openKTimeSetDialog() {
console.log("当前登录 ID (myId):", this.$store.getters.id);
console.log("当前房间 ownerId:", this.roomDetail ? this.roomDetail.ownerId : '无房间信息');
console.log("当前角色 roles:", this.$store.getters.roles);
if (!this.canSetKTime) {
this.$message.info('仅房主或管理员可设定或修改 K 时');
@ -1229,8 +1229,14 @@ export default {
this.isIconEditMode = false
},
handleResetMenuItems() {
async handleResetMenuItems() {
this.menuItems = [...this.defaultMenuItems]
try {
await saveMenuConfig({
menuItems: JSON.stringify(this.menuItems),
position: this.menuPosition || 'left'
})
} catch (e) { /* 未登录时仅本地恢复默认 */ }
},
updateMenuItems(newItems) {
@ -1308,8 +1314,43 @@ export default {
}
},
handleSaveMenuItems(savedItems) {
async handleSaveMenuItems(savedItems) {
this.menuItems = [...savedItems]
//
try {
await saveMenuConfig({
menuItems: JSON.stringify(this.menuItems),
position: this.menuPosition || 'left'
})
} catch (e) {
// LeftMenu
if (e && e.response && e.response.status === 401) {
this.$message.info('当前未登录,配置仅在本页有效;登录后保存可同步到账号')
}
}
},
/** 加载当前用户的左侧菜单配置(登录且有过保存时生效) */
async loadUserMenuConfig() {
try {
const res = await getMenuConfig()
const data = res && res.data
if (!data) return
if (data.menuItems) {
let arr = []
try {
arr = typeof data.menuItems === 'string' ? JSON.parse(data.menuItems) : data.menuItems
} catch (e) { /* 解析失败保留默认 */ }
if (Array.isArray(arr) && arr.length > 0) {
this.menuItems = arr
}
}
if (data.position && ['left', 'top', 'bottom'].includes(data.position)) {
this.menuPosition = data.position
}
} catch (e) {
// 使
}
},
attributeEdit() {
@ -1418,9 +1459,15 @@ export default {
this.$message.success('外部参数保存成功');
},
savePageLayout(position) {
async savePageLayout(position) {
this.menuPosition = position;
this.$message.success(`菜单位置已设置为:${this.getPositionLabel(position)}`);
try {
await saveMenuConfig({
menuItems: JSON.stringify(this.menuItems),
position: this.menuPosition || 'left'
})
} catch (e) { /* 未登录时仅本地生效 */ }
},
getPositionLabel(position) {
@ -1721,25 +1768,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,
@ -1750,12 +1842,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)更新平台图标位置,并汇总航段提示 */
@ -1767,9 +1871,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