1. 项目概述从零构建一个交互式MATLAB数字天象馆“天上那是什么” 这个问题几乎每个人都问过。无论是夜空中一颗特别亮的星还是一道快速划过的轨迹那份对宇宙的好奇心是共通的。作为一名长期与数据和算法打交道的工程师我一直在寻找将专业工具与个人兴趣结合的方式。这次我想用MATLAB——这个通常用于工程计算、信号处理和机器学习的强大环境——来回答这个问题并构建一个属于我自己的、可交互的“数字天象馆”。这个项目的核心目标很明确利用MATLAB的图形和计算能力在电脑屏幕上实时模拟并展示任意时间、任意地点的星空。它不是一个简单的静态星图而是一个动态的、可探索的3D模型。你可以输入你所在的经纬度和一个具体的时间比如你出生的那一刻或者某个难忘的夜晚程序就能准确地计算出当时当地可见的所有亮星、星座连线甚至模拟太阳、月亮和行星的位置将它们以三维向量的形式渲染在虚拟的天球上。最终你将获得一个可以旋转、缩放、点击查看星体信息的交互式窗口就像把一个迷你天文馆搬进了MATLAB。这个项目非常适合有一定MATLAB基础并对天文、数据可视化或3D图形编程感兴趣的朋友。它不要求你是天文学专家但会带你深入理解如何将抽象的数学公式比如赤道坐标到地平坐标的转换转化为直观的视觉图像并在这个过程中熟练运用MATLAB的矩阵运算、图形对象句柄、回调函数以及外部数据如JSON格式的星表处理等核心技能。接下来我将完整拆解从数据获取、核心算法到界面构建的每一步并分享我在实现过程中踩过的坑和总结的技巧。2. 核心思路与架构设计2.1 为什么选择MATLAB在开始敲代码之前我们先聊聊选型。市面上有天文学软件如Stellarium也有强大的Python天文库如Astropy为什么偏偏用MATLAB这背后有几个关键的考量首先矩阵运算与向量化处理的天然优势。天文计算涉及大量的球面三角学公式本质上都是对角度经纬度、赤经赤纬进行正弦、余弦运算。MATLAB的整个语法就是为矩阵和向量运算设计的一行代码就能对整个星表数据成百上千颗星完成坐标转换无需写循环效率极高且代码简洁。例如将赤道坐标转换为地平坐标涉及到时角、纬度等参数在MATLAB里可能就是几个sind(),cosd(),*矩阵乘法的组合。其次强大的图形系统与交互能力。MATLAB的figure和axes对象提供了精细的控制能力。我们可以轻松创建一个3D坐标系作为天球用scatter3绘制星点用plot3绘制星座连线用patch绘制星座区域。更重要的是其uicontrol和现代App Designer组件可以快速构建滑块、按钮、文本框用于交互式调整时间、地点。鼠标点击拾取星体信息的功能也可以通过datacursormode或自定义回调函数实现这对于一个天象馆的“探索”体验至关重要。再者数据处理的便捷性。我们需要一个准确的星表数据源。现代天文数据交换普遍采用JSON格式。MATLAB自R2016b版本起内置了jsondecode和jsonencode函数能够非常方便地将网络上的JSON格式星表例如从某些开源天文API获取解析为结构体或元胞数组无缝融入工作流。这与热词中提到的“JSON”数据处理需求完美契合。最后** Aerospace Toolbox 的加持**。虽然这不是必选项但如果你有Aerospace Toolbox事情会变得更简单。这个工具箱提供了诸如dcmeci2ecef地心惯性系到地固系的转换、planetEphemeris行星历表等函数可以更专业、更便捷地计算太阳、月亮和行星的精确位置。我们的项目会探讨在有和没有该工具箱两种情况下的实现方案。2.2 系统架构与数据流整个天象馆的软件架构可以清晰地划分为四个层次数据层负责原始天文数据的获取、加载与解析。核心是一个包含恒星基本信息星名、视星等、赤经、赤纬的JSON或CSV文件。我将演示如何从公开资源如耶鲁亮星星表精简版整理并生成一个MATLAB友好的数据文件。计算层这是项目的大脑。它接收用户输入的观测时间UTC和地理坐标经度、纬度并执行一系列核心计算时间系统转换将日常的日期时间转换为儒略日这是天文计算的标准时间。恒星位置计算基于赤经、赤纬和当前时间计算每颗星在观测地点的地平坐标方位角、高度角。这需要计算本地恒星时。太阳/月亮/行星位置计算使用简化算法或Aerospace Toolbox函数计算这些太阳系内天体的位置。坐标系统转换将球面坐标方位角、高度角转换为用于3D渲染的直角坐标。渲染层利用MATLAB的3D图形功能将计算层输出的直角坐标绘制出来。包括根据视星等决定星点的大小和亮度颜色。绘制星座连线需要额外的星座连线数据。用特殊图标标记太阳、月亮和行星。添加天球网格、地平线等辅助视觉元素。交互层提供图形用户界面允许用户动态修改参数如拖动时间滑块、输入新坐标并实时更新星图。同时实现星体点击查询功能。数据流如下用户通过界面输入参数 - 触发计算层 - 计算层读取数据层信息并运算 - 将结果传递给渲染层 - 渲染层更新图形界面 - 用户看到新的星空并可以继续交互。注意一个常见的误区是试图一次性计算并渲染所有可见天体包括暗至6等以上的数千颗星。在初期强烈建议从亮星如1等星以上和主要星座开始。这能保证原型的快速运行和调试避免被海量数据和复杂的性能优化问题淹没。迭代开发先让核心流程跑通。3. 数据准备构建你的恒星数据库巧妇难为无米之炊一个准确、结构清晰的星表是我们的基石。我们不直接从零观测而是利用现有的天文数据。3.1 寻找与解析原始星表数据网络上有很多免费的星表资源。一个非常适合入门的是“耶鲁亮星星表Bright Star Catalogue”的简化版本。你可以找到其CSV或自行转换为JSON格式的数据。一个典型的JSON星表条目可能长这样[ { name: Sirius, mag: -1.46, ra: 101.2875, dec: -16.7161, constellation: CMa }, { name: Canopus, mag: -0.72, ra: 95.9879, dec: -52.6957, constellation: Car } ]name: 星名常用名或拜耳命名。mag: 视星等数值越小越亮。太阳约为-26.7满月约-12.6天狼星-1.46。ra: 赤经单位通常是小时h但为了计算方便我们更常用度数deg。1小时15度。上述例子中的101.2875度就是6.7525小时。dec: 赤纬单位度。constellation: 星座缩写。在MATLAB中读取这样的JSON文件轻而易举starData jsondecode(fileread(bright_stars.json)); % starData 现在是一个结构体数组如果数据是CSV可以使用readtable:starTable readtable(bright_stars.csv);3.2 星座连线数据的处理单独的星点不足以勾勒出星座的图案。我们需要知道哪些星之间应该连线。这需要另一组数据一个列表其中每行指定两颗星通过索引或名称属于同一条星座连线。例如一个简单的连线表CSV格式Star1_Index,Star2_Index 1,2 2,3 4,5 ...在MATLAB中我们可以这样加载并使用它lines readmatrix(constellation_lines.csv); % 假设是数值索引 % 在绘图时循环 lines 的每一行在对应的星点坐标之间画线实操心得在整合星表和连线表时确保它们的索引或名称能正确对应是关键。我建议在生成连线表时直接使用星表中稳定的唯一ID如HR编号或精确的坐标匹配而不是依赖可能变化的行号。在代码中可以写一个根据星名查找坐标的函数来解耦两者。3.3 太阳系天体数据的获取对于太阳、月亮和行星我们有两种主要获取位置的方式使用简化公式无工具箱存在一些精度尚可的近似计算公式可以计算给定儒略日下太阳和月亮的赤道坐标。行星的计算则更为复杂。这些公式可以在经典的天文算法书籍或权威网站找到。优点是零依赖。利用 Aerospace Toolbox推荐若可用这是最专业和便捷的途径。planetEphemeris函数可以根据高精度星历如DE405直接给出行星的地心坐标。对于太阳和月亮也有相应的计算方式。这能极大提升项目的专业性和准确性。重要提示处理网络获取的JSON数据时务必注意MATLAB版本。jsondecode在较新版本中表现良好。如果遇到复杂嵌套的JSON解析后的结构体可能很深使用struct2table或动态字段名来访问数据会更方便。另外将处理好的数据保存为MAT文件.mat可以显著提高后续加载速度。4. 核心算法从宇宙坐标到屏幕像素这是整个项目的数学核心。我们需要将天体在宇宙中的“固定”坐标转换为在特定时间、特定地点观察者眼中的“视”位置。4.1 时间系统的基石儒略日计算所有精密天文计算都基于儒略日。儒略日是从公元前4713年1月1日格林尼治平午开始连续计数的天数。MATLAB自身日期序列与儒略日不同但转换不难。function jd date2jd(year, month, day, hour, minute, second) % 一个将年月日时分秒转换为儒略日的简化函数 % 注意此公式适用于公历1900年3月至2100年2月对于更精确或更广范围的需求需使用更复杂的算法 if month 2 year year - 1; month month 12; end A floor(year / 100); B 2 - A floor(A / 4); jd floor(365.25 * (year 4716)) floor(30.6001 * (month 1)) day B - 1524.5; jd jd (hour minute/60 second/3600)/24; end得到儒略日JD后可以计算格林尼治恒星时GST进而得到本地恒星时LST% 计算格林尼治恒星时简化公式单位度 T (JD - 2451545.0) / 36525.0; % 儒略世纪数 GST_deg 280.46061837 360.98564736629 * (JD - 2451545.0) 0.000387933 * T*T - T*T*T/38710000.0; GST_deg mod(GST_deg, 360); % 归一化到0-360度 % 计算本地恒星时 longitude 116.3975; % 例如北京经度东经为正 LST_deg GST_deg longitude; LST_deg mod(LST_deg, 360);LST_deg就是观测地点的春分点所在的地方时角是连接恒星赤道坐标和地平坐标的桥梁。4.2 坐标转换赤道坐标 - 地平坐标一颗星的赤道坐标(ra, dec)是相对固定的。我们需要结合本地恒星时LST和观测地纬度lat计算出它的地平坐标(az, alt)即方位角从北点向东计量和高度角从地平线向上计量。function [azimuth_deg, altitude_deg] eq2hor(ra_deg, dec_deg, lst_deg, lat_deg) % 将赤道坐标转换为地平坐标 % ra_deg: 赤经度 % dec_deg: 赤纬度 % lst_deg: 本地恒星时度 % lat_deg: 观测地纬度度北纬为正 % 计算时角 ha_deg lst_deg - ra_deg; % 时角 本地恒星时 - 赤经 ha_deg mod(ha_deg 180, 360) - 180; % 将时角规范到[-180, 180]度 % 将角度转换为弧度 ha_rad deg2rad(ha_deg); dec_rad deg2rad(dec_deg); lat_rad deg2rad(lat_deg); % 使用球面三角公式计算高度角和方位角 sin_alt sin(dec_rad) .* sin(lat_rad) cos(dec_rad) .* cos(lat_rad) .* cos(ha_rad); alt_rad asin(sin_alt); cos_az (sin(dec_rad) - sin(alt_rad) .* sin(lat_rad)) ./ (cos(alt_rad) .* cos(lat_rad)); % 防止因数值误差导致cos_az的绝对值略大于1 cos_az max(min(cos_az, 1), -1); az_rad acos(cos_az); % 根据时角的正负决定方位角的象限0到360度从北点向东 sin_ha sin(ha_rad); if sin_ha 0 azimuth_rad az_rad; else azimuth_rad 2*pi - az_rad; end azimuth_deg rad2deg(azimuth_rad); altitude_deg rad2deg(alt_rad); end这个函数是核心中的核心。注意这里使用了逐元素运算符.*和./因此你可以一次性传入整个星表的ra_deg和dec_deg向量MATLAB会并行计算所有星的位置这就是向量化编程的魅力。4.3 球面坐标到3D直角坐标的映射为了在MATLAB的3D坐标系中绘制我们需要将地平坐标(az, alt)映射到天球上。一个半径为R的天球其上的点可以表示为R 10; % 天球半径可任意设定不影响相对位置 x R * cosd(altitude_deg) .* sind(azimuth_deg); y R * cosd(altitude_deg) .* cosd(azimuth_deg); z R * sind(altitude_deg);这里我采用的约定是X轴指向东Y轴指向北Z轴指向天顶。这样地平线就是z0的平面天顶在(0,0,R)。这种布局比较符合我们对“仰视”天空的直觉。注意事项当高度角alt为负时天体在地平线以下我们通常不应该绘制它。在计算完坐标后可以通过筛选altitude_deg 0的星点来只显示可见星。5. 图形渲染与交互界面实现有了数据和坐标现在是时候让星空在MATLAB窗口中“亮”起来了。5.1 创建基础3D天球视图首先我们创建一个图形窗口和3D坐标轴并设置合适的视角和属性。fig figure(Name, MATLAB Planetarium, NumberTitle, off, Position, [100 100 1200 800]); ax axes(Parent, fig); hold(ax, on); grid(ax, on); axis(ax, equal); % 保持三个轴比例相等 view(ax, 3); % 三维视图 xlabel(ax, East); ylabel(ax, North); zlabel(ax, Zenith); % 设置一个合适的视角模拟从地面仰望 view(ax, 0, 90); % 方位角0度看向北俯仰角90度看向天顶5.2 绘制恒星与星座接下来我们根据计算出的3D坐标(x, y, z)和星等mag来绘制星点。星等决定了点的大小和颜色。% 假设 star_x, star_y, star_z, star_mag 是已经计算好的向量 % 将星等映射到点的大小和亮度。星等越小越亮点越大、颜色越白。 minMag min(star_mag); maxMag max(star_mag); % 一个简单的映射大小在5到20之间线性变化 sizes 5 15 * (maxMag - star_mag) / (maxMag - minMag); % 颜色从暗黄色暗星到亮白色亮星 intensity (maxMag - star_mag) / (maxMag - minMag); % 0到1 colors [intensity, intensity, 0.8*intensity 0.2]; % RGB增加蓝色成分让暗星偏黄 scatter3(ax, star_x, star_y, star_z, sizes, colors, filled, Marker, o);绘制星座连线% 假设 constellation_lines 是一个 Nx2 的矩阵每一行是两颗星的索引 for i 1:size(constellation_lines, 1) idx1 constellation_lines(i, 1); idx2 constellation_lines(i, 2); % 确保这两颗星都在当前可见范围内可选 if alt(idx1) 0 alt(idx2) 0 plot3(ax, [star_x(idx1), star_x(idx2)], ... [star_y(idx1), star_y(idx2)], ... [star_z(idx1), star_z(idx2)], ... Color, [0.5 0.5 1.0], LineWidth, 0.5); % 浅蓝色细线 end end5.3 添加太阳、月亮与行星这些天体需要用特殊的标记来区分。例如太阳用一个大的黄色圆盘月亮用一个灰色的圆环。% 假设 sun_x, sun_y, sun_z 已计算 plot3(ax, sun_x, sun_y, sun_z, yo, MarkerSize, 15, MarkerFaceColor, y, DisplayName, Sun); % 假设 moon_x, moon_y, moon_z 已计算 plot3(ax, moon_x, moon_y, moon_z, ko, MarkerSize, 10, MarkerFaceColor, [0.7 0.7 0.7], DisplayName, Moon); % 行星可以用不同颜色的五角星表示 plot3(ax, planet_x, planet_y, planet_z, rp, MarkerSize, 8, MarkerFaceColor, r, DisplayName, Mars);5.4 构建交互式控制面板一个静态的星图价值有限。我们需要让用户能改变时间和地点。使用MATLAB的App Designer或传统的uicontrol都可以。这里以传统方式为例% 在figure上添加控件 uicontrol(Style, text, Position, [20 750 100 20], String, Latitude:); latEdit uicontrol(Style, edit, Position, [120 750 80 20], String, 39.9); uicontrol(Style, text, Position, [20 720 100 20], String, Longitude:); lonEdit uicontrol(Style, edit, Position, [120 720 80 20], String, 116.4); uicontrol(Style, text, Position, [20 690 100 20], String, Date Time:); datetimeEdit uicontrol(Style, edit, Position, [120 690 180 20], String, datestr(now, yyyy-mm-dd HH:MM:SS)); updateBtn uicontrol(Style, pushbutton, Position, [20 650 100 30], String, Update Sky, ... Callback, updateSkyCallback);updateSkyCallback是一个回调函数它会从这些编辑框中读取新的经纬度和时间重新执行第4节的所有计算然后更新图形对象的数据而不是清空重绘以实现流畅的交互。function updateSkyCallback(src, event) new_lat str2double(get(latEdit, String)); new_lon str2double(get(lonEdit, String)); new_dt_str get(datetimeEdit, String); new_dt datetime(new_dt_str, InputFormat, yyyy-MM-dd HH:mm:ss); % 重新计算坐标... [new_az, new_alt] eq2hor(ra_all, dec_all, lst_new, new_lat); new_x R * cosd(new_alt) .* sind(new_az); new_y R * cosd(new_alt) .* cosd(new_az); new_z R * sind(new_alt); % 更新散点图的数据 set(starScatterHandle, XData, new_x(new_alt0), YData, new_y(new_alt0), ZData, new_z(new_alt0)); % 更新连线...需要更复杂的逻辑可能需要删除旧线重绘 % 更新太阳月亮位置... set(sunHandle, XData, sun_x_new, YData, sun_y_new, ZData, sun_z_new); % ... 更新其他图形对象 drawnow; % 强制刷新图形 end实操心得直接更新图形对象的XData,YData,ZData属性比每次cla清空坐标轴再重绘要高效得多尤其是在数据点较多时。对于星座连线这种由多个独立线段组成的图形更新起来比较麻烦。一种策略是将所有连线存储在一个图形对象句柄数组中然后在回调函数中循环更新每个线段的端点数据。另一种更简单但稍慢的方法是在回调开始时删除所有旧的连线对象然后根据新坐标重新绘制。6. 性能优化与高级功能拓展当星表数据量增大比如包含上万颗星或需要实时平滑动画如模拟星空旋转时性能就成为关键。6.1 向量化计算与数据筛选我们已经使用了向量化计算这是MATLAB性能的基石。此外在计算前进行筛选能大幅减少计算量地平线以下剔除在调用eq2hor前可以先用一个简单的判断粗略剔除那些赤纬过低相对于观测地纬度永远不可能升起的星。更精确的筛选是在计算高度角后进行。星等筛选只计算和绘制亮于某一星等如4等的星。大部分暗星肉眼不可见且数量庞大。分区计算将天球分成若干区域只计算和渲染当前视野内的区域。这需要更复杂的空间索引如四叉树对于MATLAB项目来说可能过于复杂但如果是超大规模星表值得考虑。6.2 图形渲染优化使用scatter3的优化参数对于大量点确保使用filled和合适的标记大小。过小的标记在渲染时开销反而可能更大。避免在循环中绘图星座连线应该在一次循环中收集所有线段的数据然后用一个plot3命令绘制多条线或者使用line函数并传入矩阵数据。设置合理的图形属性关闭不必要的图形特性可以提升速度。set(ax, SortMethod, childorder); % 渲染顺序优化 % 在交互更新时可以临时关闭一些渲染细节 set(fig, Renderer, opengl); % 使用OpenGL渲染器通常更快6.3 实现星空随时间平滑动画模拟星空从日落到日出的旋转是数字天象馆的亮点。这本质上是在循环中连续增加时间比如每分钟并更新图形。% 在某个按钮的回调中启动动画 startTime datetime(now); animationSpeed 60; % 每秒模拟的秒数 stopAnimation false; while ~stopAnimation elapsedRealTime seconds(datetime(now) - startTime); simulatedTimeDelta elapsedRealTime * animationSpeed; currentSimTime initialTime seconds(simulatedTimeDelta); % 用 currentSimTime 重新计算星空位置 % ... (计算逻辑) % 更新图形对象数据 % ... (更新逻辑) drawnow limitrate; % 使用 limitrate 限制绘制频率避免占用过多CPU pause(0.01); % 短暂暂停让出CPU控制权同时可以检测停止按钮状态 end使用drawnow limitrate比drawnow更高效它会在达到显示刷新率后忽略多余的绘制请求。6.4 集成 Aerospace Toolbox 进行高精度计算如果你拥有Aerospace Toolbox计算太阳系天体位置将变得非常准确和简单。% 计算行星位置地心赤道坐标 jd date2jd(...); % 儒略日 % 计算地球在J2000平赤道坐标系中的位置实际上是太阳的位置因为星历是地心的 [planetPos, planetVel] planetEphemeris(jd, Earth, Sun); % 单位km % planetPos 是一个1x3的向量表示从太阳指向地球的矢量。 % 对于观测者来说太阳的方向正好相反。 sunVec -planetPos; % 从地球指向太阳的矢量 % 将矢量转换为赤经赤纬 distance norm(sunVec); sunVecNorm sunVec / distance; dec_sun asind(sunVecNorm(3)); % 假设Z轴指向北天极 ra_sun atan2d(sunVecNorm(2), sunVecNorm(1)); % atan2d(Y, X) ra_sun mod(ra_sun, 360);对于月亮和其他行星只需改变planetEphemeris中的目标天体参数即可。注意该函数返回的坐标是J2000平赤道坐标系可能需要根据你的星表坐标系通常是J2000决定是否需要进行岁差、章动等修正。对于本项目级别的精度通常可以忽略。7. 常见问题与调试技巧实录在开发过程中我遇到了不少问题这里总结一下希望能帮你避坑。7.1 坐标转换结果异常症状星星全部聚集在一点或者方位完全错乱。排查检查单位这是最常见错误。确保所有三角函数的输入是度用sind,cosd还是弧度用sin,cos务必统一。我强烈建议在内部计算时全部使用弧度因为MATLAB的数学函数默认用弧度。在输入输出接口处再进行度与弧度的转换。我的示例代码中使用了度是为了公式更清晰但在实际复杂项目中用弧度更稳妥。验证公式用已知的测试用例验证eq2hor函数。例如在春分点赤经0时当本地恒星时也是0时春分点应该位于观测者的南点子午圈上方位角180度高度角取决于纬度。找一个在线的天文计算器对比结果。检查经纬度符号东经为正西经为负北纬为正南纬为负。7.2 图形显示问题症状图形窗口卡顿、更新慢或视角奇怪。解决卡顿遵循第6节的优化建议。特别是使用drawnow limitrate和避免在回调函数中进行繁重的文件I/O操作。视角不对view(az, el)函数中az是方位角绕Z轴旋转el是仰角从XY平面向上。要模拟从地面仰望通常设置view(0, 90)。你可以尝试交互式旋转图形工具栏的旋转按钮找到满意的视角后用[az, el] view命令获取角度值然后硬编码到代码中。星点大小/颜色不随星等变化检查映射到sizes和colors的向量长度是否与scatter3输入的坐标向量长度一致。确保star_mag中没有NaN或Inf。7.3 时间计算错误症状太阳位置明显不对或者星空旋转的速度和真实时间不符。排查时区问题你输入的时间是本地时间还是UTC天文计算通常使用UTC。如果你的输入是北京时间UTC8需要先减去8小时转换为UTC再计算儒略日。儒略日公式精度网上有很多儒略日计算公式精度和适用范围不同。对于1900-2100年本文给出的简化公式足够。如果需要处理历史或未来很远日期需使用更严谨的算法如Meeus的公式。恒星时计算本地恒星时公式LST GST longitude中longitude的单位必须是度且东经为正。GST的计算公式也有多种精度不同。本文给出的公式是相对简单的近似对于高精度需求1角秒需要使用包含更多项的完整公式。7.4 数据文件读取失败症状jsondecode出错或读入的数据结构不符合预期。解决使用fileread先读入字符串检查JSON格式是否正确。可以在线使用JSON验证工具。如果JSON文件很大解析成结构体后可能难以浏览。使用whos查看变量用fieldnames查看结构体字段名。对于复杂的嵌套JSONjsondecode可能会生成嵌套的结构体和元胞数组。耐心地使用点号.和花括号{}进行索引。一个实用的调试技巧在开发初期不要一次性处理所有数据。创建一个只包含5-10颗最亮星如天狼星、老人星、织女星的迷你数据集并手动计算它们在某个特定时间地点如今晚8点你家的位置的坐标。先让这个小数据集正确显示然后再扩展到完整星表。这能帮你快速定位问题是出在核心算法还是数据本身。最后这个MATLAB数字天象馆项目就像搭积木从数据、算法到可视化每一步都清晰可辨。当你第一次运行程序看到熟悉的北斗七星或猎户座在屏幕上正确亮起并能通过滑动时间滑块看着它们缓缓东升西落时那种成就感是无与伦比的。它不仅是一个编程练习更是一座连接你、代码与浩瀚星海的桥梁。你可以在此基础上继续添加更多功能比如显示深空天体M31、M42、绘制黄道线、甚至模拟日食月食让这个属于你自己的数字宇宙更加丰富多彩。