// 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 }