본문 바로가기
Python/Data 처리

[python] NOAA sea temperature netCDF4(nc)파일을 이용한 데이터 시각화 (contour plot)

by 여비코기 2021. 4. 27.

 

이번 글에서는 NOAA(미국 국립해양대기청)에서 제공하는 해수면 온도(SST) 데이터를 활용하여 시각화 해보려고 한다

 

사용할 자료는 인공위성 자료를 재분석한 0.25º x  0.25º의 해상도를 가진 전 지구 30년 평균(1982~2010) 자료인 OISSTv2자료를 사용해 보고자 한다.

 

89.875 S - 89.875 N / 0.125 E - 359.875 E의 영역을 1440x720개의 구역으로 나누어 수온 데이터를 제공한다.

 

psl.noaa.gov/data/gridded/data.noaa.oisst.v2.highres.html

 

NOAA OI SST V2: High Resolution: NOAA Physical Sciences Laboratory

 

psl.noaa.gov

 

↑NOAA OISSTv2 제공 페이지

 

해당 OISSTv2 페이지에서 사용할 자료는 육지를 나타낼 Land Mask (lsmask.oisst.v2.nc)파일과 1982~2010년 평균 데이터를 담고 있는 Sea Surface Temperature (sst.day.mean.ltm.1982-2010.nc) 두가지 파일을 다운로드 받았다.

사용 자료

1. 필요 모듈 import

import numpy as np
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
import glob
from netCDF4 import Dataset

nc파일을 다루는데 필요한 netCDF4, 그리고 시각화에 필요한 각종 모듈들을 import 하였다.

 

from matplotlib import font_manager, rc
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name) #plot내 한글 깨지는 문제 해결
mpl.rcParams['axes.unicode_minus'] = False #마이너스 부호 깨지는 문제 해결

위 코드는 그림에 한글이 들어갈 시 깨지는 현상과, 마이너스 부호가 나타나지 않는 경우가 있어 plotting작업에서는 항상 추가해주는 코드이다. 본 작업에서는 한글과 마이너스가 들어가지 않지만 들어갈 경우를 대비해 추가해주었다.

 

2. 자료 살펴보기

input_dir='./'
input_file=glob.glob(input_dir+'*.nc')

본 경로에 있는 land mask와 sst파일을 모두 불러와 준다.

input_file
Out[9]: ['.\\lsmask.oisst.v2.nc', '.\\sst.day.mean.ltm.1982-2010.nc']

 

land_data=Dataset(input_file[0],'r')
land_data

<class 'netCDF4._netCDF4.Dataset'>
root group (NETCDF3_CLASSIC data model, file format NETCDF3):
    Conventions: CF-1.0
    title: NOAA High-resolution Blended Analysis: Daily Values using AVHRR only
    institution: NOAA/NCDC
    source: NOAA/NCDC  ftp://eclipse.ncdc.noaa.gov/pub/OI-daily-v2/
    references: http://www.esrl.noaa.gov/psd/data/gridded/data.noaa.oisst.v2.highres.html
    history: Version 1.0
    comment: Reynolds, et al., 2007: Daily High-Resolution-Blended Analyses for Sea Surface Temperature. J. Climate, 20, 5473-5496.  Climatology is based on 1971-2000 OI.v2 SST, Satellite data: Navy NOAA17 NOAA18 AVHRR, Ice data: NCEP ice.
    dataset_title: NOAA Daily Optimum Interpolation Sea Surface Temperature
    References: http://www.esrl.noaa.gov/psd/data/gridded/data.noaa.oisst.v2.highres.html
    dimensions(sizes): time(1), lat(720), lon(1440)
    variables(dimensions): float64 time(time), float32 lat(lat), float32 lon(lon), float32 lsmask(time, lat, lon)
    groups: 

먼저 land_mask nc파일을 불러와 그 구조를 살펴보니 'variables'중 'lsmask'가 0과 1의 가지고 있어 0은 바다, 1은 육지의 공간을 나타내는 것을 확인할 수 있었다.

 

land mask 자료 형태

 

lon=land_data['lon'][:]
lat=land_data['lat'][:]

fig=plt.figure(figsize=(16,9))
ax = fig.add_subplot(111)

cont=ax.contourf(lon-180,lat,land,colors='#F8ECD2')
conline=ax.contour(lon-180, lat, land, colors='k',linewidths=0.2,alpha=0.8)

land mask파일에서 경도, 위도 변수를 함께 불러와 coutour의 각각 x, y축으로 설정하고 land데이터를 plot 해보았다. 

*경도(lon)의 경우 대서양 중심의 데이터를 태평양 중심으로 변환 하기 위해 -180을 빼주었다.

 

land mask contour plot

basemap이나 shp파일을 따로 불러와 지도를 그리는 것 보다는 해안선 모양과 해상도가 많이 떨어지지만 어느정도 대륙과 국가들의 해안선을 표현한 것을 확인 할 수 있다.

 

 

이번엔 수온 데이터를 담고있는 sst파일을 살펴보자.

oisst=Dataset(input_file[1],'r')
oisst

<class 'netCDF4._netCDF4.Dataset'>
root group (NETCDF3_64BIT_OFFSET data model, file format NETCDF3):
    Conventions: CF-1.5
    title: NOAA High-resolution Blended Analysis: Daily Values using AVHRR only
    institution: NOAA/NCDC
    source: NOAA/NCDC  ftp://eclipse.ncdc.noaa.gov/pub/OI-daily-v2/
    references: http://www.esrl.noaa.gov/psd/data/gridded/data.noaa.oisst.v2.highres.html
    comment: Reynolds, et al., 2007: Daily High-Resolution-Blended Analyses for Sea Surface Temperature. J. Climate, 20, 5473-5496.  Climatology is based on 1971-2000 OI.v2 SST, Satellite data: Navy NOAA17 NOAA18 AVHRR, Ice data: NCEP ice.
    history: Created 2017/08/25 by doDayLTM64
    not_missing_threshold_percent: minimum 3% values input to have non-missing output value
    dimensions(sizes): lon(1440), lat(720), time(365), nbnds(2)
    variables(dimensions): float32 lat(lat), float32 lon(lon), float64 time(time), float64 climatology_bounds(time, nbnds), float32 sst(time, lat, lon), int16 valid_yr_count(time, lat, lon)
    groups: 

sst파일 역시 lat과 lon variables에 위도, 경도 데이터가 들어있고, sst variables에는 1982~2010년 총 30년 평균 데이터가 1월 1일 ~ 12월 31일까지 Daily mean 형태로 들어있다.

 

아래 sst 데이터 형태가 나타나 있다. 육지가 존재하는 곳은 데이터가 없으며 해양에서는 각 지점의 수온 자료가 나타난다.

sst 데이터 형태

3. 데이터 시각화

OISSTv2 자료의 1월 1일 30년 평균 해수면 온도를 land mask 자료와 함께 contour를 그리면 다음과 같이 표현이 가능하다.

sst=oisst['sst'][:]

fig=plt.figure(figsize=(16,9))
ax = fig.add_subplot(111)

cont=ax.contourf(lon-180,lat,land,colors='#F8ECD2') #lon, lat은 lsmask lon, lat 사용
conline=ax.contour(lon-180, lat, land, colors='k',linewidths=0.2,alpha=0.8)

cont2=ax.contourf(lon-180, lat, sst[0], levels=300, cmap="RdBu_r",vmin=-5,vmax=35)

plt.title('OISSTv2 1982~2010 Sea Surface Temperature',fontweight='bold',fontsize=16)
plt.xlabel('Longitude',fontsize=13,fontweight='bold')
plt.ylabel('Latitude',fontsize=13,fontweight='bold')

해당 그림에서 저위도에서는 고온의 수온분포가, 고위도에서는 저온의 수온분포가 확연히 나타나는 것을 확인할 수 있다.  또한 contourf의 'levels=300'을 설정하여 작은 값들의 차이도 눈에 띌 수 있도록 하여 수온분포의 해상도를 높혀주었다.

 

 

마지막으로 1월 1일 ~ 12월 31일까지 총 365일 데이터를 평균하고, plot에는 등온선과, colorbar등 각종 옵션을 넣어줄 것이다.

sst=oisst['sst'][:]

sst_0=sst[0]
for i in range(1,len(sst)):
    sst_0+=sst[i]

sst_mean=sst_0/len(sst)

fig=plt.figure(figsize=(16,9))
ax = fig.add_subplot(111)

cont=ax.contourf(lon-180,lat,land,colors='#F8ECD2')
conline=ax.contour(lon-180, lat, land, colors='k',linewidths=0.2,alpha=0.8)

cont2=ax.contourf(lon-180, lat, sst_mean, levels=300, cmap="RdBu_r",vmin=-5,vmax=35)

plt.title('OISSTv2 1982~2010 mean Sea Surface Temperature',fontweight='bold',fontsize=16)
plt.xlabel('Longitude',fontsize=13,fontweight='bold')
plt.ylabel('Latitude',fontsize=13,fontweight='bold')


conline2=ax.contour(lon-180, lat, sst_mean, levels=19, linewidths=0.3, colors='k',alpha=0.8)
conline2.collections[5].set_linewidth(1.1)
conline2.collections[10].set_linewidth(1.1)
conline2.collections[15].set_linewidth(1.1)

plt.xticks(fontsize=11)
plt.yticks(fontsize=11)

ytick=[-90,-60,-30,0,30,60,90]
ax.set_yticks(ytick)
ax.set_yticklabels(ytick, rotation=90, va='center', minor=False)
lon_formatter = LongitudeFormatter(number_format='.1f',degree_symbol='˚',dateline_direction_label=True)
lat_formatter = LatitudeFormatter(number_format='.1f',degree_symbol='˚')
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)

v=np.linspace(-5,35,9)
axins = inset_axes(ax,
                   width="5%", # width = 10% of parent_bbox width
                   height="10%", # height : 50%
                   loc=3,
                   bbox_to_anchor=(0.02, 0.04, 7, 0.27),
                   bbox_transform=ax.transAxes,
                   borderpad=0,
                   )
clb=plt.colorbar(cont2,cax=axins,orientation='horizontal',
                 ticks=v, norm=mpl.colors.Normalize(vmin=-5, vmax=35))

ax.clabel(conline2,inline=True,fontsize=10,fmt='%1.0f')

colorbar를 통해 색상에 따른 대략적인 수온 분포를 알수 있게 하였으며, 등온선을 지도에 함께 표시해주었다.

또한, 위도와 경도의 format을 정수형에서 도분초 형태로 변환해주었다.

 

 

주로 위성자료 혹은 재분석 자료를 nc파일로 배포하는 경우가 많은데, nc파일의 구조를 이해하고 내가 사용하고자 하는 변수들을 slice해서 사용한다면 평면적인 시각화에서는 큰 어려움이 없을 것이다. 또한 time의 시간 변수가 존재하므로  3차원의 형태로 데이터들이 변화하는 모습 역시 볼 수 있을 것이다.

댓글