尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
正在載入完整 README。 以下為已儲存的摘錄。
Historically, how good is this location for astronomy on this date?
A static web app that answers the question an astronomer actually has when
planning a night, which is not "what is the average cloud cover" but:
If I drive out here on September 20, what is the chance I get several
uninterrupted dark, clear hours?檢視完整 README ↗Historically, how good is this location for astronomy on this date?
A static web app that answers the question an astronomer actually has when planning a night, which is not "what is the average cloud cover" but:
If I drive out here on September 20, what is the chance I get several uninterrupted dark, clear hours?
Average cloud cover cannot answer that. A night alternating hourly between clear and cloudy has the same mean as a night with four unbroken clear hours, and only one of them is worth the drive. So the core statistics here are probabilities of consecutive clear hours, computed from thirty years of hourly reanalysis over the hours the Sun is actually below −18°.
Python preprocesses the history offline; the browser gets small JSON files and does the astronomy itself. There is no server.
just setup # uv sync + npm install
just build schererville-in # fetch 30 years, build climatology (several minutes)
just dev # http://localhost:5173/AstroClimate/
just on its own lists every recipe. The ones you'll use:
| Command | What it does |
|---|---|
just build <id> | Fetch history and rebuild one location |
just build-all | Rebuild every location in locations/locations.yaml |
just climatology <id> | Rebuild from the existing cache, no network |
just validate | Check built JSON — run before committing data |
just status | What is cached and what is built |
just dev | Vite dev server |
just preview | Build and serve exactly what Pages will serve |
just check | Lint, typecheck, both test suites, validate |
Requires uv, Node 22+, and
just. Every recipe is a one-line wrapper — read
the justfile if you'd rather run the underlying commands directly. CI calls the
scripts itself and does not depend on just.
Search for any town or postal code and you immediately get astronomical darkness, Moon phase, moon-free darkness and tonight's forecast — astronomy is pure computation and the forecast API is global, so neither needs preprocessing.
What a searched location does not get is the thirty-year history. That still requires a build. Two ways to close the gap:
locations.yaml, run just build <id>, and
it becomes instant for everyone (below).ERA5's cloud grid is 0.25°, which is worth knowing before you add anything: Schererville and Highland are 9 km apart and return byte-identical cloud series. If you already have a location built, anywhere within ~14 km of it has the same cloud history, and building the neighbour gains you nothing.
Append to locations/locations.yaml:
- id: holland-mi
name: Holland, Michigan
latitude: 42.7875
longitude: -86.1089
elevation_m: 187
timezone: America/Detroit
label_short: Holland
Then just build holland-mi and commit the generated
app/public/data/locations/holland-mi.json. Or edit the file on main and run
the Update Location Data action, which does the same thing and commits the
result for you.
Open-Meteo archive ERA5 + ERA5-Land, hourly, 1996–2025
│
▼ scripts/fetch_history.py one request per year
cache/<id>/<year>.parquet ~4 MB per location, gitignored
│
▼ scripts/build_climatology.py stage 1: nights
one record per observing night only hours with the Sun below −18°
│
▼ scripts/build_climatology.py stage 2: day-of-year
app/public/data/locations/<id>.json 365 records, 247 KB (33 KB gzipped)
│
▼ GitHub Pages
browser score, Moon and darkness computed here
Only the derived JSON is deployed. The raw downloads never leave your machine.
Weather climatology repeats every calendar year. The lunar cycle does not. September 20 may be historically excellent for weather and still have a nearly full Moon in 2026. So the Moon is never baked into the climatology — the app combines a year-independent weather climatology with current-year astronomy:
weather climatology + current-year astronomy = expected imaging opportunity
The same reasoning applies to the score: it is computed in the browser from the published components, never stored. That is what lets the activity selector change it instantly without regenerating any data.
These are the decisions that produce plausible-looking wrong numbers if each
script picks its own. They live in scripts/common.py and are mirrored in
app/src/climatology/types.ts.
A night is keyed by its evening. The night of Sep 20→21 is 2024-09-20, in
the location's local timezone.
One hourly sample means one clear hour. ERA5 reports cloud cover instantaneously, so three consecutive clear samples strictly span two hours of elapsed time. Each sample is treated as the one-hour block beginning at its timestamp, which makes "3 consecutive clear hours" mean what a reader expects. The convention is shown in the app's provenance panel so the number is auditable.
Day index is 1–365 against a non-leap year. Sep 20 is 263 in every year. Feb 29 folds into Feb 28 rather than being discarded, and the ±7-day window wraps the year boundary — without the wrap, January and December would quietly be built from half the samples of every other date.
Files store SI. m/s, °C, mm. The browser converts for display.
以上為 README 的開頭部分。其餘內容請閱讀原文。 檢視完整 README ↗