Coding History/Team Project

팀플) REST API 별자리 정보데이터 생성.

BlackBirdIT 2024. 10. 10. 02:55

흠.. 쉽지 않네?

계속 고치다보니까 에러가 바뀜.

일단 에러가 좀 계속해서 많았는데 그래서 skyfield에 대한 정보 수집부터 했다.

skyfield 1.49 버전을 사용중이였는데, 그야 install을 그냥 진행해서 제일 최신 버전을 다운 받았으니까.

제일 정보가 많고 안정적인 버전이 1.45인 것을 확인하고 다운그레이드 진행.

pip install skyfield==1.45

이제는 좀 에러가 없었으면 좋겠다.

그래도 여전히 문제가 해결되지 않아서 더 정보를 찾아보고,

pip install skyfield-data

이런 것도 필요하다는 것을 알게되었다. 설치를 안해서 찾지 못한 것.

여기서 이 둘의 차이점이 뭐냐면

  • Skyfield: 파이썬으로 천문학 계산을 쉽게 할 수 있게 도와주는 라이브러리. 행성, 별, 인공위성 등의 위치를 계산하거나, 일식·월식 같은 천문 현상을 예측할 때 사용.
  • Skyfield-data: Skyfield가 사용하는 데이터 패키지. 행성의 위치, 별자리, 시간 계산 등에 필요한 천문 데이터를 포함. Skyfield는 이 데이터를 사용해서 정확한 계산을 제공.

아니 근데 그럼 Skyfield를 다운하면 저것도 다운이 되어야하는거 아니냐? 진짜 짜증나게하네..

여튼 다운 받아서 써보자.

이거 해도 안되어서 똥꼬쇼를 했다.

서버 돌아가는 것 까지 확인...

결과가 이상하네..

코드 수정하고,

오류남.

아 계속 오류나서 버전이 어떻게 조합해야지 맞는지 공식 문서를 읽다가 그냥 포기하고 다시 싹 다 최신 버전을 사용했다.

그냥 라이브러리 코드를 까보면서 모듈과 함수가 있는지 계속 체크하고 import 부분을 고쳐가며 불러올 수 있는지 없는지 확인했다.

# 모든 API 엔드포인트 정의

# app/routes.py

from flask import Blueprint, jsonify, request
from skyfield.api import load, Topos, N, E, load_constellation_map, position_of_radec
from skyfield.positionlib import ICRF
from datetime import datetime, timedelta
from functools import lru_cache

# Blueprint 객체 생성: 이 블루프린트를 사용해 라우트를 정의함
main = Blueprint('main', __name__)

# Skyfield에서 사용할 타임스케일 및 행성 데이터 로드
ts = load.timescale()
planets = load('de421.bsp')  # 행성 데이터 로드 (de421.bsp 파일 필요)
earth = planets['earth']  # 지구 객체 생성

# 별자리 데이터 로드
constellation_map = load_constellation_map()

# 간단한 캐시 구현 예시
@lru_cache(maxsize=128)
def get_constellation_for_date(latitude, longitude, year, month, day):
    """
    주어진 날짜와 위치(위도, 경도)에서의 별자리 정보를 반환하는 함수
    캐싱을 사용하여 최대 128개의 계산 결과를 저장해 성능 향상
    """
    t = ts.utc(year, month, day)  # 해당 날짜의 시간 객체 생성
    location = Topos(latitude * N, longitude * E)  # 위치 객체 생성
    observer = earth + location  # 관측자 위치 설정
    astrometric = observer.at(t)  # 관측 시점에서의 천체 위치 계산
    ra, dec, _ = astrometric.radec()  # 적경(ra)과 적위(dec) 계산

    # 적경과 적위를 이용해 별자리를 찾기
    position = position_of_radec(ra.hours, dec.degrees)
    constellation_name = constellation_map(position)
    return constellation_name  # 별자리 이름 반환

@main.route('/api/constellations', methods=['GET'])
def get_constellations():
    """
    사용자가 요청한 위도, 경도, 날짜 범위에 따라 별자리 정보를 반환하는 API 엔드포인트
    """
    # 쿼리 파라미터로부터 위도, 경도, 날짜 범위 정보 받기
    latitude = request.args.get('lat')
    longitude = request.args.get('lon')
    start_date_str = request.args.get('start_date')
    end_date_str = request.args.get('end_date')

    # 위도와 경도 입력 여부 확인
    if not latitude or not longitude:
        return jsonify({"error": "Latitude and Longitude are required"}), 400

    # 위도와 경도 값을 실수(float)로 변환
    try:
        latitude = float(latitude)
        longitude = float(longitude)
    except ValueError:
        return jsonify({"error": "Invalid Latitude or Longitude format"}), 400

    # 날짜 유효성 검사 및 변환
    try:
        start_date = datetime.strptime(start_date_str, '%Y-%m-%d') if start_date_str else datetime.now()
        end_date = datetime.strptime(end_date_str, '%Y-%m-%d') if end_date_str else start_date + timedelta(days=90)
    except ValueError:
        return jsonify({"error": "Invalid date format. Use YYYY-MM-DD."}), 400

    # 날짜 범위 검증
    if start_date > end_date or (end_date - start_date).days > 365:
        return jsonify({"error": "Invalid date range."}), 400

    # 결과를 저장할 리스트 초기화
    constellation_data = []
    current_date = start_date
    # 날짜 범위를 하루씩 증가시키면서 별자리 정보 계산
    while current_date <= end_date:
        try:
            # 캐시된 결과를 사용해 별자리 계산
            constellation = get_constellation_for_date(latitude, longitude, current_date.year, current_date.month, current_date.day)
            # 결과를 리스트에 추가
            constellation_data.append({
                "date": current_date.strftime('%Y-%m-%d'),
                "constellations": constellation
            })
        except Exception as e:
            # 별자리 계산 실패 시 에러 메시지 반환
            return jsonify({"error": f"Failed to calculate constellation: {str(e)}"}), 500

        # 날짜를 하루 증가
        current_date += timedelta(days=1)

    # 최종 결과를 JSON으로 반환
    return jsonify({
        "location": {"latitude": latitude, "longitude": longitude},
        "start_date": start_date.strftime('%Y-%m-%d'),
        "end_date": end_date.strftime('%Y-%m-%d'),
        "constellations": constellation_data
    })

그래서 일단 최종.

요청 결과

코드를 설명해보면 특정 위치와 날짜에 따라 별자리 정보를 제공하는 로직이다.

솔직히 어케 한건지 나도 잘 모르겠다.

여튼 일단 깃에 올리고, 이제는 관측 시간까지 반영해야한다.

일단 시간에 대한 로직도 추가했다.

데이터 검증이 필요하다.

데이터 검증.

한국의 서울을 기준으로 검증해보자.

  1. 봄 (3월 20일 ~ 6월 20일)

     http://127.0.0.1:5000/api/constellations?lat=37.5665&lon=126.9780&start_date=2024-03-20&end_date=2024-06-20

올바른 결과

  • 사자자리 (Leo)
  • 처녀자리 (Virgo)
  • 게자리 (Cancer)

  1. 여름 (6월 21일 ~ 9월 22일)

     http://127.0.0.1:5000/api/constellations?lat=37.5665&lon=126.9780&start_date=2024-06-21&end_date=2024-09-22

올바른 결과

  • 거문고자리 (Lyra)
  • 백조자리 (Cygnus)
  • 독수리자리 (Aquila)

  1. 가을 (9월 23일 ~ 12월 21일)

     http://127.0.0.1:5000/api/constellations?lat=37.5665&lon=126.9780&start_date=2024-09-23&end_date=2024-12-21

올바른 결과

  • 사자자리 (Leo)
  • 처녀자리 (Virgo)
  • 게자리 (Cancer)

  1. 겨울 (12월 22일 ~ 다음 해 3월 19일)

     http://127.0.0.1:5000/api/constellations?lat=37.5665&lon=126.9780&start_date=2024-12-22&end_date=2025-03-19

올바른 결과

  • 사자자리 (Leo)
  • 처녀자리 (Virgo)
  • 게자리 (Cancer)

일단 결과를 확인해보니까 잘 나온다.

GPT한테 물어봐서 올바른 결과라는 판단을 받음.

근데 이건 이제 어느 언제부터 언제까지 어느 특정 시간대에 무엇을 볼 수 있다. 라는 정보를 제공하는 것이지.

궁극적으로 특정 별자리가 가장 잘보이는 시기에 특정 시간까지는 제공을 하지 못한다.


이제 다시 해야할 것

1. 별자리 상승 및 하강 시간 계산:

  • Skyfield에서 제공하는 find_risings()find_settings() 함수를 사용해 각 별자리의 상승 및 하강 시간을 계산
  • 해당 데이터를 사용하여 언제 특정 별자리가 지평선 위로 올라오는지와 지평선 아래로 내려가는지를 파악할 수 있음.

    2. 최대 고도 계산:

  • 별자리가 하늘에서 가장 높이 뜨는 고도 최대 시간을 계산하는 로직 추가.
  • 고도가 가장 높을 때 별자리가 가장 잘 보이기 때문에 이 시간을 추천 시간으로 사용.

    3. 일몰 및 일출 시간 고려:

  • 별자리는 야간에 관측하는 것이 가장 좋음 때문에, 태양의 일몰 및 일출 시간을 이용해 별자리가 보이는 시간을 제한해야함.
  • 일몰 후부터 일출 전까지의 시간 범위 내에서 상승하는 별자리만 추천하는 방식을 사용하면 사용자가 별자리를 관측하기에 최적의 조건을 제공가능.

후.. 일몰 일출 로직부터 만드는게 이걸 만드는데 도움이 될 것 같다.

일단은 끊고 가자.