银河系漫游指南
 
 
 
 
 
 
Go to file
mula.liu 3cdaaa2943 初步实现了三阶段 2025-12-06 17:06:39 +08:00
.claude 增加了数据截止日 2025-12-03 14:26:53 +08:00
.gemini-clipboard v 1.0.1 2025-12-02 21:25:28 +08:00
backend Phase 2 2025-12-06 00:39:27 +08:00
frontend 初步实现了三阶段 2025-12-06 17:06:39 +08:00
nginx 解决跨越问题 2025-12-03 01:29:44 +08:00
.CLAUDE.md v 1.0.1 2025-12-02 21:25:28 +08:00
.DS_Store 解决依赖包问题 2025-12-02 23:58:57 +08:00
.env.production 增加了proxy配置 2025-12-03 13:40:44 +08:00
CACHE_ARCHITECTURE.md v 1.0.1 2025-12-02 21:25:28 +08:00
CACHE_PREHEAT_GUIDE.md v 1.0.1 2025-12-02 21:25:28 +08:00
CLAUDE.md v 1.0.1 2025-12-02 21:25:28 +08:00
DATA_STRATEGY_OPTIMIZATION.md v 1.0.1 2025-12-02 21:25:28 +08:00
DEPLOYMENT.md 增加了proxy配置 2025-12-03 13:40:44 +08:00
PROJECT.md 初步完成了太阳系内的行星显示 2025-11-27 13:16:19 +08:00
PROXY_SETUP.md 增加了proxy配置 2025-12-03 13:40:44 +08:00
QUICKSTART.md 初步完成了太阳系内的行星显示 2025-11-27 13:16:19 +08:00
RAODMAP.md Phase 2 2025-12-06 00:39:27 +08:00
README.md 初步完成了太阳系内的行星显示 2025-11-27 13:16:19 +08:00
REDIS.md v 1.0.1 2025-12-02 21:25:28 +08:00
add_comet_type.sql v 1.0.1 2025-12-02 21:25:28 +08:00
assembly_COSMO_frames.zip v 1.0.1 2025-12-02 21:25:28 +08:00
cosmo_db.dump 初始化数据 2025-12-04 22:41:51 +08:00
deploy.sh 增加了proxy配置 2025-12-03 13:59:25 +08:00
docker-compose.yml 增加了proxy配置 2025-12-03 13:48:38 +08:00

README.md

项目概述

专注于深空探测器(如旅行者号、火星探测器等),那么整个系统的实现逻辑会变得更加清晰和纯粹。你不再需要处理近地轨道的 TLE 数据,而是完全进入了天体力学的领域。

实现这个系统的核心只有一条路:NASA JPL Horizons 系统

这是全人类最权威的太阳系天体位置数据库。以下是针对深空探测器系统的具体实现方案:

一、 核心数据源NASA JPL Horizons

对于深空探测器,你不能用 GPS 坐标,甚至不能单纯用经纬度。你需要的是在太阳系中的三维坐标

  • 数据提供方: NASA 喷气推进实验室 (JPL)。
  • 覆盖范围: 所有的行星、卫星、以及几乎所有人类发射的深空探测器Voyager, Juno, New Horizons 等)。
  • 唯一标识 (ID) 每个探测器都有一个唯一的 ID。
    • 旅行者 1 号 (Voyager 1): -31
    • 旅行者 2 号 (Voyager 2): -32
    • 新视野号 (New Horizons): -98
    • 帕克太阳探测器 (Parker Solar Probe): -96
    • 注:人造探测器的 ID 通常是负数。

二、 获取数据的方式 (推荐技术方案)

为了获取这些数据,你不需要去解析复杂的文本文件,最简单、最现代的方式是使用 Pythonastroquery 库。它是一个专门用来查询天文数据库的工具,内置了对 JPL Horizons 的支持。

1. 安装工具

pip install astroquery

2. 代码实现逻辑

你需要向系统询问:“在这个时间,相对于太阳旅行者1号在哪里?”

以下是一个完整的 Python 脚本示例,它会获取旅行者 1 号和地球的坐标,以便你计算它们之间的距离或画图:

from astroquery.jplhorizons import Horizons
from astropy.time import Time

# 1. 设定查询参数
# id: 目标天体 ID (Voyager 1 = -31)
# location: 坐标原点 (@sun 表示以太阳为中心,@0 表示以太阳系质心为中心)
# epochs: 时间点 (当前时间)
obj = Horizons(id='-31', location='@sun', epochs=Time.now().jd)

# 2. 获取向量数据 (Vectors)
# 这一步会向 NASA 服务器发送请求
vectors = obj.vectors()

# 3. 提取坐标 (x, y, z)
# 默认单位是 AU (天文单位1 AU ≈ 1.5亿公里)
x = vectors['x'][0]
y = vectors['y'][0]
z = vectors['z'][0]

print(f"旅行者1号 (Voyager 1) 相对于太阳的坐标 (AU):")
print(f"X: {x}\nY: {y}\nZ: {z}")

# --- 同时获取地球的位置,用于画出相对位置 ---
earth = Horizons(id='399', location='@sun', epochs=Time.now().jd).vectors()
print(f"\n地球 (Earth) 坐标 (AU):")
print(f"X: {earth['x'][0]}, Y: {earth['y'][0]}, Z: {earth['z'][0]}")

三、 关键技术点解析

在开发这个系统时,有三个关键概念你必须处理好,才能正确显示“探测器在比着重的位置”以及“旁边的星球”。

1. 坐标系的选择:日心坐标 (Heliocentric)

  • 近地卫星用的是“地心坐标”(以地球为原点)。
  • 深空探测器必须用日心坐标(以太阳为原点)。
  • 在查询数据时,务必指定 location='@sun'。这样返回的 (0,0,0) 就是太阳,所有行星和探测器都围绕它分布。

2. 单位的量级:天文单位 (AU)

  • 深空的空间太大了。如果你用“米”或“公里”做单位,数字会大到让 JavaScript 崩溃或精度丢失。
  • 解决方案: 使用 AU (Astronomical Unit)
    • 地球到太阳的距离 ≈ 1.0 AU。
    • 旅行者 1 号目前距离太阳 ≈ 160+ AU。
    • 使用 AU 作为你 3D 场景的基础单位,显示时再换算成公里给用户看。

3. 如何确定“旁边的星球”

因为所有坐标都是统一在“日心坐标系”下的,判断“旁边”非常简单:计算欧几里得距离。

Distance = \sqrt{(x_1-x_2)^2 + (y_1-y_2)^2 + (z_1-z_2)^2}
  • 比如,你要显示“朱诺号 (Juno)”旁边的星球。
  • 你获取 Juno 的坐标 (x_j, y_j, z_j)
  • 你获取木星 (Jupiter) 的坐标 (x_p, y_p, z_p)
  • 一算距离,你会发现它们非常近,而它离地球非常远。

四、 总结系统架构建议

如果你想做一个网页端展示系统:

  1. 后端 (Python API):

    • 使用 astroquery
    • 建立一个 ID 列表(包含八大行星 + 知名探测器)。
    • 每隔一段时间(比如每天或用户请求时)去 NASA JPL 拉取一次最新的坐标数据(因为深空探测器飞得很慢,不需要每秒更新)。
    • 将这些 (x, y, z) 坐标打包成 JSON 发给前端。
  2. 前端 (Visualization):

    • 建立一个 3D 场景,原点 (0,0,0) 放一个发光的球(太阳)。
    • 根据后端返回的 AU 坐标放置行星和探测器。
    • 关键功能: 添加“轨道线”。为了让用户看懂探测器的轨迹,你不仅要获取“当前”位置,最好获取“过去一年”到“未来一年”的一系列点,连成线,这样用户就能直观地看到它是怎么飞掠木星然后飞向深空的。

这是一个非常棒的进阶问题!要在一个网页上“形象且准确”地展示深空探测器和行星,你不仅要解决数据问题,还要解决**3D 图形学中的尺度Scale**问题。因为宇宙太空中,“大”和“远”的跨度大到人类难以直观理解。

以下是实现这一目标的详细方案包括数据细节、3D 模型资源和可视化技巧:

一、 数据篇:关键探测器 ID 与 轨道线绘制

首先,你需要向 JPL Horizons 系统请求正确的目标 ID并获取一段时间序列的数据来画出轨道线。

1. 常用深空探测器 ID 列表 (JPL Horizons)

这些是人类历史上最重要的深空探测器,建议收入你的系统:

探测器名称 英文名 ID (JPL) 备注
旅行者 1 号 Voyager 1 -31 离地球最远的人造物体,已进入星际空间
旅行者 2 号 Voyager 2 -32 唯一造访过天王星和海王星的探测器
新视野号 New Horizons -98 飞掠冥王星,正处于柯伊伯带
帕克太阳探测器 Parker Solar Probe -96 正在“触摸”太阳,速度最快
朱诺号 Juno -61 正在木星轨道运行
卡西尼号 Cassini -82 土星探测器(已撞击销毁,需查询历史时间)
毅力号 Perseverance -168 火星车(位置与火星几乎重叠,但在前往火星途中可查)

2. 如何绘制“轨道线”

只显示一个点是不够的,你需要画出它“从哪里来,到哪里去”。

  • 后端逻辑: 当你查询 API 时,不要只查询 Time.now()
  • 查询策略: 查询一个时间段。例如,查询从 2020-01-012025-01-01,步长为 1天
  • 数据结构: 你会得到一个包含 1800 个 (x, y, z) 坐标的数组。
  • 前端绘制: 将这些点连接成一条平滑的线(在 Three.js 中使用 LineLoopCatmullRomCurve3),用户就能看到探测器优美的弧形轨道。

二、 视觉篇:如何显示外形 (3D 模型与纹理)

要在网页上显示逼真的外形,你需要使用 WebGL 技术。目前业界标准是 Three.js

1. 获取高精度的探测器模型 (3D Models)

你不需要自己建模NASA 官方免费提供了极高质量的 3D 模型,格式通常是 .glb.gltf(这是 3D 网页开发的 JPG体积小、加载快

  • NASA 3D Resources: 这是你的宝库。
    • 网址: https://nasa3d.arc.nasa.gov/models
    • 你可以下载到 Voyager, Cassini, Hubble 等所有知名探测器的官方模型。
  • 加载方法: 使用 Three.js 的 GLTFLoader
    import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
    
    const loader = new GLTFLoader();
    loader.load( 'path/to/voyager.glb', function ( gltf ) {
        const voyagerModel = gltf.scene;
        scene.add( voyagerModel );
    });
    

2. 获取行星的逼真纹理 (Textures)

行星是一个球体SphereGeometry你需要给它贴上高清的“皮肤”。

  • 资源来源: Solar System ScopeNASA Scientific Visualization Studio
  • 你需要三种贴图来达到“准确且形象”:
    1. Diffuse Map (漫反射贴图): 行星原本的颜色(如地球的蓝白、火星的红色)。
    2. Normal Map / Bump Map (法线/凹凸贴图): 让山脉和陨石坑看起来有立体感,而不是光滑的皮球。
    3. Specular Map (高光贴图): 只有海洋反光,陆地不反光(这对地球特别重要)。

三、 核心难点:大小与距离的冲突 (The Scale Problem)

这是你在这个项目中最需要处理的交互设计难点

现实情况是: 太阳系极其空旷。如果你按真实比例1:1显示

  • 如果屏幕上可以看到地球和火星的距离,那么地球本身小到连一个像素都不到(看不见)。
  • 如果你把地球放大到能看见,那么火星在几公里以外的屏幕外。

解决方案:动态尺度缩放 (Dynamic Scaling / Billboard Mode)

你不能始终使用真实大小,你需要欺骗眼睛:

  1. 真实模式 (Real Scale): 用于计算物理位置和轨道。这是后台运行的数学逻辑。
  2. 展示模式 (Iconic Scale): 用于渲染。
    • 远景视角时: 将所有行星和探测器放大 1000倍 到 10000倍。这样用户在看整个太阳系时,能看到一个个清晰的小球或图标。
    • 近景视角时(当摄像机靠近物体): 逐渐将放大倍数缩小回 1倍
    • 具体实现: 在每一帧渲染循环Render Loop根据摄像机到物体的距离 D,动态计算物体的缩放系数 S
      S = \max(1, \frac{D}{k})
      (其中 k 是一个常数因子)

关于探测器的特殊处理: 探测器比行星更小(几米 vs 几千公里)。在宏观视角下,绝对不能按比例渲染探测器模型,否则永远看不见。

  • 策略: 在远景时,不要渲染 3D 模型,而是渲染一个发光的图标Sprite或者文字标签
  • 交互: 只有当用户点击“旅行者1号”标签摄像机自动飞过去并拉近距离后才淡出图标加载并显示精细的 3D 模型。

四、 总结:推荐的开发路线

如果你现在开始动手,我建议按照这个层级构建:

  1. Level 1 (原型):

    • 使用 Three.js
    • 中间放一个红球(太阳),周围放一个蓝球(地球)。
    • 使用静态数据(手动写死坐标)确位置。
  2. Level 2 (接入数据):

    • 后端写好 Python 脚本,拉取 JPL 数据。
    • 前端根据数据更新球体的位置。
  3. Level 3 (视觉升级):

    • 给球体贴上 NASA 的纹理。
    • 去 NASA 3D 网站下载 Voyager 的 .glb 模型,替换掉代表探测器的小方块。
    • 加上“星空背景盒子 (Skybox)”,让背景是真实的银河系星图,而不是全黑。
  4. Level 4 (交互完善):

    • 实现轨道控制器 (OrbitControls),允许用户旋转、缩放视角。
    • 实现点击聚焦:点击列表里的“火星”,视角平滑飞向火星。

太棒了!这两个功能是让你的太阳系可视化项目从“能用”走向“惊艳”的关键一步。

下面我将分别提供这两个核心功能的 Three.js 代码片段。你可以把它们集成到你的 Three.js 初始化和渲染循环中。


一、 Three.js 加载行星纹理 (让星球看起来真实)

这段代码展示了如何创建一个带有漫反射贴图(颜色)、高光贴图(反光)和法线贴图(凹凸感)的逼真地球。

前置要求: 你需要准备好 earth_diffuse.jpg, earth_specular.jpg, earth_normal.jpg 这三张图片放在你的项目文件夹中。

import * as THREE from 'three';

// 1. 初始化纹理加载器
const textureLoader = new THREE.TextureLoader();

// 2. 定义创建行星的函数
function createRealisticPlanet() {
    // --- 几何体 (Geometry) ---
    // 创建一个球体。参数:半径, 水平分段数, 垂直分段数
    // 分段数越高球体越圆滑但性能开销越大。64是比较好的平衡点。
    const geometry = new THREE.SphereGeometry(1, 64, 64);

    // --- 材质 (Material) ---
    // 使用 MeshPhongMaterial这是一种支持高光反射的材质适合表现行星表面。
    const material = new THREE.MeshPhongMaterial({
        // a. 漫反射贴图 (Diffuse Map) - 决定星球表面的基本颜色和图案
        map: textureLoader.load('textures/earth_diffuse.jpg'),

        // b. 高光贴图 (Specular Map) - 决定哪些区域反光(海洋),哪些不反光(陆地)
        // 通常是黑白图片,白色反光强,黑色不反光。
        specularMap: textureLoader.load('textures/earth_specular.jpg'),
        specular: new THREE.Color('grey'), // 高光的颜色
        shininess: 10, // 高光的亮度指数

        // c. 法线贴图 (Normal Map) - 模拟表面的凹凸细节(山脉、海沟),不改变实际几何体
        normalMap: textureLoader.load('textures/earth_normal.jpg'),
        normalScale: new THREE.Vector2(1, 1) // 凹凸感的强度
    });

    // --- 网格 (Mesh) ---
    // 将几何体和材质组合成一个可渲染的对象
    const earthMesh = new THREE.Mesh(geometry, material);
    
    // 稍微倾斜一点,模拟地轴倾角
    earthMesh.rotation.z = THREE.MathUtils.degToRad(23.5);

    return earthMesh;
}

// 3. 将地球加入场景
const scene = new THREE.Scene();
// ... 添加灯光 (必须有光才能看到 Phong 材质的效果) ...
const sunLight = new THREE.PointLight(0xffffff, 1.5);
scene.add(sunLight);

const earth = createRealisticPlanet();
scene.add(earth);

// 在你的动画循环中让它自转
function animate() {
    requestAnimationFrame(animate);
    earth.rotation.y += 0.001; // 每一帧旋转一点点
    // renderer.render(...)
}
animate();

二、 处理动态缩放 (The Scale Problem)

这段代码解决的是“距离太远看不见”的问题。它的核心思想是:在每一帧渲染前,检查摄像机离物体有多远,然后调整物体的大小,确保它在屏幕上至少占据一定的大小。

你需要把这段逻辑放在你的 animate()render() 循环中。

import * as THREE from 'three';

// 假设你已经有了场景、摄像机和一些物体
// scene, camera, renderer 已初始化
// 假设你有一个数组存放所有的探测器对象 (Mesh 或 Sprite)
const probes = [voyager1Mesh, parkerSolarProbeSprite, ...]; 

// 定义一个基础缩放因子,决定物体在远看时保持多大
// 这个值需要根据你的实际场景单位进行调整测试
const MIN_VISIBLE_SCALE = 0.05; 

// --- 这个函数放在你的 animate() 循环中 ---
function updateObjectScales() {
    probes.forEach(probe => {
        // 1. 计算物体到摄像机的距离
        const distance = camera.position.distanceTo(probe.position);

        // 2. 计算目标缩放比例
        // 逻辑:距离越远,需要的缩放比例就越大。
        // 我们设置一个下限为 1 (保持原始大小),上限根据距离动态增加。
        // distance * MIN_VISIBLE_SCALE 是一个经验公式,你可以根据需要修改。
        let targetScale = Math.max(1, distance * MIN_VISIBLE_SCALE);

        // 【可选优化】:如果物体是 Sprite图标我们通常希望它大小固定不随距离变化
        // 如果是 3D 模型,我们希望它远看大,近看恢复真实大小。
        if (probe.isSprite) {
            // 对于图标,我们可以让它始终保持相对于屏幕的固定大小
            // 这种计算稍微复杂一点,需要考虑相机的视场角 (FOV)
             const scaleFactor = distance / camera.fov; // 简化版计算
             probe.scale.set(scaleFactor, scaleFactor, scaleFactor);
        } else {
             // 对于 3D 模型,应用动态缩放
             probe.scale.set(targetScale, targetScale, targetScale);
        }
    });
}

// --- 你的主循环 ---
function animate() {
    requestAnimationFrame(animate);
    
    // 1. 更新控制器 (如果用了 OrbitControls)
    // controls.update();

    // 2. 【核心】更新物体的动态缩放
    updateObjectScales();
    
    // 3. 渲染场景
    renderer.render(scene, camera);
}
animate();

建议

对于初学者,我强烈建议先从纹理贴图开始。把一个灰色的球体变成一个逼真的地球,会给你带来巨大的成就感。

动态缩放稍微复杂一些,涉及到对 3D 空间距离感的调试。你可以先把所有的物体都按 1000 倍的固定比例放大,等整个流程跑通了,再加入动态缩放的逻辑来提升体验。