顯示具有 astronomy 標籤的文章。 顯示所有文章
顯示具有 astronomy 標籤的文章。 顯示所有文章

2016年6月5日 星期日

moon phase 月相

月相

中央氣象局月相圖,我們可以查到每一天的月相資料, 舉凡新月、滿月、上弦月、下弦月等的日子,一目了然。

新月前後三四天,只有10%左右的部分是亮的,亮的部分在右側?左側?右上角?右下角?左上角?左下角?事實上,同一天的不同時刻看,亮的位置是會改變的,前述網站沒有呈現這個現象。

基本上,月亮的形狀主要由地球、月亮、太陽的相關位置以及觀察地點所決定。月亮面對太陽的半個球是亮的,面對地球的半個球是我們(地表觀察者)可以看到的,兩者的交集就是我們看到的部分。至於哪一個角落是亮的,因為地球的自轉,觀察者的天頂方向會隨時間改變,也就是視覺上的上方會隨時間改變,造成視覺上亮的部位改變。

在地平坐標系統下,只要有太陽與月亮的位置資料,我們就可以算出月相。

下面的圖形是用附錄中的程式製作出來的。它是2016年6月3日(陰曆4月28)每一整點在台北可以看到的月相。圖中的月形,藍色代表在地平面上,紅色代表在地平面下,可以明顯看到月相隨間改變的情形。

下面的圖形是2016年6月7日(陰曆5月初三)每一整點在台北可以看到的月相。

地平坐標系統

地平坐標系統以觀察地為原點,觀測目標的位置用距離(\(\rho\))、仰角(\(\phi\))、方位角(\(\theta\))來定位,這是球體坐標。距離指的是觀測站到目標的距離,仰角指的是觀測目標的方向與地面的夾角。方位角指的觀測目標的東西南北方位,以北方為基準,向東轉的角度就是。換句話說,北方的方位角為0度,東方為90度,南方為180度,西方為270度。

將東方、北方、天頂方向分別訂為直角座標系統下的X、Y、Z軸的方向,\((x,y,z)\)為目標的直角座標。直角坐標\((x,y,z)\)與球體坐標\((\rho,\theta,\phi)\)的關係如下: \[x=\rho\cos\phi\sin\theta, y=\rho\cos\phi\cos\theta, z=\sin\phi\]

月球上的臨時座標系統

月球的方位角用\(\theta\)表示、仰角用\(\phi\)表示,則地面望向月亮的方向\(e_2=(\cos\phi\sin\theta,\cos\phi\cos\theta,\sin\phi)\)。從月球位置出發,沿著同一方位角的大圓,往天頂的方向\(e_3\)就是\(e_2\)對\(\phi\)的微分, 也就是\(e_3=(-\sin\phi\sin\theta,-\sin\phi\cos\theta,\cos\phi)\)。令\(e_1=e_2\times e_3\), 從地表往月亮看,\(e_1\)的方向是月亮的右邊,\(e_3\)的方向是月亮的上邊,\(e_2\)的方向是月亮的後邊。以月亮中心為原點,\(\{e_1,e_2,e_3\}\)為坐標軸方向,我們建立一個臨時坐標系統。

令矩陣\(M\)的第一列為\(e_1\),第二列為\(e_2\),第三列為\(e_3\)。在地平坐標系統下,月球到太陽的方向向量坐標用\(S_e\)表示, 則在臨時坐標系統中,月球到太陽的方向向量坐標為\(S_m=M S_e\)。另一方面,在臨時坐標系統中,月球望向地球的方向向量為\((0,-1,0)\)。

在臨時坐標系統中,以月亮的半徑為單位,面向我們的半個月球以大圓\(C_1: x^2+z^2=1, z=0\)為邊界,\(C_1\) 的參數式為\((\cos\theta,0,\sin\theta)\)。

面向太陽的半個月亮的邊界\(C_2\)也是一個大圓,\(C_2\)就是過圓點且與\(S_m\) 垂直的平面\(E\) 跟單位球面相交的部分。令\(B_0=S_m\times (0,1,0)\),\(B_1\)是與\(B_0\)同向的單位向量,\(B_2=S_m\times B_1\)。 如此,\(B_1,B_2\)是平面\(E\)上兩個互相垂直的單位向量,而 \(C_2\)的參數式為\(\cos\theta\cdot B_1+\sin\theta\cdot B_2\)。

\(C_1,C_2\)兩大圓相交,互相切割,得到4段圓弧,屬於\(C_1\)的兩段用\(C_{11},C_{12}\)表示,屬於\(C_2\)的兩段用\(C_{21},C_{22}\)表示。從\(C_{11},C_{12}\)中挑出靠近太陽的一段(與\(S_m\)的內積大於0),從\(C_{21},C_{22}\)挑出靠近地球的一段(第二個坐標小於0),所得兩段圓弧圍得月球表面的部分是月亮中亮的部分。這兩段圓弧在臨時坐標系統中\(XZ\)平面的垂直投影,所圍成的區域就是視覺上的月相。

附錄中,利用python平台的套件ephem與matplotlib,我們製作函數 moonshape_t(lon,lat,tz,year,month,day,hour)來顯示月相。參數lon為經度,東經為正;lat為緯度,北緯為正,tz為時區(以小時為單位,台北為+8),year為西元,hour採24小時制,可以有小數。 執行moonshape_t('121.537','25.017',8,2016,6,3,16.5),我們就可得到,2016年6月3日下午4點半在台北(東經121.537度, 北緯25.017度)所看到的月相。

附錄

import ephem
import math
import numpy as np
import matplotlib.pyplot as plt

def spher2cart(theta,phi):
  return np.array([math.cos(phi)*math.sin(theta),math.cos(phi)*math.cos(theta),math.sin(phi)])

def spher2cartDphi(theta,phi):
  return np.array([-math.sin(phi)*math.sin(theta),-math.sin(phi)*math.cos(theta),math.cos(phi)])


def greatCircleWithPar(N):
  # [0,1.0,0] 為觀察者望向月球的方向
  # N 為月球望向太陽的方向
  # 通過月球球心與N垂直的平面切割月球的大圓C,C可以用cos(theta)*b1+sin(theta)*b2表示,
  # 其中的b1,b2是C平面上兩個互相垂直的單位向量
  b1=np.cross(N,np.array([0,1.0,0]))
  if np.linalg.norm(b1)>0.000001:
    b1=1/np.linalg.norm(b1)*b1
    b2=np.cross(N,b1)
  else:
    b1=np.array([1.,0,0])
    b2=np.array([0,0,1.])
  #print([b1,b2])
  def fun(theta):
    return math.cos(theta)*b1+math.sin(theta)*b2
  return fun

def cutarc(points):
  arcs=[]
  arc=[]
  for i in range(len(points)):
    if arc==[]:
      arc.append(points[i])
    else:
      if np.linalg.norm(points[i]-arc[-1])<0.1:
        arc.append(points[i])
      else:
        arcs.append(arc)
        arc=[points[i]]
  arcs.append(arc)  
  return arcs    


def moonshape_t(lon,lat,tz,year,month,day,hour,show=True):
  date=ephem.Date((year,month,day,hour-tz)) # GMT+8
  site=ephem.Observer()
  site.date=date
  site.lon=lon
  site.lat=lat
  sun=ephem.Sun(site)
  moon=ephem.Moon(site)
  # e2 望向月亮的方向
  e2=spher2cart(moon.az,moon.alt)
  # e3 月亮在天球上的往上方向
  e3=spher2cartDphi(moon.az,moon.alt)
  # e1 月亮在天球上的往右方向
  e1=np.cross(e2,e3) 
  M=np.stack([e1,e2,e3])
  moon2sun=sun.earth_distance*spher2cart(sun.az,sun.alt)-moon.earth_distance*spher2cart(moon.az,moon.alt)
  s_e=1/np.linalg.norm(moon2sun)*moon2sun
  # s_m: 以 {e1,e2,e3} 為坐標軸方向,月亮為原點時,太陽的方向
  s_m=M.dot(s_e)
  if moon.alt>0:
    color='b'
  else:
    color='r'
  print(['moon.alt',moon.alt,'moon.az',moon.az])
  f=greatCircleWithPar(s_m)
  thetas=np.linspace(-math.pi,math.pi,num=200)
  plt.plot([math.cos(theta) for theta in thetas],[math.sin(theta) for theta in thetas],'w-')
  points=[f(theta) for theta in thetas if f(theta)[1]<0]
  arcs=cutarc(points)
  for arc in arcs:
    plt.plot([x[0] for x in arc],[x[2] for x in arc],color+'-')
  points=[np.array([math.cos(theta),0.0,math.sin(theta)]) for theta in thetas 
    if np.dot(np.array([math.cos(theta),0.0,math.sin(theta)]),s_m)>0]
  arcs=cutarc(points)
  for arc in arcs:
    plt.plot([x[0] for x in arc],[x[2] for x in arc],color+'-')
  plt.axis('off')
  plt.axis('equal')
  plt.title(str(year)+'/'+str(month)+'/'+str(day)+'/'+str(hour))
  if show:
    plt.show()

lon='121.537'
lat='25.017'
tz=8
moonshape_t(lon,lat,tz,2016,6,3,16)
for i in range(1,25):
  plt.subplot(6,4,i)
  moonshape_t(lon,lat,tz,2016,6,3,i,False)

plt.show()
      

2016年5月21日 星期六

lunar calendar 農曆

計算農曆

只要知道太陽與月亮,在地心黃道座標系統上,隨時間改變的位置,我們就可以據以訂出農曆。

下面我們將用R平台上套件astrolibR來算出農曆。

農曆是甚麼?

我國的農曆基本上是陰陽合曆。陰曆的月以月亮為基礎,新月的時候就是初一。太陽與月亮落在同一黃經的那一天就是新月日,那天就是陰曆初一。一個月有時29天,有時30天,完全決定於兩次新月日之間相差29天還是30天。

農曆的年以太陽為基礎,地球繞太陽一圈需時365.2422天。用地心赤道坐標系統來講,太陽在春分與秋分兩次落在赤道平面上。一次在3月份另一次在9月份,3月份的那一刻叫春分,9月份的那一刻叫秋分。太陽在黃道面上,一年繞行地球一圈,以春分時太陽的位置為基準,每繞行15度得一節氣,分別是春分,清明,穀雨,立夏,小滿,芒種,夏至,小暑,大暑,立秋,處暑,白露,秋分,寒露,霜降,立冬,小雪,大雪,冬至,小寒,大寒,立春,雨水,驚蟄。太陽位於黃經0度時是春分,15度時是清明,餘此類推。位於黃經30度整數倍的節氣叫做中氣,換句話說,春分,穀雨,小滿,夏至,大暑,處暑,秋分,霜降,小雪,冬至,大寒,雨水等為中氣。節氣與太陽的位置關係密切,太陽是地球能量的主要來源,所謂春耕、夏耘、秋收、冬藏道盡了農作與太陽之間的關係。先人的對於農時的經驗,大致用節氣來表示。

節氣

中央氣象局的 天文常識系列 提到節氣

節氣的定法有兩種,古時候使用的稱為平氣法,就是把冬至到下一個冬至平分為24等分,每一節氣都是15天多。從清初時憲曆(西元1645年)起,節氣的推算改為定氣法,自春分點開始,太陽在黃道上每視行15度定一個節氣,節氣間隔的時間並不等長。

用astrolibR套件的sumpos,我們可以得到太陽隨時間改變的位置。其中的成分$longmed就是黃經,看哪一天太陽的位置通過黃經0度,那一天就是春分;看哪一天太陽的位置通過黃經15度,那一天就是清明,餘此類推。

在附錄中,我們自製了函數jieqi(year),用jieqi(2016)就可算出西元2016年的節氣。

陰曆初一

用astrolibR套件的moonpos,我們可以得到月亮隨時間改變的位置。計算什麼時刻,太陽與月亮的黃經相同,那刻所在的日子就是陰曆初一。

在附錄中,我們自製了函數lunardayone(year),用lunardayone(2016)就可算出西元2016年的那些天是陰曆初一。

陰曆的月份

平均起來一個陰曆月約有29.5天,而一年有365.2422天,12個陰曆月比一年短少約11天。為了維持一年有12個月份,我國用閏月的方式來解決。大致上,每隔約三年,設置一個月為閏月,其月份與前一個月同。假如前一個月是8月而這個月是閏月,這個月叫潤8月,而下個月叫9月。

哪一個月的初一是春節?哪一個月要閏月?

中央研究院劉智漢在 這裡提到歲首的問題:

自漢武帝太初元年(西元前104年)改曆以來,至今大都採用夏代的建寅制,即自冬至所在 月份起算的第三個月為正月
因此,冬至所在的陰曆月為11月。

Aslaksen的 文章列出了安置月份與置潤的原則。條列如下:

  • 計算時間時以東經120度為基準
  • 每天的起算時間為午夜零時
  • 新月那天為陰曆初一
  • 一歲的開始為冬至的那一刻所在的日子,以下一個冬至日的前一天為最終日。用歲來稱呼,與大年初一開始的年有所區別。冬至日的陰曆月訂為11月
  • 若一歲裡包含了完整的12個陰曆月,這一歲叫潤歲。在潤歲裡,第一個沒有中氣的月份必須閏月

根據這些原則,在附錄中自製了函數lunarCalendar(year)。用lunarCalendar(2017),我們算出2017年陰陽曆的對應,得到2017年潤6月,與中央氣象局的 資料 相符合。

附錄

library(astrolibR)
# 清明時太陽位於黃經15度,穀雨30度,...
jieqinames<-c("清明","穀雨","立夏","小滿","芒種","夏至","小暑","大暑","立秋"
              ,"處暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至","小寒","大寒","立春","雨水","驚蟄","春分")
# d1,d2 angle with unit degree.
diff<-function(d1,d2,mod){
  dif<- ((d1-d2) %% mod)
  if (dif>mod/2)
    dif<-dif-mod
  dif
}
# 節氣足碼,清明1,穀雨2,...,不是節氣0
jieqiindex<-function(year,month,day){
  jd<-jdcnv(year,month,day,0)-1/3
  sun1<-sunpos(jd)$longmed%%15
  sun2<-sunpos(jd+1)$longmed%%15
  if (diff(sun1,0,15)<=0 && diff(sun2,0,15)>0)
    floor(sunpos(jd)$longmed%%360/15)+1
  else
    0
}
# 是否陰曆初一
isLunarOne<-function(year,month,day){
  jd<-jdcnv(year,month,day,0)-1/3
  sun1<-sunpos(jd)$longmed%%360
  sun2<-sunpos(jd+1)$longmed%%360
  mon1<-moonpos(jd)$geolong%%360
  mon2<-moonpos(jd+1)$geolong%%360
  if (diff(sun1,mon1,360)>=0 && diff(sun2,mon2,360)<0)
    TRUE
  else
    FALSE
}

# 算出節氣的日子
jieqi<-function(year){
  leapyear=FALSE
  if (year %% 4 ==0) leapyear=TRUE
  if (year %% 100==0) leapyear=FALSE
  if (year %% 400==0) leapyear=TRUE
  days=c(31,28,31,30,31,30,31,31,30,31,30,31)
  if (leapyear) days[2]=29
  res<-list()
  for (month in seq(1,12)){
    for (day in 1:days[month]){
      index<-jieqiindex(year,month,day)
      if (index>0){
        res<-c(res,list(c(year,month,day,jdcnv(year,month,day,12),index)))
      }
    }
  }
  res
}


lunardayone<-function(year){
  leapyear=FALSE
  if (year %% 4 ==0) leapyear=TRUE
  if (year %% 100==0) leapyear=FALSE
  if (year %% 400==0) leapyear=TRUE
  days=c(31,28,31,30,31,30,31,31,30,31,30,31)
  if (leapyear) days[2]=29
  res<-list()
  for (month in seq(1,12)){
    for (day in 1:days[month]){
      if (isLunarOne(year,month,day)){
        res<-c(res,list(c(year,month,day,jdcnv(year,month,day,12))))
      }
    }
  }
  res
}

# 一歲裡陰的陰曆初一清單,假如一歲裡包含完整的12個陰曆月,則長度為13
suiones<-function(lunarones,sols1,sols2){
  res<-list()
  for (ele in lunarones){
    if (ele[4]>=sols1[4] && ele[4]<=sols2[4]){   # should be <=
      res<-c(res,list(ele))
    }
  }
  res
}
# 連續兩個陰曆初一間是否缺少中氣,及m1的月份是否缺少中氣
lackzhongqi<-function(m1,m2,jieqis){
  res<-TRUE
  for (i in seq_along(jieqis)){
    if (i%%2==0 && jieqis[[i]][4]>=m1[4] &&jieqis[[i]][4]<m2[4]){
      res<-FALSE
      break
    }
  }
  res
}

# 排定陰曆月份,包含閏月的設定
assignmonth<-function(sui,jieqis){
  leapsui<-FALSE
  if (length(sui)>=13){
    leapsui<-TRUE
  }
  completed<-FALSE
  suiclone<-sui
  Mn<-11
  for (i in seq_along(suiclone)){
    if (leapsui && !completed && lackzhongqi(suiclone[[i]],suiclone[[i+1]],jieqis)){
      suiclone[[i]]<-c(suiclone[[i]],Mn+0.5)    # 閏月
      completed<-TRUE
    }
    else {
      Mn<-(Mn+1)
      if (Mn>12) Mn<-Mn%%12
      suiclone[[i]]<-c(suiclone[[i]],Mn)   # 平月/普通月
    }
  }
  suiclone
}
lunarCalendar<-function(year){
  jieqis<-c(jieqi(year-1),jieqi(year),jieqi(year+1))
  lunarones<-c(lunardayone(year-1),lunardayone(year),lunardayone(year+1))
  wintersolstices<-list()
  for (ele in jieqis){
    #if (isSolstice(ele[1],ele[2],ele[3]))
    if (ele[5]==18)
      wintersolstices<-c(wintersolstices,list(ele))
  }
  firstsui<-suiones(lunarones,wintersolstices[[1]],wintersolstices[[2]])
  secondsui<-suiones(lunarones,wintersolstices[[2]],wintersolstices[[3]])
  firstsui<-assignmonth(firstsui,jieqis)
  secondsui<-assignmonth(secondsui,jieqis)
  res<-c()
  for (ele in c(firstsui,secondsui)){
    if (ele[1]==year){
      st=""
      if (ele[5]%%1>0) st<-"潤"   # ele[5]有小數部分代表閏月
      st<-paste0(st,floor(ele[5]),"月")
      res<-c(res,paste(ele[1],ele[2],ele[3],st))
    }
  }
  res
}

# test jieqi
res<-jieqi(2017)  
for (ele in res){
  print(paste(ele[1],ele[2],ele[3],jieqinames[ele[5]]))
}

# test lunardayone
lunardayone(2016)

# test lunarCalendar
lunarCalendar(2016)
lunarCalendar(2017)
lunarCalendar(2033)

2016年5月18日 星期三

Sunset and Sunrise 日出與日落

介紹

中央氣象局,我們可以查到台灣地區每天日出、日落的時刻。 在timeanddate網站則可查到世界各地的資料。這些資料是職業天文工作者,利用觀察資料配合模型計算出的。
我們用地心赤道坐標系統、太陽軌跡、地球自轉等資料、觀察地A的經緯度及時區等資料,估算A地點的整年日出日落時間。其中,太陽的軌跡與地球自轉的資料,取自運算平台 R 的套件astrolibR。用附錄中的程式,我們算出台北與布宜諾斯艾利斯2016年每月1日的日出日落時分,與網站上的資料相比,相差全在1分鐘之內。

模型與坐標

地球公轉的平面叫做黃道面,地球赤道所在的平面叫做赤道面,黃道面與赤道面的交角\(\epsilon\)就是所謂的地軸傾角。兩面的交線與交角都會隨時間改變。 參考下面動畫,目前地軸傾角約為23.44度。

我們採用所謂的 地心赤道坐標系統 ,將地球中心固定放在原點,赤道平面就是XY平面,地心往北極方向為Z軸,地球以Z軸正向為軸自轉。右手握拳拇指伸出,拇指方向為Z軸時,四指的方向是自轉的方向。請參考下面的動畫

太陽軌道所在的平面叫做黃道面,黃道面與赤道面的交角23.44度,就是地軸傾角\(\epsilon\)。一年之中太陽有兩次運行到赤道面上,分別是春分與秋分。春分之前,太陽在赤道面的下方,之後在赤道面的上方。在春分或秋分時刻,地球南北半球各洽有一半是白天,一半是夜晚。春分之後太陽照到北半球的部分開始大於南半球,並且差距越來越大,直到夏至。此時,太陽在離赤道面最高的地方。夏至後,差距開始縮小,直到秋分,太陽照到地球南北半球的部分再度相等。秋分前後,太陽由赤道面的上方跑到赤道面的下方,秋分之後太陽照到北半球的部分開始小於南半球,並且差距越來越大,直到冬至。此時,太陽在赤道面下方離赤道面最遠的地方。
我們將春分那一瞬間,地球指向太陽的方向為X軸,以地球到太陽的距離為單位,將當時太陽的坐標定為(1,0,0), 地球的半徑用R表示。有了Z軸與X軸,Y軸方向的訂定如一般右手系的直角坐標系統。地球的半徑用R表示。
在地心赤道坐標系統中,假設物體的直角座標為\((x,y,z)\),該物體在XY平面上投影的幅角叫做赤經\(\alpha\),\((x,y,z)\)與XY平面的夾角叫做赤緯\(\delta\), \((x,y,z)\)的長度用\(\rho\)表示,則有下列關係: \[x=\rho \cos\delta \cos\alpha, y=\rho \cos\delta \sin\alpha, z=\rho \sin\delta\]

Julian 日數(JD)

太陽的軌跡以及地球自轉的狀態,需要與時間建立關係。在天文學上,用Julian日數來代表時間。所謂Julian日數(JD),是以西元前4713年1月1日中午的時刻起算的日數。西元前4713年1月1日12:00的JD=0,西元前4712年1月1日00:00的JD=-0.5。astrolibR套件中的函數jdcnv(year,month,day,hour)可用來算JD。例如,jdcnv(1,2,3,4.1)=1721458.671 代表西元1年2月3日4:06的JD為1721458.671。注意,jdcnv不能用來算西元前的JD。

地面上定點的赤經

A地的經緯度為\(E_A\) 經 \(N_A\) 緯,地球自轉一圈時,A地也繞圓圈轉一圈,圓圈的半徑是\(R \cos N_A\)。 這裡,有一點要注意,地球並不是一天自轉一圈。地球轉動,同一經線所在半平面,連續兩次掃過太陽的時間間隔才是一天。由於太陽一天繞行地球1/365.2425圈,所以地球每天要轉動比一圈多一點點的366.2425/365.2425圈,才能讓同一經線半平面再次掃過太陽。換句話說,地球一年之中自轉了366.2425圈。令\(\theta_A\)代表A地的赤經,則A地的直角坐標\(\vec{A}\)為\((R \cos N_A\cos \theta_A,R\cos N_A\sin\theta_A,R\sin N_A)\)。
\(\theta_A\)隨時間改變,我們可以用astrolibR套件中的函數ct2lst(long,tz,jd)計算之,函數值以時角為單位。這裡的時角是角度單位而非時間單位,1時角度相當於15度。1時角,細分為60分,每分又細分為60秒。例如,ct2lst(0,0,jdcnv(2016,3,20,4.5))=16.3761,代表格林威治時間2016年3月20日4:30, 倫敦(東經0度,時區GMT+0)的赤經為16.3761時角。ct2lst(121.537,8,jdcnv(2016,3,20,4.5)) =0.4786,代表格林威治時間2016年3月20日4:30, 台北(東經121.537度,時區GMT+8)的赤經為0.4786時角。

太陽的仰角與昇落方程式

A地的地平面向上單位法向量\(\vec{n}\)為\(\frac{1}{R}\vec{A}\)。用\(\vec{S}=(s_x,s_y,s_z)\)代表太陽的坐標, \(\vec{S}-\vec{A}\)就是從A地觀察太陽的方向。內積\(\vec{n}\cdot(\vec{S}-\vec{A})\)可用來判定太陽與地平面的相關位置。內積大於0時,代表太陽在地平面上方,是白天;小於0代表太陽在地平面下方,是夜晚; 等於0代表洽在地平面,也就是日出日落時分。因此,A地日出日落時刻滿足方程式 \(\vec{n}\cdot(\vec{S}-\vec{A})=0\)。也就是\(\vec{n}\cdot\vec{S}=R\), R的值4.25875e-05很小,用0來近似,變成\(\vec{n}\cdot\vec{S}=0\),其中\(\vec{n}\cdot\vec{S}\)是太陽中心位置與天頂方向的夾角餘弦,夾角餘弦等於0代表太陽中心落在地平線。因為太陽上緣露出地平就算日出,以至於太陽中心仍在地平下方就已看到太陽。又因為光線進入大氣層會發生折射,即使太陽上縁略低於地平就可看見陽光。根據 維基百科,太陽圓盤視角為32分。一般認為,折射影響的角度有34分,再加上太陽的圓盤又貢獻了16分,太陽中心在地平下方角度50分時為日出日落時刻,也就是\(\vec{n}\cdot\vec{S}=\cos(90^{\circ}50')\)。所以,日出日落時刻是解下列方程式(簡稱昇落方程式) \[s_x\cos N_A\cos \theta_A+s_y\cos N_A\sin\theta_A+s_z\sin N_A +\sin(50')=0\] 要用上述方程式求解日出日落時刻,我們已知\(\theta_A\)隨時間的變化,還需要知道太陽隨時間變化的位置。

太陽的軌跡

太陽的位置可用astrolibR套件中的函數sunpos(jd)求得。引數jd是JD,函數值的成分$ra,$dec分別是太陽位置的赤經、赤緯。例如,令s等於sunpos(jdcnv(2016,5,19,12)),則s$ra是格林威治時間2016年5月19日中午12點的時候太陽位置的赤經而s$dec是赤緯,單位是度。

台北的日出日落

將太陽的赤經緯以及台北的緯度及赤經資料,代入昇落方程式求解,解出來的JD就是台北的日出日落時間。數值計算上,我們只要計算一天之中,每一分鐘台北昇落方程式左邊的數值,前後兩分鐘數值由負變正,就找到日出時刻。前後兩分鐘數值由正變負,就找到日落時刻。不過這個時間是格林威治時間,換算成台北時區的時間還要再加上8小時。
附錄中的函數sunriseset(E,N,timezone,month,day),其函數值就是日出日落的時刻。所需引數,E是觀察地點的經度,東經為正,西經為負,N是緯度,北緯為正,南緯為負,兩者都是度度量。timezone是觀察地的時區,以小時為單位,台北時區為GMT+8,所以台北的timezone為8,布宜諾斯艾利斯的時區為GMT-4,但因實施夏令時間,布宜諾斯艾利斯的timezone為-3。由 latlong 查出城市的經緯度,由 timeanddate 查出時區,就可以用函數sunriseset算出2016年month月份day日的日出日落時刻。
用sunriseset(E,N,timezone,month,day)函數算出台北2016年每月1日的日出日落時間如下表,日出/日落欄位是sunriseset函數計算所得, sunrise/sunset欄位取自 中央氣象局 。表中,可以看出sunriseset的計算與中央氣象局的資料最多相差1分。
日出 sunrise 日落 sunset
1 1 06:39 06:39 17:15 17:15
2 1 06:37 06:37 17:38 17:38
3 1 06:17 06:17 17:56 17:56
4 1 05:45 05:46 18:10 18:10
5 1 05:18 05:18 18:24 18:24
6 1 05:04 05:04 18:40 18:40
7 1 05:08 05:08 18:48 18:48
8 1 05:21 05:21 18:39 18:39
9 1 05:35 05:35 18:13 18:13
10 1 05:46 05:46 17:41 17:41
11 1 06:01 06:01 17:13 17:13
12 1 06:22 06:22 17:04 17:04

布宜諾斯艾利斯的日出日落

接著用sunriseset(E,N,timezone,month,day)函數算出布宜諾斯艾利斯2016年每月1日的日出日落時間如下表,日出/日落欄位是sunriseset函數計算所得, sunrise/sunset欄位取自 timeanddate 。表中,可以看出sunriseset的計算與timeanddate網站的資料,完全相同。
日出 sunrise 日落 sunset
1 1 05:44 05:44 20:10 20:10
2 1 06:13 06:13 20:00 20:00
3 1 06:41 06:41 19:30 19:30
4 1 07:06 07:06 18:47 18:47
5 1 07:30 07:30 18:11 18:11
6 1 07:52 07:52 17:51 17:51
7 1 08:01 08:01 17:54 17:54
8 1 07:47 07:47 18:13 18:13
9 1 07:12 07:12 18:35 18:35
10 1 06:30 06:30 18:57 18:57
11 1 05:51 05:51 19:23 19:23
12 1 05:34 05:34 19:52 19:52

附錄


options(digits=14)
library(astrolibR)
Rad<-pi/180

N.taipei<-25.017    
E.taipei<-121.537   
timezone.taipei<- 8
correctionangle<- -50/60

sunheight<-function(t,E,N,timezone){
  jd<-t-timezone/24
  sun<-sunpos(jd)
  sx<-cos(sun$dec*Rad)*cos(sun$ra*Rad)
  sy<-cos(sun$dec*Rad)*sin(sun$ra*Rad)
  sz<-sin(sun$dec*Rad)

  theta<-ct2lst(E,timezone,jd)*pi/12
  sx*cos(N*Rad)*cos(theta)+
  sy*cos(N*Rad)*sin(theta)+
  sz*sin(N*Rad)-sin(correctionangle*Rad)
}

timestring<-function(i){
  min<-i%%60
  hour<-(i-min)/60
  sprintf("%02d:%02d",hour,min)
}

sunriseset<-function(E,N,timezone,month,day){
  minutes<-seq(1,1440)/1440
  day<-jdcnv(2016,month,day,0)
  v1<-sunheight(day+minutes[1],E,N,timezone)
  sunset<-"-"
  sunrise<-"-"
  for (i in 1:1439){
    v2<-sunheight(day+minutes[i+1],E,N,timezone)
    product<-v1*v2
    if (product<0){
      if (abs(v1)<abs(v2))
        j<-i
      else
        j<-i+1
      if (v1<0)
        sunrise<-timestring(j)
      else
        sunset<-timestring(j)
    } else if (product==0) {
      if (v1==0) {
        if (v2>0)
          sunrise<-timestring(i)
        else
          sunset<-timestring(i)
      } else {
        if (v1>0)
          sunset<-timestring(i+1)
        else
          sunrise<-timestring(i+1)
      }
    }
    v1<-v2
  }
  paste0(sunrise,"\t",sunset)
}

msg<-""
for (month in 1:12){
  day<-1
    msg<-paste0(msg,month,'\t',day,'\t',sunriseset(E.taipei,N.taipei,timezone.taipei,month,day),'\n')
}
cat(msg)

E.buenosaires<--58.381559
N.buenosaires<--34.603684
timezone.buenosaires<--3

msg<-""
for (month in 1:12){
  day<-1
    msg<-paste0(msg,month,'\t',day,'\t',sunriseset(E.buenosaires,N.buenosaires,timezone.buenosaires,month,day),'\n')
}
cat(msg)
      

MongoDB Atlas

IP 地址白名單 為了安全,mongodb atlas 資料庫,除了連線時需要提供使用者名稱與密碼之外,可以設定IP地址(IP Address)白名單。只有列在白名單的電腦可以存取它。 白名單中,可以是單一IP,也可以是 CIDR  IP 區間( CIDR -notated ra...