天天看點

計算太陽高度角(javascript實作)

1 /******************************************/
  2 /************** 相關轉換工具 ***************/
  3 /******************************************/
  4 /**
  5  * 
  6  * @param {Number} hour 
  7  * @param {Number} mim 
  8  * @param {Number} sec 
  9  * @param {Boolean} pm 
 10  * @param {Boolean} dst 
 11  * @returns 傳回目前時間過了多少分鐘(0點開始計時)
 12  */
 13 function getLocalTime(hour, mim, sec, pm=false, dst=false) {
 14     if (pm && hour < 12) dochr += 12;
 15     if (dst) dochr -= 1
 16     return hour * 60 + mim + sec / 60.0
 17 }
 18 
 19 // 判斷是否是閏年
 20 function isLeapYear(year) {
 21     return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
 22 }
 23 
 24 // 弧度制轉角度制
 25 function radToDeg(angleRad) {
 26     return 180.0 * angleRad / Math.PI;
 27 }
 28 
 29 // 角度制轉弧度制
 30 function degToRad(angleDeg) {
 31     return Math.PI * angleDeg / 180.0;
 32 }
 33 
 34 // 根據<年,月,日>擷取儒略日
 35 function getJulianDay(year, month, day) {
 36     var A = Math.floor(year / 100)
 37     var B = 2 - A + Math.floor(A / 4)
 38     var JD = Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5
 39     return JD
 40 }
 41 
 42 /**
 43  * @param {Number} JulianDay => 儒略日
 44  * 儒略日的起始為公元前 4713 年 1 月 1 日中午 12 點
 45  * 每過一“平太陽日”加一 => 即通常意義上的“一天”
 46  * 地球在軌道上做的是不等速運動,一年之内真太陽日的長度不斷改變,于是引進平太陽的概念
 47  * 一年中<真太陽日>的平均即為<平太陽日>
 48  * 2000 年 1 月 1 日的 UT12:00 是儒略日 2451545
 49  * @returns 儒略日至 2000 年始經過的世紀數(一儒略年有 365.25 天)
 50  */
 51 function calcTimeJulianCent(JulianDay) {
 52     return (JulianDay - 2451545.0) / 36525.0;
 53 }
 54 
 55 // 将 d 控制在 0~Range 内
 56 function correctRange(value, Range) {
 57     value = value % Range;
 58     if (value < 0) {
 59         value += Range;
 60     }
 61     return value;
 62 }
 63 
 64 /**
 65  * 将時間(以分鐘為機關)格式化
 66  * @param {Number} minutes 
 67  * @param {Number} flag
 68  * flag = 1 格式化成 HH
 69  * flag = 2 格式化成 HH:MM
 70  * flag = 3 格式化成 HH:MM:SS
 71  */
 72  function timeString(minutes, flag) {
 73     if (minutes >= 0 && minutes < 1440) {
 74         var floatHour = minutes / 60.0;
 75         var hour = Math.floor(floatHour);
 76 
 77         var floatMinute = 60.0 * (floatHour - Math.floor(floatHour));
 78         var minute = Math.floor(floatMinute);
 79 
 80         var floatSec = 60.0 * (floatMinute - Math.floor(floatMinute));
 81         var second = Math.floor(floatSec + 0.5); // 大于 59.5s 就向上取整
 82 
 83         if (second > 59) { // second>59 隻會是 60s
 84             second = 0;
 85             minute += 1;
 86         }
 87         // 需要格式化成 HH:MM 時
 88         if (flag == 2 && second >= 30) minute++; // 當超過 30s 時分鐘數進 1
 89         if (minute > 59) {
 90             minute = 0;
 91             hour += 1;
 92         }
 93         var output = zeroPad(hour, 2) + ':' + zeroPad(minute, 2);
 94         if (flag > 2) output = output + ':' + zeroPad(second, 2);
 95     } else {
 96         var output = 'error'
 97     }
 98     return output;
 99 }
100 
101 // 不足補 0 
102 function zeroPad(n, digits) {
103     n = String(n);
104     while (n.length < digits) {
105         n = '0' + n;
106     }
107     return n;
108 }
109 
110 /******************************************/
111 /********** 太陽坐标計算開始 ***************/
112 /******************************************/
113 
114 /***
115  * @param {Number} t => Julian century(經過的世紀數)
116  * 計算太陽的平黃經(geometric mean longitude [L0])
117  * 黃經:天球黃道坐标系中的經度 => 用來确定天體在天球上的位置
118  */
119 function calcGeomMeanLongSun(t) {
120     let L0 = 280.46646 + 36000.76983 * t + 0.0003032 * Math.pow(t, 2);
121     return correctRange(L0, 360);
122 }
123 
124 /**
125  * 計算太陽的平近點角(mean anomaly [M])
126  * 平近點角:軌道上的物體在輔助圓上相對于中心點的運作角度
127  * 參考:https://en.wikipedia.org/wiki/Mean_anomaly
128  */
129 function calcGeomMeanAnomalySun(t) {
130     let M = 357.52911 + 35999.05029 * t - 0.0001537 * Math.pow(t, 2);
131     return correctRange(M, 360);
132 }
133 
134 /**
135  * 計算地球軌道偏心率(eccentricity [e])
136  * 數學上稱為“離心率”
137  * 對于橢圓 => 兩焦點間的距離(2c)和長軸長度(2a)的比值,即 e=c/a
138  */
139 function calcEccentricityEarthOrbit(t) {
140     let e = 0.016708634 - 0.000042037 * t + 0.0000001267 * Math.pow(t, 2);
141     return e;
142 }
143 
144 /**
145  * 圓心方程(Equation of the Center)
146  * 計算角差(橢圓軌道上實際位置與它在同一周期的圓形軌道上勻速運動時所占位置之間的角差)
147  * 參考 https://en.wikipedia.org/wiki/Equation_of_the_center
148  */
149 function calcSunEqOfCenter(t) {
150     var M = calcGeomMeanAnomalySun(t); // 太陽平近點角
151     var mrad = degToRad(M);
152     var sinm = Math.sin(mrad);
153     var sin2m = Math.sin(2 * mrad);
154     var sin3m = Math.sin(3 * mrad);
155     var C = sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289;
156     return C; // 機關為度
157 }
158 
159 // 計算太陽真實經度(True Longitude)
160 function calcSunTrueLong(t) {
161     var l0 = calcGeomMeanLongSun(t); // 太陽平黃經
162     var C = calcSunEqOfCenter(t); // 中心方程
163     var Ltrue = correctRange(l0 + C, 360);
164     return Ltrue; // 機關為度
165 }
166 
167 // 計算太陽真實平近點角(True Anomaly)
168 function calcSunTrueAnomaly(t) {
169     var M = calcGeomMeanAnomalySun(t);
170     var C = calcSunEqOfCenter(t);
171     var nu = correctRange(M + C, 360);
172     return nu; // ν(nu)機關為度
173 }
174 
175 /**
176  * 計算半徑矢量 => 太陽中心到地球中心的距離
177  */
178 function calcSunRadVector(t) {
179     var n = calcSunTrueAnomaly(t);
180     var e = calcEccentricityEarthOrbit(t);
181     var R = (1.000001018 * (1 - Math.pow(e, 2))) / (1 + e * Math.cos(degToRad(n)));
182     return R;
183 }
184 
185 /**
186  * 計算太陽表觀經度
187  * 參考 https://en.wikipedia.org/wiki/Apparent_longitude
188  */
189 function calcSunApparentLong(t) {
190     var Ltrue = calcSunTrueLong(t);
191     var omega = 125.04 - 1934.136 * t;
192     var Lapp = Ltrue - 0.00569 - 0.00478 * Math.sin(degToRad(omega));
193     return Lapp; // 機關為度
194 }
195 
196 /**
197  * 計算黃赤交角
198  */
199 function calcMeanObliquityOfEcliptic(t) {
200     var seconds = 21.448 - t * (46.8150 + t * (0.00059 - t * (0.001813)));
201     var e0 = 23.0 + (26.0 + (seconds / 60.0)) / 60.0;
202     return e0; // 機關為度
203 }
204 
205 /**
206  * 計算太陽坐标
207  */
208 function calcObliquityCorrection(t) {
209     var e0 = calcMeanObliquityOfEcliptic(t);
210     var omega = 125.04 - 1934.136 * t;
211     var e = e0 + 0.00256 * Math.cos(degToRad(omega));
212     return e; // 機關為度
213 }
214 
215 // 計算太陽赤經
216 function calcSunRtAscension(t) {
217     var e = calcObliquityCorrection(t);
218     var Lapp = calcSunApparentLong(t);
219     var tananum = (Math.cos(degToRad(e)) * Math.sin(degToRad(Lapp)));
220     var tanadenom = (Math.cos(degToRad(Lapp)));
221     var alpha = radToDeg(Math.atan2(tananum, tanadenom));
222     return alpha.toFixed(2); // 機關為度
223 }
224 
225 // 計算太陽赤緯
226 function calcSunDeclination(t) {
227     var e = calcObliquityCorrection(t);
228     var Lapp = calcSunApparentLong(t);
229     var sint = Math.sin(degToRad(e)) * Math.sin(degToRad(Lapp));
230     var theta = radToDeg(Math.asin(sint));
231     return theta.toFixed(2); // 機關為度
232 }
233 
234 /**
235  * 計算真太陽日與平太陽日之差(即“時差”)
236  * 與目前是幾點無關 => 即計算的是 00:00:00 的時差
237  */
238 function calcEquationOfTime(t) {
239     var epsilon = calcObliquityCorrection(t);
240     var l0 = calcGeomMeanLongSun(t);
241     var e = calcEccentricityEarthOrbit(t);
242     var m = calcGeomMeanAnomalySun(t);
243 
244     var y = Math.tan(degToRad(epsilon) / 2.0);
245     y *= y;
246 
247     var sin2l0 = Math.sin(2.0 * degToRad(l0));
248     var sinm = Math.sin(degToRad(m));
249     var cos2l0 = Math.cos(2.0 * degToRad(l0));
250     var sin4l0 = Math.sin(4.0 * degToRad(l0));
251     var sin2m = Math.sin(2.0 * degToRad(m));
252 
253     var Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m;
254     return Math.floor(radToDeg(Etime) * 4.0 * 100 + 0.5) / 100.0; // 機關為分鐘
255 }
256 
257 /**
258  * 根據目前時間對儒略日進行修正
259  */
260 function CorrectJD(t, hour, mim, sec, tz) {
261     //  擷取分鐘數
262     let mims = getLocalTime(hour, mim, sec);
263     // 擷取時間修正的儒略日(+小時數-時區差)
264     let JD = t + mims / 1440.0 - tz / 24.0;
265     return JD;
266 }
267 
268 // 計算日出角
269 function calcHourAngleSunrise(lat, solarDec) {
270     var latRad = degToRad(lat);
271     var sdRad = degToRad(solarDec);
272     var HAarg = (Math.cos(degToRad(90.833)) / (Math.cos(latRad) * Math.cos(sdRad)) - Math.tan(latRad) * Math.tan(sdRad));
273     var HA = Math.acos(HAarg);
274     return HA; // 機關弧度
275 }
276 
277 // 計算日落角
278 function calcHourAngleSunset(lat, solarDec) {
279     return -calcHourAngleSunrise(lat, solarDec); // 機關弧度
280 }
281 
282 /******************************************/
283 /********** 太陽坐标計算結束 ***************/
284 /******************************************/
285 
286 /**
287  * 計算地球上給定位置、給定日期“太陽正午”的世界協調時間(UTC)
288  * 太陽高度角最大的時候
289  * @return HH:MM:SS 格式的正午時間
290  */
291 function calcSolNoon(julianday, longitude, timezone, dst) {
292     var tnoon = calcTimeJulianCent(julianday - longitude / 360.0)
293     var eqTime = calcEquationOfTime(tnoon)
294     var solNoonOffset = 720.0 - (longitude * 4) - eqTime // 機關為分鐘
295     var newt = calcTimeJulianCent(jd + solNoonOffset / 1440.0)
296     eqTime = calcEquationOfTime(newt)
297     solNoonLocal = 720 - (longitude * 4) - eqTime + (timezone * 60.0) // 機關為分鐘
298     if (dst) solNoonLocal += 60.0 // 如果采用夏時令則加 60 分鐘(提前 1 小時)
299     // 将時間控制在 24 小時(1440 分鐘) 内
300     solNoonLocal = correctRange(solNoonLocal, 1440.0)
301     return timeString(solNoonLocal, 3);
302 }
303 
304 /**
305  * @param {Number} rise rise = 1 計算日出時間, 0 計算日落時間
306  * @param {Number} JD 儒略日
307  * @param {Number} latitude 緯度
308  * @param {Number} longitude 經度
309  * 傳回日出日落時間的分鐘數
310  */
311 function calcSunriseSetUTC(rise, JD, latitude, longitude) {
312     var t = calcTimeJulianCent(JD);
313     var eqTime = calcEquationOfTime(t);
314     var solarDec = calcSunDeclination(t);
315     var hourAngle = calcHourAngleSunrise(latitude, solarDec);
316     if (!rise) hourAngle = -hourAngle;
317     var delta = longitude + radToDeg(hourAngle);
318     var timeUTC = 720 - (4.0 * delta) - eqTime; // 機關為分鐘
319     return timeUTC;
320 }
321 
322 // 計算日出日落時間
323 function calcSunriseSet(rise, JD, latitude, longitude, timezone, dst) {
324     var timeUTC = calcSunriseSetUTC(rise, JD, latitude, longitude);
325     var newTimeUTC = calcSunriseSetUTC(rise, JD + timeUTC / 1440.0, latitude, longitude);
326     // 時區修正的日出日落分鐘數
327     var timeLocal = newTimeUTC + (timezone * 60.0)
328     // 夏時令修正
329     timeLocal += ((dst) ? 60.0 : 0.0);
330     // 控制 timeLocal 範圍在 0~1440.0 内
331     timeLocal = correctRange(timeLocal, 1440.0);
332     return timeString(timeLocal, 2);   
333 }
334 
335 /**
336  * @param {Number} localtime => 通過 getLocalTime 獲得的時間
337  * 計算太陽高度角
338  */
339 function calcelevation(t, localtime, latitude, longitude, zone) {
340     var eqTime = calcEquationOfTime(t); // 時差
341     var theta = calcSunDeclination(t); // 赤緯
342     var solarTimeFix = eqTime + 4.0 * longitude - 60.0 * zone;
343 
344     var trueSolarTime = localtime + solarTimeFix;
345     
346     trueSolarTime = correctRange(trueSolarTime, 1440);
347 
348     var hourAngle = trueSolarTime / 4.0 - 180.0;
349     if (hourAngle < -180) hourAngle += 360.0;
350 
351     var haRad = degToRad(hourAngle);
352     var csz = Math.sin(degToRad(latitude)) * Math.sin(degToRad(theta)) + Math.cos(degToRad(latitude)) * Math.cos(degToRad(theta)) * Math.cos(haRad)
353     if (csz > 1.0) {
354         csz = 1.0
355     } else if (csz < -1.0) {
356         csz = -1.0
357     }
358     var zenith = radToDeg(Math.acos(csz))
359 
360     var exoatmElevation = 90.0 - zenith
361 
362     if (exoatmElevation > 85.0) {
363         var refractionCorrection = 0.0;
364     } else {
365         var te = Math.tan(degToRad(exoatmElevation));
366         if (exoatmElevation > 5.0) {
367             var refractionCorrection = 58.1 / te - 0.07 / (te * te * te) + 0.000086 / (te * te * te * te * te);
368         } else if (exoatmElevation > -0.575) {
369             var refractionCorrection = 1735.0 + exoatmElevation * (-518.2 + exoatmElevation * (103.4 + exoatmElevation * (-12.79 + exoatmElevation * 0.711)));
370         } else {
371             var refractionCorrection = -20.774 / te;
372         }
373         refractionCorrection = refractionCorrection / 3600.0;
374     }
375 
376     var solarZen = zenith - refractionCorrection;
377 
378     if (solarZen > 108.0) {
379         document.getElementById("azbox").value = "dark";
380         document.getElementById("elbox").value = "dark";
381     } else {
382         // 太陽高度角
383         return Math.floor((90.0 - solarZen) * 100 + 0.5) / 100.0
384     }
385 }
386 
387 /**
388  * @param {Number} ZeroAzimuth 零方位角
389  * 零方位角 = 北, ZeroAzimuth=0
390  * 零方位角 = 南, ZeroAzimuth=180
391  */
392 function calcazimuth(t, localtime, latitude, longitude, zone, ZeroAzimuth) {
393     var eqTime = calcEquationOfTime(t); // 時差
394     var theta = calcSunDeclination(t); // 赤緯
395     var solarTimeFix = eqTime + 4.0 * longitude - 60.0 * zone;
396 
397     var trueSolarTime = localtime + solarTimeFix;
398     
399     trueSolarTime = correctRange(trueSolarTime, 1440);
400 
401     var hourAngle = trueSolarTime / 4.0 - 180.0;
402     if (hourAngle < -180) hourAngle += 360.0;
403 
404     var haRad = degToRad(hourAngle);
405     var csz = Math.sin(degToRad(latitude)) * Math.sin(degToRad(theta)) + Math.cos(degToRad(latitude)) * Math.cos(degToRad(theta)) * Math.cos(haRad)
406     if (csz > 1.0) {
407         csz = 1.0
408     } else if (csz < -1.0) {
409         csz = -1.0
410     }
411     var zenith = radToDeg(Math.acos(csz))
412     var azDenom = (Math.cos(degToRad(latitude)) * Math.sin(degToRad(zenith)))
413     
414     if (Math.abs(azDenom) > 0.001) {
415         azRad = ((Math.sin(degToRad(latitude)) * Math.cos(degToRad(zenith))) - Math.sin(degToRad(theta))) / azDenom
416         if (Math.abs(azRad) > 1.0) {
417             if (azRad < 0) {
418                 azRad = -1.0
419             } else {
420                 azRad = 1.0
421             }
422         }
423         var azimuth = 180.0 - radToDeg(Math.acos(azRad))
424         if (hourAngle > 0.0) {
425             azimuth = -azimuth
426         }
427     } else {
428         if (latitude > 0.0) {
429             azimuth = 180.0
430         } else {
431             azimuth = 0.0
432         }
433     }
434     if (azimuth < 0.0) {
435         azimuth += 360.0
436     }
437     return (Math.floor(azimuth * 100 + 0.5) - ZeroAzimuth * 100) / 100.0;
438 }      
相關方法      
  • getLocalTime  計算今天過了多少分鐘
  • calcSunriseSet   計算日出日落時間
  • calcSolNoon   計算正午時間
  • calcSunRtAscension  計算太陽赤經(未進行時間修正)
  • calcSunDeclination  計算太陽赤緯(未進行時間修正)
  • calcSunRadVector  計算矢量半徑(未進行時間修正)
  • calcMeanObliquityOfEcliptic  計算黃赤交角(未進行時間修正)
  • calcEquationOfTime  計算時差(未進行時間修正)
  • getJulianDay  擷取儒略日
  • CorrectJD  時間修正的儒略日
  • calcTimeJulianCent  根據儒略日擷取世紀數
  • calcelevation  計算太陽高度角
  • calcazimuth  計算方位角

使用

 

// 擷取目前時間
let date = new Date()
let year = date.getFullYear()
let month = date.getMonth()
let day = date.getDate()

let hour = date.getHours()
let mim = date.getMinutes()
let second = date.getSeconds()

// 擷取儒略日
let jd = getJulianDay(year, month+1, day);

// 經緯度
let latitude = 30.214512
let longitude = 120.225365

// 時區
let tz = 8

// 今天已過了多少分鐘
let tl = getLocalTime(hour,mim,second)

// 使用時間、時區修正的分鐘數
let t = jd + tl / 1440.0 - tz / 24.0

// 世紀數
t =  calcTimeJulianCent(t)

console.log(calcazimuth(t,tl, latitude, longitude,tz, 0)) // 方位角
console.log(calcelevation(t,tl, latitude, longitude,tz)) // 高度角
console.log(calcEquationOfTime(t)) // 時差
console.log(calcSunRtAscension(t)) // 赤經
console.log(calcSunDeclination(t)) //赤緯      

驗證位址  https://gml.noaa.gov/grad/solcalc/

​​ESRL Global Monitoring Laboratory - Global Radiation and Aerosols (noaa.gov)​​