태풍:베스트트랙
차이
문서의 선택한 두 판 사이의 차이를 보여줍니다.
| 양쪽 이전 판이전 판다음 판 | 이전 판 | ||
| 태풍:베스트트랙 [2026/05/04 08:42] – [① 베스트트랙 양식(예시)] admin | 태풍:베스트트랙 [2026/05/06 08:24] (현재) – [4. 베스트트랙 제공 위치] admin | ||
|---|---|---|---|
| 줄 10: | 줄 10: | ||
| 베스트트랙은 태풍 종료 후 다음과 같은 절차를 통해 생산되고, | 베스트트랙은 태풍 종료 후 다음과 같은 절차를 통해 생산되고, | ||
| + | |||
| ^ 자료 \\ 수집 | ^ 자료 \\ 수집 | ||
| 줄 16: | 줄 17: | ||
| 태풍의 위치(위‧경도), | 태풍의 위치(위‧경도), | ||
| - | ====① 베스트트랙 양식(예시)==== | + | ====3.1 베스트트랙 양식(예시)==== |
| - | ^ | + | ^ |
| ^ ::: ^ ::: ^ 년 ^ 월 ^ 일 ^ 시 ^ 경도(°E) | ^ ::: ^ ::: ^ 년 ^ 월 ^ 일 ^ 시 ^ 경도(°E) | ||
| | TS | 2401 | 2024 | 05 | 25 | 12 | 122.2 | 13.3 | 18 | 1000 | 180 | 110 | 270.0 | -999 | -999 | -999.9 | | TS | 2401 | 2024 | 05 | 25 | 12 | 122.2 | 13.3 | 18 | 1000 | 180 | 110 | 270.0 | -999 | -999 | -999.9 | ||
| - | ====② 베스트트랙 세부내용==== | + | ====3.2 베스트트랙 세부내용==== |
| ^ 종류 | ^ 종류 | ||
| 줄 49: | 줄 50: | ||
| =====4. 베스트트랙 제공 위치===== | =====4. 베스트트랙 제공 위치===== | ||
| - | • 기상청 누리집(https:// | + | |
| - | • 기상청 API허브(https:// | + | |
| - | • IBTrACS(International Best Track Archive for Climate Stewardship) | + | |
| + | |||
| + | ===== 5. 활용방안 ===== | ||
| - | =====5. 활용방안===== | ||
| 소스코드 : {{ : | 소스코드 : {{ : | ||
| - | ====① 데이터 정제 함수==== | + | ==== 5.1. 라이브러리 설치 및 기본 설정 ==== |
| + | |||
| + | 태풍 베스트트랙 자료를 조회하고 태풍 중심 위치 및 강풍반경을 시각화하기 위해 필요한 라이브러리를 설치합니다. | ||
| <code py> | <code py> | ||
| - | def clean_besttrack_df(df): | + | pip install requests pandas matplotlib cartopy numpy |
| - | if df.empty: | + | |
| - | return df | + | |
| - | df = df.copy() | + | |
| - | numeric_cols = [" | + | |
| - | for col in numeric_cols: | + | |
| - | df[col] = pd.to_numeric(df[col], | + | |
| - | # 결측값 처리 | + | |
| - | for col in [" | + | |
| - | df.loc[df[col] <= -9, col] = pd.NA | + | |
| - | # YY가 2자리면 2000년대 보정 | + | |
| - | if df[" | + | |
| - | df[" | + | |
| - | else: | + | |
| - | df[" | + | |
| - | def make_dt(row): | + | |
| - | try: | + | |
| - | return datetime( | + | |
| - | int(row[" | + | |
| - | int(row[" | + | |
| - | int(row[" | + | |
| - | int(row[" | + | |
| - | ) | + | |
| - | except Exception: | + | |
| - | return pd.NaT | + | |
| - | df[" | + | |
| - | # 유효한 범위만 남김 | + | |
| - | df = df.dropna(subset=[" | + | |
| - | df = df[(df[" | + | |
| - | df = df[(df[" | + | |
| - | # 0~360 경도를 -180~180으로 바꾸고 싶으면 아래 사용 | + | |
| - | # df[" | + | |
| - | df = df.sort_values(" | + | |
| - | return df | + | |
| </ | </ | ||
| - | ====② 지도 시각화 함수==== | + | |
| <code py> | <code py> | ||
| - | def plot_typhoon_track(df): | + | import os |
| - | # 1. 지도 및 투영법 설정 | + | from datetime import datetime |
| - | fig = plt.figure(figsize=(12, | + | |
| - | ax = plt.axes(projection=ccrs.PlateCarree()) | + | import numpy as np |
| - | # 2. 지형지물 추가 (육지, 바다, 해안선) | + | import requests |
| - | ax.add_feature(cfeature.LAND, | + | import pandas as pd |
| - | ax.add_feature(cfeature.OCEAN, | + | |
| - | ax.add_feature(cfeature.COASTLINE, | + | import matplotlib |
| - | # 3. 태풍 경로 및 중심점 그리기 | + | matplotlib.use("Agg") |
| - | ax.plot(df[" | + | import matplotlib.pyplot as plt |
| - | ax.scatter(df[" | + | from matplotlib import font_manager |
| - | # 4. 강풍 반경 원 그리기 (반복문을 통해 R15, R25 표시) | + | from matplotlib.lines import Line2D |
| - | for i, row in df.iterrows(): | + | from matplotlib.patches import Patch |
| - | | + | |
| - | | + | import cartopy.crs as ccrs |
| - | # 5. 결과물 저장 | + | import cartopy.feature as cfeature |
| - | plt.savefig(OUTPUT_PNG, | + | from cartopy.geodesic import Geodesic |
| </ | </ | ||
| - | ====③ 예상결과==== | ||
| + | API 인증키와 API 주소, 조회할 태풍 정보를 설정합니다. | ||
| + | |||
| + | <code py> | ||
| + | AUTH_KEY = " | ||
| + | |||
| + | BASE_URL = " | ||
| + | |||
| + | YEAR = 2023 | ||
| + | TCID = " | ||
| + | TY_NAME = f" | ||
| + | |||
| + | HELP = 1 | ||
| + | |||
| + | RAW_TEXT_FILE = f" | ||
| + | OUTPUT_CSV = f" | ||
| + | OUTPUT_PNG = f" | ||
| + | </ | ||
| + | |||
| + | BASE_URL은 (신)기상자료개방포털 > API > 태풍 > 태풍 베스트트랙메뉴의 호출 URL을 사용합니다. | ||
| + | |||
| + | YEAR에는 태풍 발생 연도를 입력합니다. | ||
| + | |||
| + | TCID에는 태풍 번호를 입력합니다. | ||
| + | |||
| + | 예를 들어 2023년 11호 태풍은 2311로 입력합니다. | ||
| + | |||
| + | ==== 5.2. 태풍 베스트트랙 자료 조회 및 정리 ==== | ||
| + | |||
| + | 태풍 베스트트랙 API를 호출하여 응답 원문 TEXT를 받아옵니다. | ||
| + | |||
| + | <code py> | ||
| + | def fetch_besttrack_text(year, | ||
| + | params = { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | |||
| + | response = requests.get(BASE_URL, | ||
| + | response.raise_for_status() | ||
| + | |||
| + | text = response.text | ||
| + | |||
| + | with open(RAW_TEXT_FILE, | ||
| + | f.write(text) | ||
| + | |||
| + | return text | ||
| + | </ | ||
| + | |||
| + | API 응답에는 설명 줄과 실제 데이터 줄이 함께 포함됩니다. | ||
| + | |||
| + | 설명 줄은 #으로 시작하므로 제외하고 실제 데이터 줄만 사용합니다. | ||
| + | |||
| + | <code py> | ||
| + | def extract_data_lines(text): | ||
| + | lines = [] | ||
| + | |||
| + | for raw_line in text.splitlines(): | ||
| + | line = raw_line.strip() | ||
| + | |||
| + | if not line: | ||
| + | continue | ||
| + | |||
| + | if line.startswith("#" | ||
| + | continue | ||
| + | |||
| + | lines.append(line) | ||
| + | |||
| + | return lines | ||
| + | </ | ||
| + | |||
| + | 베스트트랙 응답 자료의 컬럼은 다음과 같습니다. | ||
| + | |||
| + | <code py> | ||
| + | COLS = [ | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | ] | ||
| + | </ | ||
| + | |||
| + | 응답 텍스트를 pandas DataFrame으로 변환합니다. | ||
| + | |||
| + | <code py> | ||
| + | def parse_besttrack_text(text): | ||
| + | data_lines = extract_data_lines(text) | ||
| + | |||
| + | rows = [] | ||
| + | |||
| + | for line in data_lines: | ||
| + | clean = line.replace(",", | ||
| + | parts = [p for p in clean.split() if p] | ||
| + | |||
| + | if len(parts) < 12: | ||
| + | continue | ||
| + | |||
| + | rows.append(parts[: | ||
| + | |||
| + | df = pd.DataFrame(rows, | ||
| + | return df | ||
| + | </ | ||
| + | |||
| + | 숫자형 컬럼과 시간 컬럼을 변환하고 결측값을 처리합니다. | ||
| + | |||
| + | <code py> | ||
| + | def clean_besttrack_df(df): | ||
| + | numeric_cols = [ | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | ] | ||
| + | |||
| + | for col in numeric_cols: | ||
| + | df[col] = pd.to_numeric(df[col], | ||
| + | |||
| + | for col in [" | ||
| + | df.loc[df[col] <= -9, col] = pd.NA | ||
| + | |||
| + | if df[" | ||
| + | df[" | ||
| + | lambda x: int(2000 + x) if pd.notna(x) else pd.NA | ||
| + | ) | ||
| + | else: | ||
| + | df[" | ||
| + | |||
| + | def make_dt(row): | ||
| + | try: | ||
| + | return datetime( | ||
| + | int(row[" | ||
| + | int(row[" | ||
| + | int(row[" | ||
| + | int(row[" | ||
| + | ) | ||
| + | except Exception: | ||
| + | return pd.NaT | ||
| + | |||
| + | df[" | ||
| + | |||
| + | df = df.dropna(subset=[" | ||
| + | |||
| + | df = df.sort_values(" | ||
| + | |||
| + | return df | ||
| + | </ | ||
| + | |||
| + | ==== 5.3. 태풍 중심 위치 및 강풍반경 표출 ==== | ||
| + | |||
| + | 지도 표출을 위한 기본 지도를 생성합니다. | ||
| + | |||
| + | <code py> | ||
| + | def setup_map(): | ||
| + | fig = plt.figure(figsize=(12, | ||
| + | ax = plt.axes(projection=ccrs.PlateCarree()) | ||
| + | |||
| + | ax.add_feature(cfeature.LAND, | ||
| + | ax.add_feature(cfeature.OCEAN, | ||
| + | ax.add_feature(cfeature.COASTLINE, | ||
| + | ax.add_feature(cfeature.BORDERS, | ||
| + | |||
| + | ax.gridlines( | ||
| + | draw_labels=True, | ||
| + | linewidth=0.4, | ||
| + | linestyle=" | ||
| + | alpha=0.5 | ||
| + | ) | ||
| + | |||
| + | return fig, ax | ||
| + | </ | ||
| + | |||
| + | 태풍 중심 위치를 선과 점으로 연결하여 태풍 경로를 표출합니다. | ||
| + | |||
| + | <code py> | ||
| + | ax.plot( | ||
| + | df[" | ||
| + | df[" | ||
| + | color=" | ||
| + | linewidth=1.5, | ||
| + | linestyle=" | ||
| + | transform=ccrs.PlateCarree() | ||
| + | ) | ||
| + | |||
| + | ax.scatter( | ||
| + | df[" | ||
| + | df[" | ||
| + | s=30, | ||
| + | color=" | ||
| + | edgecolors=" | ||
| + | linewidths=0.3, | ||
| + | transform=ccrs.PlateCarree() | ||
| + | ) | ||
| + | </ | ||
| + | |||
| + | 15m/s 및 25m/s 풍속반경을 원 형태로 표출합니다. | ||
| + | |||
| + | <code py> | ||
| + | draw_wind_radius( | ||
| + | ax=ax, | ||
| + | lon=row[" | ||
| + | lat=row[" | ||
| + | radius_value=row[" | ||
| + | edgecolor=" | ||
| + | facecolor=" | ||
| + | alpha=0.10, | ||
| + | linewidth=0.8 | ||
| + | ) | ||
| + | |||
| + | draw_wind_radius( | ||
| + | ax=ax, | ||
| + | lon=row[" | ||
| + | lat=row[" | ||
| + | radius_value=row[" | ||
| + | edgecolor=" | ||
| + | facecolor=" | ||
| + | alpha=0.06, | ||
| + | linewidth=1.0 | ||
| + | ) | ||
| + | </ | ||
| + | |||
| + | 태풍 생성 지점, 소멸 지점, 최대세력 지점을 함께 표시합니다. | ||
| + | |||
| + | <code py> | ||
| + | ax.scatter( | ||
| + | first_row[" | ||
| + | first_row[" | ||
| + | s=120, | ||
| + | color=" | ||
| + | edgecolors=" | ||
| + | marker=" | ||
| + | ) | ||
| + | |||
| + | ax.scatter( | ||
| + | last_row[" | ||
| + | last_row[" | ||
| + | s=120, | ||
| + | color=" | ||
| + | edgecolors=" | ||
| + | marker=" | ||
| + | ) | ||
| + | |||
| + | ax.scatter( | ||
| + | peak_row[" | ||
| + | peak_row[" | ||
| + | s=180, | ||
| + | color=" | ||
| + | edgecolors=" | ||
| + | marker=" | ||
| + | ) | ||
| + | </ | ||
| + | |||
| + | 최종 태풍 경로 지도는 PNG 파일로 저장합니다. | ||
| + | |||
| + | <code py> | ||
| + | plt.savefig( | ||
| + | OUTPUT_PNG, | ||
| + | dpi=150, | ||
| + | bbox_inches=" | ||
| + | ) | ||
| + | </ | ||
| + | |||
| + | 전체 실행 코드는 다음과 같습니다. | ||
| + | |||
| + | <code py> | ||
| + | text = fetch_besttrack_text(YEAR, | ||
| + | |||
| + | df = parse_besttrack_text(text) | ||
| + | |||
| + | df = clean_besttrack_df(df) | ||
| + | |||
| + | df.to_csv( | ||
| + | OUTPUT_CSV, | ||
| + | index=False, | ||
| + | encoding=" | ||
| + | ) | ||
| + | |||
| + | plot_typhoon_track(df) | ||
| + | </ | ||
| + | |||
| + | 실행하면 다음 파일이 생성됩니다. | ||
| {{: | {{: | ||
| {{: | {{: | ||
| + | |||