Python 实现月相计算与可视化系统
本项目使用 Python 结合 Matplotlib 库实现月相计算与可视化。通过朔望月周期算法精确计算指定日期月相,生成时间轴图表、曲线图及当前月相图。利用 Base64 编码将图像嵌入 HTML,配合 CSS3 动画与 JavaScript 交互特效打造星空背景与流星效果。代码包含核心计算引擎与界面生成器,支持自定义日期与节日标记,适用于中秋节等特殊日期的天文科普展示。

本项目使用 Python 结合 Matplotlib 库实现月相计算与可视化。通过朔望月周期算法精确计算指定日期月相,生成时间轴图表、曲线图及当前月相图。利用 Base64 编码将图像嵌入 HTML,配合 CSS3 动画与 JavaScript 交互特效打造星空背景与流星效果。代码包含核心计算引擎与界面生成器,支持自定义日期与节日标记,适用于中秋节等特殊日期的天文科普展示。

我们将构建一个功能丰富的月相可视化系统,主要包含以下特性:
# 安装依赖 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')
# 设置 matplotlib 为非交互式后端
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):
# 月相周期约为 29.53 天
self.lunar_cycle = 29.530588853
# 更新参考新月日期(2025 年 9 月 25 日新月)
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 * 1.8, y - size * 1.3)]
for sx, sy in star_positions:
star = patches.RegularPolygon((sx, sy), 5, radius=size * 0.12, facecolor='gold', alpha=0.9)
ax.add_patch(star)
def get_phase_name(self, phase):
"""获取月相名称"""
if phase < 0.1:
return "新月"
elif phase < 0.35:
return "蛾眉月"
elif phase < 0.65:
return "上弦月"
elif phase < 0.9:
return "盈凸月"
else:
return "满月"
绘制要点:
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(15, mid_autumn_phase, 'r*', markersize=15, label=f'Festival Phase({mid_autumn_phase:.2f})')
设计要点:
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;
animation: gradient-shift 3s 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() {
const shootingStar = document.createElement('div');
shootingStar.className = 'shooting-star';
const startX = Math.random() * window.innerWidth;
const startY = Math.random() * (window.innerHeight * 0.5);
shootingStar.style.left = startX + 'px';
shootingStar.style.top = startY + 'px';
shootingStar.style.animation = 'shooting 1.5s linear forwards';
document.body.appendChild(shootingStar);
setTimeout(() => {
shootingStar.remove();
}, 1500);
}
特效实现:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 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