python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python月相可视化

基于Python实现一个简单的月相可视化器

作者:颜颜yan_

这篇文章介绍了一个使用Python创建中秋节月相可视化系统的项目,项目通过精确的天文计算和优雅的可视化技术,实现了月相变化的动态展示,感兴趣的小伙伴可以了解下

引言

中秋节,这个承载着千年文化的传统节日,以其独特的满月寓意着团圆与和谐。我们不妨用Python这门优雅的编程语言,来创造一个富有诗意的中秋节月相可视化器。本文将带您通过代码的艺术,重现天空中月亮的盈亏变化,并在中秋节这个特殊的日子里,为我们的程序增添一抹传统文化的色彩。

项目概述

我们将构建一个功能丰富的月相可视化系统,主要包含以下特性:

技术架构解析

# 安装依赖
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)

核心算法:

可视化渲染类

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 "满月"

绘制要点:

四种图表实现详解

时间轴图表 - 连续月相展示

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')

实现关键:

月相曲线图 - 数学规律可视化

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})')

设计要点:

当前月相图

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))

技术特点:

图像Base64编码

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')

要点:

HTML界面生成

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()

CSS3特效设计

/* 星空背景动画 */
@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;
}

JavaScript交互特效

// 创建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);
}

特效实现:

结语

正如古人所言:"但愿人长久,千里共婵娟。"愿我们的代码也能像这轮明月一样,在技术的夜空中永远闪耀着智慧的光芒,连接着传统与未来,架起文化与科技的桥梁。

以上就是基于Python实现一个简单的月相可视化器的详细内容,更多关于Python月相可视化的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文