Analemma
允许从任何地理位置可视化太阳和月亮轨迹的 Web 工具。
一个用于自动化摄影采集并以交互方式可视化太阳与月亮“8”字轨迹(analemma)的完整系统。
该项目自动、定期地从公共网络摄像头采集图像,生成能够呈现太阳和月亮随时间视运动的序列(延时摄影)。图像通过 GitHub Actions 在精确的天文时刻采集,随后由高性能的现代 Web 查看器(PWA)加以呈现。整个系统无需传统数据库,而是依托 Git 仓库自身的文件系统运行。
软件架构
为保持代码的可维护性、可扩展性与可测试性,摄影编排器的核心(src/)严格遵循 Clean Architecture 与 Domain-Driven Design (DDD) 的原则进行构建。
1. 领域层(Domain Layer)
这是软件的核心。这里存放纯粹的业务规则,独立于任何框架或外部基础设施:
- 实体(
Location、Camera):Location封装地理数据(国家、州/省、城市)并生成动态标识符。Camera关联到某个Location,并保存其朝向方位。 - 值对象(Value Objects):例如
CelestialObject,用于统一领域词汇。 - 契约(接口):例如
ScheduleRepository,定义领域期望如何获取或持久化数据的抽象。
2. 应用层(Application Layer)
包含主要用例。它作为编排者,协调领域层与基础设施层以满足系统需求。
Scheduler:负责检查每个Location的计划时间。如果当前执行时间与某次计划采集相符(或处于阈值范围内),它会协调相应的采集服务获取图像。
3. 基础设施层(Infrastructure Layer)
代码在此与外部服务、I/O 和框架进行交互:
- 采集服务:使用 Puppeteer(
PuppeteerCaptureService)访问公共网络摄像头网页,等待多媒体组件加载完成,并从视频流或 canvas 中干净地提取画面(截图),不包含网页界面。 ConfigScheduleRepository:一个动态仓库,根据配置计算src/config/locations.ts中登记的每个Location坐标处的精确采集时间。
编排周期
编排器设计为通过 GitHub Actions 周期性运行,并借助定时任务(cronjob)优化资源使用。
sequenceDiagram
participant GH as GitHub Actions (Cron)
participant App as Scheduler
participant Repo as ConfigScheduleRepository
participant Cam as PuppeteerCaptureService
participant FS as File System / Git
GH->>App: 执行每小时周期(第 0 分钟)
App->>Repo: 查询当天的采集时间
Repo-->>App: 返回 { sun: '12:05', moon: '03:45' }
App->>App: 判断系统时间(America/La_Paz)是否匹配
alt 在阈值内匹配太阳事件
App->>Cam: 启动 Puppeteer Headless
Cam-->>App: 提取并返回图像缓冲区(截图)
App->>FS: 将文件保存至 'captures/sun/{location}/{camera}/{YYYY-MM-DD}.jpg'
GH->>FS: 执行 Git Commit 并 Push 到仓库
end
配置与环境变量
系统依赖集中式配置,运行所需的条件很少,其中最关键的是时间同步。
| 变量 | 说明 |
|---|---|
TZ | 关键: 确保时区配置为 America/La_Paz(UTC-4)。应用以此为基准,将预期的天文时间表与每小时的定时任务进行同步和比较。 |
Web 查看器
这是一个交互式 Web 应用,专门用于流畅地浏览由项目主编排器生成的数以万计的摄影采集(延时摄影),呈现太阳和月亮在长时间跨度内的运行轨迹。
UI 架构与用户体验(UX)
该 Web 应用旨在提供出色的性能,尤其是在处理大量高分辨率图像时,不会占用过多内存或影响浏览器的流畅度。
流畅的过渡
应用实现了一种导航机制:拦截用户点击,并在不重新加载整个页面的情况下将下一页动态载入 DOM。这确保了界面状态(例如视觉主题或主导航)保持连续。
深色模式持久化
深色/浅色配色方案从系统中检测,并在用户的各次会话之间保持一致。得益于与页面过渡的同步,在不同视图之间导航时不会出现视觉闪烁。
查看器的核心:播放器
在浏览数千张图像时(例如,一年的 Analemma 包含 365 帧高分辨率画面),一个关键挑战是终端用户设备的内存可能耗尽。为此,图像播放器采用了轻量且经过优化的架构:
stateDiagram-v2
[*] --> Initialization: 加载数据集 URL
Initialization --> BufferedLoading: 预加载第一张图像(预览)
BufferedLoading --> BackgroundLoading: 渐进式批量加载
BackgroundLoading --> Ready: 达到最低阈值(例如 20%)
Ready --> Playback: 用户开始播放
Playback --> Tick: 根据 FPS 更新图像
Tick --> UpdateFrame: 渲染下一帧
UpdateFrame --> Playback: 如有更多帧则循环
1. 异步缓冲加载(Buffered Loading)
播放器不会强制浏览器在允许交互之前下载全部图像,而是启动一个后台进程,以小批量异步方式下载图像。当本地已有最低比例的帧可用时即启用播放,从而避免画面卡顿。
2. 动态速度控制
用户可以完全控制播放速度,并可实时调整。播放器会即时重新计算帧之间的切换间隔(FPS),且不会中断播放。
无需关系型数据库的索引
整个系统的设计无需 SQL 或 NoSQL 数据库等复杂基础设施。
取而代之的是,Web 查看器在构建过程中直接为存放图像的静态目录结构(captures/)建立索引:
graph LR
A["构建过程"] -->|读取文件系统| B(静态目录 'captures/')
B --> C{解析层级结构}
C -->|按地点和摄像头分类| D[对摄影序列进行分组]
D --> E[生成 JSON 元数据索引]
E -.->|为 Web 视图提供数据| F(渲染与 UI 筛选)
Web 界面使用这一轻量索引,可在客户端执行快速、精确的筛选,而无需额外的网络请求或产生延迟。
面向移动设备与离线使用
Web 查看器具备多项高级功能,以扩大其适用范围:
- 优化的元数据,确保在社交网络上分享的链接包含具有代表性的图像和描述。
- 配置为 Progressive Web App (PWA),可直接安装到设备上。
- 基础离线模式,通过 Service Worker 实施缓存策略,即使在网络不稳定时也能提供连续的体验。
- 自托管字体,避免额外的加载时间或对外部服务器的依赖。
拖动可移动图示。按 + 和 − 缩放,方向键移动,0 重置视图。
一起合作
软件工程师、高级全栈开发工程师,常驻玻利维亚Santa Cruz,远程办公。欢迎新的工作机会。