Gonggi 앱에서 일출 일몰 시각을 한국천문연구원에서 제공하는 공공데이터를 통한 API 호출 (https://www.data.go.kr/data/15012688/openapi.do) 을 통해 가져오도록 설계했다가, 각각의 다른 원천 데이터로부터 동기화되는 데이터의 양이 많아진 관계로, 앱 처리 성능에 영향이 발생함에 따라 해당 기능을 함수로 따로 구현해 사용하였습니다, API 를 사용하면 인터넷에 연결되어 있어야 한다는 제약을 받으면서도, 받아오기까지 최소 500 ~ 600 ms 정도의 시간이 소요되지만, 아래의 코드를 실행하면 약 28 ms 소요되므로 API 와 비교해 월등하게 빠른 처리 속도를 보장합니다.
사용 및 구현된 소스 코드와 설명은 아래와 같습니다.
private void degtorad(object deg)
{
return Math.PI * deg / (double)180;
}
private void radtodeg(object rad)
{
return 180 * rad / Math.PI;
}
private void sin(object deg)
{
return Math.Sin(degtorad(deg));
}
private void cos(object deg)
{
return Math.Cos(degtorad(deg));
}
private void tan(object deg)
{
return Math.Tan(degtorad(deg));
}
private void asin(object x)
{
return radtodeg(Math.Asin(x));
}
private void acos(object x)
{
return radtodeg(Math.Acos(x));
}
private void atan(object x)
{
return radtodeg(Math.Atan(x));
}
private void modpos(object x, object m)
{
return ((x % m) + m) % m;
}
// 위도 (lat) 와 경도 (lng) 를 나타내는 상수를 선언하는 부분입니다.
// 위도와 경도는 지구 상의 특정 위치를 나타내는 좌표입니다.
// 위도는 적도로부터 북쪽 또는 남쪽으로 얼마나 떨어져 있는지를 의미하며,
// 경도는 그리니치 천문대로부터 동쪽 또는 서쪽으로 얼마나 떨어져 있는지를 의미합니다.
private const string lat = "[위도 값]";
private const string lng = "[경도 값]";
// official': 90.833333,
// 90deg 50'
// civil': 96,
// nautical': 102,
// astronomical': 108
// 천정각은 태양이 지평선과 이루는 각도를 말합니다.
// 천정각이 "90.833333" 으로 태양이 지평선에서 정확히 90.833333 도 높이에 있을 시점으로,
// 태양이 지평선 바로 위 정오에 가장 높이 떠 있을 시점을 의미합니다.
private const var zenith = 90.833333;
private DateTimeOffset SolarCalculation(DateTime eventDate, bool isRising)
{
// 먼저 올해의 날짜를 계산합니다.
// eventDate가 1년 중 몇 번째 날인지를 계산하는 부분입니다.
// 계산한 결과는 변 N 에 저장됩니다. 태양의 위치를 계산하는 데 중요한 조건 중 하나입니다.
var N = eventDate.DayOfYear;
// 경도 값을 시간 값으로 변환하고 대략적인 시간을 계산합니다.
// 경도를 시간 값으로 변환하고, 일출 또는 일몰의 대략적인 시간을 계산하는 부분입니다.
// 계산한 결과는 변수 t 에 저장니다.
// 여기서 경도를 15 로 나누는 이유는 1 시간 당 지구가 15 도 자전하기 때문입니다.
// 따라서 경도를 15 로 나누면 해당 위치의 시간대를 얻을 수 있습니다.
var lngHour = lng / (double)15;
// t 는 1 년 중 몇 번째 날인지 (N) 와 그 날의 특정 시간대를 합친 값입니다.
// 일출이면 6 (오전 6 시), 일몰이면 18 (오후 6 시) 을 반환합니다.
var t = N + ((isRising ? 6 : 18 - lngHour) / 24);
// Calculate the Sun's mean anomaly.
// 태양의 평균 편차를 계산합니다.
// 계산한 결과는 M에 저장됩니다.
// 태양의 평균 편차는 태양이 지구 주위를 원형 궤도로 돌아가는 것처럼 가정했을 때의 위치를 뜻합니다.
// 실제로는 태양의 궤도는 완벽한 원형이 아니라 약간 타원형이므로, 결과 값은 실제 태양의 위치와 약간 차이가 있습니다.
// t 는 특정 날짜를 기준으로 한 시간입니다. 이 공식은 태양의 움직임을 근사화한 것으로, 태양이 일 년 동안 하늘을 움직이는 경로를 모형화합니다.
// M 은 평균 경도를 나타냅니다. 0.9856 은 태양이 하루에 움직이는 평균 각도입니다. -3.289은 초기 평균 경도를 조정하는 상수 입니다
var M = (0.9856 * t) - 3.289;
// Calculate the Sun's true longitude.
// 태양의 실제 경도를 계산합니다.
// 계산한 결과는 변수 L 에 저장됩니다. 실제 경도는 태양의 실제 위치를 나타냅니다.
// 이 값은 태양의 평균 편차 (M) 와 태양의 궤도가 타원형인 것을 보정하는 항목들을 합친 것입니다.
// L 은 실제 경도를 나타냅니다.
// 1.916 * sin(M) 와 0.02 * sin(2 * M)은 태양의 궤도가 완전한 원형이 아니라 타원형이므로 발생하는 경도의 변화를 보정하기 위한 계산식입니다.
// 282.634 는 초기 실제 경도를 조정하는 상수입니다.
var L = M + (1.916 * sin(M)) + (0.02 * sin(2 * M)) + 282.634;
// NOTE: L potentially needs to be adjusted into the range [0, 360] by adding / subtracting 360.
// 참고: 경도는 잠재적으로 360 을 가산하거나 차감해서 [0, 360] 범위로 조정해야 합니다.
L = modpos(L, 360);
// Calculate the Sun's right ascension
// 태양의 적경을 계산합니다.
// 여기서 0.91764 는 태양의 궤도 기울기를 나타내는 상수 입니다.
// 태양의 궤도가 지구의 적도에 대해 약 23.44 도로 기울어져 있음을 고려하여 도출된 값입니다.
// 기울기를 고려하지 않으면 태양의 위치를 정확하게 계산할 수 없습니다.
var RA = atan(0.91764 * tan(L));
// RA potentially needs to be adjusted into the range [0, 360] by adding / subtracting 360.
// 적경은 잠재적으로 360 을 가산하거나 차감해서 [0, 360] 범위로 조정해야 합니다.
// 적경을 0 도 이상 360 도 미만의 범위로 조정하는 부분입니다. 적경은 하늘에서 별이나 태양이 어디에 위치해 있는지를 나타내는 좌표 중 하나입니다.
// 적경은 보통 도 단위로 표현되며, 0 도에서 360 도 사이의 값으로 나타낼 수 있습니다.
// 해당 범위를 벗어나는 값은 혼동을 일으킬 수 있으므로, modpos 함수를 사용하여 적경을 이 범위 내로 조정합니다.
// 시계가 12 시를 넘어가면 다시 1 시로 돌아가는 것처럼, 적경도 360 도 이상이라면 이후부터는 0 도 부터 가산됩니다.
RA = modpos(RA, 360);
// Right ascension value needs to be in the same quadrant as L
// 적경 값은 경도와 동일한 사분면에 있어야 합니다.
// 적경(RA)을 실제 경도(L)와 같은 사분면에 위치하도록 조정하는 부분입니다. 이를 이해하기 위해서는 먼저 사분면에 대한 이해가 필요합니다.
// 평면을 4 개의 영역으로 나눈 각 영역을 통틀어 사분면이라 합니다.
// 보통 원이나 원형의 도표에서 사용되며, 각각의 사분면은 90 도의 각도를 가집니다.
// 따라서 360 도를 4 로 나누면 90 도가 되는데, 하나의 사분면을 의미합니다.
// 실제 경도(L)와 적경(RA)이 속한 사분면을 계산하는 부분입니다.
// Math.Floor(L / 90) * 90는 실제 경도를 90으로 나눈 후 버림하여 정수 부분만 가져온 후 다시 90을 곱합니다.
// 이를 통해 실제 경도가 속한 사분면의 시작점을 얻을 수 있습니다. 적경에 대해서도 동일한 연산을 수행합니다.
var Lquad = (Math.Floor(L / (double)90)) * 90;
var RAquad = (Math.Floor(RA / (double)90)) * 90;
// 적경을 실제 경도와 같은 사분면에 위치하도록 조정하는 부분입니다.
// Lquad - RAquad는 실제 경도와 적경이 속한 사분면 사이의 차이를 계산합니다.
// 계산한 차를 적경에 더함으로써 적경을 실제 경도와 같은 사분면에 위치하도록 조정합니다.
// 결과적으로 적경과 실제 경도가 같은 사분면에 위치하게 되므로, 태양의 위치를 보다 정확하게 계산할 수 있습니다.
RA += (Lquad - RAquad);
// Right ascension value needs to be converted into hours.
// 적경 값을 시간으로 변환합니다.
// 적경은 원래 도 단위로 표현되지만, 여기서는 시간 단위로 변환하여 사용합니다.
RA /= (double)15;
// 태양의 적위를 계산합니다.
// 태양의 적위를 계산하는 부분입니다. 여기서 0.39782 는 태양의 궤도 기울기의 사인 (Sine) 값을 나타내는 상수입니다.
// 태양의 궤도가 지구의 적도에 대해 약 23.44 도로 기울어져 있음을 고려한 것입니다.
// 태양의 기울기를 고려하지 않으면 위치를 정확하게 계산할 수 없습니다.
var sinDec = 0.39782 * sin(L);
var cosDec = cos(asin(sinDec));
// 태양의 현지 시간 각도를 계산합니다.
// 만약 계산한 결과가 1보다 크거나 -1보다 작다면, 즉 계산 결과가 유효하지 않다면 Null 을 반환합니다.
var cosH = (cos(zenith) - (sinDec * sin(lat))) / (double)(cosDec * cos(lat));
if (cosH > 1 | cosH < -1)
return default(DateTimeOffset);
// Finish calculating local hour angle and convert into hours.
// 현지 시간 각도 계산을 마치고 시간으로 변환합니다.
// If(isRising, 360 - acos(cosH), acos(cosH)) 부분은 일출이면 360 - acos(cosH), 일몰이면 acos(cosH) 를 적용합니다.
// 그리고 그 결과를 15 로 나누어 시간으로 변환합니다.
// 변수 H 는 앞서 계산한 현지 시간 각도, RA는 적경, t 는 일출 또는 일몰의 대략적인 시간을 의미합니다.
var H = isRising ? 360 - acos(cosH) : acos(cosH) / (double)15;
// Calculate local mean time of rising / setting.
// 해가 뜨거나 지는 현지 평균 시간을 계산합니다.
// 태양의 위치를 계산하는 공식의 일부입니다.
// Tt 는 해가 뜨거나 지는 현지 평균 시간을 나타냅니다.
// H 는 현지 시간 각도, RA 는 적경,
// t 는 일출 또는 일몰의 대략적인 시간을 나타냅니다.
// 0.06571 과 -6.622 는 태양의 움직임을 근사화하는 데 사용되는 상수 값입니다.
// 0.06571 은 태양의 움직임에 대한 일일 변화율을 나타내는 상수 입니다. 이 값은 태양이 하루 동안 얼마나 움직이는지를 나타내는 값으로,
// 0.06571 이라는 값에 t 를 곱하면 특정 시간에 대한 태양의 움직임을 계산할 수 있습니다.
// -6.622는 초기 시간 오프셋을 나타내는 상수로 태양의 위치를 계산할 때 사용되는 시간 체계와 실제 시간 체계 사이의 차이를 보정하는 데 사용됩니다.
// 두 가지 상수는 태양의 움직임을 정확하게 모형화하는 데 필요한 값들로, 천문학자들이 오랜 기간 수집한 데이터를 바탕으로 결정된 값들입니다.
var Tt = H + RA - (0.06571 * t) - 6.622;
// Adjust back to UTC.
// 협정 세계 시 기준으로 다시 조정합니다.
var utc = Tt - lngHour;
// 협정 세계 시를 [0, 24] 범위로 조정하는 부분입니다.
// NOTE: UTC potentially needs to be adjusted into the range [0, 24] by adding / subtracting 24.
// UTC는 잠재적으로 24 를 가산하거나 차감해서 [0, 24] 범위로 조정해야 합니다.
utc = modpos(utc, 24);
// 협정 세계 시를 시간과 분으로 변환하는 부분입니다.
int hours = Math.Floor(utc);
int minutes = Math.Round(60 * (utc - hours));
// 분 값이 60 이상이라면 시간을 1 증가시키고 분을 0 으로 설정하는 부분입니다.
// 만약 시간이 24 이상이라면 날짜 (일자) 를 1 증가시키고 시간을 0 으로 설정하는 부분입니다.
if (minutes == 60)
{
minutes = 0;
hours += 1;
if (hours == 24)
{
hours = 0;
eventDate.AddDays(1);
}
}
// 계산한 결과를 DateTimeOffset 타입으로 변환하고 반환하는 부분입니다. 만약 일출 시간을 계산했다면, 반환되는 시간에서 하루를 차감합니다.
// SolarCalculation 함수는 주어진 날짜에 대해 일출 또는 일몰 시간을 계산하는 역할을 합니다.
DateTimeOffset dateOut = new DateTimeOffset(eventDate.Year, eventDate.Month, eventDate.Day, hours, minutes, 0, new TimeSpan(0, 0, 0));
return isRising ? dateOut.AddDays(-1) : dateOut;
}
실행 및 구현 결과



위의 소스 코드를 이용한 함수를 이용해 도출한 결과 (일출, 일몰 시각) 를 한국천문연구원 천문우주지식정보 에서 제공하는 월별 해 / 달 출몰 시각 정보와 비교하며 신뢰성을 검증해보았습니다. 한 달 (31 일) 간 일출 일몰 시각 모두 일치하는 항목은 총 10 개 정도로 32.2%, 일출 일몰 시각 중 하나가 일치하면서 ± 1 분의 차이를 보이는 항목은 총 19 개 정도로 61.3%, 일출, 일몰 시간이 모두 일치하지 않았지만 ± 1 분의 차이를 보이는 항목은 2 개 정도로 6.5 % 정도의 비율을 보여주고 있습니다.

다만, 한국천문연구원에서 제공하는 일출 / 일몰, 월출 / 월몰 정보의 경우 위도, 경도나 읍, 면, 동 단위 기준이 아닌, 시 / 도 단위를 기준으로 도출되는 정보로서 보다 포괄적이고, 넓은 범위를 기준으로 계산된 결과임을 감안한다면 윗 코드의 신뢰도는 실생활에 적용할 수 있을 수준의 신뢰도를 보여준다 로 정리될 수 있습니다.
추가 링크
https://gml.noaa.gov/grad/solcalc/calcdetails.html
https://edwilliams.org/sunrise_sunset_algorithm.htm
https://astro.kasi.re.kr/life/pageView/6
고맙습니다.
