diff --git a/tools/globe/check.mjs b/tools/globe/check.mjs new file mode 100644 index 0000000..f2508f5 --- /dev/null +++ b/tools/globe/check.mjs @@ -0,0 +1,45 @@ +// Checks of the globe's pure functions against almanac values, dev only (Node's own test runner, nothing installed): +// node --test tools/globe/check.mjs +// sun.js is the Astronomical Almanac's low-precision formulas, good to about 0.01° between 1950 and 2050. +import { test } from "node:test" +import assert from "node:assert/strict" +import { analemma, seasonsOf, sunAt, sunAtPlace } from "../../wwwroot/js/sun.js" + +const near = (actual, expected, tolerance, what) => + assert.ok(Math.abs(actual - expected) <= tolerance, `${what}: ${actual} is not within ${tolerance} of ${expected}`) + +test("the June solstice of 2026 puts the Sun over the Tropic of Cancer", () => { + near(sunAt(new Date("2026-06-21T08:24:00Z")).lat, 23.435, 0.01, "declination") +}) + +test("the March equinox of 2026 puts the Sun over the equator", () => { + near(sunAt(new Date("2026-03-20T14:46:00Z")).lat, 0, 0.02, "declination") +}) + +test("perihelion on 2026-01-03 is the year's nearest Sun", () => { + near(sunAt(new Date("2026-01-03T17:15:00Z")).distance, 0.98329, 0.0002, "distance in AU") +}) + +test("Turin's longest day is about 15 h 38 min", () => { + near(sunAtPlace(sunAt(new Date("2026-06-21T12:00:00Z")), 45.07, 7.69).dayLength, 15.63, 0.05, "day length in hours") +}) + +test("early October is autumn in the north, and the December solstice comes next", () => { + const seasons = seasonsOf(sunAt(new Date("2026-10-06T12:00:00Z"))) + assert.equal(seasons.north, "autumn") + assert.equal(seasons.south, "spring") + assert.equal(seasons.nextEvent, "december-solstice") +}) + +test("the analemma spans the tropics and the equation of time", () => { + const start = new Date("2026-10-06T12:00:00Z") + const points = analemma(start) + assert.equal(points.length, 366) + assert.equal(points[0].lat, sunAt(start).lat) + near(Math.max(...points.map(p => p.lat)), 23.44, 0.05, "northernmost") + near(Math.min(...points.map(p => p.lat)), -23.44, 0.05, "southernmost") + // at 12:00 UTC the Sun is overhead near the prime meridian, early or late by the equation of time (about ±16 min, ±4°) + const longitudes = points.map(p => p.lng) + near(Math.max(...longitudes), 3.7, 0.4, "easternmost") + near(Math.min(...longitudes), -4.1, 0.4, "westernmost") +}) diff --git a/wwwroot/js/sun.js b/wwwroot/js/sun.js index e854e58..b7d6bec 100644 --- a/wwwroot/js/sun.js +++ b/wwwroot/js/sun.js @@ -65,6 +65,15 @@ export function seasonsOf(sun) { } } +// Where the Sun is overhead at this same UTC time on each of the coming days (the first is the date itself): traced on +// the globe, the analemma, its figure-8 the year's declination and the equation of time. +export function analemma(date, days = 366) { + const points = [] + for (let day = 0; day < days; day++) + points.push(sunAt(new Date(date.getTime() + day * 86400000))) + return points +} + // 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) {