fix: 修复3D地球坐标映射多个严重bug
## Bug修复详情 ### 1. 致命错误:球面距离计算 (calculateDistance) - 问题:使用勾股定理计算经纬度距离,在球体表面完全错误 - 修复:改用Haversine公式计算球面大圆距离 - 影响:赤道1度=111km,极地1度=19km,原计算误差巨大 ### 2. 经度范围规范化 (vector3ToLatLon) - 问题:Math.atan2返回[-180°,180°],转换后可能超出标准范围 - 修复:添加while循环规范化到[-180, 180]区间 - 影响:避免本初子午线附近返回360°的异常值 ### 3. 屏幕坐标转换支持非全屏 (screenToEarthCoords) - 问题:假设Canvas永远全屏,非全屏时点击偏移严重 - 修复:新增domElement参数,使用getBoundingClientRect()计算相对坐标 - 影响:嵌入式3D地球组件也能精准拾取 ### 4. 地球旋转时经纬度映射错误 - 问题:Raycaster返回世界坐标,未考虑地球自转 - 修复:使用earth.worldToLocal()转换到本地坐标空间 - 影响:地球旋转时经纬度显示正确跟随 ## 新增功能 - CelesTrak卫星数据采集器 - Space-Track卫星数据采集器 - 卫星可视化模块(500颗,实时SGP4轨道计算) - 海底光缆悬停显示info-card - 统一info-card组件 - 工具栏按钮(Stellarium风格) - 缩放控制(百分比显示) - Docker volume映射(代码热更新) ## 文件变更 - utils.js: 坐标转换核心逻辑修复 - satellites.js: 新增卫星可视化 - cables.js: 悬停交互支持 - main.js: 悬停/锁定逻辑 - controls.js: 工具栏UI - info-card.js: 统一卡片组件 - docker-compose.yml: volume映射 - restart.sh: 简化重启脚本
This commit is contained in:
@@ -8,14 +8,15 @@ import {
|
||||
updateCoordinatesDisplay,
|
||||
updateZoomDisplay,
|
||||
updateEarthStats,
|
||||
updateCableDetails,
|
||||
setLoading,
|
||||
showTooltip,
|
||||
hideTooltip
|
||||
} from './ui.js';
|
||||
import { createEarth, createClouds, createTerrain, createStars, createGridLines, toggleTerrain, getEarth } from './earth.js';
|
||||
import { loadGeoJSONFromPath, loadLandingPoints, handleCableClick, clearCableSelection, getCableLines, getCablesById } from './cables.js';
|
||||
import { createSatellites, loadSatellites, updateSatellitePositions, toggleSatellites, toggleTrails, getShowSatellites, selectSatellite, getSatelliteData, getSatellitePoints } from './satellites.js';
|
||||
import { setupControls, getAutoRotate, getShowTerrain, zoomLevel, setAutoRotate, toggleAutoRotate } from './controls.js';
|
||||
import { initInfoCard, showInfoCard, hideInfoCard, getCurrentType, setInfoCardNoBorder } from './info-card.js';
|
||||
|
||||
export let scene, camera, renderer;
|
||||
let simplex;
|
||||
@@ -49,11 +50,13 @@ export function init() {
|
||||
document.getElementById('container').appendChild(renderer.domElement);
|
||||
|
||||
addLights();
|
||||
initInfoCard();
|
||||
const earthObj = createEarth(scene);
|
||||
createClouds(scene, earthObj);
|
||||
createTerrain(scene, earthObj, simplex);
|
||||
createStars(scene);
|
||||
createGridLines(scene, earthObj);
|
||||
createSatellites(scene, earthObj);
|
||||
|
||||
setupControls(camera, renderer, scene, earthObj);
|
||||
setupEventListeners(camera, renderer);
|
||||
@@ -80,7 +83,19 @@ function addLights() {
|
||||
scene.add(pointLight);
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
let earthTexture = null;
|
||||
|
||||
async function loadData(showWhiteSphere = false) {
|
||||
if (showWhiteSphere) {
|
||||
const earth = getEarth();
|
||||
if (earth && earth.material) {
|
||||
earthTexture = earth.material.map;
|
||||
earth.material.map = null;
|
||||
earth.material.color.setHex(0xffffff);
|
||||
earth.material.needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
console.log('开始加载电缆数据...');
|
||||
@@ -88,11 +103,29 @@ async function loadData() {
|
||||
console.log('电缆数据加载完成');
|
||||
await loadLandingPoints(scene, getEarth());
|
||||
console.log('登陆点数据加载完成');
|
||||
|
||||
const satCount = await loadSatellites();
|
||||
console.log(`卫星数据加载完成: ${satCount} 颗`);
|
||||
updateSatellitePositions();
|
||||
console.log('卫星位置已更新');
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
showStatusMessage('加载数据失败: ' + error.message, 'error');
|
||||
}
|
||||
setLoading(false);
|
||||
|
||||
if (showWhiteSphere) {
|
||||
const earth = getEarth();
|
||||
if (earth && earth.material) {
|
||||
earth.material.map = earthTexture;
|
||||
earth.material.color.setHex(0xffffff);
|
||||
earth.material.needsUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function reloadData() {
|
||||
await loadData(true);
|
||||
}
|
||||
|
||||
function setupEventListeners(camera, renderer) {
|
||||
@@ -159,7 +192,7 @@ function onMouseMove(event, camera) {
|
||||
});
|
||||
hoveredCable = null;
|
||||
}
|
||||
|
||||
|
||||
if (intersects.length > 0) {
|
||||
const cable = intersects[0].object;
|
||||
const cableId = cable.userData.cableId;
|
||||
@@ -171,34 +204,25 @@ function onMouseMove(event, camera) {
|
||||
c.material.opacity = 1;
|
||||
});
|
||||
hoveredCable = cable;
|
||||
|
||||
showInfoCard('cable', {
|
||||
name: cable.userData.name,
|
||||
owner: cable.userData.owner,
|
||||
status: cable.userData.status,
|
||||
length: cable.userData.length,
|
||||
coords: cable.userData.coords,
|
||||
rfs: cable.userData.rfs
|
||||
});
|
||||
setInfoCardNoBorder(true);
|
||||
}
|
||||
|
||||
const userData = cable.userData;
|
||||
document.getElementById('cable-name').textContent =
|
||||
userData.name || userData.shortname || '未命名电缆';
|
||||
document.getElementById('cable-owner').textContent = userData.owner || '-';
|
||||
document.getElementById('cable-status').textContent = userData.status || '-';
|
||||
document.getElementById('cable-length').textContent = userData.length || '-';
|
||||
document.getElementById('cable-coords').textContent = '-';
|
||||
document.getElementById('cable-rfs').textContent = userData.rfs || '-';
|
||||
|
||||
hideTooltip();
|
||||
} else {
|
||||
if (lockedCable && lockedCableData) {
|
||||
document.getElementById('cable-name').textContent =
|
||||
lockedCableData.name || lockedCableData.shortname || '未命名电缆';
|
||||
document.getElementById('cable-owner').textContent = lockedCableData.owner || '-';
|
||||
document.getElementById('cable-status').textContent = lockedCableData.status || '-';
|
||||
document.getElementById('cable-length').textContent = lockedCableData.length || '-';
|
||||
document.getElementById('cable-coords').textContent = '-';
|
||||
document.getElementById('cable-rfs').textContent = lockedCableData.rfs || '-';
|
||||
if (lockedCable) {
|
||||
handleCableClick(lockedCable);
|
||||
} else {
|
||||
document.getElementById('cable-name').textContent = '点击电缆查看详情';
|
||||
document.getElementById('cable-owner').textContent = '-';
|
||||
document.getElementById('cable-status').textContent = '-';
|
||||
document.getElementById('cable-length').textContent = '-';
|
||||
document.getElementById('cable-coords').textContent = '-';
|
||||
document.getElementById('cable-rfs').textContent = '-';
|
||||
hideInfoCard();
|
||||
}
|
||||
|
||||
const earthPoint = screenToEarthCoords(event.clientX, event.clientY, camera, earth);
|
||||
@@ -278,6 +302,45 @@ function onClick(event, camera, renderer) {
|
||||
|
||||
setAutoRotate(false);
|
||||
handleCableClick(clickedCable);
|
||||
|
||||
showInfoCard('cable', {
|
||||
name: clickedCable.userData.name,
|
||||
owner: clickedCable.userData.owner,
|
||||
status: clickedCable.userData.status,
|
||||
length: clickedCable.userData.length,
|
||||
coords: clickedCable.userData.coords,
|
||||
rfs: clickedCable.userData.rfs
|
||||
});
|
||||
} else if (getShowSatellites()) {
|
||||
const satIntersects = raycaster.intersectObject(getSatellitePoints());
|
||||
|
||||
if (satIntersects.length > 0) {
|
||||
const index = satIntersects[0].index;
|
||||
const sat = selectSatellite(index);
|
||||
|
||||
if (sat && sat.properties) {
|
||||
const props = sat.properties;
|
||||
|
||||
const meanMotion = props.mean_motion || 0;
|
||||
const period = meanMotion > 0 ? (1440 / meanMotion).toFixed(1) : '-';
|
||||
|
||||
const ecc = props.eccentricity || 0;
|
||||
const earthRadius = 6371;
|
||||
const perigee = (earthRadius * (1 - ecc)).toFixed(0);
|
||||
const apogee = (earthRadius * (1 + ecc)).toFixed(0);
|
||||
|
||||
showInfoCard('satellite', {
|
||||
name: props.name,
|
||||
norad_id: props.norad_cat_id,
|
||||
inclination: props.inclination ? props.inclination.toFixed(2) : '-',
|
||||
period: period,
|
||||
perigee: perigee,
|
||||
apogee: apogee
|
||||
});
|
||||
|
||||
showStatusMessage('已选择: ' + props.name, 'info');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (lockedCable) {
|
||||
const prevCableId = lockedCable.userData.cableId;
|
||||
@@ -315,6 +378,8 @@ function animate() {
|
||||
});
|
||||
}
|
||||
|
||||
updateSatellitePositions(16);
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
@@ -334,4 +399,9 @@ window.clearLockedCable = function() {
|
||||
clearCableSelection();
|
||||
};
|
||||
|
||||
window.clearSelection = function() {
|
||||
hideInfoCard();
|
||||
window.clearLockedCable();
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
Reference in New Issue
Block a user