83 lines
4.0 KiB
JavaScript
83 lines
4.0 KiB
JavaScript
// 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
|
|||
|
|
}
|