Coding History/Team Project

팀플) REST API 일몰 일출 정보 데이터 시간의 정확성 올리기. (Google Time Zone API)

BlackBirdIT 2024. 10. 11. 12:50

시간의 정확성 올리기 를 하기위해서 google time zone apipython프로젝트에 도입할 생각이다.

구글로그인을 이미 Spring에서 구현을 해서 구글 클라우드 콘솔에 API KEY는 있다. 여기서 끌어오면 될듯!

그래서 이걸 검색해서 추가해주자.

추가하면 거주지와 카드 정보 등록이 있는데, 데이터 한도가 초과하지 않는한 결제 될 일은 없으니 걱정말고 일단 생성하자.(초과에 임박하면 알림 설정도 있더라)

이것도 혹시 모르니까 설정해두자. 데이터때문에 200달러를 날리고 싶지는 않으니까..

도커 컨테이너 주소로 설정해줬다.

이건 비번은 필요없댄다.

여튼 이제는 python 프로젝트로 가져가보자.

우선 api KEYSpringReact에서 했던 것과 동일하게 .env를 통해서 하자.

pip install python-dotenv

이 라이브러리도 설치해주고.

도커도 .env을 읽어와야하기 때문에 docker-compose.yml 파일 생성후 간단한 설정을 해주자.

version: '3.8'

services:
  app:
    build: .
    container_name: star-info-api-container
    ports:
      - "5555:5000"
    env_file:
      - .env
    volumes:
      - .:/app
    command: ["python", "run.py"]

이렇게.

이제 servicesget_timezone_info.py생성.
코드짜고...

# get_timezone_info.py

import requests
import os


def get_timezone_info(lat, lon, timestamp):
    api_key = os.getenv('GOOGLE_TIMEZONE_API_KEY')
    if not api_key:
        raise ValueError("Google Time Zone API key is not set in environment variables.")

    base_url = "https://maps.googleapis.com/maps/api/timezone/json"

    params = {
        'location': f'{lat},{lon}',
        'timestamp': timestamp,
        'key': api_key
    }

    response = requests.get(base_url, params=params)

    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f"API request failed with status code {response.status_code}: {response.text}")

일단은 이렇게 했다.

그니까 내가 할 작업은 UTD 시간현지 시간으로 변환후에 그 시간 데이터를 제공하는 것.

sunrise_sunset_service.py의 로직 수정이 필요하다. 기존의 UTC 시간을 현지 시간으로 바꿔주기만 하면 된다.

# services/sunrise_sunset_service.py

from datetime import datetime, timedelta
from skyfield.api import Topos, N, E
from skyfield import almanac
from app.global_resources import ts, planets  # 전역 리소스 임포트
from services.get_timezone_info import get_timezone_info  # 타임존 정보 가져오는 함수 import


def convert_to_local_time(utc_time, offset_sec):
    return utc_time + timedelta(seconds=offset_sec)


def calculate_sunrise_sunset(latitude, longitude, date):
    """
    주어진 위치(위도, 경도)와 날짜에 대한 일출 및 일몰 시간을 계산하는 함수 (현지 시간 기준)

    Args:
        latitude (float): 위도
        longitude (float): 경도
        date (datetime): 일출 및 일몰을 계산할 날짜

    Returns:
        dict: 일출 및 일몰 시간이 포함된 딕셔너리 (현지 시간 기준)
    """
    location = Topos(latitude * N, longitude * E)

    # 해당 날짜의 시작 시간과 끝 시간 설정
    t0 = ts.utc(date.year, date.month, date.day)
    t1 = ts.utc(date.year, date.month, date.day, 23, 59, 59)

    # 일출 및 일몰 시간 계산
    times, events = almanac.find_discrete(t0, t1, almanac.sunrise_sunset(planets, location))

    sunrise_utc = None
    sunset_utc = None
    for t, event in zip(times, events):
        if event == 0:  # 0은 일출
            sunrise_utc = t.utc_datetime()
        elif event == 1:  # 1은 일몰
            sunset_utc = t.utc_datetime()

    if sunrise_utc is None or sunset_utc is None:
        return {"error": "일출 또는 일몰 시간을 계산할 수 없습니다."}

    # Google Time Zone API를 사용하여 타임존 정보 가져오기
    timestamp = int(sunrise_utc.timestamp())  # timestamp는 일출 시간 기준으로 사용
    try:
        timezone_info = get_timezone_info(latitude, longitude, timestamp)
        offset_sec = timezone_info['rawOffset'] + timezone_info.get('dstOffset', 0)
    except Exception as e:
        return {"error": f"타임존 정보를 가져오는 데 실패했습니다: {str(e)}"}

    # UTC 시간 -> 현지 시간으로 변환
    sunrise_local = convert_to_local_time(sunrise_utc, offset_sec)
    sunset_local = convert_to_local_time(sunset_utc, offset_sec)

    return {
        "sunrise": sunrise_local.isoformat(),
        "sunset": sunset_local.isoformat(),
    }


__all__ = ['calculate_sunrise_sunset']

이렇게 완료했다. 실상 코드는 그렇게 많이 추가되진 않았다.

라우터에 전달하는 값도 어차피 시간인 것은 같으니 바뀔것은 없다. 이제 요청 보내고 결과 확인하면 된다.

인 줄 알았으나.

하하하. 중간에 도커 한번 켜볼걸...

No module named 'services' 이거 읽어보니까 services 폴더를 못찾는 것 같으니까

from .get_timezone_info import get_timezone_info  # 타임존 정보 가져오는 함수 import

상대경로로 만들어서 실행해보자.

오케이!! 상대경로로 하니까 잘된다. 뭐가 이렇게 민감하냐 진짜. 뭐 할 때 마다 무섭다 진짜로.

아무튼 요청해보자.

요청 목록

  1. 서울의 일출/일몰 시간 요청 (Latitude: 37.5665, Longitude: 126.9780, Date: 2024-01-01)
http://localhost:5555/api/sunrise_sunset?lat=37.5665&lon=126.9780&date=2024-01-01

** - 예상 응답:**

    {
    "location": {
        "latitude": 37.5665,
        "longitude": 126.978
    },
    "date": "2024-01-01",
    "sunrise_sunset": {
        "sunrise": "2024-01-01T07:43:00+09:00",  // 예시 (현지 시간대)
        "sunset": "2024-01-01T17:15:00+09:00"    // 예시 (현지 시간대)
    }
}
  1. 뉴욕의 일출/일몰 시간 요청 (Latitude: 40.7128, Longitude: -74.0060, Date: 2024-01-01)
http://localhost:5555/api/sunrise_sunset?lat=40.7128&lon=-74.0060&date=2024-01-01

** - 예상 응답:**

{
    "location": {
        "latitude": 40.7128,
        "longitude": -74.006
    },
    "date": "2024-01-01",
    "sunrise_sunset": {
        "sunrise": "2024-01-01T07:20:00-05:00",  // 예시 (현지 시간대, 뉴욕은 UTC-5)
        "sunset": "2024-01-01T16:40:00-05:00"    // 예시 (현지 시간대)
    }
}

  1. 예기치 못한 상황 (잘못된 위도/경도 값 예: Latitude: 'abc', Longitude: 126.9780, Date: 2024-01-01)
http://localhost:5555/api/sunrise_sunset?lat=abc&lon=126.9780&date=2024-01-01

** - 예상 응답:**

{
    "error": "Invalid Latitude or Longitude format. Please provide valid numerical values."
}

이렇게 진행할 것이고, 예상응답도 아래에 썼다.


검증

1번 상황

처음부터 될리가 없지 ㅋㅋㅋ 뭐가 문제인지 확인해봐야겠네.

아 뭐가 문젠지 알 것 같다. 일단 .env의 파일을 못불러와서 환경변수로 설정한 API KEY를 못가져온게 원인인데,

내가 설치만 하고 requirements.txt에 추가를 안해줬다. 이걸 추가하지 않으면 도커에서 빌드할 때 해당 라이브러리가 없이 진행된다. 아마 그래서 못가져온듯. 로그 찍어보려고 하다가 확인해봤는데 로그 지워야겠네.

여튼 다시 ㄱㄱ.

오케이 서버 잘 켜지고.

다시 1번 상황.

오케이! API키는 가져온다! 이건 로직을 고치면 되는 문제다.

우선 뭐가 문제였냐면 'UTC'와 '일광 절약 시간제'에 대해서 알아야한다.

UTC는 뭐 다들 알건데 국제적인 표준 시간의 기준을 말 하는 것이고, '일광 절약 시간제'는 간단히 말해서 서머 타임, 즉 하절기에 표준시를 원래 시간보다 한 시간 앞당긴 시간을 쓰는 것을 말한다. 해가 오래뜨는 특정한 지역에서 도입하는 제도인데, 한국은 해당 사항이 없긴하지만 난 지금 외국의 시간까지 일몰 일출이 정확했으면 좋겠어서 이렇게 만들고 있는것,

그에 대한 계산을 정확히 하기 위해서 rawOffsetdstOffset을 사용중이였는데 이 값이 비어서 오류가 난 것이다.

그래서 이 값을 API에서 받아와서 적용을 한 것이였는데 rawOffset값이 제대로 오지 않아서 오류가 발생한 것이다. 쓰다보니까 써머타임이 있는 국가를 설정하지 않아서 if문으로 관리했다.

        # 일광 절약 시간 적용 여부에 따른 오프셋 계산
        if dst_offset != 0:
            offset_sec = raw_offset + dst_offset
        else:
            offset_sec = raw_offset

저 값이 0이 아니라면 써머타임 중이라는 뜻.

여튼 본론으로 가서 왜 rawOffset값이 오지 않았는가? 에 대한 고민을 할 필요가 있다.

print("Time Zone API Response:", timezone_info)  # 응답 내용 출력

로그 찍어봐야지 뭐,,

일단 로그 결과를 보면

대충 해석하면 내가 돈이 나갈까봐 무서워서 제한을 걸어뒀는데 그거 때문에 이렇게 된듯??

그래서 서버를 열 때

이 url도 추가해줬다. 실제 내가 요청하는 url도 함께.

이렇게.

다시 해보자!

추가해도 안되네.

그냥 제한 해제 해야겠다 귀찮다.

Time Zone API Response: {'dstOffset': 0, 'rawOffset': 32400, 
'status': 'OK', 'timeZoneId': 'Asia/Seoul', 
'timeZoneName': 'Korean Standard Time'}

해제하니까 로그도 잘 찍히고,

결과도 결과처럼 잘 나왔다.

근데 잘 보면 sunset이 다음날로 넘어감 ㅋㅋㅋ

수정하자..

일단 그것도 수정까지는 했는데 결과물이 내 생각과 다르게, 그러니까 sunsetsunries의 결과 값이 바뀐 것 같다. 해가 오전 5시에 지지는 않을거 아냐? 그래서 그 부분도 보완.


보완 후 검증

이젠 결과가 그럴듯 하다.

실제 검색해서 오차가 얼마나 있는지 확인해보자.

해당 사이트의 자료고 나의 결과와 비교하면 일출은 17시 23분으로 일치하고, 일몰은 23년 12월 31일의 데이터를 감안하고 보더라도 17시 23분으로 일치함을 증명한다.

다른 나라의 경우도 한번 볼까?

미국/뉴욕의 결과.

날짜가 하루 뒤로 이동한걸 보니 아직도 로직에는 수정이 필요할 것 같다.

그래도 일단 시간값만 비교해보자면 일출과 일몰 1분차로 꽤 정확하다.

수정했다.

음 근데 서울 값이 일출 값에 +1이 됐네.

일단 영국 값까지 요청해보자.

영국에서는 오차가 46분씩이나 나네.. 일은 정확하다.

한국데이터만 정확하게 계산 하자로 결정

안되겠다. 전세계를 기준으로 계산을 하려고 했는데 확인해야할 사항도 너무 많고 넣어야할 데이터도 너무 많아지고 로직이 복잡해진다. 그냥 한국만 기준으로 하는게 맞을 것 같다.

다음 포스터에서 코드 결과값 검증하자.