Python 实现月相可视化系统:天文计算与 Web 渲染详解
介绍使用 Python 构建月相可视化系统的过程。通过天文算法计算月相周期,利用 Matplotlib 绘制月相图表,并生成包含 CSS3 动画和 JavaScript 交互的 HTML 界面。系统支持时间轴展示、曲线图分析及当前月相显示,适用于中秋节等场景的文化科技融合展示。

介绍使用 Python 构建月相可视化系统的过程。通过天文算法计算月相周期,利用 Matplotlib 绘制月相图表,并生成包含 CSS3 动画和 JavaScript 交互的 HTML 界面。系统支持时间轴展示、曲线图分析及当前月相显示,适用于中秋节等场景的文化科技融合展示。

我们将构建一个功能丰富的月相可视化系统,主要包含以下特性:
# 安装依赖
pip install numpy matplotlib datetime base64 io warnings
moon-phase-visualizer/
├── moon_calculator.py # 核心计算引擎
├── generate_html.py # HTML 生成器
└── moon_phase_2025_mid_autumn.html # 生成的界面
月相变化周期是 29.53 天,选定 2025 年 9 月 25 日作为参考新月。通过计算目标日期与参考点的时间差,结合朔望月周期进行数学建模。
算法的关键是将连续时间变化映射到 0-1 的月相值:前半周期(新月→满月),后半周期(满月→新月)。
matplotlib 中文显示问题通过多字体回退解决:微软雅黑 → 黑体 → 其他系统字体。 月亮绘制用圆形 + 椭圆阴影实现,阴影宽度根据月相值动态计算。中秋节特效包括多层光晕和星星装饰。
moon_calculator.py - 核心计算引擎import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from datetime import datetime, timedelta
import math
import base64
import io
import warnings
from matplotlib import rcParams
warnings.filterwarnings('ignore')
plt.switch_backend('Agg')
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
class MoonPhaseCalculator:
"""月相计算器类"""
def __init__(self):
self.lunar_cycle = 29.530588853
self.reference_new_moon = datetime(2025, 9, 25)
def get_moon_phase(self, date):
"""计算指定日期的月相"""
days_since_new_moon = (date - self.reference_new_moon).total_seconds() / (24 * 3600)
cycle_position = (days_since_new_moon % self.lunar_cycle) / self.lunar_cycle
if cycle_position <= 0.5:
phase = cycle_position * 2
else:
phase = 2 - (cycle_position * 2)
return phase
def get_moon_age(self, date):
"""计算月龄"""
days_since_new_moon = (date - self.reference_new_moon).total_seconds() / (24 * 3600)
return days_since_new_moon % self.lunar_cycle
def is_mid_autumn_festival(self, date):
"""判断是否为中秋节(2025 年 10 月 6 日)"""
mid_autumn_2025 = datetime(2025, 10, 6)
return abs((date - mid_autumn_2025).days) == 0
def get_mid_autumn_date(self, year=2025):
"""获取指定年份的中秋节日期"""
if year == 2025:
return datetime(2025, 10, 6)
elif year == 2024:
return datetime(2024, 9, 17)
elif year == 2026:
return datetime(2026, 9, 25)
else:
return datetime(2025, 10, 6)
核心算法:
lunar_cycle = 29.530588853:朔望月精确天数get_moon_phase() 通过时间差和模运算计算月相值total_seconds() 确保计算精度class WebMoonVisualizer:
"""Web 版月相可视化器"""
def __init__(self):
self.calculator = MoonPhaseCalculator()
def draw_moon(self, ax, phase, size=1.0, position=(0, 0), is_mid_autumn=False):
"""绘制月亮形状"""
x, y = position
circle = patches.Circle((x, y), size, facecolor='lightyellow', edgecolor='gold', linewidth=2)
ax.add_patch(circle)
if phase < 0.95:
shadow_width = size * 2 * (1 - phase)
if shadow_width > 0.1:
shadow = patches.Ellipse((x + size * 0.1, y), shadow_width, size * 2, facecolor='darkgray', alpha=0.6)
ax.add_patch(shadow)
if is_mid_autumn:
for i in range(4):
halo = patches.Circle((x, y), size * (1.3 + i * 0.15), facecolor='orange', alpha=0.12 - i * 0.03, edgecolor='none')
ax.add_patch(halo)
star_positions = [
(x - size * 2.0, y + size * 1.0),
(x + size * 2.0, y + size * 1.0),
(x - size * 1.8, y - size * 1.3),
(x + size * , y - size * )
]
sx, sy star_positions:
star = patches.RegularPolygon((sx, sy), , radius=size * , facecolor=, alpha=)
ax.add_patch(star)
():
phase < :
phase < :
phase < :
phase < :
:
绘制要点:
patches.Circle 绘制月亮基础形状shadow_width = size * 2 * (1 - phase)
def create_timeline_chart(self, center_date=None, days=30):
"""创建时间轴图表(修复重叠问题)"""
if center_date is None:
center_date = datetime(2025, 10, 6)
start_date = center_date - timedelta(days=15)
fig, ax = plt.subplots(figsize=(24, 12))
for i in range(days):
current_date = start_date + timedelta(days=i)
phase = self.calculator.get_moon_phase(current_date)
is_mid_autumn = self.calculator.is_mid_autumn_festival(current_date)
x_pos = i * 3.5
y_pos = 0
self.draw_moon(ax, phase, size=1.0, position=(x_pos, y_pos), is_mid_autumn=is_mid_autumn)
ax.text(x_pos, -3.0, current_date.strftime('%m-%d'), ha='center', va='top', fontsize=11, fontweight='bold')
if is_mid_autumn:
ax.text(x_pos, 3.5, '2025 年中秋节', ha='center', va='bottom', fontsize=14, fontweight='bold', color='red')
实现关键:
x_pos = i * 3.5:间距设置避免重叠
def create_phase_chart(self):
"""创建月相图表"""
fig, ax = plt.subplots(figsize=(12, 8))
ax.set_facecolor('#001133')
today = datetime(2025, 10, 6)
dates_range = [today + timedelta(days=i - 15) for i in range(31)]
phase_values = [self.calculator.get_moon_phase(d) for d in dates_range]
ax.plot(range(31), phase_values, 'gold', linewidth=4, marker='o', markersize=6, markerfacecolor='yellow', markeredgecolor='orange')
ax.fill_between(range(31), phase_values, alpha=0.3, color='gold')
ax.axhline(y=1.0, color='red', linestyle='--', alpha=0.8, linewidth=2, label='Full Moon')
ax.axhline(y=0.0, color='silver', linestyle='--', alpha=0.8, linewidth=2, label='New Moon')
ax.axvline(x=15, color='lime', linestyle=':', alpha=0.8, linewidth=3, label='Mid-Autumn Festival')
mid_autumn_phase = self.calculator.get_moon_phase(today)
ax.plot(, mid_autumn_phase, , markersize=, label=)
设计要点:
fill_between 创建填充区域增强视觉效果
def create_current_moon(self):
"""创建当前月相图"""
fig, ax = plt.subplots(figsize=(8, 8))
ax.set_facecolor('#001133')
today = datetime(2025, 10, 6)
phase_today = self.calculator.get_moon_phase(today)
is_today_mid_autumn = self.calculator.is_mid_autumn_festival(today)
self.draw_moon(ax, phase_today, size=3.0, position=(0, 0), is_mid_autumn=is_today_mid_autumn)
ax.set_xlim(-6, 6)
ax.set_ylim(-6, 6)
ax.set_aspect('equal')
ax.axis('off')
phase_name = self.get_phase_name(phase_today)
title = f'2025 Mid-Autumn Festival Moon Phase - {phase_name}\n{today.strftime("%Y-%m-%d")}\nPhase Value: {phase_today:.3f}'
if is_today_mid_autumn:
title += '\nHappy Mid-Autumn Festival!'
ax.text(0, -5, title, ha='center', va='top', fontsize=14, color='white', fontweight='bold', bbox=dict(boxstyle="round,pad=0.8", facecolor="darkblue", alpha=0.8))
技术特点:
size=3.0 大尺寸突出视觉效果
def fig_to_base64(self, fig):
"""将 matplotlib 图形转换为 base64 字符串"""
buffer = io.BytesIO()
fig.savefig(buffer, format='png', facecolor=fig.get_facecolor(), bbox_inches='tight', dpi=150)
buffer.seek(0)
image_png = buffer.getvalue()
buffer.close()
plt.close(fig)
graphic = base64.b64encode(image_png)
return graphic.decode('utf-8')
要点:
dpi=150 平衡质量与大小plt.close(fig) 释放内存防止泄漏generate_html.py - 界面组装器from moon_calculator import WebMoonVisualizer
from datetime import datetime
def generate_html():
visualizer = WebMoonVisualizer()
mid_autumn_date = datetime(2025, 10, 6)
timeline_fig = visualizer.create_timeline_chart(mid_autumn_date, days=30)
timeline_img = visualizer.fig_to_base64(timeline_fig)
phase_fig = visualizer.create_phase_chart()
phase_img = visualizer.fig_to_base64(phase_fig)
current_fig = visualizer.create_current_moon()
current_img = visualizer.fig_to_base64(current_fig)
annual_fig = visualizer.create_annual_overview()
annual_img = visualizer.fig_to_base64(annual_fig)
moon_info = visualizer.get_moon_info()
/* 星空背景动画 */
@keyframes twinkle {
0%, 100% { opacity: 0.3; }
50% { opacity: 1; }
}
/* 流星效果 */
@keyframes shooting {
0% { transform: rotate(-45deg) translateX(0) translateY(0); opacity: 1; }
100% { transform: rotate(-45deg) translateX(-800px) translateY(800px); opacity: 0; }
}
/* 月亮背景浮动 */
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-15px); }
}
/* 渐变文字效果 */
.highlight {
background: linear-gradient(45deg, #ff6b35, #f7931e, #ffd700);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
: gradient-shift ease infinite;
}

// 创建 120 颗星星
function createStars() {
const starsContainer = document.getElementById('stars');
const numberOfStars = 120;
for (let i = 0; i < numberOfStars; i++) {
const star = document.createElement('div');
star.className = 'star';
star.style.left = Math.random() * 100 + '%';
star.style.top = Math.random() * 100 + '%';
const size = Math.random() * 3 + 1;
star.style.width = size + 'px';
star.style.height = size + 'px';
star.style.animationDelay = Math.random() * 3 + 's';
starsContainer.appendChild(star);
}
}
// 流星效果
function createShootingStar() {
shootingStar = .();
shootingStar. = ;
startX = .() * .;
startY = .() * (. * );
shootingStar.. = startX + ;
shootingStar.. = startY + ;
shootingStar.. = ;
..(shootingStar);
( {
shootingStar.();
}, );
}
特效实现:
愿我们的代码也能像这轮明月一样,在技术的夜空中永远闪耀着智慧的光芒,连接着传统与未来,架起文化与科技的桥梁。
中秋节快乐!代码如明月,永远照亮前行的路!🌕🥮

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online