대한민국 기상청에서는 각종 기상요소들(기온, 기압, 습도, 풍향, 풍속 등)에 대한 전국적인 관측을 위하여 종관기상관측장비(ASOS : Automated Synoptic Observing System)들을 전국에 걸쳐 다수의 지점들에 설치하여 운영하고 있습니다. 2024년 12월 기준으로는 97개의 지점들이 운영되고 있으며 각 지점별로 17종 이상의 기상요소들을 주기적으로 관측하고 있습니다. 이러한 ASOS 지점들에 대한 정보 및 각 지점별 관측요소 데이터는 기상자료 개방포털 웹페이지를 통하여 누구나 받을 수 있도록 서비스가 되고 있습니다. 그래서 기상청 ASOS 지점들의 분포 자료 및 각 지점별 기상관측자료를 받아서 IDL에서 이를 처리하고 가시화하는 작업들에 대한 예제를 앞으로 몇 차례에 걸쳐서 순차적으로 소개를 해보고자 합니다. 오늘은 첫번째 순서로서 ASOS 지점들의 분포 자료를 IDL에서 읽고 처리하여 그 분포를 지도상에 표출해보는 방법을 예제와 함께 소개해보겠습니다.
전국 97개 ASOS 지점들에 대한 분포 자료는 앞서 언급한 기상자료 개방포털 웹페이지에서 종관기상관측 지점정보 메타데이터 섹션에서 받을 수 있습니다. 해당 웹페이지에서 이러한 섹션을 찾아보면 그 모습은 대략 다음과 같습니다.
이 자료는 엑셀 파일로 받을 수 있는데 IDL에서 원활한 처리를 위해서는 XLS 보다는 CSV 파일로 받아야 합니다. 이를 위해서는 우측 하단에 보이는 CSV 버튼을 누르면 됩니다. CSV 파일은 기본적으로는 텍스트 파일이며 각 라인에서는 값들이 코마(,)로 분할되어 있습니다. 그리고 일반적인 텍스트 에디터 앱으로도 읽을 수 있고 물론 엑셀에서도 읽을 수 있습니다. 제가 받았던 ASOS 지점 메타자료 CSV 파일을 엑셀로 열어본 모습은 다음과 같습니다.
위의 모습은 전체 내용의 전반부에 해당됩니다. 참고로 이 CSV 파일은 아래에도 첨부해놓습니다. 제가 2025년 1월 2일에 수신한 자료 파일입니다.
위의 스크린샷을 보면 이 자료 파일 내에는 여러 개의 컬럼들이 존재하는데, 일단 지금 하고자 하는 작업에 있어서 가장 중요한 정보들은 지점번호(컬럼 1), 종료일(컬럼 3), 지점명(컬럼 4), 위도(컬럼 7), 경도(컬럼 8) 정도라고 볼 수 있습니다. 여기서 '종료일' 컬럼에 구체적인 날짜가 있는 경우는 해당 지점의 ASOS 장비의 운영이 중단된 날짜가 됩니다. 그런데 대개의 경우는 그 지점에서 ASOS 장비의 운영이 완전히 중단되었다기보다는 아마도 새로운 장비로 교체된 시점인 경우로 보입니다. 예를 들어 99번 지점인 파주의 경우를 보면 두 라인이 연달아 보이는데, 아마 기존 장비에서 새로운 장비로 교체된 시점이 2013년 10월 22일이었던 것으로 추측됩니다. 어쨌든 IDL에서 이 데이터 파일을 읽을 때에는 운영이 종료된 장비 및 해당 지점들은 제외해야 하기 때문에 이 종료일 컬럼도 처리에 있어서 중요한 역할을 합니다.
그러면 지금부터 본격적인 작업을 시작해봅시다. 먼저 CSV 파일을 읽어서 필요한 컬럼들을 배열로 가져오는 작업부터 시작해야 합니다. 이 과정에서는 제가 예전에도 소개했던 READCOL 명령을 사용하고자 합니다(원래 CSV 파일을 읽는 용도의 READ_CSV 함수를 사용하는 것도 가능한데, 제가 시도해본 바로는 무슨 이유에서인지 READ_CSV 함수로는 잘 읽히질 않아서 그냥 READCOL 명령을 사용하기로 합니다). 다만 READCOL 명령은 IDL에 기본 내장된 것이 아니고 IDL Astro 라이브러리를 추가로 설치해야 사용이 가능한데, IDL에서 READCOL 명령을 사용하기 위하여 필요한 준비 과정에 대해서는 관련 게시물의 내용을 참조하시기 바랍니다.
file = 'ASOS_stations.csv'
READCOL, file, numbers, exp_dates, names, lats, lons, $
FORMAT='I,X,A,A,X,X,F,F', DELIMITER=',', /PRESERVE_NULL
HELP, numbers, exp_dates, names, lats, lons
여기서는 앞서 가장 중요한 정보들이라고 했던 5개의 컬럼들을 각각 numbers, exp_dates, names, lats, lons라는 이름의 배열로 가져오도록 하였습니다. 참고로 READCOL 명령을 사용하는데 있어서 DELIMETER 키워드에 ','을 부여한 것은 하나의 라인상에서 각 컬럼은 코마(,)로 분할되어 있기 때문에 분할의 기준이 될 문자를 ','로 확실하게 명시한 것입니다. 그리고 PRESERVE_NULL 키워드를 사용한 것은 코마로 분할된 마디 내에 아무런 내용도 없는 경우 즉 !null인 경우에도 이를 어떤 값으로든 가져오라는 의미입니다. 이러한 설정을 하지 않으면 !null인 부분을 정말 아무것도 없는 것으로 취급하여 컬럼별 분할을 올바르게 하지 못할 수도 있기 때문입니다. 어쨌든 HELP에 의하여 출력된 정보를 보면 다음과 같습니다.
NUMBERS INT = Array[144]
EXP_DATES STRING = Array[144]
NAMES STRING = Array[144]
LATS FLOAT = Array[144]
LONS FLOAT = Array[144]
이와 같이 각 배열이 144개의 값들로 구성되었다는 것은 총 144개의 지점들에 대한 정보를 획득하였다는 의미가 됩니다. 다만 여기서 유의해야 할 것은 앞서도 잠시 언급했듯이 운영이 중단된 지점 항목들은 제외시켜야 한다는 것입니다. 이러한 판단을 위해서는 exp_dates의 값들 중에서 살려야 할 항목과 버려야 할 항목을 구분하는 기준이 필요합니다. 그 기준을 어떻게 정할 것인지를 확인하기 위하여 일단 위의 5개의 배열의 값들을 순차적으로 출력해봅시다.
FOR j = 0, N_ELEMENTS(numbers)-1 DO $
PRINT, numbers[j], exp_dates[j], names[j], lons[j], lats[j], $
FORMAT='(I3, 2X, A12, 2X, A8, 2X, F7.3, 2X, F7.4)'
이와 같이 반복형 구문을 사용하여 5개 배열의 값들을 순서대로 출력해보면 다음과 같습니다(편의상 앞부분 일부만 제시합니다).
90 0 속초 128.565 38.2509
93 0 북춘천 127.754 37.9474
95 0 철원 127.304 38.1479
98 0 동두천 127.061 37.9019
99 0 파주 126.767 37.8859
99 2013-10-22 파주 126.767 37.8859
100 0 대관령 128.718 37.6771
100 2006-11-07 대관령 128.759 37.6869
101 0 춘천 127.736 37.9026
이러한 내용을 보면 운영이 중단된 지점은 '2013-10-22'와 같이 구체적인 날짜 정보가 있는 반면 그렇지 않은 지점은 그냥 '0'으로 표기되어 있는 것이 눈에 띕니다. '0'이라는 문자가 등장한 것은 앞서 READCOL 명령에서 /PRESERVE_NULL 키워드를 사용하였기 때문이라고 보면 됩니다. 따라서 exp_dates 배열을 구성하는 문자값들 중에서 그 값이 '0'인 경우는 살리고 그 이외의 경우는 제외하는 방식으로 한번 더 걸러내는 것이 필요합니다. 이러한 선별 과정은 다음과 같이 처리합니다.
ww = WHERE(STRTRIM(exp_dates, 2) EQ '0', count)
numbers = numbers[ww]
names = names[ww]
lats = lats[ww]
lons = lons[ww]
HELP, numbers, names, lons, lats
이와 같이 WHERE 함수를 사용하여 exp_dates 배열의 값이 '0'인 경우만 살리도록 하였습니다. 다만 이 과정에서 굳이 STRTRIM 함수까지 동원한 이유는 0으로 보이는 문자값이 실제로는 무슨 이유에서인지 ' 0'과 같은 식으로 쓸데없는 공백이 함께 붙어있기 때문에 이러한 공백을 제거하고 판단하기 위한 것입니다. 어쨌든 이번에 다시 HELP에 의하여 출력된 내용을 보면 다음과 같습니다.
NUMBERS INT = Array[97]
NAMES STRING = Array[97]
LONS FLOAT = Array[97]
LATS FLOAT = Array[97]
이와 같이 실제로 유효한 지점들의 갯수가 총 97개임을 알 수 있습니다. 이제는 exp_dates 배열은 더 이상 필요가 없기 때문에 위의 4개의 배열들만으로 충분합니다. 반복형 구문을 사용하여 이 4개 배열의 값들을 출력해보면 다음과 같습니다(앞부분 일부만 제시합니다).
FOR j = 0, N_ELEMENTS(numbers)-1 DO $
PRINT, numbers[j], names[j], lons[j], lats[j], $
FORMAT='(I3, 2X, A8, 2X, F7.3, 2X, F7.4)'
90 속초 128.565 38.2509
93 북춘천 127.754 37.9474
95 철원 127.304 38.1479
98 동두천 127.061 37.9019
99 파주 126.767 37.8859
100 대관령 128.718 37.6771
101 춘천 127.736 37.9026
그리고 이렇게 획득한 ASOS 지점 정보 배열들은 나중을 위해서 따로 SAV 파일로 저장해두는 것이 좋을 것 같습니다. 즉 다음과 같이 SAVE 명령을 사용하여 .sav 파일로 저장해두는 것입니다.
SAVE, numbers, names, lons, lats, FILENAME='ASOS_stations.sav'
이렇게 해두면 ASOS 97개 지점 정보를 담은 배열들을 다른 IDL 프로그램에서도 RESTORE 명령을 사용하여 언제든지 바로 불러와서 사용할 수 있게 됩니다. 제가 저장해둔 SAV 파일을 아래에 첨부해둡니다.
이제 이와 같이 현재 유효한 ASOS 지점들 97개에 대한 지점번호, 지점명, 경도, 위도 정보들이 확보되었으므로 이러한 지점들의 분포를 한반도 지도 상에 표시하는 작업을 진행해봅시다. 이 작업은 먼저 한반도 영역을 커버하는 지도를 표시한 다음 위의 배열들을 이용하여 각 지점들을 경위도 좌표 기반으로 표시하는 순서로 진행하면 되는데, 그 과정은 다음과 같이 처리해봅시다.
win = WINDOW(DIMENSIONS=[800, 800], /NO_TOOLBAR)
m = MAP('Geographic', LIMIT=[33, 124, 40, 131], FONT_SIZE=11, MARGIN=0.08, /CURRENT)
mc = MAPCONTINENTS(/HIRES, FILL_COLOR='light gray')
sp = SCATTERPLOT(lons, lats, SYMBOL='circle', /SYM_FILLED, $
SYM_SIZE=0.5, SYM_COLOR='crimson', /OVERPLOT)
m.MapGrid.LINESTYLE = 1
m.MapGrid.LABEL_POSITION = 0
m.MapGrid.HORIZON_LINESTYLE = 0
여기서는 먼저 MAP 및 MAPCONTINENTS 함수를 사용하여 한반도 영역의 지도를 표시합니다. 그 다음에는 97개 지점들의 위치 좌표 데이터인 lons 및 lats 배열을 SCATTERPLOT 함수에 투입하여 모든 ASOS 지점들이 적절한 크기 및 색상의 원형 기호로 표시되도록 하였습니다. 표출 결과를 보면 다음 그림과 같습니다.
이와 같은 방식으로 97개의 ASOS 지점들의 위치 분포를 한반도 지도상에서 확인할 수 있는 그림을 얻을 수 있습니다. 그러면 여기서 한 단계만 더 나아가봅시다. 이번에는 특정한 하나의 지점에 대하여 그 지점번호 및 지점명을 함께 표시하도록 해보고자 합니다. 예를 들어서 127번 충주 지점을 그 대상으로 해봅시다. 여기서는 대상 지점의 원형 기호는 정상적으로 표시하되 나머지 지점들에 대해서는 어느 정도 투명하게 보이도록 처리해보고자 합니다. 이를 위하여 먼저 앞서 제시되었던 예제 코드의 내용에서 SCATTERPLOT 함수가 사용된 부분만 다음과 같이 수정해봅시다.
sp = SCATTERPLOT(lons, lats, SYMBOL='circle', /SYM_FILLED, $
SYM_SIZE=0.5, SYM_COLOR='crimson', TRANSPARENCY=60, /OVERPLOT)
이와 같이 TRANSPARENCY 속성을 60으로 설정하여 일단 모든 지점 기호들이 약간 투명하게 보이도록 처리해둡시다. 그 다음에는 대상 지점에 대한 경위도 좌표를 SYMBOL 함수에 투입하여 해당 지점의 기호만 약간 큰 불투명한 색상으로 설정해봅시다. 그 과정은 다음과 같습니다.
num = 127
ww = WHERE(numbers EQ num, count)
sym = SYMBOL(lons[ww], lats[ww], 'circle', /SYM_FILLED, SYM_SIZE=0.7, $
SYM_COLOR='crimson', /DATA)
그리고 대상 지점의 원형 기호의 바로 윗부분에 지점번호 및 지점명을 문자로 삽입해보고자 합니다. 일단 지점번호는 numbers[ww]인데 이 값 자체는 정수형이기 때문에 STRING 함수를 사용하여 적절한 형식의 문자로 변환해주면 됩니다. 그런데 지점명인 names[ww]의 경우는 영문이 아닌 한글 문자입니다. 따라서 IDL에서 한글 문자를 그림상에 표시하는 것이 가능할 것인가의 문제가 대두되는데요. 가능합니다. 다만 이를 위해서는 사전 작업이 좀 필요한데, 그 방법에 관해서는 제가 예전에 올렸던 관련 게시물의 내용을 참조하시기 바랍니다. 여기서는 이러한 사전 설정이 다 완료된 상황을 가정하여 진행해보겠습니다. 지점번호 및 지점명에 해당되는 문자열을 삽입하는 과정은 다음과 같이 처리해봅니다.
str = names[ww]+'('+STRING(numbers[ww], FORMAT='(I0)')+')'
tx = TEXT(lons[ww], lats[ww]+0.08, str, /FILL_BACKGROUND, FILL_COLOR='yellow', $
ALIGNMENT=0.5, FONT_NAME='Malgun', FONT_SIZE=11, /DATA)
여기서는 문자열이 위치할 좌표 및 내용을 TEXT 함수에 투입하였는데, 이 때 FONT_NAME 속성을 'Malgun'으로 설정하여 한글 문자가 표시가 될 수 있도록 하였습니다. 그리고 문자 표시의 가독성을 높이기 위하여 배경색을 따로 설정해보았습니다. 이러한 과정들을 반영하여 실행하면 그 결과는 다음 그림과 같습니다.
따라서 기상자료 개방포털에서 입수한 ASOS 지점 정보가 수록된 CSV 파일을 IDL에서 읽어서 지점별 정보들을 배열로 획득하고 이를 바탕으로 지점 분포도를 표출해보는 작업은 대략 위와 같은 과정이 하나의 예시가 될 것 같습니다. 물론 그 다음 단계를 생각해본다면, 각종 기상요소들에 대한 관측값들이 포함된 자료를 따로 또 입수하여 이러한 데이터의 지점별 분포를 위와 같은 방식의 그림으로 표출하는 것도 생각해볼 수 있을 것입니다. 또는 특정한 지점에 대하여 일정 기간 동안의 시계열 변화를 표출해보는 것도 가능할 것입니다. 이러한 작업의 방법 및 관련 예제들도 향후 준비가 되면 순차적으로 소개해보도록 하겠습니다.
'IDL > Programming' 카테고리의 다른 글
JSON 데이터의 수신/처리/표출 예제 (0) | 2024.12.23 |
---|---|
HttpRequest 클래스 소개 (1) | 2024.12.09 |
LABEL_DATE 함수의 활용법 (1) | 2024.09.24 |
IDL 8.9에서 추가된 상수 정의 기능 (1) | 2023.06.29 |
IDL 8.9의 Literal Strings 문법 (0) | 2023.06.28 |