Files
decePubClient/wwwroot/js/sun.js
T

92 lines
4.4 KiB
JavaScript
Raw Normal View History

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