IDL/Programming

JSON 데이터의 수신/처리/표출 예제

이상우_IDL 2024. 12. 23. 17:19
728x90
반응형

IDL에서 JSON 형식의 파일을 읽는 방법에 관해서는 제가 이 블로그에서 몇 차례 관련 게시물들(링크 1, 링크 2, 링크 3)을 올리면서 자세히 소개한 바 있고, JSON 형식의 파일을 읽고 간단하게 표출을 해보는 예제도 함께 소개한 바 있습니다. 오늘 소개할 내용도 기본적인 골격은 크게 다르지는 않습니다. 그냥 실제로 배포되는 JSON 형식의 데이터 파일을 수신하여 IDL에서 읽고 그림으로 표출하는 과정에 대한 하나의 실전 예제 정도로 생각해주시면 될 것 같습니다. 일단 예제로 사용할 JSON 파일은 아래에 링크를 통하여 다운로드받을 수 있습니다.

 

https://services.swpc.noaa.gov/products/solar-wind/plasma-7-day.json

 

이 파일에 수록된 데이터는 항상 최신의 데이터로 주기적으로 업데이트되기 때문에, 여러분이 받게 될 파일과 제가 여기서 사용한 파일에 수록된 데이터 값들은 서로 다를 것입니다. 어쨌든 이러한 JSON 형식의 데이터를 처리하고 표출하는 과정에 대한 예제를 살펴보고자 하는데요. 다만 IDL에서 JSON 데이터를 받아들이는데 있어서는 두가지 방식으로 접근해보고자 합니다. 첫번째는 위 링크의 JSON 파일을 다운로드받아둔 상태에서 IDL에서 이 파일을 읽어서 처리하는 방식이고, 두번째는 네트워크를 통하여 위의 링크로부터 데이터를 바로 직접 수신하여 처리하는 방식입니다. 즉 두번째 방식에서는 파일을 다운로드하지 않습니다.

 

그러면 첫번째 방식부터 살펴봅시다. 위의 링크로부터 제가 다운로드받은 JSON 파일에 수록된 데이터의 모습은 다음과 같습니다(전체 내용의 극초반 일부에 해당됩니다).

 

[["time_tag","density","speed","temperature"],["2024-12-12 09:34:00.000","2.64","349.1","53112"],["2024-12-12 09:35:00.000","2.78","348.4","57604"],["2024-12-12 09:36:00.000","2.57","345.1","58036"],["2024-12-12 09:38:00.000","2.40",null,null],["2024-12-12 09:39:00.000","2.40","348.9","48940"],["2024-12-12 09:40:00.000","2.47","345.8","56113"],["2024-12-12 09:41:00.000","2.44","347.1","59665"],["2024-12-12 09:42:00.000","2.74","346.9","58491"],["2024-12-12 09:43:00.000","2.63","351.4","48825"], ................

 

이러한 형태는 제가 앞서 소개한 관련 게시물들 중 하나에서 언급했던 List within List의 형태라고 볼 수 있습니다. 데이터의 특성을 보면 관측 시각 정보, 밀도(density), 속도(speed), 온도(temperature) 등 4종의 값들의 묶음이 시간 순서대로 수록되어 있습니다. 이러한 묶음의 총 갯수는 파일을 수신하는 시기에 따라 조금씩 변동이 있을 수 있지만, 이 파일의 경우는 총 8363개입니다. 어쨌든 이러한 데이터가 주어졌을 때 우리의 목표는 3종의 데이터들(밀도, 속도, 온도)을 각각 X축이 시간이 되는 시계열 플롯의 형태로 표출하는 것이 될 것입니다. 구체적으로는 3종의 시계열 플롯들을 하나의 그래픽창 내에 표출하는 것이 좋을 것 같습니다. 이러한 작업을 지금부터 순차적으로 진행해봅시다. 가장 먼저 필요한 작업은 JSON 파일을 읽어서 4종의 배열들을 얻는 것입니다. 이를 위하여 JSON_PARSE 함수를 사용하여 JSON 파일을 읽어봅시다.

 

file = 'plasma-7-day.json'
result = JSON_PARSE(file)
HELP, result

HELP, result[10]

 

RESULT          LIST  <ID=583271  NELEMENTS=8363>
<Expression>    LIST  <ID=583282  NELEMENTS=4>

2024-12-12 09:44:00.000
2.67
350.9
43387

 

여기서 result에 관하여 출력된 내용을 보면 총 8363개의 항목들로 구성된 리스트(List) 형태의 데이터임을 확인할 수 있습니다. 그리고 result 내의 하나의 항목에 대해서도 그 정보를 출력해보면 4개의 항목들로 구성된 리스트 형태의 데이터임을 역시 확인할 수 있습니다. 즉 4개의 문자값들로 구성된 리스트가 총 8363개 존재하는 List within List의 형태로 전체 데이터가 구성되어 있습니다. 여기서 4개의 문자값들은 시각, 밀도, 속도, 온도에 해당되고, 전체 8363개는 개별 시각에 해당됩니다. 따라서 4종의 데이터를 시계열로 추출하여 각각의 배열로 가져와야 합니다. 다만 8363개의 리스트들 중 첫번째 항목은 주석 문자들에 해당되므로 실제 시계열 리스트들의 총 갯수는 8362개가 됩니다. 어쨌든 4종의 데이터 배열들을 얻는 과정은 반복형 구문으로 처리해야 하는데 그 과정은 다음과 같습니다.

 

n = N_ELEMENTS(result)
tjs = !null
dens = !null
spds = !null
temps = !null
FOR j = 1, n-1 DO BEGIN
  lst = result[j]
  tm_str = lst[0]
  year = STRMID(tm_str, 0, 4)
  month = STRMID(tm_str, 5, 2)
  day = STRMID(tm_str, 8, 2)
  hour = STRMID(tm_str, 11, 2)
  minute = STRMID(tm_str, 14, 2)
  tj = JULDAY(month, day, year, hour, minute, 0)
  tjs = [tjs, tj]
  IF lst[1] EQ !null THEN dens = [dens, !values.f_nan] ELSE dens = [dens, FLOAT(lst[1])]
  IF lst[2] EQ !null THEN spds = [spds, !values.f_nan] ELSE spds = [spds, FLOAT(lst[2])]
  IF lst[3] EQ !null THEN temps = [temps, !values.f_nan] ELSE temps = [temps, FLOAT(lst[3])]
ENDFOR
HELP, tjs, dens, spds, temps

 

여기서는 시각, 밀도, 속도, 온도 값들로 구성될 배열을 각각 tjs, dens, spds, temps로 정의하였습니다. 이 배열들은 처음에는 !null로 정의되지만 반복이 진행되면서 해당 값들을 계속 누적시켜서 결국에는 각각 8362개의 값들로 구성된 배열로 완성이 되도록 하였습니다. 반복형 구문 내에서는 매 회차마다 개별 리스트인 lst를 구성하는 4개의 문자값들을 각각의 특성에 맞게 변환하여 각 배열에 누적시킵니다. 밀도, 속도, 온도 등의 개별 물리량에 해당되는 문자값인 lst[1], lst[2], lst[3]은 실수형 변환을 위하여 FLOAT 함수만 사용해주면 됩니다. 하지만 시각 정보를 담은 문자값인 tm_str은 년, 월, 일, 시, 분 값들로 분할 추출하여 줄리안 날짜 단위의 값들로 변환해야 하기 때문에 JULDAY 함수에 해당 정보들이 투입될 수 있도록 하였습니다. 그리고 JSON 파일의 내부를 보면 물리량 값들 중에 null 값이 있습니다. 이것은 관측값이 없는 경우에 해당되므로, 이러한 경우에 대해서는 NaN에 해당되는 값이 배열에 들어가도록 할 필요가 있습니다. 그래서 IF 구문을 사용하여 null 값이 발생할 경우에 대한 처리를 한 것입니다. 이러한 반복 작업이 모두 완료되면 tjs, dens, spds, temps 배열을 얻게 됩니다. HELP에 의하여 출력된 정보를 보면 다음과 같습니다. 각각 8362개의 값들로 구성된 배열이 되었음을 알 수 있습니다.

 

TJS             DOUBLE    = Array[8362]
DENS            FLOAT     = Array[8362]
SPDS            FLOAT     = Array[8362]
TEMPS           FLOAT     = Array[8362]

 

JSON 파일을 읽어서 IDL에서 처리 가능한 배열로 가져오는 과정은 일단 마무리되었습니다. 그 다음은 표출 과정이 될 것입니다. 3종의 데이터들은 X축이 날짜 및 시각에 해당된다는 공통점이 있기 때문에, 그래픽창을 띄우고 3종의 데이터 플롯들을 세로 방향으로 나란히 표출하는 것이 좋을 것 같습니다. 그 과정은 다음과 같이 처리해봅시다.

 

win = WINDOW(DIMENSIONS=[1200, 600], /NO_TOOLBAR)
dummy = LABEL_DATE(DATE_FORMAT=['%M %D!C%H:%I'])
p1 = PLOT(tjs, dens, COLOR='tomato', XSHOWTEXT=0, $
  XTICKUNITS='day', XTICKINTERVAL=1, XMINOR=3, $
  SYMBOL='circle', /SYM_FILLED, SYM_SIZE=0.2, LINESTYLE=6, $
  XTICKFORMAT='label_date', YTITLE='Density', $
  MARGIN=[0.06, 0.02, 0.03, 0.1], /CURRENT, LAYOUT=[1, 3, 1])
p2 = PLOT(tjs, spds, COLOR='purple', XSHOWTEXT=0, $
  XTICKUNITS='day', XTICKINTERVAL=1, XMINOR=3, $
  SYMBOL='circle', /SYM_FILLED, SYM_SIZE=0.2, LINESTYLE=6, $
  XTICKFORMAT='label_date', YTITLE='Speed', $
  MARGIN=[0.06, 0.03, 0.03, 0.03], /CURRENT, LAYOUT=[1, 3, 2])
p3 = PLOT(tjs, temps, COLOR='dark green', $
  XTICKUNITS='day', XTICKINTERVAL=1, XMINOR=3, $
  SYMBOL='circle', /SYM_FILLED, SYM_SIZE=0.2, LINESTYLE=6, $
  XTICKFORMAT='label_date', YTITLE='Temperature', $
  MARGIN=[0.06, 0.15, 0.03, 0.02], /CURRENT, LAYOUT=[1, 3, 3])

 

여기서는 WINDOW 함수로 적절한 크기의 그래픽창을 띄운 후 3종의 플롯들을 세로 방향으로 정렬하기 위하여 각 플롯(p1, p2, p3)에 대하여 LAYOUT 속성을 사용하여 위치를 배정하였습니다. 그리고 각 플롯은 X축이 날짜가 되어야 하기 때문에 LABEL_DATE 함수를 사용하여 적절한 날짜 표기 형식을 정의한 후 그 정보를 각 PLOT 함수의 XTICKFORMAT 속성에 부여하는 방식으로 처리하였습니다. 이와 같은 처리 방법에 관해서는 앞서 언급했던 관련 게시물의 내용을 참조하시면 됩니다. 그리고 전체적인 표출의 특성상 X축의 눈금 문자들은 맨 아래 세번째 플롯에 대해서만 필요하고 나머지(첫번째 및 두번째) 플롯들에 대해서는 굳이 필요하지 않기 때문에 처음 두 플롯들에 대해서는 XSHOWTEXT 속성의 값을 0으로 설정하였습니다. 그리고 전체적으로 데이터 포인트들은 점들로 표시되도록 하면서 서로 선으로 이어지지는 않도록 하였습니다. 최종적인 표출 결과는 다음 그림과 같습니다.

 

대략 이 정도면 데이터의 특성에 맞는 표출이 된 것으로 보입니다. 물론 세부적으로 좀 더 다듬어서 더 나은 표출 결과를 얻는 것도 가능할 것입니다.

 

그러면 이제 두번째 방식을 살펴봅시다. 앞선 예제에서는 JSON 형식의 파일을 유저가 직접 받아놓은 후 IDL 프로그램이 이 파일을 읽도록 하였습니다. 하지만 유저가 이미 받아놓은 파일을 사용하지 않고 IDL이 직접 네트워크를 통하여 이 데이터를 수신하여 바로 처리하도록 하는 것도 가능합니다. 바로 얼마전에 관련 게시물을 통하여 소개했던 HttpRequest 클래스를 사용하면 됩니다. 그러면 이번에는 이와 같은 방식으로 가장 최신의 데이터를 수신하여 그림을 표출하도록 해봅시다. 이를 위해서는 JSON 데이터를 가져오는 맨 처음 부분의 내용만 수정하면 됩니다. 즉 앞선 예제에서 맨 처음 부분에 다음과 같은 내용이 있었는데요.

 

file = 'plasma-7-day.json'
result = JSON_PARSE(file)

 

이 부분을 다음과 같은 내용으로 대체하면 됩니다.

 

obj = HttpRequest.Get('https://services.swpc.noaa.gov/products/solar-wind/plasma-7-day.json')
result = obj.JSON()

 

여기서는 HttpRequest 클래스의 Get 메서드를 사용하여 JSON 파일의 링크와 연결한 후 이 객체 레퍼런스에 대하여 JSON 메서드를 적용하여 JSON 형식의 데이터를 바로 수신하여 result라는 항목으로 가져온 것입니다. 이러한 수정사항을 반영하여 전체 내용을 다시 실행해봅시다. 그러면 유저가 이 예제를 실행한 시점 기준의 최신의 데이터가 반영된 결과를 얻게 될 것입니다. 제가 현재 이 게시물을 작성하고 있는 시점 기준으로 최신의 데이터가 반영된 결과를 얻은 모습은 다음과 같습니다.

 

 

 

이 글이 도움이 되었다면 게시물에 대하여 공감 버튼(하트 모양) 클릭 및 블로그 구독도 해주시면 더 큰 힘이 됩니다. 감사합니다.

반응형