Stargazing Viewing Conditions (Clear Sky Chart)

Site cleardarksky.comTask stargazing-seeing-condition-5hqn4aVersion v1Updated Jul 13, 2026Category astronomy

Given a location, return the best stargazing time slots for tonight and the next couple of nights from its ClearDarkSky Clear Sky Chart, plus warnings (clouds, moon, poor seeing/transparency, wind, smoke, dew). Read-only. This skill was captured from a live agent session on cleardarksky.com and publishes here verbatim, exactly as an agent receives it.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Purpose

Given a location, determine the best stargazing time slots for tonight and the next couple of nights from cleardarksky.com's "Clear Sky Chart" (the Allan Rahill / Canadian Meteorological Centre astronomy forecast), and surface what to watch out for (clouds, bright moon, poor transparency/seeing, wind, smoke, dew). The chart's famous colored grid is also fully encoded as text inside the page's image-map <area title="..."> attributes — so the entire task is solvable with a single HTTP fetch of the chart page and a parse, with no image/vision reading, no browser, no auth, and no proxy. Read-only.

When to Use

  • "Is tonight good for stargazing near {place}? When is it best?"
  • Planning an observing / astrophotography session over the next 2–3 nights.
  • Deciding go / no-go for a star party, and flagging dew, moonlight, or smoke risk.
  • Any flow that needs hourly cloud / transparency / seeing / darkness / moon data for an observing site in North America.

Workflow

The recommended method is a single cheap page read — no OCR. The colored GIF is decorative; every hour of the forecast is duplicated as readable text in the page's HTML image-map <area title> attributes. Point browserless_agent at the chart URL (goto, waitUntil: "load") and pull the cells with one evaluate: [...document.querySelectorAll('area')].map(a => ({ title: a.title, href: a.getAttribute('href') })) — return that compact array, not the full page HTML. (Under restricted egress the same works from browserless_function: page.goto(chartUrl) then the same page.evaluate.)

1. Resolve the chart key for the location

Each chart has a short key (e.g. SpcrObAZ, ChrSprPkPA, Ottawa). Find it with the find_chart.py CGI (returns HTML; links point to ../c/<KEY>key.html):

  • By name (most common):
    GET https://www.cleardarksky.com/cgi-bin/find_chart.py?type=text&keys=<URL-ENCODED NAME>&Mn=dobsonian&doit=Find
    Parse result anchors matching href=../c/([A-Za-z0-9]+)key\.html>\s*<name>. There are ~6,300 charts (observatories, parks, towns) across North America; an exact city may map to a nearby named observing site — pick the closest/best match and note it.
  • By coordinates (when no named site matches): ?type=llmap&olat=<lat>&olong=<lon>&unit=1 (decimal degrees) returns nearby charts.
  • By request IP: ?type=geoLocate.

The Mn= param is cosmetic. A trailing ?1 on ...key.html links is just a cache-buster.

2. Fetch the chart page

GET https://www.cleardarksky.com/c/<KEY>key.html

Returns 200, plain HTML, no cookies/auth/anti-bot. Extract three things from the HTML:

  • tz offset → from the tz=-X.X token in the "Sun & Moon Data" link.
  • Model run date (YYYYMMDD, run at 12:00 UTC) → from any f.php?p=YYYYMMDD… href.
  • Last updated YYYY-MM-DD HH:MM:SS (local) → freshness check.

3. Parse the image-map cells

Each <area> carries the forecast value in its title and (for the weather rows) a code in its href.

  • Hourly weather rows — one cell per hour, e.g. <area title="21:00: Clear (12Z+9hr)" coords="..." href="../f.php?p=202606173C00912SpcrObAZ">. The letter after the date+server-digit in p= identifies the quantity:
    coderowexample values
    CCloud CoverClear, 10%90% covered, Overcast
    TTransparencyTransparent, Above Average, Average, Below Average, Poor, Too cloudy to forecast
    SSeeingExcellent 5/5Bad 1/5, Too cloudy to forecast
    WSmokeNo Smoke, …µg/m³
    DWindspeed range (e.g. 9 to 18 km/hr)
    HHumidity% range
    RTemperaturetemp range
  • Darkness / Moon row — finer 10-minute cells, NO f.php href: <area title="22:00 Limiting Mag:5.9, SunAlt: -24.4&deg;, MoonAlt 3.3&deg;, MoonIllum 13%" coords="...">. This gives, per 10 min: limiting magnitude (sky darkness), Sun altitude, Moon altitude, Moon illumination %.

4. Build the timeline and align rows

  • Absolute UTC of an hourly cell = (model-run date at 12:00 UTC) + Nhr (the (12Z+Nhr) age). Local time = UTC + tz. The displayed H:MM is already local to the site — exactly what the stargazer wants.
  • Align the rows by their column x-coordinate (coords="x,…"), NOT by forecast age. Different rows use slightly different age bases (e.g. the Seeing row starts at 12Z+3hr while Cloud starts at 12Z+4hr). Column x is the single source of truth that the same time column lines up across all rows.
  • For each hourly column, attach the Sun/Moon/limiting-mag from the darkness cell with the nearest x.

5. Score and group "best" slots, per night

A good observing hour = dark (SunAlt below ≈ −12°; astronomical-dark is below −18°) AND Cloud Clear/10–20% covered AND Transparency Average or better AND Seeing Average 3/5 or better. Bonus when the Moon is below the horizon (MoonAlt < 0) or illumination is low. Group consecutive good hours into local-time ranges and bucket them by the night they belong to (hours after local noon, plus the early-morning hours of the next date, form one "night"). The chart spans ≈81 hourly cells (~3.4 days) → it always covers tonight + the next 2–3 nights.

6. Emit warnings

Flag: bright Moon up during dark hours (high MoonIllum with MoonAlt > 0); Seeing dips to Poor/Bad; Transparency Below Average/Poor; high Wind; any Smoke; very high Humidity (dew/frost risk on optics).

Snapshot alternative

If reading the <area> nodes via evaluate ever misses cells, a snapshot of the chart page also works: the <area title> cells surface as StaticText nodes in the accessibility tree, so the same parse applies. Do not try to read the colored GIF visually — the data lives in the image-map titles, which evaluate/snapshot expose but a plain text of body does not.

Site-Specific Gotchas

  • The data is in image-map <area title> text, not only the color GIF. This is the whole trick — never OCR/vision the chart image; parse the titles.
  • Align rows by column x-coordinate, never by forecast age. The Seeing row's age base is offset by one hour vs. the other rows (12Z+3hr vs 12Z+4hr); age-keying silently misaligns Seeing (and potentially others) by a column.
  • Hourly title format is H:MM: VALUE (12Z+Nhr) — there are TWO colons. When capturing the value, skip past the H:MM: time prefix or you'll capture 00: Clear instead of Clear.
  • The server digit before the quantity code varies. In p=... the code is preceded by a server id that is 1 for some charts and 3 for others (…1C004… vs …3C004…). Match \d{8}\d([A-Z])\d{5}, do not hardcode 1.
  • The Darkness/Moon row has no f.php href and its y-coordinate differs between charts. Identify it purely by the Limiting Mag:…, SunAlt:…, MoonAlt …, MoonIllum …% title pattern, and x-align it to the hourly columns. It is at 10-minute resolution while the weather rows are hourly.
  • Times are LOCAL to the observing site, derived from the tz=-X.X token; the model run is 12:00 UTC on the date in the f.php?p= param. Don't assume the first cell is 12:00 local — for a UTC−7 site the first cell is 09:00.
  • Wind/Temperature units depend on the chart's units setting (imperial vs metric → mph/°F vs km/hr/°C). The page declares it in a units = "…" JS var; read the actual title text rather than assuming a unit.
  • The Smoke row often has fewer cells (shorter forecast horizon) than the other rows — expect gaps; treat missing smoke as "No Smoke / unknown".
  • No free machine-readable live feed. The site sells CSV archives, but those are historical only (the homepage samples run 2016–2020) and behind a fee. There is no public live JSON/CSV API — the chart page's image-map text is the live data source.
  • No anti-bot, no auth, no proxy. Direct fetches of both find_chart.py and /c/<KEY>key.html return 200. The pages carry NOARCHIVE/no-cache meta and Google Analytics, but content is fully open. The pre-run probe showed no anti-bot, and the successful run used a bare browserless_agent session (no proxy, no extra stealth).
  • Darkness scale reference: SunAlt < −18° = astronomical night (darkest); −12° to −18° = nautical twilight; Limiting Mag higher = darker sky (≈6+ excellent, negative = daylight); MoonAlt > 0 = moon above horizon washing out faint objects; MoonIllum is the lunar phase (% illuminated).
  • Forecast horizon ≈ 81 hours (~3.4 days) → reliably covers "tonight + the next couple of nights." Forecast skill degrades with age; trust the first night most.

Expected Output

{
  "success": true,
  "location": "Spencer's Observatory",
  "chart_key": "SpcrObAZ",
  "tz": "UTC-7",
  "model_run_utc": "2026-06-17T12:00:00Z",
  "last_updated_local": "2026-06-17 09:58:54",
  "nights": [
    {
      "night": "2026-06-17",
      "best_slots": ["22:00-04:00"],
      "conditions": {
        "cloud": "Clear",
        "transparency": "Transparent",
        "seeing": "Average 3/5",
        "limiting_mag": 5.9
      },
      "moon_illum_pct": 16,
      "moon_up_during_dark": true,
      "warnings": ["High humidity 80-90% — dew risk on optics"]
    },
    {
      "night": "2026-06-18",
      "best_slots": ["21:00-04:00"],
      "conditions": {
        "cloud": "Clear",
        "transparency": "Transparent",
        "seeing": "Average 3/5 (Good 4/5 by 04:00)"
      },
      "moon_illum_pct": 25,
      "moon_up_during_dark": true,
      "warnings": ["Seeing dips to Poor 2/5 01:00-03:00"]
    },
    {
      "night": "2026-06-19",
      "best_slots": ["21:00-04:00"],
      "conditions": {
        "cloud": "Clear",
        "transparency": "Transparent",
        "seeing": "Good 4/5",
        "limiting_mag": 5.5
      },
      "moon_illum_pct": 35,
      "moon_up_during_dark": true,
      "warnings": []
    }
  ],
  "best_overall_night": "2026-06-19",
  "error_reasoning": null
}

Outcome shapes:

  • Good viewing — one or more best_slots per night (local-time ranges), as above.
  • No good window (clouds/poor conditions during dark hours) — chart parsed fine, but a night has clear conditions failing the dark-hour test:
    {
      "success": true,
      "location": "Cherry Springs State Park",
      "chart_key": "ChrSprPkPA",
      "nights": [
        {
          "night": "2026-06-17",
          "best_slots": [],
          "moon_illum_pct": 15,
          "warnings": [
            "Overcast / poor transparency most of the night",
            "High humidity — dew risk"
          ]
        }
      ],
      "error_reasoning": null
    }
  • No chart found for the location:
    {
      "success": false,
      "location": "<input>",
      "chart_key": null,
      "error_reasoning": "No Clear Sky Chart matched; try find_chart.py llmap by lat/long for the nearest site."
    }

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=cleardarksky.com&task=stargazing-seeing-condition-5hqn4a