docs
darkmap.tinyland.dev — a tailnet-only planning surface for instrumentation, sensor, and astronomy field work.
What this tool is for
darkmap is a planning + readout surface for night-dependent measurement work — instrumentation and sensor calibration, ground-based astronomy and astrophotography, satellite-overhead windows, temperature- and night-dependent radar campaigns, and dark-sky-site logistics.
The original problem statement was "lightpollutionmap.info but without the ads and the latency." We solved that and kept going. The current surface is squarely tuned for our needs and the needs of the dark-sky / spectroscopy community on this tailnet.
Feature surface
- VIIRS DNB radiance — annual composites 2012-2019 + monthly composites Apr 2012 → Apr 2026 (169 months) with a play / scrub time slider
- Falchi 2016 World Atlas — both the styled visualization and the raw mcd/m² grid (with Bortle / class mapping in the point-query readout)
- Per-view ephemeris — sun + moon position, twilight gantt (astro / nautical / civil), sky compass with sun trajectory arc + moon position + atmospheric airmass
- Real-terrain horizon — 36-ray, 10-distance raycast over AWS Mapzen Terrarium tiles; sun / moon altitude is reported relative to the local horizon polygon at its azimuth, and event times can be refined to true-horizon crossings
- Per-viewport range pill — 4×4 grid sampling across the visible viewport so e.g. civil dusk "Δ 26 min" surfaces the spread when you're looking at a state-sized region
- Geocoder — search for a place (typo-tolerant), or paste coords as decimal / DMS / DMM
- Shareable URLs — the hash captures view + active layers + basemap + ephemeris cursor + monthly slider position + autoplay flag
Sources, attribution, inspiration
darkmap was originally inspired by Jurij Stare's lightpollutionmap.info — the canonical web map for the underlying VIIRS / Falchi datasets. We have since broadened scope significantly (per-view ephemeris, real-terrain horizon raycasting, instrumented sensor and astrophotography workflows) and now stand on our own. We acknowledge the original idea + the upstream GeoServer that hosts publicly available scientific data:
- NASA VIIRS DNB
- Suomi-NPP Visible Infrared Imaging Radiometer Suite Day/Night Band. Composites by NOAA NCEI + the Earth Observation Group.
- Falchi 2016 World Atlas
- Falchi et al., Sci. Adv. 2 (2016) — "The new world atlas of artificial night sky brightness". mcd/m² radiance grid + classification.
- lightpollutionmap.info GeoServer
- Public WMS at
www2.lightpollutionmap.info/geoserver. We proxy through/api/rasterto strip Prebid + ad-tracking headers and inject sane caching. - AWS Mapzen Terrarium
- Free, global, RGB-encoded elevation tiles at
s3://elevation-tiles-prod/terrarium/— the input to the horizon-polygon raycaster. - Photon (komoot)
- Typo-tolerant OSM-backed geocoder; data © OpenStreetMap contributors, ODbL. Proxied through
/api/geocode. - astronomy-engine
- cosinekitty's high-precision VSOP87 + lunar ephemeris, validated against USNO MICA to ~1 arcsec. The sun / moon / twilight math is theirs.
- Carto Dark Matter / ESRI / OpenStreetMap
- The three basemap options.
Scientific notes
VIIRS units
VIIRS DNB radiance is reported in nW · cm⁻² · sr⁻¹ on the upstream rasters. The styled composites apply a NOAA color ramp; the raw GRAY_INDEX values are what feed our Bortle-class mapping in the point-readout panel.
Falchi class boundaries
Falchi's six-class scheme — Pristine / Wilderness / Rural / Suburban / Urban / Inner City — maps onto mcd/m² thresholds. We surface the class label in the readout next to the raw radiance.
Airmass
The X chip in the sky compass uses Kasten & Young (1989) — better than plane-parallel sec(z) below ~30° altitude where atmospheric curvature dominates.
Horizon raycaster
Default 36 rays × 10 distance samples (250 m → 25 km). Each sample uses a refraction-adjusted earth-curvature drop (R_eff = 7/6 · R) when computing angular elevation. Polygon results are cached per (lat, lon) rounded to ~0.001° + options key; revisit is instant.
Tech stack
- SvelteKit (adapter-node), Svelte 5 runes, Skeleton 4.15.2, Tailwind v4
- MapLibre GL JS 5 for the map surface
- Effect.ts service layers — RasterClient, EphemerisClient, HorizonProvider, GeocoderClient
- astronomy-engine (cosinekitty) for sun / moon math
- Bazel 8 + Bzlmod via tinyland-inc/bazel-registry, with the MassageIthaca cache-attachment-contract pattern for RBE
- OpenTofu, kustomize, Tailscale operator on the blahaj RKE2 cluster. State in rustfs S3. Tailnet-only via CIDR whitelist on nginx ingress.
See the repo for AGENTS.md, README.md, and the full deploy runbook.