github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/common/astronomics/moonphase/moonphase.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2016-2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package moonphase 20 21 import ( 22 "math" 23 "time" 24 ) 25 26 // Moon ... 27 type Moon struct { 28 phase float64 29 illum float64 30 age float64 31 dist float64 32 angdia float64 33 sundist float64 34 sunangdia float64 35 pdata float64 36 quarters [8]float64 37 timespace float64 38 } 39 40 var synmonth float64 = 29.53058868 // Synodic month (new Moon to new Moon) 41 42 // New ... 43 func New(t time.Time) (moonP *Moon) { 44 moonP = new(Moon) 45 46 // Astronomical constants 47 var epoch float64 = 2444238.5 // 1989 January 0.0 48 49 //Constants defining the Sun's apparent orbit 50 var elonge float64 = 278.833540 // Ecliptic longitude of the Sun at epoch 1980.0 51 var elongp float64 = 282.596403 // Ecliptic longitude of the Sun at perigee 52 var eccent float64 = 0.016718 // Eccentricity of Earth's orbit 53 var sunsmax float64 = 1.495985e8 // Sun's angular size, degrees, at semi-major axis distance 54 var sunangsiz float64 = 0.533128 55 56 // Elements of the Moon's orbit, epoch 1980.0 57 var mmlong float64 = 64.975464 // Moon's mean longitude at the epoch 58 var mmlongp float64 = 349.383063 // Mean longitude of the perigee at the epoch 59 var mecc float64 = 0.054900 // Eccentricity of the Moon's orbit 60 var mangsiz float64 = 0.5181 // Moon's angular size at distance a from Earth 61 var msmax float64 = 384401 // Semi-major axis of Moon's orbit in km 62 63 moonP.timespace = float64(t.Unix()) 64 moonP.pdata = utcToJulian(float64(t.Unix())) 65 // Calculation of the Sun's position 66 var day = moonP.pdata - epoch // Date within epoch 67 68 var n float64 = fixangle((360 / 365.2422) * day) // Mean anomaly of the Sun 69 var m float64 = fixangle(n + elonge - elongp) // Convert from perigee co-orginates to epoch 1980.0 70 var ec = kepler(m, eccent) // Solve equation of Kepler 71 ec = math.Sqrt((1+eccent)/(1-eccent)) * math.Tan(ec/2) 72 ec = 2 * rad2deg(math.Atan(ec)) // True anomaly 73 var lambdasun float64 = fixangle(ec + elongp) // Sun's geocentric ecliptic longitude 74 75 var f float64 = ((1 + eccent*cos(deg2rad(ec))) / (1 - eccent*eccent)) // Orbital distance factor 76 var sunDist float64 = sunsmax / f // Distance to Sun in km 77 var sunAng float64 = f * sunangsiz // Sun's angular size in degrees 78 79 // Calsulation of the Moon's position 80 var ml float64 = fixangle(13.1763966*day + mmlong) // Moon's mean longitude 81 var mm float64 = fixangle(ml - 0.1114041*day - mmlongp) // Moon's mean anomaly 82 var ev float64 = 1.2739 * sin(deg2rad(2*(ml-lambdasun)-mm)) // Evection 83 var ae float64 = 0.1858 * sin(deg2rad(m)) // Annual equation 84 var a3 float64 = 0.37 * sin(deg2rad(m)) // Correction term 85 var mmP float64 = mm + ev - ae - a3 // Corrected anomaly 86 var mec float64 = 6.2886 * sin(deg2rad(mmP)) // Correction for the equation of the centre 87 var a4 float64 = 0.214 * sin(deg2rad(2*mmP)) // Another correction term 88 var lP float64 = ml + ev + mec - ae + a4 // Corrected longitude 89 var v float64 = 0.6583 * sin(deg2rad(2*(lP-lambdasun))) // Variation 90 var lPP float64 = lP + v // True longitude 91 92 // Calculation of the phase of the Moon 93 var moonAge float64 = lPP - lambdasun // Age of the Moon in degrees 94 var moonPhase float64 = (1 - cos(deg2rad(moonAge))) / 2 // Phase of the Moon 95 96 // Distance of moon from the centre of the Earth 97 var moonDist float64 = (msmax * (1 - mecc*mecc)) / (1 + mecc*cos(deg2rad(mmP+mec))) 98 99 var moonDFrac float64 = moonDist / msmax 100 var moonAng float64 = mangsiz / moonDFrac // Moon's angular diameter 101 102 // store result 103 moonP.phase = fixangle(moonAge) / 360 // Phase (0 to 1) 104 moonP.illum = moonPhase // Illuminated fraction (0 to 1) 105 moonP.age = synmonth * moonP.phase // Age of moon (days) 106 moonP.dist = moonDist // Distance (kilometres) 107 moonP.angdia = moonAng // Angular diameter (degreees) 108 moonP.sundist = sunDist // Distance to Sun (kilometres) 109 moonP.sunangdia = sunAng // Sun's angular diameter (degrees) 110 moonP.phaseHunt() 111 return moonP 112 } 113 114 func sin(a float64) float64 { 115 return math.Sin(a) 116 } 117 118 func cos(a float64) float64 { 119 return math.Cos(a) 120 } 121 122 func rad2deg(r float64) float64 { 123 return (r * 180) / math.Pi 124 } 125 126 func deg2rad(d float64) float64 { 127 return (d * math.Pi) / 180 128 } 129 130 func fixangle(a float64) float64 { 131 return (a - 360*math.Floor(a/360)) 132 } 133 134 func kepler(m float64, ecc float64) float64 { 135 epsilon := 0.000001 136 m = deg2rad(m) 137 e := m 138 var delta float64 139 delta = e - ecc*math.Sin(e) - m 140 e -= delta / (1 - ecc*math.Cos(e)) 141 for math.Abs(delta) > epsilon { 142 delta = e - ecc*math.Sin(e) - m 143 e -= delta / (1 - ecc*math.Cos(e)) 144 } 145 return e 146 } 147 148 func (m *Moon) phaseHunt() { 149 var sdate = utcToJulian(m.timespace) 150 var adate = sdate - 45 151 var ats = m.timespace - 86400*45 152 t := time.Unix(int64(ats), 0) 153 var yy = float64(t.Year()) 154 var mm = float64(t.Month()) 155 156 //var k1 = math.Floor(float64(yy+((mm-1)*(1/12))-1900) * 12.3685) 157 var k1 = math.Floor(float64(yy+((mm-1)*(1.0/12.0))-1900) * 12.3685) 158 var nt1 = meanPhase(adate, k1) 159 adate = nt1 160 var nt2, k2 float64 161 162 for { 163 adate += synmonth 164 k2 = k1 + 1 165 nt2 = meanPhase(adate, k2) 166 if math.Abs(nt2-sdate) < 0.75 { 167 nt2 = truePhase(k2, 0.0) 168 } 169 if nt1 <= sdate && nt2 > sdate { 170 break 171 } 172 nt1 = nt2 173 k1 = k2 174 } 175 176 var data [8]float64 177 178 data[0] = truePhase(k1, 0.0) 179 data[1] = truePhase(k1, 0.25) 180 data[2] = truePhase(k1, 0.5) 181 data[3] = truePhase(k1, 0.75) 182 data[4] = truePhase(k2, 0.0) 183 data[5] = truePhase(k2, 0.25) 184 data[6] = truePhase(k2, 0.5) 185 data[7] = truePhase(k2, 0.75) 186 187 for i := 0; i < 8; i++ { 188 m.quarters[i] = (data[i] - 2440587.5) * 86400 // convert to UNIX time 189 } 190 } 191 192 func utcToJulian(t float64) float64 { 193 return t/86400 + 2440587.5 194 } 195 196 //func julianToUtc(t float64) float64 { 197 // return t*86400 + 2440587.5 198 //} 199 200 /* 201 * 202 203 Calculates time of the mean new Moon for a given 204 base date. This argument K to this function is the 205 precomputed synodic month index, given by: 206 K = (year - 1900) * 12.3685 207 where year is expressed as a year aand fractional year 208 */ 209 func meanPhase(sdate float64, k float64) float64 { 210 // Time in Julian centuries from 1900 January 0.5 211 var t float64 = (sdate - 2415020.0) / 36525 212 var t2 float64 = t * t 213 var t3 float64 = t2 * t 214 215 nt := 2415020.75933 + synmonth*k + 216 0.0001178*t2 - 217 0.000000155*t3 + 218 0.00033*sin(deg2rad(166.56+132.87*t-0.009173*t2)) 219 220 return nt 221 } 222 223 func truePhase(k float64, phase float64) float64 { 224 k += phase // AddEntity phase to new moon time 225 var t float64 = k / 1236.85 // Time in Julian centures from 1900 January 0.5 226 var t2 float64 = t * t 227 var t3 float64 = t2 * t 228 var pt float64 229 pt = 2415020.75933 + synmonth*k + 230 0.0001178*t2 - 231 0.000000155*t3 + 232 0.00033*sin(deg2rad(166.56+132.87*t-0.009173*t2)) 233 234 var m, mprime, f float64 235 m = 359.2242 + 29.10535608*k - 0.0000333*t2 - 0.00000347*t3 // Sun's mean anomaly 236 mprime = 306.0253 + 385.81691806*k + 0.0107306*t2 + 0.00001236*t3 // Moon's mean anomaly 237 f = 21.2964 + 390.67050646*k - 0.0016528*t2 - 0.00000239*t3 // Moon's argument of latitude 238 239 if phase < 0.01 || math.Abs(phase-0.5) < 0.01 { 240 // Corrections for New and Full Moon 241 pt += (0.1734-0.000393*t)*sin(deg2rad(m)) + 242 0.0021*sin(deg2rad(2*m)) - 243 0.4068*sin(deg2rad(mprime)) + 244 0.0161*sin(deg2rad(2*mprime)) - 245 0.0004*sin(deg2rad(3*mprime)) + 246 0.0104*sin(deg2rad(2*f)) - 247 0.0051*sin(deg2rad(m+mprime)) - 248 0.0074*sin(deg2rad(m-mprime)) + 249 0.0004*sin(deg2rad(2*f+m)) - 250 0.0004*sin(deg2rad(2*f-m)) - 251 0.0006*sin(deg2rad(2*f+mprime)) + 252 0.0010*sin(deg2rad(2*f-mprime)) + 253 0.0005*sin(deg2rad(m+2*mprime)) 254 } else if math.Abs(phase-0.25) < 0.01 || math.Abs(phase-0.75) < 0.01 { 255 pt += (0.1721-0.0004*t)*sin(deg2rad(m)) + 256 0.0021*sin(deg2rad(2*m)) - 257 0.6280*sin(deg2rad(mprime)) + 258 0.0089*sin(deg2rad(2*mprime)) - 259 0.0004*sin(deg2rad(3*mprime)) + 260 0.0079*sin(deg2rad(2*f)) - 261 0.0119*sin(deg2rad(m+mprime)) - 262 0.0047*sin(deg2rad(m-mprime)) + 263 0.0003*sin(deg2rad(2*f+m)) - 264 0.0004*sin(deg2rad(2*f-m)) - 265 0.0006*sin(deg2rad(2*f+mprime)) + 266 0.0021*sin(deg2rad(2*f-mprime)) + 267 0.0003*sin(deg2rad(m+2*mprime)) + 268 0.0004*sin(deg2rad(m-2*mprime)) - 269 0.0003*sin(deg2rad(2*m+mprime)) 270 if phase < 0.5 { // First quarter correction 271 pt += 0.0028 - 0.0004*cos(deg2rad(m)) + 0.0003*cos(deg2rad(mprime)) 272 } else { // Last quarter correction 273 pt += -0.0028 + 0.0004*cos(deg2rad(m)) - 0.0003*cos(deg2rad(mprime)) 274 } 275 } 276 277 return pt 278 } 279 280 // Phase ... 281 func (m *Moon) Phase() float64 { 282 return m.phase 283 } 284 285 // Illumination ... 286 func (m *Moon) Illumination() float64 { 287 return m.illum 288 } 289 290 // Age ... 291 func (m *Moon) Age() float64 { 292 return m.age 293 } 294 295 // Distance ... 296 func (m *Moon) Distance() float64 { 297 return m.dist 298 } 299 300 // Diameter ... 301 func (m *Moon) Diameter() float64 { 302 return m.angdia 303 } 304 305 // SunDistance ... 306 func (m *Moon) SunDistance() float64 { 307 return m.sundist 308 } 309 310 // SunDiameter ... 311 func (m *Moon) SunDiameter() float64 { 312 return m.sunangdia 313 } 314 315 // NewMoon ... 316 func (m *Moon) NewMoon() float64 { 317 return m.quarters[0] 318 } 319 320 // FirstQuarter ... 321 func (m *Moon) FirstQuarter() float64 { 322 return m.quarters[1] 323 } 324 325 // FullMoon ... 326 func (m *Moon) FullMoon() float64 { 327 return m.quarters[2] 328 } 329 330 // LastQuarter ... 331 func (m *Moon) LastQuarter() float64 { 332 return m.quarters[3] 333 } 334 335 // NextNewMoon ... 336 func (m *Moon) NextNewMoon() float64 { 337 return m.quarters[4] 338 } 339 340 // NextFirstQuarter ... 341 func (m *Moon) NextFirstQuarter() float64 { 342 return m.quarters[1] 343 } 344 345 // NextFullMoon ... 346 func (m *Moon) NextFullMoon() float64 { 347 return m.quarters[6] 348 } 349 350 // NextLastQuarter ... 351 func (m *Moon) NextLastQuarter() float64 { 352 return m.quarters[7] 353 } 354 355 // PhaseName ... 356 func (m *Moon) PhaseName() string { 357 names := map[int]string{ 358 0: "New Moon", 359 1: "Waxing Crescent", 360 2: "First Quarter", 361 3: "Waxing Gibbous", 362 4: "Full Moon", 363 5: "Waning Gibbous", 364 6: "Third Quarter", 365 7: "Waning Crescent", 366 8: "New Moon", 367 } 368 369 i := int(math.Floor((m.phase + 0.0625) * 8)) 370 return names[i] 371 }