[解決済み] 太陽の位置(時間帯、緯度、経度)。
質問
この質問は 以前 という質問で、3年ちょっと前に回答がありました。回答はあったのですが、解決策に不具合が見つかりました。
以下のコードはRで書かれています。私はそれを別の言語に移植しましたが、問題は私の移植に起因するものではないことを確認するために、元のコードを直接Rでテストしています。
sunPosition <- function(year, month, day, hour=12, min=0, sec=0,
lat=46.5, long=6.5) {
twopi <- 2 * pi
deg2rad <- pi / 180
# Get day of the year, e.g. Feb 1 = 32, Mar 1 = 61 on leap years
month.days <- c(0,31,28,31,30,31,30,31,31,30,31,30)
day <- day + cumsum(month.days)[month]
leapdays <- year %% 4 == 0 & (year %% 400 == 0 | year %% 100 != 0) & day >= 60
day[leapdays] <- day[leapdays] + 1
# Get Julian date - 2400000
hour <- hour + min / 60 + sec / 3600 # hour plus fraction
delta <- year - 1949
leap <- trunc(delta / 4) # former leapyears
jd <- 32916.5 + delta * 365 + leap + day + hour / 24
# The input to the Atronomer's almanach is the difference between
# the Julian date and JD 2451545.0 (noon, 1 January 2000)
time <- jd - 51545.
# Ecliptic coordinates
# Mean longitude
mnlong <- 280.460 + .9856474 * time
mnlong <- mnlong %% 360
mnlong[mnlong < 0] <- mnlong[mnlong < 0] + 360
# Mean anomaly
mnanom <- 357.528 + .9856003 * time
mnanom <- mnanom %% 360
mnanom[mnanom < 0] <- mnanom[mnanom < 0] + 360
mnanom <- mnanom * deg2rad
# Ecliptic longitude and obliquity of ecliptic
eclong <- mnlong + 1.915 * sin(mnanom) + 0.020 * sin(2 * mnanom)
eclong <- eclong %% 360
eclong[eclong < 0] <- eclong[eclong < 0] + 360
oblqec <- 23.429 - 0.0000004 * time
eclong <- eclong * deg2rad
oblqec <- oblqec * deg2rad
# Celestial coordinates
# Right ascension and declination
num <- cos(oblqec) * sin(eclong)
den <- cos(eclong)
ra <- atan(num / den)
ra[den < 0] <- ra[den < 0] + pi
ra[den >= 0 & num < 0] <- ra[den >= 0 & num < 0] + twopi
dec <- asin(sin(oblqec) * sin(eclong))
# Local coordinates
# Greenwich mean sidereal time
gmst <- 6.697375 + .0657098242 * time + hour
gmst <- gmst %% 24
gmst[gmst < 0] <- gmst[gmst < 0] + 24.
# Local mean sidereal time
lmst <- gmst + long / 15.
lmst <- lmst %% 24.
lmst[lmst < 0] <- lmst[lmst < 0] + 24.
lmst <- lmst * 15. * deg2rad
# Hour angle
ha <- lmst - ra
ha[ha < -pi] <- ha[ha < -pi] + twopi
ha[ha > pi] <- ha[ha > pi] - twopi
# Latitude to radians
lat <- lat * deg2rad
# Azimuth and elevation
el <- asin(sin(dec) * sin(lat) + cos(dec) * cos(lat) * cos(ha))
az <- asin(-cos(dec) * sin(ha) / cos(el))
elc <- asin(sin(dec) / sin(lat))
az[el >= elc] <- pi - az[el >= elc]
az[el <= elc & ha > 0] <- az[el <= elc & ha > 0] + twopi
el <- el / deg2rad
az <- az / deg2rad
lat <- lat / deg2rad
return(list(elevation=el, azimuth=az))
}
私が直面している問題は、それが返す方位が誤っているように見えることです。たとえば、0ºE と 41ºS, 3ºS, 3ºN と 41ºN の位置で 12:00 の (南) 夏至にこの関数を実行すると、次のようになります。
> sunPosition(2012,12,22,12,0,0,-41,0)
$elevation
[1] 72.42113
$azimuth
[1] 180.9211
> sunPosition(2012,12,22,12,0,0,-3,0)
$elevation
[1] 69.57493
$azimuth
[1] -0.79713
Warning message:
In asin(sin(dec)/sin(lat)) : NaNs produced
> sunPosition(2012,12,22,12,0,0,3,0)
$elevation
[1] 63.57538
$azimuth
[1] -0.6250971
Warning message:
In asin(sin(dec)/sin(lat)) : NaNs produced
> sunPosition(2012,12,22,12,0,0,41,0)
$elevation
[1] 25.57642
$azimuth
[1] 180.3084
これらの数値は正しいとは思えません。最初の 2 つはほぼ同じで、3 つ目は少し低く、4 つ目はかなり低くなるはずです。しかし、最初の方位はほぼ真北であるべきなのに、この数値は全く逆です。残りの3つはほぼ真南を向いているはずだが、最後の1つだけがそうなっている。真ん中の2つは北にずれており、やはり180°ずれています。
ご覧のように、低緯度(赤道に近い)地域では、いくつかのエラーが発生します。
私はこの部分に原因があると考えており、エラーは3行目(冒頭の
elc
).
# Azimuth and elevation
el <- asin(sin(dec) * sin(lat) + cos(dec) * cos(lat) * cos(ha))
az <- asin(-cos(dec) * sin(ha) / cos(el))
elc <- asin(sin(dec) / sin(lat))
az[el >= elc] <- pi - az[el >= elc]
az[el <= elc & ha > 0] <- az[el <= elc & ha > 0] + twopi
ググってみると、C言語で書かれた同じようなコードの塊が見つかりました。
az <- atan(sin(ha) / (cos(ha) * sin(lat) - tan(dec) * cos(lat)))
この出力は正しい方向に向かっているように見えますが、度数に変換したときに常に正しい答えを出すようにすることはできません。
正しい方位を計算させるためのコードの修正(上記の数行だけだと思われます)は、素晴らしいことです。
どのように解決するのですか?
これは重要なトピックのようなので、通常より長い回答を投稿しました。もしこのアルゴリズムが将来他の人に使われるのであれば、それが導き出された文献への参照を伴うことが重要だと考えています。
短い回答
ご指摘のとおり、投稿されたコードは、赤道付近や南半球の場所では正しく動作しません。
これを修正するには、元のコードのこれらの行を置き換えるだけです。
elc <- asin(sin(dec) / sin(lat))
az[el >= elc] <- pi - az[el >= elc]
az[el <= elc & ha > 0] <- az[el <= elc & ha > 0] + twopi
をこれらと一緒に
cosAzPos <- (0 <= sin(dec) - sin(el) * sin(lat))
sinAzNeg <- (sin(az) < 0)
az[cosAzPos & sinAzNeg] <- az[cosAzPos & sinAzNeg] + twopi
az[!cosAzPos] <- pi - az[!cosAzPos]
これで、地球上のどの場所でも動作するようになったはずです。
ディスカッション
あなたの例のコードは、J.J. Michalsky (Solar Energy. 40:227-235) による1988年の記事からほぼそのまま採用されています。その記事は、R. Walraven による 1978 年の記事 (Solar Energy. 20:393-397) で提示されたアルゴリズムを改良したものです。 Walravenは、この方法がカリフォルニア州デービス(北緯38° 33' 14" 西経121° 44' 17" )の偏光放射計を正確に位置づけるために数年間使用され、成功を収めたと報告しています。
Michalsky と Walraven の両方のコードには、重要な/致命的なエラーが含まれています。
Michalsky 特に、Michalsky のアルゴリズムは米国のほとんどの地域でうまく機能しますが、赤道付近や南半球の地域では (あなたが発見したように) 失敗しています。1989年、オーストラリアのビクトリア州のJ.W.スペンサーも同じことを指摘しています(Solar Energy. 42(4):353):
拝啓。
計算された方位を正しい象限に割り当てるMichalskyの方法は、Walravenから派生したものですが、南(負)緯度に適用すると正しい値を与えません。また、臨界高度(elc)の計算も緯度がゼロの場合、ゼロで割ってしまうので失敗します。これらの反論は両方とも、cos(方位)の符号を考慮することによって正しい象限に方位を割り当てることで簡単に回避することができます。
あなたのコードへの私の編集は、その公開されたコメントでSpencerによって提案された修正に基づきます。私は単にそれらを多少変更し、R 関数
sunPosition()
が「ベクトル化」されたままであること(つまり、一度に1つの点を渡される必要があるのではなく、点の位置のベクトル上で適切に動作すること)を保証するために、それらを多少変更しただけです。
関数の精度
sunPosition()
をテストするために
sunPosition()
が正しく動作することを確認するために、その結果を米国海洋大気庁(National Oceanic and Atmospheric Administration)の
ソーラー計算機
. いずれも、2012年南半球の夏至(12月22日)の正午(午後12時)の太陽位置を計算したものです。結果はすべて0.02度以内で一致しています。
testPts <- data.frame(lat = c(-41,-3,3, 41),
long = c(0, 0, 0, 0))
# Sun's position as returned by the NOAA Solar Calculator,
NOAA <- data.frame(elevNOAA = c(72.44, 69.57, 63.57, 25.6),
azNOAA = c(359.09, 180.79, 180.62, 180.3))
# Sun's position as returned by sunPosition()
sunPos <- sunPosition(year = 2012,
month = 12,
day = 22,
hour = 12,
min = 0,
sec = 0,
lat = testPts$lat,
long = testPts$long)
cbind(testPts, NOAA, sunPos)
# lat long elevNOAA azNOAA elevation azimuth
# 1 -41 0 72.44 359.09 72.43112 359.0787
# 2 -3 0 69.57 180.79 69.56493 180.7965
# 3 3 0 63.57 180.62 63.56539 180.6247
# 4 41 0 25.60 180.30 25.56642 180.3083
その他のコードのエラー
投稿されたコードには、少なくとも 2 つの他の (非常に小さな) エラーがあります。1 つは、うるう年の 2 月 29 日と 3 月 1 日が両方ともその年の 61 日目として集計されることです。2つ目の誤りは、原著論文のタイプミスに由来するもので、Michalskyが1989年のノートで訂正しています(Solar Energy. 43(5):323).
このコードブロックは、コメントアウトされ、すぐに修正されたバージョンが続く、問題のある行を示します。
# leapdays <- year %% 4 == 0 & (year %% 400 == 0 | year %% 100 != 0) & day >= 60
leapdays <- year %% 4 == 0 & (year %% 400 == 0 | year %% 100 != 0) &
day >= 60 & !(month==2 & day==60)
# oblqec <- 23.429 - 0.0000004 * time
oblqec <- 23.439 - 0.0000004 * time
の修正版
sunPosition()
上記で検証した修正版のコードを掲載します。
sunPosition <- function(year, month, day, hour=12, min=0, sec=0,
lat=46.5, long=6.5) {
twopi <- 2 * pi
deg2rad <- pi / 180
# Get day of the year, e.g. Feb 1 = 32, Mar 1 = 61 on leap years
month.days <- c(0,31,28,31,30,31,30,31,31,30,31,30)
day <- day + cumsum(month.days)[month]
leapdays <- year %% 4 == 0 & (year %% 400 == 0 | year %% 100 != 0) &
day >= 60 & !(month==2 & day==60)
day[leapdays] <- day[leapdays] + 1
# Get Julian date - 2400000
hour <- hour + min / 60 + sec / 3600 # hour plus fraction
delta <- year - 1949
leap <- trunc(delta / 4) # former leapyears
jd <- 32916.5 + delta * 365 + leap + day + hour / 24
# The input to the Atronomer's almanach is the difference between
# the Julian date and JD 2451545.0 (noon, 1 January 2000)
time <- jd - 51545.
# Ecliptic coordinates
# Mean longitude
mnlong <- 280.460 + .9856474 * time
mnlong <- mnlong %% 360
mnlong[mnlong < 0] <- mnlong[mnlong < 0] + 360
# Mean anomaly
mnanom <- 357.528 + .9856003 * time
mnanom <- mnanom %% 360
mnanom[mnanom < 0] <- mnanom[mnanom < 0] + 360
mnanom <- mnanom * deg2rad
# Ecliptic longitude and obliquity of ecliptic
eclong <- mnlong + 1.915 * sin(mnanom) + 0.020 * sin(2 * mnanom)
eclong <- eclong %% 360
eclong[eclong < 0] <- eclong[eclong < 0] + 360
oblqec <- 23.439 - 0.0000004 * time
eclong <- eclong * deg2rad
oblqec <- oblqec * deg2rad
# Celestial coordinates
# Right ascension and declination
num <- cos(oblqec) * sin(eclong)
den <- cos(eclong)
ra <- atan(num / den)
ra[den < 0] <- ra[den < 0] + pi
ra[den >= 0 & num < 0] <- ra[den >= 0 & num < 0] + twopi
dec <- asin(sin(oblqec) * sin(eclong))
# Local coordinates
# Greenwich mean sidereal time
gmst <- 6.697375 + .0657098242 * time + hour
gmst <- gmst %% 24
gmst[gmst < 0] <- gmst[gmst < 0] + 24.
# Local mean sidereal time
lmst <- gmst + long / 15.
lmst <- lmst %% 24.
lmst[lmst < 0] <- lmst[lmst < 0] + 24.
lmst <- lmst * 15. * deg2rad
# Hour angle
ha <- lmst - ra
ha[ha < -pi] <- ha[ha < -pi] + twopi
ha[ha > pi] <- ha[ha > pi] - twopi
# Latitude to radians
lat <- lat * deg2rad
# Azimuth and elevation
el <- asin(sin(dec) * sin(lat) + cos(dec) * cos(lat) * cos(ha))
az <- asin(-cos(dec) * sin(ha) / cos(el))
# For logic and names, see Spencer, J.W. 1989. Solar Energy. 42(4):353
cosAzPos <- (0 <= sin(dec) - sin(el) * sin(lat))
sinAzNeg <- (sin(az) < 0)
az[cosAzPos & sinAzNeg] <- az[cosAzPos & sinAzNeg] + twopi
az[!cosAzPos] <- pi - az[!cosAzPos]
# if (0 < sin(dec) - sin(el) * sin(lat)) {
# if(sin(az) < 0) az <- az + twopi
# } else {
# az <- pi - az
# }
el <- el / deg2rad
az <- az / deg2rad
lat <- lat / deg2rad
return(list(elevation=el, azimuth=az))
}
参考文献
Michalsky, J.J. 1988. 天文年鑑の太陽位置の近似値アルゴリズム(1950-2050)。太陽エネルギー。40(3):227-235.
Michalsky, J.J. 1989. Errata. 太陽エネルギー。43(5):323.
スペンサー、J.W.1989。The Astronomical Almanac's Algorithm for Approximate Solar Position (1950-2050) "へのコメント。太陽エネルギー。42(4):353.
Walraven, R. 1978. 太陽の位置の計算. 太陽エネルギー。20:393-397.
関連
-
R言語のエラーメッセージと関連する解決策
-
データボックス内の行/列の削除/追加を行うR言語
-
Rのexpand.grid()コマンド
-
[解決済み] 文字列ベクトルを代入して、列名を持つ空のデータフレームを作成する?重複
-
[解決済み] 簡単な面接問題が難しくなった:1~100の数字が与えられたとき、ちょうどk個の数字が欠けていることを見つけなさい。
-
[解決済み] JavaScriptで整数の除算を行い、余りを別途取得する方法は?
-
[解決済み] グループ化関数(tapply、by、aggregate)と*applyファミリ
-
[解決済み] 2つの緯度経度点間の距離を計算する?(ハバーシンの公式)
-
[解決済み] データフレーム列の名前によるドロップ
-
[解決済み] Rの代入演算子"="と"<-"の違いは何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
8.2 カマグラ(No.31〜No.40)
-
R: hclust(d, method = method)でのエラー : 外部関数呼び出しは NA/NaN/Inf(arg10) を持つことができません。
-
[解決策】 plot.new() のエラー:図の余白が大きすぎる。
-
ggplot2 からグリッドと背景色を削除する。
-
[解決済み] データフレームを結合(マージ)する方法(内側、外側、左側、右側)
-
[解決済み] xkcd風のグラフを作るには?
-
[解決済み] 情報を損なわずに因数を整数値に変換するには?
-
[解決済み] Rでオブジェクト(変数)が定義されているかどうかを確認するには?
-
[解決済み】ggplot2で軸のタイトルやラベルの大きさを変更する。
-
[解決済み】エラー:Rで関数が見つかりませんでした。