지진_화산:지진
차이
문서의 선택한 두 판 사이의 차이를 보여줍니다.
| 양쪽 이전 판이전 판다음 판 | 이전 판 | ||
| 지진_화산:지진 [2026/04/23 08:32] – [2. 관측 (Observation)] admin | 지진_화산:지진 [2026/05/06 08:37] (현재) – [3. 산출물 (Data Products)] admin | ||
|---|---|---|---|
| 줄 1: | 줄 1: | ||
| ======지진 (Earthquake) 데이터====== | ======지진 (Earthquake) 데이터====== | ||
| - | [[지진_화산: | + | [[지진_화산: |
| 출처: https:// | 출처: https:// | ||
| 줄 16: | 줄 16: | ||
| 국가지진관측망은 지면의 미세한 움직임을 실시간으로 포착하기 위해 다수의 관측소와 고정밀 센서로 구성됩니다. | 국가지진관측망은 지면의 미세한 움직임을 실시간으로 포착하기 위해 다수의 관측소와 고정밀 센서로 구성됩니다. | ||
| - | | + | |
| - | | + | |
| + | * **속도계(Velocity Sensor)**: 지면 운동의 속도를 측정하며, | ||
| + | * **가속도계(Acceleration Sensor)**: 강지진 발생 시 포화(Saturation) 없이 강한 흔들림을 측정하며, | ||
| + | | ||
| - | | ||
| - | |||
| - | | ||
| - | |||
| - | | ||
| - | |||
| - | * 관측장소는 지상에 설치하는 것을 원칙으로 한다. 다만, 장애물 등의 영향이나 토지의 사용이 어려운 경우에는 건물의 옥상 등 적절한 장소에 설치할 수 있음 | ||
| - | * 관측장소의 면적은 35제곱미터 이상 | ||
| - | * 관측장소의 형태는 원형 또는 정사각형을 원칙으로 함 | ||
| - | * 관측장소가 지상인 경우에는 지면에 자연잔디를조성하고, | ||
| - | * 관측장소에는 기상측기 외의 다른 시설물의 설치를 최소화하여야 함 | ||
| - | * 관측장소에는 기상측기의 보호를 위하여 통풍에 지장이 없는 울타리를 설치 | ||
| - | * 건물 옥상에 온도계를 설치할 경우에는 건물의 복사열을 최소화할 수 있는 조치를 해야 함 | ||
| {{지진_화산: | {{지진_화산: | ||
| 줄 38: | 줄 28: | ||
| =====3. 산출물 (Data Products)===== | =====3. 산출물 (Data Products)===== | ||
| - | • 파형 데이터(Waveform Data): 관측소에서 기록된 시계열 데이터로, | + | |
| - | • 지진 카탈로그(Event Catalog): 지진 발생 시각, 진앙 위치(위경도), | + | |
| - | • 지진조기경보(EEW): | + | |
| - | • 계기진도 데이터: 관측된 최대지반가속도(PGA) 또는 속도(PGV)를 기반으로 산출된 지역별 진도 값(MMI 등급)입니다. | + | |
| {{지진_화산: | {{지진_화산: | ||
| 줄 60: | 줄 50: | ||
| 기상청은 울릉도, 임원항 및 정동진에 해일파고계(15m 이상 파고 관측 가능), 연안 방재 관측 시스템(17개소), | 기상청은 울릉도, 임원항 및 정동진에 해일파고계(15m 이상 파고 관측 가능), 연안 방재 관측 시스템(17개소), | ||
| - | • 해일파고계: | + | |
| - | • 조위관측소 공유: 국립해양조사원의 고정밀 조위 관측 데이터를 실시간으로 수신하여 보완적인 감시망을 운영합니다. | + | |
| {{지진_화산: | {{지진_화산: | ||
| 줄 69: | 줄 59: | ||
| =====3. 산출물 (Data Products)===== | =====3. 산출물 (Data Products)===== | ||
| - | • 수치모의 시나리오 DB: 가상 지진 파원에 따른 예상 파고 및 도달 시간을 미리 계산한 6,000여 개의 시나리오 세트입니다. | + | |
| - | • 지진해일 특보: 예상 파고가 일정 기준(주의보 0.5m, 경보 1.0m 이상)을 초과할 것으로 예상될 때 발표되는 격자형 공간 데이터입니다. | + | |
| - | • 실시간 해수면 변동 자료: 관측소별로 기록된 지진해일의 최대 파고와 내습 지속 시간 데이터입니다. | + | |
| {{지진_화산: | {{지진_화산: | ||
| - | =====4. 활용방안===== | + | ===== 4. 활용방안 ===== |
| 소스코드 : {{ : | 소스코드 : {{ : | ||
| - | ====① 데이터 파싱 함수==== | + | ==== 4.1. 라이브러리 설치 및 기본 설정 |
| + | |||
| + | 지진목록 자료를 조회하고 최근 1년 지진의 진앙 분포도를 시각화하기 위해 필요한 라이브러리를 설치합니다. | ||
| <code py> | <code py> | ||
| - | def parse_text_to_dataframe(text): | + | pip install requests pandas matplotlib cartopy |
| - | """ | + | |
| - | | + | |
| - | lines = extract_data_lines(text) | + | |
| - | if len(lines) == 0: | + | |
| - | | + | |
| - | rows = [] | + | |
| - | for line in lines: | + | |
| - | line = line.strip() | + | |
| - | # 맨 끝 ", | + | |
| - | if line.endswith(", | + | |
| - | line = line[:-2] | + | |
| - | # 앞 7개는 고정 필드, 나머지는 통째로 받음 | + | |
| - | parts = line.split(",", | + | |
| - | if len(parts) < 8: | + | |
| - | | + | |
| - | tp = parts[0].strip() | + | |
| - | tm_fc = parts[1].strip() | + | |
| - | seq = parts[2].strip() | + | |
| - | | + | |
| - | mt = parts[4].strip() | + | |
| - | lat = parts[5].strip() | + | |
| - | lon = parts[6].strip() | + | |
| - | # 나머지 전체에서 LOC만 추출 | + | |
| - | rest = parts[7].strip() | + | |
| - | loc = rest.split(",", | + | |
| - | | + | |
| - | if len(rows) == 0: | + | |
| - | | + | |
| - | df = pd.DataFrame(rows, | + | |
| - | | + | |
| </ | </ | ||
| - | ====② 전체 분포도 시각화 함수==== | + | |
| <code py> | <code py> | ||
| - | def plot_all_map(df): | + | import os |
| - | """ | + | from datetime import datetime, timedelta |
| - | if df.empty: | + | |
| - | print(" | + | import requests |
| - | return | + | import pandas as pd |
| - | fig, ax = setup_map() | + | |
| - | tmp = df.copy() | + | import matplotlib |
| - | tmp[" | + | matplotlib.use("Agg") |
| - | tmp["COLOR"] = tmp[" | + | import matplotlib.pyplot as plt |
| - | ax.scatter(tmp[" | + | from matplotlib import font_manager |
| - | add_color_legend(ax) | + | from matplotlib.lines import Line2D |
| - | ax.set_title(" | + | |
| - | plt.subplots_adjust(right=0.82) | + | import cartopy.crs as ccrs |
| - | plt.savefig(OUTPUT_PNG_ALL, | + | import cartopy.feature as cfeature |
| - | plt.close() | + | |
| - | | + | |
| </ | </ | ||
| - | ====③ 예상결과==== | + | API 인증키와 API 주소, 조회 기간, 결과 파일명을 설정합니다. |
| + | |||
| + | <code py> | ||
| + | AUTH_KEY | ||
| + | |||
| + | BASE_URL | ||
| + | |||
| + | START_TM | ||
| + | END_TM | ||
| + | |||
| + | DISP = 1 | ||
| + | HELP = 1 | ||
| + | |||
| + | OUTPUT_CSV = " | ||
| + | RAW_TEXT_FILE = " | ||
| + | |||
| + | OUTPUT_PNG_ALL = " | ||
| + | OUTPUT_PNG_M2 = " | ||
| + | OUTPUT_PNG_M4 = " | ||
| + | OUTPUT_PNG_M6 = " | ||
| + | OUTPUT_PNG_M8 = " | ||
| + | </ | ||
| + | |||
| + | BASE_URL은 (신)기상자료개방포털 > API > 지진/ | ||
| + | 메뉴의 호출 URL을 사용합니다. | ||
| + | |||
| + | START_TM과 END_TM을 None으로 설정하면 현재 시각 기준 최근 1년 기간을 자동으로 조회합니다. | ||
| + | |||
| + | ==== 4.2. 지진목록 자료 조회 및 정리 ==== | ||
| + | |||
| + | 조회 시작시각과 종료시각을 yyyyMMddHHmm 형식으로 생성합니다. | ||
| + | |||
| + | <code py> | ||
| + | def make_time_range(): | ||
| + | if START_TM is not None and END_TM is not None: | ||
| + | return START_TM, END_TM | ||
| + | |||
| + | now = datetime.now() | ||
| + | one_year_ago = now - timedelta(days=365) | ||
| + | |||
| + | tm1 = one_year_ago.strftime(" | ||
| + | tm2 = now.strftime(" | ||
| + | return tm1, tm2 | ||
| + | </ | ||
| + | |||
| + | 지진목록 API를 호출하여 응답 원문 TEXT를 받아옵니다. | ||
| + | |||
| + | <code py> | ||
| + | def fetch_earthquake_text(tm1, | ||
| + | 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 | ||
| + | |||
| + | if line[0].isdigit(): | ||
| + | lines.append(line) | ||
| + | |||
| + | return lines | ||
| + | </ | ||
| + | |||
| + | 응답 데이터 중 진앙 분포도 작성에 필요한 컬럼만 사용합니다. | ||
| + | |||
| + | <code py> | ||
| + | COLUMNS = [ | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | ] | ||
| + | </ | ||
| + | |||
| + | 추출한 데이터 줄을 pandas DataFrame으로 변환합니다. | ||
| + | |||
| + | <code py> | ||
| + | def parse_text_to_dataframe(text): | ||
| + | lines = extract_data_lines(text) | ||
| + | |||
| + | if len(lines) == 0: | ||
| + | return pd.DataFrame(columns=COLUMNS) | ||
| + | |||
| + | rows = [] | ||
| + | |||
| + | for line in lines: | ||
| + | line = line.strip() | ||
| + | |||
| + | if line.endswith(", | ||
| + | line = line[:-2] | ||
| + | |||
| + | parts = line.split(",", | ||
| + | |||
| + | if len(parts) < 8: | ||
| + | continue | ||
| + | |||
| + | tp = parts[0].strip() | ||
| + | tm_fc = parts[1].strip() | ||
| + | seq = parts[2].strip() | ||
| + | tm_eqk_msc = parts[3].strip() | ||
| + | mt = parts[4].strip() | ||
| + | lat = parts[5].strip() | ||
| + | lon = parts[6].strip() | ||
| + | |||
| + | rest = parts[7].strip() | ||
| + | loc = rest.split(",", | ||
| + | |||
| + | rows.append([tp, | ||
| + | |||
| + | df = pd.DataFrame(rows, | ||
| + | return df | ||
| + | </ | ||
| + | |||
| + | 숫자형 컬럼과 시간 컬럼을 변환하고, | ||
| + | |||
| + | <code py> | ||
| + | def clean_dataframe(df): | ||
| + | if df.empty: | ||
| + | return df | ||
| + | |||
| + | df = df.copy() | ||
| + | |||
| + | numeric_cols = [" | ||
| + | for col in numeric_cols: | ||
| + | df[col] = pd.to_numeric(df[col], | ||
| + | |||
| + | df[" | ||
| + | df[" | ||
| + | format=" | ||
| + | errors=" | ||
| + | ) | ||
| + | |||
| + | df[" | ||
| + | df[" | ||
| + | format=" | ||
| + | errors=" | ||
| + | ) | ||
| + | |||
| + | df = df.dropna(subset=[" | ||
| + | df = df.sort_values(" | ||
| + | |||
| + | return df | ||
| + | </ | ||
| + | |||
| + | ==== 4.3. 지진 진앙 분포도 저장 ==== | ||
| + | |||
| + | 지진 규모를 M2, M4, M6, M8 구간으로 분류합니다. | ||
| + | |||
| + | <code py> | ||
| + | def classify_magnitude(mt): | ||
| + | if pd.isna(mt): | ||
| + | return " | ||
| + | |||
| + | mt = float(mt) | ||
| + | |||
| + | if mt < 4.0: | ||
| + | return " | ||
| + | elif mt < 6.0: | ||
| + | return " | ||
| + | elif mt < 8.0: | ||
| + | return " | ||
| + | else: | ||
| + | return " | ||
| + | </ | ||
| + | |||
| + | 규모 구간별 색상을 지정합니다. | ||
| + | |||
| + | <code py> | ||
| + | COLOR_M2 = "# | ||
| + | COLOR_M4 = "# | ||
| + | COLOR_M6 = "# | ||
| + | COLOR_M8 = "# | ||
| + | |||
| + | def magnitude_class_to_color(cls): | ||
| + | if cls == " | ||
| + | return COLOR_M2 | ||
| + | elif cls == " | ||
| + | return COLOR_M4 | ||
| + | elif cls == " | ||
| + | return COLOR_M6 | ||
| + | elif cls == " | ||
| + | return COLOR_M8 | ||
| + | else: | ||
| + | return " | ||
| + | </ | ||
| + | |||
| + | 전지구 지도 배경을 생성합니다. | ||
| + | |||
| + | <code py> | ||
| + | def setup_map(): | ||
| + | fig = plt.figure(figsize=(16, | ||
| + | ax = plt.axes(projection=ccrs.PlateCarree()) | ||
| + | ax.set_global() | ||
| + | |||
| + | ax.coastlines(linewidth=0.8) | ||
| + | ax.add_feature(cfeature.BORDERS, | ||
| + | ax.add_feature(cfeature.LAND, | ||
| + | ax.add_feature(cfeature.OCEAN, | ||
| + | ax.gridlines(draw_labels=False, | ||
| + | |||
| + | return fig, ax | ||
| + | </ | ||
| + | |||
| + | 전체 지진 진앙 분포도를 저장합니다. | ||
| + | |||
| + | <code py> | ||
| + | def plot_all_map(df): | ||
| + | if df.empty: | ||
| + | print(" | ||
| + | return | ||
| + | |||
| + | fig, ax = setup_map() | ||
| + | |||
| + | tmp = df.copy() | ||
| + | tmp[" | ||
| + | tmp[" | ||
| + | |||
| + | ax.scatter( | ||
| + | tmp[" | ||
| + | tmp[" | ||
| + | s=32, | ||
| + | c=tmp[" | ||
| + | alpha=0.85, | ||
| + | edgecolors=" | ||
| + | linewidths=0.25, | ||
| + | transform=ccrs.PlateCarree() | ||
| + | ) | ||
| + | |||
| + | ax.set_title(" | ||
| + | |||
| + | plt.savefig(OUTPUT_PNG_ALL, | ||
| + | plt.close() | ||
| + | </ | ||
| + | |||
| + | 규모 구간별 지도도 각각 저장합니다. | ||
| + | |||
| + | <code py> | ||
| + | def plot_single_class_map(df, | ||
| + | if df.empty: | ||
| + | return | ||
| + | |||
| + | tmp = df.copy() | ||
| + | tmp[" | ||
| + | tmp = tmp[tmp[" | ||
| + | |||
| + | if tmp.empty: | ||
| + | return | ||
| + | |||
| + | color = magnitude_class_to_color(mag_class) | ||
| + | |||
| + | fig, ax = setup_map() | ||
| + | |||
| + | ax.scatter( | ||
| + | tmp[" | ||
| + | tmp[" | ||
| + | s=32, | ||
| + | color=color, | ||
| + | alpha=0.85, | ||
| + | edgecolors=" | ||
| + | linewidths=0.25, | ||
| + | transform=ccrs.PlateCarree() | ||
| + | ) | ||
| + | |||
| + | ax.set_title(f" | ||
| + | |||
| + | plt.savefig(output_png, | ||
| + | plt.close() | ||
| + | </ | ||
| + | |||
| + | 전체 실행 코드는 다음과 같습니다. | ||
| + | |||
| + | <code py> | ||
| + | tm1, tm2 = make_time_range() | ||
| + | |||
| + | text = fetch_earthquake_text(tm1, | ||
| + | |||
| + | df = parse_text_to_dataframe(text) | ||
| + | |||
| + | df = clean_dataframe(df) | ||
| + | |||
| + | df.to_csv(OUTPUT_CSV, | ||
| + | |||
| + | plot_all_map(df) | ||
| + | plot_single_class_map(df, | ||
| + | plot_single_class_map(df, | ||
| + | plot_single_class_map(df, | ||
| + | plot_single_class_map(df, | ||
| + | </ | ||
| + | |||
| + | 실행하면 다음 파일이 생성됩니다. | ||
| {{: | {{: | ||
| {{: | {{: | ||
| 줄 141: | 줄 425: | ||
| {{: | {{: | ||
| {{: | {{: | ||
| - | |||
| - | |||