计算太阳高度角(javascript实现)

 

代码来源 https://gitee.com/zhonghongqiang/calculateSunAzEl.git
代码参考 https://www.jianshu.com/p/ecdccbfe0a88
太阳坐标计算参考资料 https://squarewidget.com/solar-coordinates/

  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)

posted @ 2021-08-30 19:03  ICE99125  阅读(490)  评论(0编辑  收藏  举报