# API 요청을 위한 requests 라이브러리와
# 그래프를 그리기 위한 seaborn 라이브러리,
# 지도를 표출하기 위한 folium 라이브러리를 설치합니다.
%pip install requests seaborn folium
==== 7.2. 해양관측 지점정보 다운로드 ====
기상청 API허브에 회원가입 후 자신의 인증키를 사용하여 API 요청을 할 수 있습니다.
API 인증키를 코드상에 직접 남겨 사용하는 방식은 다른 사람에게 코드를 공유할때 인증키가 노출되는 문제가 생길 수 있습니다.
따라서 인증키를 환경변수로 등록하여 코드상에 노출되지 않도록 사용하는 것이 바람직합니다.
import os # 환경변수를 불러오는 표준 라이브러리
# 바람직하지 않은 방법
# my_api_key = 'MY_PRIVATE_API_AUTHENTICATE_KEY'
# 인증키를 'myapikey'라는 이름의 환경변수로 미리 등록해놓은 뒤 이를 불러와서 사용합니다.
my_api_key = os.environ['myapikey']
먼저 해양관측 지점 정보를 조회해 파일로 다운로드합니다.
import requests # API 요청을 보내고 받는 라이브러리
import json # API 응답을 json형태로 가공하기 위한 라이브러리
# 해양관측 지점정보 API url
api_url = 'https://apihub.kma.go.kr/api/typ01/url/stn_inf.php'
# 요청인자로 지점종류, 해당 시점, 지점번호, 도움말추가 여부가 있습니다.
# 1. 지점종류를 입력합니다.
# 해양 관측은 BUOY(부이)를 입력합니다.
stn_type = 'BUOY'
# 2. 해당 시점 및 특정 지점번호를 입력합니다.
# 여기선 현재 시각에 존재하는 모든 지점번호를 조회하기 위해 값을 입력하지 않습니다.
# 3. 도움말추가 여부를 입력합니다.
# 파일 내에 각 필드에 대한 도움말을 확인할 수 있습니다.
help = 1
# API 요청인자들을 묶어 dictionary로 정의합니다.
api_parameters = {
'inf': stn_type,
'help': help,
'authKey': my_api_key
}
# API 응답을 저장할 파일 형식은 txt 파일로 저장합니다.
file_name = f'{stn_type.lower()}_stn_inf.txt'
# 저장된 파일이 없는 경우에만 API를 요청해 파일을 다운로드합니다.
if not os.path.exists(file_name):
# API 요청인자와 함께 API 요청
response = requests.get(api_url, params=api_parameters)
# 잘못된 응답을 받을 경우 에러 메세지 출력
if response.status_code != 200:
raise requests.RequestException(
json.loads(response.content)['result']['message']
)
else:
# 그외의 올바른 응답에 대해서만 UTF-8 txt 파일 형태로 저장합니다.
with open(file_name, 'wb') as f:
f.write(response.content.decode('euc-kr').encode('utf-8'))
print(f'{file_name} 파일 다운로드 완료')
//''buoy_stn_inf.txt 파일 다운로드 완료''//
다운로드 받은 파일을 열면 BUOY 각 지점 번호와 이름, 지점 정보를 확인할 수 있습니다.
BUOY 각 지점들의 위치를 지도로 표출합니다.
import pandas as pd # dataframe을 다루는 패키지
# 다운로드 받은 txt파일을 dataframe으로 변환합니다.
# 도움말을 비롯한 파일의 첫 20줄과 마지막 1줄을 제외하여 변환합니다.
# 각 필드를 구분하도록 너비를 정의하고 존재하는 모든 필드를 사용합니다.
stn_info = pd.read_fwf(
file_name,
skiprows=20,
skipfooter=1,
names=['STN_ID', 'LON', 'LAT', 'STN_SP', 'HT', 'STN_AD', 'STN_KO', 'STN_EN', 'FCT_ID'],
index_col=['STN_ID'],
colspecs=((0, 5), (7, 19), (22, 33), (34, 47), (48, 52), (53, 56), (57, 71), (72, 95), (96, 104)),
converters={'STN_SP': str}
)
# dataframe의 첫 5개의 데이터를 표출합니다.
stn_info.head(5)
^ STN_ID^ LON^ LAT^ STN_SP^ HT^ STN_AD^ STN_KO^ STN_EN^ FCT_ID^
| 1791| 126.3933| 37.1133| 40000004| 0.0| 0| 평택풍도| ----| NaN|
| 2787| 126.6161| 37.3800| 40000003| 0.0| 0| 인천송도| ----| NaN|
| 4777| 126.3039| 36.9781| 40000006| 0.0| 0| 대산| ----| NaN|
| 5776| 126.5864| 35.9742| 40000005| 0.0| 0| 군산| ----| NaN|
| 6785| 126.3097| 34.7514| 40000007| 0.0| 0| 목포| ----| NaN|
해양관측 지점정보에는 해양기상부이 외에도 등표기상관측장비, 파고부이가 존재합니다.
이들 중 관리관서번호(STN_AD)가 존재하는 지점이 해양기상부이입니다.
# 관리관서번호(STN_AD)가 존재하는 해양기상부이 지점으로 필터링해
# dataframe의 첫 5개의 데이터를 표출합니다.
buoy_stn_info = stn_info[stn_info.STN_AD > 0]
buoy_stn_info.head(5)
^ STN_ID^ LON^ LAT^ STN_SP^ HT^ STN_AD^ STN_KO^ STN_EN^ FCT_ID^
| 21229| 131.1144| 37.4554| 00000001| 0.0| 143| 울릉도| Ulleungdo| 12C20000|
| 22101| 126.0188| 37.2361| 00000001| 0.0| 112| 덕적도| Deokjeokdo| 12A20000|
| 22102| 125.7769| 34.7933| 00000001| 0.0| 165| 칠발도| Chilbaldo| 12A30000|
| 22103| 127.5014| 34.0014| 00000001| 0.0| 156| 거문도| Geomundo| 12B10000|
| 22104| 128.9000| 34.7667| 00000001| 0.0| 159| 거제도| Geojedo| 12B20000|
import folium # 지도로 표출하기 위한 라이브러리
# 너비, 높이 크기가 800인 지도 생성
fig = folium.Figure(width=800, height=800)
# 지도의 위·경도 중심과 범위를 정해 맵을 생성합니다.
map = folium.Map(
location=[37.5, 127],
zoom_start=6,
min_zoom=6,
min_lat=30,
max_lat=44,
min_lon=123,
max_lon=132,
max_bounds=True,
).add_to(fig)
# dataframe의 데이터를 읽어 지도위에 마커를 추가합니다.
# marker를 클릭하면 팝업으로 지점명, 위·경도와 해발고도를 표출하도록 html을 추가합니다.
for idx, row in buoy_stn_info.iterrows():
html=f"""
{row['STN_KO']}({idx})
위도: {row['LAT']}
경도: {row['LON']}
해발고도: {row['HT']}m
"""
iframe = folium.IFrame(html=html, width=170, height=170)
popup = folium.Popup(iframe, max_width=1000)
folium.Marker(
location=row[['LAT', 'LON']].tolist(),
popup=popup,
).add_to(map)
# 지도를 표출합니다.
fig
{{:기상관측:해양:buoy_map.png?400|}}
해양기상부이 지점들 중 하나를 골라 시계열 그래프를 그려봅니다.
==== 7.3. 시계열 그래프 표출 ====
시계열 그래프를 표출하기 전, 그래프에 한글을 표출하려면 한글 폰트의 설정이 필요합니다.
만약, 한글 표출을 하지 않는다면 이 부분을 실행하지 않아도 좋습니다.
# Linux Ubuntu에서의 나눔폰트 설치와 캐시를 제거합니다.
!sudo apt-get install -y fonts-nanum
!sudo fc-cache -fv
!rm -rf ~/.cache/matplotlib
import seaborn as sns # 그래프를 그리는 라이브러리
import matplotlib.pyplot as plt # 그래프를 그리는 라이브러리
# 한글을 출력하기 위해 사용하는 기본 폰트를 나눔고딕으로 설정합니다.
plt.rc("font", family = "NanumGothic")
sns.set_theme(font="NanumGothic", rc={"axes.unicode_minus": False}, style='darkgrid')
import requests # API 요청을 보내고 받는 라이브러리
import json # API 응답을 json형태로 가공하기 위한 라이브러리
# 해양기상부이 자료 조회 API url
api_url = 'https://apihub.kma.go.kr/api/typ01/url/kma_buoy2.php'
# 요청인자로 시작시간, 종료시간, 지점번호, 도움말추가 여부가 있습니다.
# 1. 시작시간과 종료시간을 년월일시분 형태로 입력합니다.
start_time = '202402010000'
end_time = '202403010000'
# 2. 앞에서 확인한 지점번호를 입력합니다.
stn_id = 22185
# 3. 도움말추가 여부를 입력합니다.
# 파일 내에 각 필드에 대한 도움말을 확인할 수 있습니다.
help = 1
# API 요청인자들을 묶어 dictionary로 정의합니다.
api_parameters = {
'tm1': start_time,
'tm2': end_time,
'stn': stn_id,
'help': help,
'authKey': my_api_key
}
# API 응답을 저장할 파일 형식은 txt 파일로 저장합니다.
file_name = f"buoy_data_{stn_id}_{start_time}_{end_time}.txt"
# 저장된 파일이 없는 경우에만 API를 요청해 파일을 다운로드합니다.
if not os.path.exists(file_name):
# API 요청인자와 함께 API 요청
response = requests.get(api_url, params=api_parameters)
# 잘못된 응답을 받을 경우 에러 메세지 출력
if response.status_code != 200:
raise requests.RequestException(
json.loads(response.content)['result']['message']
)
# 짧은 에러메세지를 응답으로 받은 경우 에러 메세지 출력
elif len(response.content) < 100:
raise ValueError(response.content.decode('euc-kr').split('#')[2])
else:
# 그외의 올바른 응답에 대해서만 UTF-8 txt 파일 형태로 저장합니다.
with open(file_name, 'wb') as f:
f.write(response.content.decode('euc-kr').encode('utf-8'))
print(f"{file_name} 파일 다운로드 완료")
//''buoy_data_22185_202402010000_202403010000.txt 파일 다운로드 완료''//
다운로드 받은 해양기상부이 파일을 열어 자료를 확인할 수 있습니다.
풍속, 기압, 습도, 기온, 파고 시계열 그래프를 표출합니다.
import pandas as pd # dataframe을 다루는 패키지
import numpy as np # 배열을 다루는 패키지
# 다운로드 받은 txt파일을 dataframe으로 변환합니다.
# 도움말을 비롯한 파일의 첫 28줄과 마지막 1줄을 제외하여 변환합니다.
# 각 필드는 쉼표로 구분되어있으며 QC필드를 제외한 모든 필드를 사용합니다.
column_names = [
'TIME', 'STN_ID', 'WD1', 'WS1', 'WS1_GST', 'WD2', 'WS2', 'WS2_GST',
'PA', 'HM', 'TA', 'TW', 'WH_MAX', 'WH_SIG', 'WH_AVE', 'WP', 'WO',
'AQC', 'MQC', 'NONE'
]
buoy_data = pd.read_csv(
file_name,
skiprows=28,
skipfooter=1,
names=column_names,
parse_dates=['TIME'],
index_col=['TIME', 'STN_ID'],
usecols=column_names[:-3],
engine='python'
)
# 도움말에 따르면 값이 -50 이하라면 관측이 없거나, 에러처리된 것을 표시합니다.
# 따라서 그 값을 NaN으로 변환합니다.
buoy_data[buoy_data <= -50] = np.nan
# dataframe의 첫 5개의 데이터를 표출합니다.
buoy_data.head(5)
^ TIME^ STN_ID^ WD1^ WS1^ WS1_GST^ WD2^ WS2^ WS2_GST^ PA^ HM^ TA^ TW^ WH_MAX^ WH_SIG^ WH_AVE^ WP^WO^
| 2024-02-01 00:00:00| 22185| NaN| 8.5| 10.6| 340| 8.5| 10.6| 1026.2| 84.0| 2.0| 4.7| 1.8| 1.2| 0.9| 4.1| 303|
| 2024-02-01 00:30:00| 22185| NaN| 9.2| 11.3| 345| 9.2| 11..| 1026.3| 78.0| 1.7| 4.7| 1.8| 1.2| 0.9| 4.1| 312|
| 2024-02-01 01:00:00| 22185| NaN| 9.3| 11.5| 339| 9.3| 11.4| 1026.4| 75.0| 1.7| 4.5| 2.1| 1.2| 0.9| 3.9| 316|
| 2024-02-01 01:30:00| 22185| NaN| 8.5| 11.1| 347| 8.5| 11.3| 1026.3| 73.0| 1.6| 4.3| 2.3| 1.3| 0.9| 4.0| 316|
| 2024-02-01 02:00:00| 22185| NaN| 7.6| 10.0| 355| 7.7| 10.1| 1026.6| 77.0| 1.1| 4.0| 2.2| 1.3| 1.0| 4.0| 309|
# 필드 중 풍속(WS), GUST 풍속(WS_GST)을 표출합니다.
vars = ['WS1', 'WS1_GST']
# 그래프를 그릴 크기를 지정합니다.
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
# 그래프의 제목을 지정합니다.
# 제목에 한글 지점명이 사용됩니다.
ax.set_title(
f"{stn_info[stn_info.index == stn_id]['STN_KO'].iloc[0]}({stn_id}) "
f"{buoy_data.index.get_level_values('TIME')[0]} - {buoy_data.index.get_level_values('TIME')[-1]}")
# x축의 라벨을 45도 기울여서 사용합니다.
ax.tick_params(axis='x', rotation=45)
# 각 요소별로 시계열 그래프를 그립니다.
for var in vars:
sns.lineplot(buoy_data, x=buoy_data.index.get_level_values('TIME'), y=var, label=var, ax=ax)
# 그래프를 표출합니다.
plt.show()
{{:기상관측:해양:buoy_ws.png?600|}}
# 필드 중 해면기압(PA)을 표출합니다.
vars = ['PA']
# 그래프를 그릴 크기를 지정합니다.
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
# 그래프의 제목을 지정합니다.
# 제목에 한글 지점명이 사용됩니다.
ax.set_title(
f"{stn_info[stn_info.index == stn_id]['STN_KO'].iloc[0]}({stn_id}) "
f"{buoy_data.index.get_level_values('TIME')[0]} - {buoy_data.index.get_level_values('TIME')[-1]}")
# x축의 라벨을 45도 기울여서 사용합니다.
ax.tick_params(axis='x', rotation=45)
# 해면기압 시계열 그래프를 그립니다.
for var in vars:
sns.lineplot(buoy_data, x=buoy_data.index.get_level_values('TIME'), y=var, label=var, ax=ax)
# 그래프를 표출합니다.
plt.show()
{{:기상관측:해양:buoy_pa.png?600|}}
# 필드 중 상대 습도(HM)를 표출합니다.
vars = ['HM']
# 그래프를 그릴 크기를 지정합니다.
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
# 그래프의 제목을 지정합니다.
# 제목에 한글 지점명이 사용됩니다.
ax.set_title(
f"{stn_info[stn_info.index == stn_id]['STN_KO'].iloc[0]}({stn_id}) "
f"{buoy_data.index.get_level_values('TIME')[0]} - {buoy_data.index.get_level_values('TIME')[-1]}")
# x축의 라벨을 45도 기울여서 사용합니다.
ax.tick_params(axis='x', rotation=45)
# 상대 습도 시계열 그래프를 그립니다.
for var in vars:
sns.lineplot(buoy_data, x=buoy_data.index.get_level_values('TIME'), y=var, label=var, ax=ax)
# 그래프를 표출합니다.
plt.show()
{{:기상관측:해양:buoy_hm.png?600|}}
# 필드 중 기온(TA), 해수면 온도(TW)를 표출합니다.
vars = ['TA', 'TW']
# 그래프를 그릴 크기를 지정합니다.
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
# 그래프의 제목을 지정합니다.
# 제목에 한글 지점명이 사용됩니다.
ax.set_title(
f"{stn_info[stn_info.index == stn_id]['STN_KO'].iloc[0]}({stn_id}) "
f"{buoy_data.index.get_level_values('TIME')[0]} - {buoy_data.index.get_level_values('TIME')[-1]}")
# x축의 라벨을 45도 기울여서 사용합니다.
ax.tick_params(axis='x', rotation=45)
# 각 요소별로 시계열 그래프를 그립니다.
for var in vars:
sns.lineplot(buoy_data, x=buoy_data.index.get_level_values('TIME'), y=var, label=var, ax=ax)
# 그래프를 표출합니다.
plt.show()
{{:기상관측:해양:buoy_ta.png?600|}}
# 필드 중 최대파고(WH_MAX), 유의파고(WH_SIG), 평균파고(WH_AVE)를 표출합니다.
vars = ['WH_MAX', 'WH_SIG', 'WH_AVE']
# 그래프를 그릴 크기를 지정합니다.
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
# 그래프의 제목을 지정합니다.
# 제목에 한글 지점명이 사용됩니다.
ax.set_title(
f"{stn_info[stn_info.index == stn_id]['STN_KO'].iloc[0]}({stn_id}) "
f"{buoy_data.index.get_level_values('TIME')[0]} - {buoy_data.index.get_level_values('TIME')[-1]}")
# x축의 라벨을 45도 기울여서 사용합니다.
ax.tick_params(axis='x', rotation=45)
# 각 요소별로 시계열 그래프를 그립니다.
for var in vars:
sns.lineplot(buoy_data, x=buoy_data.index.get_level_values('TIME'), y=var, label=var, ax=ax)
# 그래프를 표출합니다.
plt.show()
{{:기상관측:해양:buoy_wh.png?600|}}