c8d12e7f45
- 月球轨道图使用绿色配色,与地球轨道图(蓝色)区分 - 默认偏心率 e=0.0549(月球真实值) - 标注半长轴 a / 半短轴 b / 偏心距 c - 标注近地点/远地点距离 - 地球在左焦点,月球在椭圆轨道运行 - 独立滑块控制月球偏心率
822 lines
26 KiB
HTML
822 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>case04 — 日地月三体系统</title>
|
||
<script>
|
||
MathJax = {
|
||
tex: { inlineMath: [['$','$'], ['\\(','\\)']], displayMath: [['$$','$$'], ['\\[','\\]']] },
|
||
svg: { fontCache: 'global' }
|
||
};
|
||
</script>
|
||
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js" async></script>
|
||
<style>
|
||
:root {
|
||
--bg: #0d1117;
|
||
--surface: #161b22;
|
||
--border: #30363d;
|
||
--text: #c9d1d9;
|
||
--text-dim: #8b949e;
|
||
--accent: #58a6ff;
|
||
--green: #3fb950;
|
||
--orange: #d29922;
|
||
--red: #f85149;
|
||
--teal: #56d4dd;
|
||
}
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", Helvetica, Arial, sans-serif;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
line-height: 1.8;
|
||
padding: 40px 24px;
|
||
}
|
||
.container { max-width: 860px; margin: 0 auto; }
|
||
|
||
h1 { font-size: 1.8rem; margin-bottom: 6px; color: #f0f6fc; }
|
||
h1 small { font-size: 1rem; color: var(--text-dim); font-weight: 400; }
|
||
.subtitle { color: var(--text-dim); margin-bottom: 32px; }
|
||
|
||
h2 {
|
||
font-size: 1.35rem;
|
||
margin-top: 36px;
|
||
margin-bottom: 14px;
|
||
padding-bottom: 6px;
|
||
border-bottom: 1px solid var(--border);
|
||
color: #f0f6fc;
|
||
}
|
||
h3 { font-size: 1.1rem; margin-top: 24px; margin-bottom: 10px; color: #f0f6fc; }
|
||
|
||
p { margin-bottom: 12px; color: var(--text); }
|
||
|
||
.card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
padding: 20px 24px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin: 12px 0;
|
||
font-size: 0.9rem;
|
||
}
|
||
th, td {
|
||
text-align: left;
|
||
padding: 8px 14px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
th { color: var(--text-dim); font-weight: 600; background: #1c2128; }
|
||
|
||
.highlight {
|
||
background: #1f242e;
|
||
border-left: 3px solid var(--accent);
|
||
padding: 10px 16px;
|
||
margin: 12px 0;
|
||
border-radius: 0 6px 6px 0;
|
||
font-size: 0.9rem;
|
||
}
|
||
.highlight-eq {
|
||
background: #1f242e;
|
||
border-left: 3px solid var(--teal);
|
||
padding: 14px 18px;
|
||
margin: 14px 0;
|
||
border-radius: 0 6px 6px 0;
|
||
}
|
||
.highlight-eq p { margin: 6px 0; }
|
||
|
||
code {
|
||
background: #1c2128;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
font-family: "SF Mono", "Fira Code", monospace;
|
||
font-size: 0.85em;
|
||
color: var(--green);
|
||
}
|
||
|
||
.tag {
|
||
display: inline-block;
|
||
padding: 2px 10px;
|
||
border-radius: 4px;
|
||
font-size: 0.78rem;
|
||
font-weight: 500;
|
||
margin: 2px;
|
||
}
|
||
.tag-python { background: #3572A533; color: #3572A5; }
|
||
.tag-gravity { background: #d2992233; color: var(--orange); }
|
||
|
||
ul, ol { margin: 8px 0 12px 24px; }
|
||
li { margin-bottom: 4px; }
|
||
|
||
.formula-block {
|
||
overflow-x: auto;
|
||
padding: 8px 0;
|
||
}
|
||
|
||
.diagram-wrap {
|
||
background: #0d1117;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
margin: 16px 0;
|
||
text-align: center;
|
||
}
|
||
.diagram-wrap canvas {
|
||
display: block;
|
||
margin: 0 auto;
|
||
max-width: 100%;
|
||
height: auto;
|
||
}
|
||
.diagram-controls {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 16px;
|
||
margin-top: 12px;
|
||
}
|
||
.diagram-controls label {
|
||
font-size: 0.85rem;
|
||
color: var(--text-dim);
|
||
}
|
||
.diagram-controls input[type="range"] {
|
||
width: 200px;
|
||
accent-color: var(--accent);
|
||
}
|
||
.diagram-controls .val {
|
||
font-size: 0.9rem;
|
||
font-weight: 500;
|
||
color: var(--teal);
|
||
min-width: 48px;
|
||
display: inline-block;
|
||
text-align: center;
|
||
}
|
||
.diagram-info {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
gap: 20px;
|
||
margin-top: 10px;
|
||
font-size: 0.82rem;
|
||
color: var(--text-dim);
|
||
}
|
||
.diagram-info span {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.diagram-info .dot {
|
||
display: inline-block;
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 50%;
|
||
margin-right: 2px;
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
body { padding: 16px; }
|
||
.diagram-controls { flex-direction: column; gap: 8px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
|
||
<h1>case04 — 日地月三体系统 <small>稳定轨道版本</small></h1>
|
||
<p class="subtitle">太阳、地球、月球三体系统,采用真实比例的质量和万有引力模拟,地球和月球维持稳定椭圆轨道。</p>
|
||
|
||
<div class="card" style="display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-bottom:20px;">
|
||
<span class="tag tag-python">Python 引擎</span>
|
||
<span class="tag tag-gravity">万有引力</span>
|
||
<span style="color:var(--text-dim); font-size:0.85rem; margin-left:8px;">3 粒子 | leapfrog 算法 | 稳定轨道</span>
|
||
</div>
|
||
|
||
<h2>一、物理系统概览</h2>
|
||
|
||
<div class="card">
|
||
<p>本案例模拟真实的日-地-月三体系统:</p>
|
||
<ul>
|
||
<li><strong>太阳</strong>:位于原点,质量为 $M_\odot$,固定不动(fix_x=fix_y=fix_z=1)</li>
|
||
<li><strong>地球</strong>:绕太阳公转,质量为 $M_\oplus$,近日点出发</li>
|
||
<li><strong>月球</strong>:绕地球公转(同时跟随地球绕太阳),质量为 $M_\text{moon}$</li>
|
||
</ul>
|
||
<p>原子间万有引力由 <code>gravity_strength</code> 缩放控制,$F = G \dfrac{m_1 m_2}{r^2}$。</p>
|
||
</div>
|
||
|
||
<h2>二、天体真实参数</h2>
|
||
|
||
<h3>2.1 质量</h3>
|
||
|
||
<table>
|
||
<tr><th>天体</th><th>质量 / kg</th><th>模拟质量(月球=1)</th></tr>
|
||
<tr><td>太阳</td><td>$1.989 \times 10^{30}$</td><td>$27\,000$</td></tr>
|
||
<tr><td>地球</td><td>$5.972 \times 10^{24}$</td><td>$81$</td></tr>
|
||
<tr><td>月球</td><td>$7.35 \times 10^{22}$</td><td>$1$</td></tr>
|
||
</table>
|
||
|
||
<div class="highlight">
|
||
<strong>质量比</strong>:$M_\odot : M_\oplus : M_\text{moon} \approx 27\,100\,000 : 81.3 : 1$<br>
|
||
模拟中采用缩放比例 $27\,000 : 81 : 1$(太阳质量缩至 $1/1000$ 以保持数值稳定)。
|
||
</div>
|
||
|
||
<h3>2.2 轨道参数</h3>
|
||
|
||
<table>
|
||
<tr><th>轨道</th><th>半长轴 $a$</th><th>偏心率 $e$</th><th>半短轴 $b$</th><th>周期</th></tr>
|
||
<tr><td>地球绕太阳</td><td>$1.4960 \times 10^8$ km (1 AU)</td><td>$0.0167$</td><td>$1.4958 \times 10^8$ km</td><td>365.25 天</td></tr>
|
||
<tr><td>月球绕地球</td><td>$3.844 \times 10^5$ km</td><td>$0.0549$</td><td>$3.838 \times 10^5$ km</td><td>27.32 天</td></tr>
|
||
</table>
|
||
|
||
<div class="highlight">
|
||
<strong>比例</strong>:$R_{\text{日地}} : R_{\text{地月}} \approx 389 : 1$<br>
|
||
地月距离约为日地距离的 $1/389$。
|
||
</div>
|
||
|
||
<h2>三、轨道力学</h2>
|
||
|
||
<h3>3.1 交互式轨道示意图</h3>
|
||
|
||
<p>拖动下方滑块改变偏心率 $e$,观察轨道形状的变化和参数标注:</p>
|
||
|
||
<div class="diagram-wrap">
|
||
<canvas id="orbitCanvas" width="760" height="400"></canvas>
|
||
<div class="diagram-controls">
|
||
<label>偏心率 e = <span class="val" id="eDisplay">0.0170</span>(地球真实值 0.0167)</label>
|
||
<input type="range" id="eSlider" min="0" max="850" value="17">
|
||
</div>
|
||
<div class="diagram-info" id="diagramInfo"></div>
|
||
</div>
|
||
|
||
<h3>3.2 月球绕地球轨道</h3>
|
||
|
||
<p>拖动下方滑块改变月球轨道偏心率 $e_\text{moon}$(真实值 0.0549):</p>
|
||
|
||
<div class="diagram-wrap">
|
||
<canvas id="moonCanvas" width="760" height="400"></canvas>
|
||
<div class="diagram-controls">
|
||
<label>偏心率 e = <span class="val" id="moonEDisplay">0.0549</span>(月球真实值 0.0549)</label>
|
||
<input type="range" id="moonESlider" min="0" max="850" value="55">
|
||
</div>
|
||
<div class="diagram-info" id="moonInfo"></div>
|
||
</div>
|
||
|
||
<h3>3.3 偏心率定义</h3>
|
||
|
||
<div class="highlight-eq">
|
||
<p>偏心率 $e$ 描述椭圆轨道偏离正圆的程度:</p>
|
||
<div class="formula-block">
|
||
$$e = \frac{c}{a}$$
|
||
</div>
|
||
<p>其中 $c = ea$ 为偏心距(焦点到椭圆中心的距离),$a$ 为半长轴。</p>
|
||
</div>
|
||
|
||
<table>
|
||
<tr><th>$e$ 值</th><th>轨道形状</th><th>说明</th></tr>
|
||
<tr><td>$e = 0$</td><td>正圆形</td><td>速度恒定,距离恒定</td></tr>
|
||
<tr><td>$0 < e < 1$</td><td>椭圆</td><td>近日点/近地点速度最大,远日点/远地点速度最小</td></tr>
|
||
<tr><td>$e = 1$</td><td>抛物线</td><td>逃逸轨道,速度恰好达到逃逸速度</td></tr>
|
||
<tr><td>$e > 1$</td><td>双曲线</td><td>飞越轨道,速度超过逃逸速度</td></tr>
|
||
</table>
|
||
|
||
<h3>3.4 半长轴与半短轴的关系</h3>
|
||
|
||
<div class="highlight-eq">
|
||
<div class="formula-block">
|
||
$$b = a\sqrt{1 - e^2}$$
|
||
</div>
|
||
<p>当 $e \ll 1$ 时,$b \approx a\left(1 - \dfrac{e^2}{2}\right)$,椭圆度非常微小。</p>
|
||
</div>
|
||
|
||
<h3>3.5 近日点与远日点</h3>
|
||
|
||
<div class="highlight-eq">
|
||
<p>近日点(距太阳最近)和远日点(距太阳最远)的距离分别为:</p>
|
||
<div class="formula-block">
|
||
$$r_{\text{peri}} = a(1 - e), \qquad r_{\text{ap}} = a(1 + e)$$
|
||
</div>
|
||
<p>在近日点轨道速度最大,在远日点轨道速度最小:</p>
|
||
<div class="formula-block">
|
||
$$v_{\text{peri}} = \sqrt{\frac{GM(1+e)}{a(1-e)}}, \qquad
|
||
v_{\text{ap}} = \sqrt{\frac{GM(1-e)}{a(1+e)}}$$
|
||
</div>
|
||
</div>
|
||
|
||
<h3>3.6 开普勒三大定律</h3>
|
||
|
||
<ol>
|
||
<li><strong>椭圆定律</strong>:行星轨道是椭圆,太阳位于椭圆的一个焦点上。</li>
|
||
<li><strong>面积定律</strong>:行星与太阳的连线在相等时间内扫过相等的面积。</li>
|
||
<li><strong>周期定律</strong>:公转周期的平方与半长轴的立方成正比:$T^2 \propto a^3$。</li>
|
||
</ol>
|
||
|
||
<div class="highlight">
|
||
<strong>验证</strong>:在本模拟中,你可以通过轨迹图观察面积定律是否成立——地球在近日点移动更快,远日点移动更慢。
|
||
</div>
|
||
|
||
<h2>四、模拟参数</h2>
|
||
|
||
<h3>4.1 缩放说明</h3>
|
||
|
||
<p>真实尺度无法直接用于模拟(日地距离 1.5 亿 km),因此采用缩放参数。当前 case04 的配置为教学演示而简化:</p>
|
||
|
||
<table>
|
||
<tr><th>参数</th><th>值</th><th>说明</th></tr>
|
||
<tr><td><code>gravity_strength</code></td><td>100.0</td><td>万有引力强度</td></tr>
|
||
<tr><td><code>box_a</code></td><td>30.0</td><td>盒子半边长</td></tr>
|
||
<tr><td><code>method</code></td><td>leapfrog</td><td>蛙跳法(辛积分器,能量守恒)</td></tr>
|
||
<tr><td><code>T_total</code></td><td>10.0 s</td><td>总模拟时间</td></tr>
|
||
<tr><td><code>NSTEP</code></td><td>2</td><td>抽帧间隔(密采帧)</td></tr>
|
||
</table>
|
||
|
||
<h3>4.2 初始构型</h3>
|
||
|
||
<table>
|
||
<tr><th>天体</th><th>质量</th><th>位置 $(x,y,z)$</th><th>速度 $(v_x,v_y,v_z)$</th><th>固定约束</th></tr>
|
||
<tr><td>太阳</td><td>$27\,000$</td><td>$(0,0,0)$</td><td>$(0,0,0)$</td><td>全部固定</td></tr>
|
||
<tr><td>地球</td><td>$81$</td><td>$(10,0,0)$</td><td>$(0,0,520)$</td><td>无</td></tr>
|
||
<tr><td>月球</td><td>$1$</td><td>$(11.5,0,0)$</td><td>$(0,0,526)$</td><td>无</td></tr>
|
||
</table>
|
||
|
||
<p>地球在 $z$ 方向获得初速度 $v=520$,产生绕太阳的轨道运动;月球在地球基础上附加 $v=6$ 的绕地速度,形成绕地球的微小轨道。速度由圆形轨道公式 $v = \sqrt{G_{\text{eff}} M / r}$ 计算,其中 $G_{\text{eff}} = \text{gravity\_strength} = 100$。</p>
|
||
|
||
<h2>五、使用方法</h2>
|
||
|
||
<div class="card">
|
||
<code style="display:block; padding:14px 18px; margin-bottom:10px;">
|
||
# 进入 case04 目录并运行<br>
|
||
cd examples/case04<br>
|
||
python run_dynamics.py<br><br>
|
||
# 仅输出轨迹图,跳过动画<br>
|
||
python run_dynamics.py --no-plot
|
||
</code>
|
||
<p>配置文件:<code>input/input.txt</code>(物理参数)、<code>input/coord.txt</code>(初始位置/速度)。</p>
|
||
</div>
|
||
|
||
<h2>六、与 case03 的对比</h2>
|
||
|
||
<table>
|
||
<tr><th></th><th>case03(失败案例)</th><th>case04(成功案例)</th></tr>
|
||
<tr><td>轨道状态</td><td>轨道发散或碰撞</td><td>稳定椭圆轨道</td></tr>
|
||
<tr><td>关键差异</td><td>初始速度或质量比不恰当</td><td>合理的初值和参数</td></tr>
|
||
<tr><td>教学意义</td><td>展示参数选择的重要性</td><td>展示正确的三体运动</td></tr>
|
||
</table>
|
||
|
||
<div class="highlight">
|
||
<strong>教学建议</strong>:先运行 case03 观察失稳,再运行 case04 对比稳定轨道,理解初始条件对数值模拟的关键影响。
|
||
</div>
|
||
|
||
<h2>七、已知局限</h2>
|
||
|
||
<ul>
|
||
<li>太阳真实质量为月球 $27\,100\,000$ 倍,模拟中缩至 $27\,000$ 倍以保持数值稳定($1/1000$)</li>
|
||
<li>轨道半径未按真实比例缩放(真实日地距是地月距的 389 倍,模拟中仅为 6.7 倍)</li>
|
||
<li>未考虑月球轨道倾角(真实地月轨道有约 5° 的倾角)</li>
|
||
<li>leapfrog 算法为能量守恒的辛积分器,长期稳定,但步长过大时仍可能偏离真实轨道</li>
|
||
</ul>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
(function() {
|
||
var canvas = document.getElementById('orbitCanvas');
|
||
var ctx = canvas.getContext('2d');
|
||
var slider = document.getElementById('eSlider');
|
||
var eDisplay = document.getElementById('eDisplay');
|
||
var infoDiv = document.getElementById('diagramInfo');
|
||
|
||
var W = 760, H = 400;
|
||
var cx = 380, cy = 200;
|
||
|
||
// 实际绘图区域半径
|
||
var A = 140;
|
||
|
||
function draw(e) {
|
||
ctx.clearRect(0, 0, W, H);
|
||
|
||
var b = A * Math.sqrt(1 - e * e);
|
||
var f = e * A;
|
||
|
||
// 长轴辅助线
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx - A - 20, cy);
|
||
ctx.lineTo(cx + A + 20, cy);
|
||
ctx.strokeStyle = '#30363d';
|
||
ctx.lineWidth = 0.5;
|
||
ctx.setLineDash([4, 3]);
|
||
ctx.stroke();
|
||
ctx.setLineDash([]);
|
||
|
||
// 椭圆轨道(中心在 cx+f,左焦点=cx 为太阳)
|
||
ctx.beginPath();
|
||
ctx.ellipse(cx + f, cy, A, b, 0, 0, Math.PI * 2);
|
||
ctx.strokeStyle = '#58a6ff';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
|
||
// 半长轴 a(标注线,从椭圆中心到右顶点)
|
||
var aY = cy + 28;
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx + f, aY);
|
||
ctx.lineTo(cx + f + A, aY);
|
||
ctx.strokeStyle = '#c9d1d9';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
// 箭头
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx - f + 6, aY - 4);
|
||
ctx.lineTo(cx - f, aY);
|
||
ctx.lineTo(cx - f + 6, aY + 4);
|
||
ctx.strokeStyle = '#c9d1d9';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx - f + A - 6, aY - 4);
|
||
ctx.lineTo(cx - f + A, aY);
|
||
ctx.lineTo(cx - f + A - 6, aY + 4);
|
||
ctx.strokeStyle = '#c9d1d9';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.fillStyle = '#c9d1d9';
|
||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'top';
|
||
ctx.fillText('a = ' + A.toFixed(0), cx + f + A/2, aY + 6);
|
||
|
||
// 偏心距 c = ea(从左焦点到椭圆中心)
|
||
var cY = cy - b - 20;
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx, cY);
|
||
ctx.lineTo(cx + f, cY);
|
||
ctx.strokeStyle = '#d29922';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx - f + 5, cY - 4);
|
||
ctx.lineTo(cx - f, cY);
|
||
ctx.lineTo(cx - f + 5, cY + 4);
|
||
ctx.strokeStyle = '#d29922';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx - 5, cY - 4);
|
||
ctx.lineTo(cx, cY);
|
||
ctx.lineTo(cx - 5, cY + 4);
|
||
ctx.strokeStyle = '#d29922';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.fillStyle = '#d29922';
|
||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'bottom';
|
||
ctx.fillText('c = ea = ' + f.toFixed(1), cx + f/2, cY - 4);
|
||
|
||
// 半短轴 b 标注(从椭圆中心到上顶点)
|
||
var bX = cx + f + A + 18;
|
||
ctx.beginPath();
|
||
ctx.moveTo(bX, cy);
|
||
ctx.lineTo(bX, cy - b);
|
||
ctx.strokeStyle = '#3fb950';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(bX - 4, cy - 6);
|
||
ctx.lineTo(bX, cy);
|
||
ctx.lineTo(bX + 4, cy - 6);
|
||
ctx.strokeStyle = '#3fb950';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(bX - 4, cy - b + 6);
|
||
ctx.lineTo(bX, cy - b);
|
||
ctx.lineTo(bX + 4, cy - b + 6);
|
||
ctx.strokeStyle = '#3fb950';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.fillStyle = '#3fb950';
|
||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'left';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText('b = ' + b.toFixed(1), bX + 8, cy - b/2);
|
||
|
||
// 太阳(焦点)
|
||
ctx.beginPath();
|
||
ctx.arc(cx, cy, 7, 0, Math.PI * 2);
|
||
ctx.fillStyle = '#d29922';
|
||
ctx.fill();
|
||
ctx.strokeStyle = '#f0883e';
|
||
ctx.lineWidth = 1;
|
||
ctx.stroke();
|
||
ctx.fillStyle = '#f0f6fc';
|
||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'bottom';
|
||
ctx.fillText('太阳(左焦点)', cx, cy - 12);
|
||
|
||
// 近日点(左顶点:距左焦点最近)
|
||
var periX = cx + f - A;
|
||
ctx.fillStyle = '#58a6ff';
|
||
ctx.beginPath();
|
||
ctx.arc(periX, cy, 4, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
ctx.fillStyle = '#8b949e';
|
||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'top';
|
||
ctx.fillText('近日点 r = a(1-e) = ' + (A * (1 - e)).toFixed(1), periX, cy + 12);
|
||
|
||
// 远日点标注(右顶点:距左焦点最远)
|
||
var apX = cx + f + A;
|
||
ctx.fillStyle = '#58a6ff';
|
||
ctx.beginPath();
|
||
ctx.arc(apX, cy, 4, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
ctx.fillStyle = '#8b949e';
|
||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'top';
|
||
ctx.fillText('远日点 r = a(1+e) = ' + (A * (1 + e)).toFixed(1), apX, cy + 12);
|
||
|
||
// 地球(轨道上的点)
|
||
var angle = 0.4;
|
||
var ex = cx + f + A * Math.cos(angle);
|
||
var ey = cy - b * Math.sin(angle);
|
||
ctx.beginPath();
|
||
ctx.arc(ex, ey, 5, 0, Math.PI * 2);
|
||
ctx.fillStyle = '#58a6ff';
|
||
ctx.fill();
|
||
ctx.strokeStyle = '#1f6feb';
|
||
ctx.lineWidth = 1;
|
||
ctx.stroke();
|
||
ctx.fillStyle = '#58a6ff';
|
||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'left';
|
||
ctx.textBaseline = 'bottom';
|
||
ctx.fillText('地球', ex + 8, ey);
|
||
|
||
// 速度矢量
|
||
var vlen = 32;
|
||
var vx = -A * Math.sin(angle) * 0.7;
|
||
var vy = b * Math.cos(angle) * 0.7;
|
||
var vl = Math.sqrt(vx*vx + vy*vy);
|
||
if (vl > 0) {
|
||
vx = vx / vl * vlen;
|
||
vy = vy / vl * vlen;
|
||
}
|
||
ctx.beginPath();
|
||
ctx.moveTo(ex, ey);
|
||
ctx.lineTo(ex + vx, ey + vy);
|
||
ctx.strokeStyle = '#3fb950';
|
||
ctx.lineWidth = 2;
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
var tipX = ex + vx, tipY = ey + vy;
|
||
var a1 = 0.3;
|
||
ctx.moveTo(tipX, tipY);
|
||
ctx.lineTo(tipX - vx * a1 + vy * a1 * 0.5, tipY - vy * a1 - vx * a1 * 0.5);
|
||
ctx.lineTo(tipX - vx * a1 - vy * a1 * 0.5, tipY - vy * a1 + vx * a1 * 0.5);
|
||
ctx.closePath();
|
||
ctx.fillStyle = '#3fb950';
|
||
ctx.fill();
|
||
|
||
// 信息面板
|
||
var roundE = Math.round(e * 10000) / 10000;
|
||
infoDiv.innerHTML =
|
||
'<span><span class="dot" style="background:#58a6ff"></span>椭圆轨道 e = ' + roundE.toFixed(4) + '</span>' +
|
||
'<span><span class="dot" style="background:#d29922"></span>偏心距 c = ' + f.toFixed(1) + '</span>' +
|
||
'<span><span class="dot" style="background:#3fb950"></span>b/a = ' + (b / A).toFixed(4) + '</span>' +
|
||
'<span>近日点 ' + (A * (1 - e)).toFixed(1) + ' / 远日点 ' + (A * (1 + e)).toFixed(1) + '</span>';
|
||
}
|
||
|
||
function update() {
|
||
var val = parseInt(slider.value);
|
||
var e = val / 1000;
|
||
eDisplay.textContent = e.toFixed(4);
|
||
draw(e);
|
||
}
|
||
|
||
slider.addEventListener('input', update);
|
||
update();
|
||
})();
|
||
|
||
// ── 月球绕地球轨道图 ──
|
||
(function() {
|
||
var canvas = document.getElementById('moonCanvas');
|
||
var ctx = canvas.getContext('2d');
|
||
var slider = document.getElementById('moonESlider');
|
||
var eDisplay = document.getElementById('moonEDisplay');
|
||
var infoDiv = document.getElementById('moonInfo');
|
||
|
||
var W = 760, H = 400;
|
||
var cx = 380, cy = 200;
|
||
var A = 140;
|
||
|
||
function draw(e) {
|
||
ctx.clearRect(0, 0, W, H);
|
||
|
||
var b = A * Math.sqrt(1 - e * e);
|
||
var f = e * A;
|
||
|
||
// 长轴辅助线
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx - A - 20, cy);
|
||
ctx.lineTo(cx + A + 20, cy);
|
||
ctx.strokeStyle = '#30363d';
|
||
ctx.lineWidth = 0.5;
|
||
ctx.setLineDash([4, 3]);
|
||
ctx.stroke();
|
||
ctx.setLineDash([]);
|
||
|
||
// 椭圆轨道(中心在 cx+f,左焦点=cx 为地球)
|
||
ctx.beginPath();
|
||
ctx.ellipse(cx + f, cy, A, b, 0, 0, Math.PI * 2);
|
||
ctx.strokeStyle = '#7ee787';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
|
||
// 半长轴 a(标注线,从椭圆中心到右顶点)
|
||
var aY = cy + 28;
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx + f, aY);
|
||
ctx.lineTo(cx + f + A, aY);
|
||
ctx.strokeStyle = '#c9d1d9';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx + f + 6, aY - 4);
|
||
ctx.lineTo(cx + f, aY);
|
||
ctx.lineTo(cx + f + 6, aY + 4);
|
||
ctx.strokeStyle = '#c9d1d9';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx + f + A - 6, aY - 4);
|
||
ctx.lineTo(cx + f + A, aY);
|
||
ctx.lineTo(cx + f + A - 6, aY + 4);
|
||
ctx.strokeStyle = '#c9d1d9';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.fillStyle = '#c9d1d9';
|
||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'top';
|
||
ctx.fillText('a = ' + A.toFixed(0), cx + f + A/2, aY + 6);
|
||
|
||
// 偏心距 c = ea(从左焦点=地球 到椭圆中心)
|
||
var cY = cy - b - 20;
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx, cY);
|
||
ctx.lineTo(cx + f, cY);
|
||
ctx.strokeStyle = '#d29922';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx + 5, cY - 4);
|
||
ctx.lineTo(cx, cY);
|
||
ctx.lineTo(cx + 5, cY + 4);
|
||
ctx.strokeStyle = '#d29922';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx + f - 5, cY - 4);
|
||
ctx.lineTo(cx + f, cY);
|
||
ctx.lineTo(cx + f - 5, cY + 4);
|
||
ctx.strokeStyle = '#d29922';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.fillStyle = '#d29922';
|
||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'bottom';
|
||
ctx.fillText('c = ea = ' + f.toFixed(1), cx + f/2, cY - 4);
|
||
|
||
// 半短轴 b 标注(从椭圆中心到上顶点)
|
||
var bX = cx + f + A + 18;
|
||
ctx.beginPath();
|
||
ctx.moveTo(bX, cy);
|
||
ctx.lineTo(bX, cy - b);
|
||
ctx.strokeStyle = '#3fb950';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(bX - 4, cy - 6);
|
||
ctx.lineTo(bX, cy);
|
||
ctx.lineTo(bX + 4, cy - 6);
|
||
ctx.strokeStyle = '#3fb950';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(bX - 4, cy - b + 6);
|
||
ctx.lineTo(bX, cy - b);
|
||
ctx.lineTo(bX + 4, cy - b + 6);
|
||
ctx.strokeStyle = '#3fb950';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.stroke();
|
||
ctx.fillStyle = '#3fb950';
|
||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'left';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText('b = ' + b.toFixed(1), bX + 8, cy - b/2);
|
||
|
||
// 地球(左焦点)
|
||
ctx.beginPath();
|
||
ctx.arc(cx, cy, 8, 0, Math.PI * 2);
|
||
ctx.fillStyle = '#58a6ff';
|
||
ctx.fill();
|
||
ctx.strokeStyle = '#1f6feb';
|
||
ctx.lineWidth = 1;
|
||
ctx.stroke();
|
||
ctx.fillStyle = '#f0f6fc';
|
||
ctx.font = '13px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'bottom';
|
||
ctx.fillText('地球(左焦点)', cx, cy - 14);
|
||
|
||
// 近地点(左顶点:距地球最近)
|
||
var periX = cx + f - A;
|
||
ctx.fillStyle = '#7ee787';
|
||
ctx.beginPath();
|
||
ctx.arc(periX, cy, 4, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
ctx.fillStyle = '#8b949e';
|
||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'top';
|
||
ctx.fillText('近地点 r = a(1-e) = ' + (A * (1 - e)).toFixed(1), periX, cy + 12);
|
||
|
||
// 远地点(右顶点:距地球最远)
|
||
var apX = cx + f + A;
|
||
ctx.fillStyle = '#7ee787';
|
||
ctx.beginPath();
|
||
ctx.arc(apX, cy, 4, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
ctx.fillStyle = '#8b949e';
|
||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'top';
|
||
ctx.fillText('远地点 r = a(1+e) = ' + (A * (1 + e)).toFixed(1), apX, cy + 12);
|
||
|
||
// 月球(轨道上的点)
|
||
var angle = 0.6;
|
||
var mx = cx + f + A * Math.cos(angle);
|
||
var my = cy - b * Math.sin(angle);
|
||
ctx.beginPath();
|
||
ctx.arc(mx, my, 4, 0, Math.PI * 2);
|
||
ctx.fillStyle = '#e2e8f0';
|
||
ctx.fill();
|
||
ctx.strokeStyle = '#8b949e';
|
||
ctx.lineWidth = 0.5;
|
||
ctx.stroke();
|
||
ctx.fillStyle = '#e2e8f0';
|
||
ctx.font = '12px "Segoe UI", Arial, sans-serif';
|
||
ctx.textAlign = 'left';
|
||
ctx.textBaseline = 'bottom';
|
||
ctx.fillText('月球', mx + 8, my);
|
||
|
||
// 速度矢量
|
||
var vlen = 32;
|
||
var vx = -A * Math.sin(angle) * 0.7;
|
||
var vy = b * Math.cos(angle) * 0.7;
|
||
var vl = Math.sqrt(vx*vx + vy*vy);
|
||
if (vl > 0) { vx = vx/vl*vlen; vy = vy/vl*vlen; }
|
||
ctx.beginPath();
|
||
ctx.moveTo(mx, my);
|
||
ctx.lineTo(mx + vx, my + vy);
|
||
ctx.strokeStyle = '#3fb950';
|
||
ctx.lineWidth = 2;
|
||
ctx.stroke();
|
||
var tipX = mx + vx, tipY = my + vy;
|
||
ctx.beginPath();
|
||
ctx.moveTo(tipX, tipY);
|
||
ctx.lineTo(tipX - vx*0.3 + vy*0.15, tipY - vy*0.3 - vx*0.15);
|
||
ctx.lineTo(tipX - vx*0.3 - vy*0.15, tipY - vy*0.3 + vx*0.15);
|
||
ctx.closePath();
|
||
ctx.fillStyle = '#3fb950';
|
||
ctx.fill();
|
||
|
||
// 信息面板
|
||
var roundE = Math.round(e * 10000) / 10000;
|
||
infoDiv.innerHTML =
|
||
'<span><span class="dot" style="background:#7ee787"></span>椭圆轨道 e = ' + roundE.toFixed(4) + '</span>' +
|
||
'<span><span class="dot" style="background:#d29922"></span>偏心距 c = ' + f.toFixed(1) + '</span>' +
|
||
'<span><span class="dot" style="background:#3fb950"></span>b/a = ' + (b / A).toFixed(4) + '</span>' +
|
||
'<span>近地点 ' + (A * (1 - e)).toFixed(1) + ' / 远地点 ' + (A * (1 + e)).toFixed(1) + '</span>';
|
||
}
|
||
|
||
function update() {
|
||
var val = parseInt(slider.value);
|
||
var e = val / 1000;
|
||
eDisplay.textContent = e.toFixed(4);
|
||
draw(e);
|
||
}
|
||
|
||
slider.addEventListener('input', update);
|
||
update();
|
||
})();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|