Coding History/Team Project

팀플) 유성우 데이터 정제하기 (유성우 폐기 정밀 예측 현실적으로 불가능, 새로운 우주 이벤트 고민, 행성의 가시성 공신력 더하기)

BlackBirdIT 2024. 10. 15. 09:18

우선 전의 포스팅에 어떻게 정제할지는 생각을 해뒀고, 이제 천천히 구현을 해보면 된다.

그렇게 거름망을 만들어서 여러번 테스트를 해봤는데 아무런 데이터도 나오지 않는게 이상해서 공식문서를 좀 더 자세하게 읽어보니까 예측보다는 기록에 중점을 둔 api였다. 이거 지워야겠네..

유성우 데이터는 예측하기가 굉장히 어려워서 예측을 중점으로 둔 api는 존재하지 않는 것 같다. 우주 이벤트 관련된 정보를 나는 제공하고 싶기 때문에 유성우가 아닌 다른 대체할 데이터를 만들든 api를 찾든 해야될 것 같은데 우주 이벤트면 뭐가 있을까


예측이 가능한가?

내가 이제 시간을 허비한 이유는 이 대전제가 틀렸기 때문이였다.

그러니까 천문지식의 부재로 내가 추출하고 싶은데이터가 현실적으로 예측이 가능한가에 대한 고민이 조금 부족했던 것 같다.

그럼 이제 대전제를 "예측이 가능한가?" 를 두고 어떤 우주 이벤트를 넣는게 좋을지 고민하면 된다.

그래서 생각해본게,

일단 두가지인데.

  1. 행성의 대접근.
  2. 혜성 접근.

이거 두가지다.

이 두가지 데이터는 왜 예측이 가능하냐?

  • 행성 대접근 (Planetary Conjunctions):
    • 이는 두 개 이상의 행성이 같은 경도에서 일직선으로 나타나는 천문 현상으로, 주로 지구에서 바라보았을 때 가까이 있는 것처럼 보이는 현상을 의미함. 이 현상은 미리 예측이 가능하고, 망원경을 통해 관측하기에도 좋기 때문에 관측이 용이.
    • 예측 가능한 행성들의 대접근 이벤트들은 오래전에 예측된 궤도 정보로부터 계산할 수 있음. NASA의 Horizon 시스템이나 다른 천문 시뮬레이션 도구를 활용하면 이러한 접근 이벤트의 시각과 위치를 확인할 수 있음.
    • 이미 행성의 가시성에 대한 API는 만들어 두었으니 서로 엮으면 더 좋은 데이터가 될 것 같음.
  • 혜성 접근 및 궤도 정보:
    • 혜성들은 보통 길고 타원형의 궤도를 가지며 태양을 중심으로 도는 천체. 태양에 가까워질 때는 그 궤도와 접근 시기가 미리 예측 가능하고, 그에 따른 관측이 가능. 특히 혜성이 태양과 지구에 가까워지면 맨눈이나 소형 망원경으로도 쉽게 볼 수 있음.
    • 국제천문연맹(IAU)이나 NASA의 소행성 체계(NASA’s Near-Earth Object Program) 같은 곳에서 혜성의 궤도 데이터를 제공하고, 이걸 기반으로 혜성 접근을 예보할 수 있음.

이런 이유가 있다.

그럼 뭐다? 다시 NASA API로 돌아가야지. 사실 내가 유성우에서 문제가 생길거란걸 살짝은 예상하고 있었어서 API KEY를 지우지 않고 작업중이였다.


NASA API 뭘 사용해야할까

1. Planetary Conjunctions (행성 대접근 이벤트)

  • 데이터 출처: NASA JPL Horizons API 또는 Skyfield 라이브러리를 사용할 수 있다. Skyfield는 천체의 위치를 계산할 수 있는 강력한 도구이고, 행성들의 위치 데이터를 기반으로 가까운 시기에 행성들이 서로 접근하는 순간을 계산할 수 있다.

  • 예측 가능한 정보:

    • 이벤트 날짜
    • 대접근이 발생하는 두 행성의 이름
    • 두 행성 간의 최소 각도 및 위치
  • 관측 가능 여부:

    • 사용자의 위치 (위도, 경도)를 기준으로 해당 이벤트가 관측 가능한지 확인
    • 해당 시간대의 일몰/일출 시간을 참고해 가시성 평가
  • 행성의 가시성 api를 먼저 돌리고 이 로직에 접근해서 행성 대접근 이벤트가 생기는지 검증하는 식으로 로직을 짜면 좋을듯?

2. Comet Approaches (혜성 접근 이벤트)

  • 데이터 출처: NASA JPL's Small-Body Database API
  • 이 API는 혜성이나 소행성의 궤도 정보를 제공하여 지구 접근 여부를 알 수 있음.
  • 예측 가능한 정보:
    • 혜성의 접근 날짜
    • 접근 거리 및 속도
    • 혜성의 궤도 정보 및 궤도 요소
  • 관측 가능 여부:
    • 사용자의 위치를 기준으로 혜성의 고도 및 방위를 계산하고, 해당 혜성이 관측 가능한 시간대를 확인
    • 일몰/일출 시간과 연동하여 관측 가능 여부 판단

그럼 일단 NASA JPL Horizons API를 사용할지 지금 사용하는 Skyfield를 사용할지 아니면 두가지를 엮어볼지와, NASA JPL's Small-Body Database API는 연결 확정이고..

고민을 조금 해봐야될 것 같다.

NASA JPL Horizons API를 사용하면 얻을 수 있는 정보가 정확한 천체의 위치정보다. 그럼 지금 내가 만든 행성의 가시성에 대한 정보도 더욱 공신력을 갖게 만들 수 있고, 이를 바탕으로 행성 대접근 이벤트를 만든다면 그 또한 공신력을 가질테니 두개를 엮는게 맞는 것 같다. 일단 이것부터 만들고 혜성 접근 이벤트로 들어가자.


NASA JPL Horizons API연결부터 시작하자.

우선 공식 문서는 이거다.

우선 데이터 요청부터 약간 난항을 겪긴했는데

추가 정보까지 읽어보면서 요청 자체는 성공했다.

근데 이렇게 나오면 어케 써먹지?

일단은 대충 어떤 느낌으로 요청을 보내는지 알았다.

행성에는 각각 코드가 필요하다.

그래서 내가 보내는 요청의 url은

http://localhost:5555/api/planet_visibility?planet=Mercury&lat=37.5665&lon=126.9780&date=2024-10-10

이런식인데 공식 문서에 의하면,

199: 수성 (Mercury)
299: 금성 (Venus)
399: 지구 (Earth)
499: 화성 (Mars)
599: 목성 (Jupiter)
699: 토성 (Saturn)
799: 천왕성 (Uranus)
899: 해왕성 (Neptune)
999: 명왕성 (Pluto)

이렇게 변환도 해야될듯.. 이렇게 요청하면 https://ssd.jpl.nasa.gov/api/horizons.api?format=text&COMMAND=%27MB%27&OBJ_DATA=%27YES%27&MAKE_EPHEM=%27NO%27목록을 볼 수 있다.

귀찮네

우선 계속해서 조금의 어려움이 있었는데 뭐가 어려웠냐 하면,

https://ssd.jpl.nasa.gov/api/horizons.api?
format=text&COMMAND=%27499%27&OBJ_DATA=%27YES%27&
MAKE_EPHEM=%27YES%27&EPHEM_TYPE=%27OBSERVER%27&
CENTER=%27500@399%27&START_TIME=%272024-01-01%27&STOP_TIME=%272024-01-02%27&
STEP_SIZE=%271%20h%27&QUANTITIES=%271,9,20,23,24,29%27

이게 내가 직접 크롬에서 처음 요청에 성공했던 데이터고,

    url = "https://ssd.jpl.nasa.gov/api/horizons.api"
    params = {
        "format": "json",
        "COMMAND": f"'{planet_code}'",
        "CENTER": "'500@399'",  # 지오센터 기준
        "MAKE_EPHEM": "YES",
        "EPHEM_TYPE": "OBSERVER",
        "OBJ_DATA": "YES",
        "START_TIME": f"'{date.strftime('%Y-%m-%d')}'",
        "STOP_TIME": f"'{(date + timedelta(days=1)).strftime('%Y-%m-%d')}'",
        "STEP_SIZE": "'1 h'",
        "QUANTITIES": "'1,9,20,23'"  # 필요한 데이터만 요청 (시간, 적경/적위, 태양 거리 등)
    }

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

이게 이제 요청을 하기 위해서 꾸린 건데(이건 이제 성공한 케이스), 여기서 올바른 값으로 url 요청을 시도하는게 조금 어려웠다.

그러니까 정확히 말하면 포메팅하는데 어려움을 겪었는데, 이제 해결은 한 상태다..

이제 요청을 하면 result로 해당 행성에 맞춘 값까지 가져올 수 있는 상태다.

우선 데이터가 뭔지부터 분석할 필요가 있었다.

난 잘 모르니까 GPT한테 분석하라고 시켰고, 여기서 내가 필요한 정보는 "QUANTITIES": "'20,23'"만 있으면 됐다.

그래서 요청 url 수정했고, 뽑은 데이터에서 사용할 것은

  • delta: 관측자와 행성 간의 거리 (천문단위)
  • deldot: 거리에 대한 변화율 (양수면 멀어지고, 음수면 가까워지는 중)
  • S-O-T: 태양-관측자-목표 간의 각도, 관측 조건에 따라 행성의 위치를 알 수 있음.

이 정도?

그럼 이제 행성 가시성에 대한 정보도 더욱 공신력있게 변경할 수 있다. S-O-T의 정보까지 합치면 된다.


그래서 일단 뭘 해야하냐면,,,

얘네 api 사용해서 결과를 뽑으면 result에 문장으로 다 묶어서 보여준다. 그래서 파싱을 거쳐야되는데 이게 좀 어려울 것 같다.

우선 파싱에 성공했다.

해당 값이 원하는 값이였고, 뽑긴했다.

다만, 파싱된 데이터를 원하는 형태로 사용하기 위해서는 추가적으로 각 라인을 필요한 필드별로 분리해서 딕셔너리 형태로 만드는 작업이 필요했다. 각 필드를 시간, 적경, 적위, 거리 등의 키로 쉽게 접근할 수 있게 만들어야한다.

해야지 뭐.

이것까지도 성공..

    if response.status_code == 200:
        try:
            data = response.json()
            # print(f"Response Data: {data}")  # 응답 데이터 로그
            if 'result' in data:
                # 파싱 로직 추가
                result_lines = data['result'].splitlines()
                parsed_data = []
                extracting = False
                for line in result_lines:
                    if "$$SOE" in line:
                        extracting = True
                        continue
                    elif "$$EOE" in line:
                        extracting = False
                        break
                    if extracting:
                        parsed_data.append(line)
                print(f"Parsed Data: {parsed_data}")  # 파싱된 데이터 로그

                # 파싱된 데이터를 딕셔너리 형태로 변환
                parsed_dict = []
                for entry in parsed_data:
                    parts = entry.split()
                    parsed_dict.append({
                        "time": f"{parts[0]} {parts[1]}",
                        "ra": f"{parts[2]} {parts[3]} {parts[4]}",
                        "dec": f"{parts[5]} {parts[6]} {parts[7]}",
                        "delta": parts[8],
                        "deldot": parts[9],
                        "s-o-t": parts[10]
                    })
                print(f"Parsed Dictionary: {parsed_dict}")  # 딕셔너리 형태의 파싱 데이터 로그
                return {"data": parsed_dict}
            else:
                return {"error": "Unexpected response format from Horizons API."}
        except ValueError as e:
            print(f"JSON parsing error: {e}")  # JSON 파싱 에러 로그
            return {"error": "Failed to parse JSON response from Horizons API."}
    else:
        return {"error": f"Failed to retrieve data from Horizons API. Status code: {response.status_code}"}

이게 파싱과 딕셔너리 형태로 변환하는 로직이다.

그럼 이제 이 데이터를 가시성 로직에 끌고 와서 추가하면 더욱 공신력 있는 데이터가 될 것이다!!

    # 파싱된 Horizons 데이터 추가
    horizons_data = planet_data.get("data")
    if not horizons_data:
        return {"error": "No valid data from Horizons API."}

    # 가장 가까운 시간의 데이터 사용
    closest_data = horizons_data[0]
    delta = float(closest_data["delta"])
    s_o_t = float(closest_data["s-o-t"])

### 기존 로직

    # 가시성 판단 추가 로직
    visibility_judgment = "Unknown"
    if delta < 1.5 and s_o_t > 30:
        visibility_judgment = "Good visibility"
    elif 1.5 <= delta < 2.5 and s_o_t > 20:
        visibility_judgment = "Moderate visibility"
    else:
        visibility_judgment = "Poor visibility"

### 기존 로직

    # 결과 반환
    return {
        "planet": planet_name,
        "date": date.strftime("%Y-%m-%d"),
        "location": {
            "latitude": latitude,
            "longitude": longitude
        },
        "visible": visible,
        "best_time": best_time if isinstance(best_time, str) else best_time.strftime("%H:%M"),
        "right_ascension": f"{ra.hours:.2f}h",  # 적경 값을 시간 단위로 변환하여 반환
        "declination": f"{dec.degrees:.2f}°",  # 적위 값을 도 단위로 반환
        "distance_to_earth": f"{delta:.2f} AU",  # 지구와의 거리 추가
        "sun_observer_target_angle": f"{s_o_t:.2f}°",  # 태양-관측자-행성 각도 추가
        "visibility_judgment": visibility_judgment  # 가시성 판단 추가
    }

이런 느낌으로 코딩을 했고 반환 결과는

이렇게 된다.

  1. best_time: 관측하기 가장 좋은 시간을 나타내며, Skyfield 라이브러리로 계산되었음.
  2. date: 요청 날짜를 나타냄.
  3. declination: 적위, 천구상에서의 대상 위치를 도 단위로 나타냅니다. Skyfield 라이브러리로 계산.
  4. distance_to_earth: 지구와의 거리, Horizons API에서 가져온 delta 값을 사용.
  5. location: 요청된 위도와 경도를 그대로 반환.
  6. planet: 요청된 행성의 이름.
  7. right_ascension: 적경, 천구상에서의 대상 위치를 시간 단위로 나타냅니다. Skyfield 라이브러리로 계산.
  8. sun_observer_target_angle: 태양-관측자-행성 사이의 각도, Horizons API에서 가져온 s-o-t 값을 사용.
  9. visibility_judgment: Horizons 데이터(deltas-o-t)를 바탕으로 가시성 판단을 추가.
  10. visible: 특정 시간대에 행성이 보일 가능성이 있는지 여부.

Horizons API 데이터 사용 부분:

  • distance_to_earth: Horizons의 delta 값.
  • sun_observer_target_angle: Horizons의 s-o-t 값.
  • visibility_judgment: 위 두 값을 바탕으로 가시성 판단을 결정.

이런 느낌?

여튼 이러면 행성의 가시성의 공신력을 더하기 완료. 이제는 행성의 대접근 이벤트 로직을 짜면 된다.