Files
decePubClient/wwwroot/js/sun.js
T
thepraandClaude Opus 5.5 0f68e74e38
Build / Build (push) Successful in 46s
Deploy / decepub.thepra.dev (push) Successful in 1m52s
The globe under the Sun of now or of a post, with borders and country names
The Sun lights the globe where it really is (wwwroot/js/sun.js, the Astronomical Almanac's low-precision solar
coordinates): the day map shaded by the Sun's height and the Earth–Sun distance, warm city lights on the night side,
and a twilight blend about 13° wide between them. It shows now, refreshed every 30 s, or the moment of the hovered,
focused or pinned post: a click on a card pins it, and the subsolar point travels there the shorter way round. The
panel's top corner shows that moment, the phase of the day and solar time at the post's place, and the season in its
hemisphere (or in both, for a post without a place). Its tooltip shows the subsolar point, declination, axial tilt,
distance, irradiance, day length and the next equinox or solstice.

Land borders are thin paths. Country names are in the reader's language and appear as the camera comes closer:
largest countries first, away from the globe's edge, never overlapping. They come from world-atlas 2.0.2 (Natural
Earth 1:50m), prepared by tools/globe/prepare-countries.py, and are vendored with their hashes in SOURCES.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
2026-10-04 20:23:42 +02:00

84 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Where the Sun is, for the globe's day and night (wwwroot/js/globe.js): the Astronomical Almanac's low-precision solar
// coordinates, good to about 0.01° between 1950 and 2050. Everything here is a pure function of a date.
const radians = Math.PI / 180
const J2000 = Date.UTC(2000, 0, 1, 12)//2000-01-01 12:00 UTC
// The subsolar point (where the Sun is overhead), the Earth–Sun distance and the angles that place the Sun in the year.
export function sunAt(date) {
const days = (date.getTime() - J2000) / 86400000
const meanLongitude = 280.460 + 0.9856474 * days
const meanAnomaly = (357.528 + 0.9856003 * days) * radians
const eclipticLongitude = degrees(meanLongitude + 1.915 * Math.sin(meanAnomaly) + 0.020 * Math.sin(2 * meanAnomaly))
const obliquity = 23.439 - 0.0000004 * days
const declination = Math.asin(Math.sin(obliquity * radians) * Math.sin(eclipticLongitude * radians)) / radians
const rightAscension = Math.atan2(Math.cos(obliquity * radians) * Math.sin(eclipticLongitude * radians), Math.cos(eclipticLongitude * radians)) / radians
const siderealTime = 280.46061837 + 360.98564736629 * days
const distance = 1.00014 - 0.01671 * Math.cos(meanAnomaly) - 0.00014 * Math.cos(2 * meanAnomaly)
return {
date,
lat: declination,
lng: longitude(rightAscension - siderealTime),
distance,
irradiance: 1 / (distance * distance),
eclipticLongitude,
obliquity
}
}
// What the Sun does at a place: its elevation, the phase of the day, the local solar time, the day's length, the season
// in that hemisphere and the next equinox or solstice.
export function sunAtPlace(sun, lat, lng) {
const hourAngle = longitude(lng - sun.lng)
const elevation = Math.asin(
Math.sin(lat * radians) * Math.sin(sun.lat * radians) + Math.cos(lat * radians) * Math.cos(sun.lat * radians) * Math.cos(hourAngle * radians)) / radians
const solarHours = (((12 + hourAngle / 15) % 24) + 24) % 24
// sunrise and sunset as almanacs give them: the Sun's upper edge on the horizon, refraction included (−0.833°)
const sunset = (Math.sin(-0.833 * radians) - Math.sin(lat * radians) * Math.sin(sun.lat * radians)) /
(Math.cos(lat * radians) * Math.cos(sun.lat * radians))
const dayLength = sunset <= -1 ? 24 : sunset >= 1 ? 0 : 2 * Math.acos(sunset) / radians / 15
const north = lat >= 0
const seasons = seasonsOf(sun)
return {
elevation,
phase: elevation > 6 ? "day" : elevation > 0 ? "golden" : elevation > -6 ? "civil" : elevation > -12 ? "nautical" : elevation > -18 ? "astronomical" : "night",
morning: hourAngle < 0,
solarTime: `${String(Math.floor(solarHours)).padStart(2, "0")}:${String(Math.floor(solarHours % 1 * 60)).padStart(2, "0")}`,
dayLength,
hemisphere: north ? "north" : "south",
season: north ? seasons.north : seasons.south,
nextEvent: seasons.nextEvent,
nextEventDays: seasons.nextEventDays
}
}
// The astronomical season in each hemisphere (from the equinoxes and solstices, by the Sun's ecliptic longitude) and the
// next equinox or solstice, in days at the Sun's mean pace.
export function seasonsOf(sun) {
const quarter = Math.floor(sun.eclipticLongitude / 90) % 4
const nextBoundary = (quarter + 1) * 90
return {
north: ["spring", "summer", "autumn", "winter"][quarter],
south: ["autumn", "winter", "spring", "summer"][quarter],
nextEvent: { 90: "june-solstice", 180: "september-equinox", 270: "december-solstice", 360: "march-equinox" }[nextBoundary],
nextEventDays: Math.round((nextBoundary - sun.eclipticLongitude) / 0.9856)
}
}
// The Sun's direction in the frame of three.js's SphereGeometry, the frame the globe's shader draws in: texture u is
// 0 at longitude −180°, and a point is (−cos 2πu · cos lat, sin lat, sin 2πu · cos lat).
export function directionOf(sun) {
const u = (sun.lng + 180) / 360 * 2 * Math.PI
const lat = sun.lat * radians
return [-Math.cos(u) * Math.cos(lat), Math.sin(lat), Math.sin(u) * Math.cos(lat)]
}
function degrees(value) {
return ((value % 360) + 360) % 360
}
function longitude(value) {
const wrapped = degrees(value + 180) - 180
return wrapped === -180 ? 180 : wrapped
}