티스토리 뷰

어떤 솔루션이 일출, 일몰 시간을 기반으로 판단할 때 서버에서 일출/일몰 시간이 필요한데요. 다만 각 나라별로 일출, 일몰의 시간이 다르기 때문에 정확한 값은 일출, 일몰 시간을 제공해주는 자료를 통해서 확인 할 수 있습니다.

제공하는 API 사용하는 방법

https://sunrise-sunset.org/api

 

Sunset and sunrise times API - Sunrise-Sunset.org

Sunset and sunrise times API We offer a free API that provides sunset and sunrise times for a given latitude and longitude. Please note that attribution is required if you use our API. Check "Usage limits and attribution" section below for more information

sunrise-sunset.org

바로 위 사이트에서 제공하는 API를 사용하면 되는데요. 해당 서버에다가 요청하고 결과를 받는 REST 방식이다 보니 500ms 정도의 시간이 필요합니다. 우리가 만들 서버의 경우 이 정보를 받아와서 DB에 접근하고 어떠한 계산을 하게되면 기본적으로 500ms 를 초과하는 요청이 될 것인데 1회 요청에의 응답속도가 이렇게 느리다면 클라이언트에서는 굉장히 답답함을 느낄 수 있을 것입니다. 오늘 소개해드릴 일출, 일몰 시간에 대한 것은 GPS 정보 기반으로 계산하는 방법입니다.

  • lat (float): Latitude in decimal degrees. Required.
  • lng (float): Longitude in decimal degrees. Required.
  • date (string): Date in YYYY-MM-DD format. Also accepts other date formats and even relative date formats. If not present, date defaults to current date. Optional.
  • callback (string): Callback function name for JSONP response. Optional.
  • formatted (integer): 0 or 1 (1 is default). Time values in response will be expressed following ISO 8601 and day_length will be expressed in seconds. Optional.

 

사용 예

https://api.sunrise-sunset.org/json?lat=36.7201600&lng=-4.4203400&date=2023-1-16

결과

{"results":{"sunrise":"7:27:30 AM","sunset":"5:27:11 PM","solar_noon":"12:27:21 PM","day_length":"09:59:41","civil_twilight_begin":"7:00:50 AM","civil_twilight_end":"5:53:51 PM","nautical_twilight_begin":"6:29:04 AM","nautical_twilight_end":"6:25:37 PM","astronomical_twilight_begin":"5:58:02 AM","astronomical_twilight_end":"6:56:39 PM"},"status":"OK"}

오늘은 아침 7시27분30초에 해가 뜨고, 5시27분 11초에 해가지네요, 지금 글을 쓰는 시각은 저녁 6시를 막 넘었으니, 앗 집에가야 겠군요!

직접 계산하는 방법

이론

noaa.gov (미국 해양대기청)에서 낸 공식 자료로, 위도, 경도, 타임존 입력값을 통해 정확한 일출 일몰 시간을 구하는 수학식을 기술해놓았습니다.

구현

from math import pi, sin, cos, tan, acos, asin, atan, atan2
from datetime import datetime
import time

HH = 0
MM = 1

SUN_DIAMETER = 0.53
AIR_REF = (34.0/60.0)


def is_leap_year(year):
    if year % 4 == 0 and year % 100 == 0 and year % 400 != 0:
        return False
    elif year % 4 == 0 and year % 100 != 0:
        return True
    elif year % 4 == 0 and year % 100 == 0 and year % 400 == 0:
        return True
    else:
        return False


def get_julian_day(year, month, day):
    tmp = -7.0 * (float(year) + (float(month) + 9.0) / 12.0) / \
        4.0 + 275.0 * float(month) / 9.0 + float(day)
    tmp += float(year * 367)
    return (tmp - 730531.5 + 12.0 / 24.0)


def get_range_radian(x):
    b = float(x / (2*pi))
    a = float((2*pi) * (b - int(b)))

    if a < 0:
        a = (2*pi) + a
    return a


def get_ha(lat, decl):
    dfo = pi/180.0 * (0.5 * SUN_DIAMETER + AIR_REF)
    if lat < 0.0:
        dfo = -dfo
    fo = tan(decl + dfo) * tan(lat * pi / 180.0)
    if fo > 1.0:
        fo = 1.0
    fo = asin(fo) + pi / 2.0

    return fo


def get_sun_longitude(days):
    longitude = get_range_radian(
        280.461 * pi / 180.0 + 0.9856474 * pi/180.0 * days)
    g = get_range_radian(357.528 * pi/180.0 + 0.9856003 * pi/180.0 * days)

    return get_range_radian(longitude + 1.915 * pi/180.0 * sin(g) + 0.02 * pi/180.0 * sin(2*g)), longitude


def convert_dtime_to_rtime(dhour):
    hour = int(dhour)
    minute = int((dhour - hour) * 60)

    return hour, minute


def calculate_sunset_sunrise(latitude, longitude, timezone):
    today = datetime.today()

    days = get_julian_day(today.year, today.month, today.day)
    gamma, mean_longitude = get_sun_longitude(days)
    obliq = 23.439 * pi/180.0 - 0.0000004 * pi/180.0 * days

    alpha = atan2(cos(obliq)*sin(gamma), cos(gamma))
    delta = asin(sin(obliq)*sin(gamma))

    ll = mean_longitude - alpha

    if mean_longitude < pi:
        ll += (2*pi)
    eq = 1440.0 * (1.0 - ll / (2*pi))

    ha = get_ha(latitude, delta)

    sunrise = 12.0 - 12.0 * ha/pi + timezone - longitude/15.0 + eq/60.0
    sunset = 12.0 + 12.0 * ha/pi + timezone - longitude/15.0 + eq/60.0

    if sunrise > 24.0:
        sunrise -= 24.0
    if sunset > 24.0:
        sunset -= 24.0

    sunrise_time = [0, 0]
    sunset_time = [0, 0]

    sunrise_time[HH], sunrise_time[MM] = convert_dtime_to_rtime(sunrise)
    sunset_time[HH], sunset_time[MM] = convert_dtime_to_rtime(sunset)

    ret_sunrise, ret_sunset = "", ""

    if sunrise_time[HH] < 10:
        ret_sunrise += "0"
    ret_sunrise += str(sunrise_time[HH])
    ret_sunrise += ":"
    if sunrise_time[MM] < 10:
        ret_sunrise += "0"
    ret_sunrise += str(sunrise_time[MM]-1)

    if sunset_time[HH] < 10:
        ret_sunset += "0"
    ret_sunset += str(sunset_time[HH])
    ret_sunset += ":"
    if sunset_time[MM] < 10:
        ret_sunset += "0"
    ret_sunset += str(sunset_time[MM]+1)

    return ret_sunrise, ret_sunset


if __name__ == '__main__':
    s = time.time()
    sunrise, sunset = calculate_sunset_sunrise(37.5642135, 127.0016985, 9)
    # sunrise, sunset = calculate_sunset_sunrise(-33.8674769, 151.2069776, 11)
    print("Sunrise:", sunrise)
    print("Sunset:", sunset)
    e = time.time()

    print("time:", (e-s)*1000, "ms")

위 코드와 같이 calculate_sunset_sunrise는 위도와 경도 값 그리고 타임존 시간 값을 기반으로 계산되어 집니다. 물론 그 안에 들어가면 날짜정보도 필요하구요. 편의상 오늘 날짜를 넣었습니다. 그리고 식에 따라서 계산합니다. 이렇게 했을 때 실행시간은 0.13ms 걸렸기 때문에 API를 사용했을때 500ms 에 비행 엄청나게 빠른 레이턴시를 나타냅니다.

어떤 방식으로 하던 굉장히 틀린 결과를 내지는 않습니다. 일출/일몰의 시간이 정말 1초도 차이나는 서비스를 만들지 않는 이상 어느정도의 오차는 크게 영향이 없을 것인데, 혹시나 자신의 서비스가 지향하는 방식은 무엇으로 해야하는지 상황에 맞게 API를 가져다 쓰거나, 직접 계산해서 사용하시면 됩니다.

 

댓글