본문 바로가기

Function

스마트서울 도시데이터센서(S-DoT)기온 데이터 시각화

 

대표

 

 

서울 열린데이터 광장에는 서울 전역에 설치된 온도, 습도, 미세먼지 등의 센서들이 측정한 환경정보를 제공하고 있다.

여기서는 그 중 기온데이터를 지도에 등온선과 그라데이션으로 그려보는 작업을 해보겠다. 시각화는 cpp와 OpenGL 라이브러리를 통해서 작업했기 때문에 모든 과정을 설명할 수는 없지만, 핵심 방법론은 간단하므로 그 부분을 설명해보려 한다.

 

데이터는 아래에 있다. 지속적으로 올라오는데 포맷도 한번 바뀌어서 전체를 병합하면 안된다.

 

 

열린데이터광장 메인

데이터분류,데이터검색,데이터활용

data.seoul.go.kr

간혹 엉뚱한 시점에 40도가 넘는이상치도 있고, 누락된 곳도 있으므로 분석을 하려면 어느 정도 전처리가 필요할 것 같다. 여기서는 그저 시각화 연습이 목적이므로 이상치는 특별히 검증하는 과정을 거치지 않았고 누락된것만 보간 작업에서 문제가 생기지 않도록 적절히 처리해주었다.

 

센서 고유번호와 데이터는 있는데, 센서 위치는 아래와 같은 내용을 별도로 이메일로 신청해야 한다.

....왜 그럴까?????

 

신청하는 사람도 불편하고 보내주는 사람도 상당히 번거로울 것 같다.

게다가 센서가 종종 추가되기 때문에, 한 번 받아두면 계속 쓸 수 있는 것이 아니라 지속적으로 무언가를 하려면 반복적으로 이메일을 보내서 받아야 할 것 같다. 게다가 '나이'와 '직업'은 왜 적어야 하는지도 잘 모르겠다. 

그냥 같은 곳에서 다운받을 수 있게 해 주면 좋겠다.

아 참. 잊기 전에 미리 적어야겠다.

"본 자료는 서울시 서울시 IoT 도시데이터 자료를 활용한 것이며, 연구 결과는 서울시와 관련 없음을 밝힙니다"

 

이 글에 사용한 데이터는 2021년 6월~8월인데, 센서 위치 정보는 2020년 12월에 받아둔 것이라 이미 6개월 사이에 새로 생긴 센서의 정보들도 있었다. 다시 신청하기는 번거로와서 그냥 새로운 센서 값들은 건너뛰고 그렸다.

 

 

 

센서 위치 정보를 받아서 지도에 뿌려봤다.

꽤 촘촘한 편이다. 문제는 관측값이 없는 곳들의 값을 추정해서 그림으로 그리는 것인데, 사실 이런건 공간분석에서 기본적인 시각화에 속하기 때문에 메뉴 혹은 기본 플러그인에 다 있다. 

 

여기서는 직접 그려야 하기 때문에 직접 공식을 구현해서 사용했다. 사실 거창하게 '구현'이라 할 것도 없고, 한두줄로 쓸 수 있는 기본적인 가중평균 방식이다. 가까운 곳의 영향을 좀 더 크게 받도록 하는 보간이고, 아래의 글에 잘 설명되어 있다.

 

 

Inverse Distance Weighting (IDW) Interpolation - GIS Geography

Inverse Distance Weighting (IDW) interpolation estimates unknown values with specifying search distance, closest points, power setting & barriers.

gisgeography.com

 

다만, 일반적으로 등온선을 그릴 때 이 방식으로 하는지는 잘 모르겠다. 관측값을 통해서 전문적인 연구를 해야 한다면, 학계/업계에서 사용하는 주류 방식을 더 찾아보기 바란다. 

 

 

 

데이터는 한 시간 간격으로 있으므로, 그 사이의 시각들은 직선 보간 방식으로 계산했다. 예를 들어 13시 45분의 값을 계산할 때는 13시의 값과 14시의 값만 사용했다.

 

850개의 좌표 위치와 보간된 850개의 측정값을 UNIFORM BUFFER 에 넣어 셰이더로 보냈다. vertex shader에서는 우리나라를 뒤덮는 큰 삼각형 두개로 사각형을 만든 후, fragment shader에서 각각의 픽셀마다 850개 값의 바탕으로 값을 계산했다.

#version 430 core

#define SIZE 850

layout (binding = 0) uniform locBuffer {
	ivec2 xy[SIZE];
};

layout (binding = 1) uniform dataBuffer {
	float data[SIZE];
};


uniform float powerValue;

in Vertex {
	vec3 pos;
} vertex;


out vec4 color;

vec3 c5 = vec3(255/ 255.0, 0 /255.0, 0 / 255.0);
vec3 c4 =  vec3(255/ 255.0, 151 /255.0, 1 / 255.0);
vec3 c3 = vec3(255/ 255.0, 233 /255.0, 1 / 255.0);
vec3 c2 =  vec3(128/ 255.0, 255 /255.0, 1 / 255.0);
vec3 c1 =  vec3(1/ 255.0, 61 /255.0, 255 / 255.0);

void main(void)
{

	vec2 pos = vertex.pos.xy;
	float scoreSum = 0.0;
	float distSum = 0.0;

    for (int i=0 ; i<SIZE ; i++) {
		
		if (data[i] > 50.0f) continue;

		float dist = pow(distance(pos, vec2(xy[i].x, xy[i].y)),powerValue);       

		scoreSum += data[i] / dist;
		distSum +=  (1.0 / dist);
	}

	float result = scoreSum  / distSum;

	vec3 col;

	int line = int(result * 10) % 10;
	float tempr = float(int(result * 10)) / 10.0f;
	float alpha = 0.8;
	if (line==9) {
	    float t1 = smoothstep(tempr+0.1, tempr, result); //0.09~0.10 구간을 뒤집은 후 smoothstep
		alpha = t1*0.8;
	} 
	
	{
		if (result>38.0) col = c5;
		else if (result>34.0)  col = mix(c4, c5, (result-34.0)/4.0);
		else if (result>30.0) col = mix(c3, c4, (result-30.0)/4.0);
		else if (result>26.0) col = mix(c2, c3, (result-26.0)/4.0);
		else if (result>18.0) col = mix(c1, c2,(result-18.0)/8.0);		
		else col = c1;		
	}
	color = vec4(col, alpha);

	
}

4K (3840 x 2160)으로 렌더링하니 그리 빠르지는 않다. 아무리 uniform buffer에 넣었어도 계산량이 3840 x 2160 x 850 이라 그런 것 같다. 사실 한 점의 값을 계산할 때 850개 관측값을 다 사용할 필요는 없지만(사실 이렇게 하면 안 될 것 같기도 하고...) 1초에 3프레임 정도라 그냥 렌더링해서 영상으로 만들었다.

 

 

보간 공식에 사용하는 지수 값은 4로 했다. 즉, 4제곱했다. 위의 링크 글에서는 그냥 사용하거나 2제곱만을 비교하는데, 2제곱으로 그려보니 인접한 비슷한 측정값 사이에도 골짜기가 많이 생기는 듯해서 4로 결정했다. (업계/학계의 정석 같은게 있을것 같기도 한데 찾아보지 않았다)

 

 

 

지수(코드에서 powerValue) 를 1로 두면 아래 그림과 같다. 숫자들이 관측위치의 기온이다.

등온선 간격이 0.1도인데, 너무 자기 자신만 뽈록 튀어나와서 제대로 된 등온선이 만들어지지 않는다.

 

 

지수 값을 2로 두면 아래 그림과 같다. 그림 중앙에 30.4~30.3~30.2~30.3~로 이어지는 부분들은 사실상 연속된 콘타로 표현되어야 할 것 같은데, 군데군데 작은 오름들처럼 올라온다. 

 

 

지수 값을 4로 두면 아래 그림과 같다. 아까보다 연속된 값 부분들은 좀 더 자연스러운데, 다른 부분이 살짝 무리스럽게 보이기도 한다. 색이 칠해지지 않고 빈 부분은 셰이더에서 등온선을 '대강' 표현해서 그렇다. 제대로, 즉 일정한 굵기의 선으로 그리려면 Marching Square 라는 알고리즘을 사용해서 전체 과정을 두 번으로 나누어 그려야 한다.

 

지수 값을 8로 올리면 이제 좀 무리스러워 보이기 시작한다.

 

어찌 보면 지수 값 2도 나름대로 자연스러운 부분이 있는 것 같기는 한데, 아까 관찰한 부분들은 4가 훨씬 더 자연스러웠었다. 위에 링크했던 설명 글 역시 1과 2만 비교하는 것으로 보아, 대부분 2를 사용하는 것 같기도 하다. 정확한 것은 더 찾아봐야 알 수 있을 것 같다. 이 글은 정답을 적기 위한 글이 아니라 시도를 기록하는 용도이므로 이 쯤에서 결정짓는다(고 변명해본다 ㅎ)

 

 

그래서 비교적 지역별로 온도 차이가 많이 나는 날을 임의로 골라봤다.

 

2021년 6월 27일, 7월 15일, 16일, 8월 12일 정도다.

등온선을 0.1도씩 표현하고 1초에 한시간씩 돌렸더니,

와... 정신없다.

서울 바깥 부분은 측정값이 없어서 공식상 저렇게 나올 수 밖에 없는데, 완전 정신없다. 시간을 빨리 돌릴 때는 등온선 없이 색상만으로 표현해야 할 것 같다. 큰 4k모니터에서 보면 기온을 표현한 숫자도 볼 수 있다.

 

서울 곳곳의 국지적 온도가 다르고 중간중간에 동쪽 서쪽에서 기온이 뚝 떨어지거나 북서쪽에서 찬 기운이 밀려오는 것이 보인다. 

 

 

 

같은 영상인데, 서울 지도만 마스킹해봤다.

 

 

어지러운게 좀 덜하긴 하지만 시간을 너무 빨리 돌렸더니 모아레 현상처럼 보인다.

 

 

이번에는 1초에 10분씩만 진행시켜서 속도를 늦추고 7월 15일과 16일 이틀만 표현했다.

등온선도 1도 간격으로 듬성듬성 그렸다.

이제 좀 그럴 듯 하다. 

사실 등온선은 모두 선의 굵기가 같아야 하지만, fragment shader에서 가장 편하게 처리할 수 있는 방법으로 등온선을 그리다보니 어떤 부분들은 선에 해당하는 간극이 넓게 벌어져있다. 조금이나마 간극을 줄여보려고 이렇게저렇게 바꿔봤는데, 온도가 높은 부분의 면이 낮은 부분 면 위에 올라가 있는 것처럼 표현이 되었다. 그리 나쁘지 않아서 그냥 그렇게 두었다.

 

서울 주변도 다시 마스킹 처리 하지 않고 그대로 보이게 두었다.

시간이 빠를때는 모아레 현상처럼 정신없이 보였는데, 속도를 다소 느리게 하니 기온 변화 정도에 대한 일종의 indicator 역할을 하는 것 같다. 측정값이 없는 서울 외부는 서울 내부의 측정값에 의해서 크게 좌우되는데, 서울 내부의 약간의 변화가 지렛대처럼 외부에 증폭된 변화를 야기시켜 변화 정도를 더 잘 보여주는 역할을 한다. 그리고 기온이 오르다가 떨어지는 시점에서도 마치 팔을 아우성치듯 휘젓는 방향이 멈칫하고 바뀌는 것을 볼 수 있다.

 

 

관전포인트

*. 새벽 5시경에 기온이 낮아지다가 올라가는 변화가 눈으로 감지된다.

*. 해가 뜨기 시작하고 기온의 변화가 급격하게 변한다.

*. 7월 15일은 14시가 조금 넘어서 기온이 떨어지기 시작한다

*. 7월 15일은 용산->중랑구->강동구 순서로 기온이 떨어진다.

*. 7월 15일은 서울 동쪽과 서쪽의 기온이 10도까지 차이난다. (비가 왔나??)

*. 7월 16일은 최고 기온의 피크가 좀 더 늦다. 어떤 지역은 16시까지도 기온이 올라간다.

*. 7월 16일은 서쪽부터 시원해진다.

*. 7월 16일의 서쪽지역은 기온이 떨어지다가 20시에 다시 잠시 올라가기도 한다.

*. 가끔씩 화면에 온도가 많이 높은 빨간색 부분들이 등장하는 것은 결측값 보간을 잘못 해서 그렇다.(7월 16일 18:59 같은 부분). 다 만들고 나서 깨달았으므로 코드는 수정했지만 결과물은 그냥 그대로 두었다.

 

역시 4k로 올려놓아서 큰 모니터에서는 기온 변화도 숫자로 확인할 수 있다.

 

10월인데 너무 더워서 내가 다니는 동네만 더운지 확인해보려고 작업을 시작했는데, 최근 데이터는 올라와 있지 않았고, 작업을 하는 동안 비가 오면서 거짓말처럼 기온이 뚝 떨어져버렸다.

 

 

 

-----------------------------------------------------------------------

2021.10.11. 16:00 추가

 

 

등온선에 약간의 음영을 추가해서 판끼리 겹칠 때 위아래의 느낌이 좀 더 잘 드러나도록 했다.

1도 단위에는 큰 음영을 주고 0.1도 단위에는 약한 음영을 주어 디테일을 다시 살려보았다.

이번에는 비교적 최근인 9월 21일~23일 데이터를 사용했다.

 

음영을 추가했으므로 기온이 높은 곳과 낮은 곳이 확실히 드러난다. 어디부터 온도가 올라가는지 혹은 어디부터 내려가는지 눈에 더 잘 보인다. 온도 범위가 크기 때문에, 색상으로만 구분하기에는 약간 한계가 있었는데, 즉 색상을 다르게 해도 한 눈에 잘 구분되지 않았는데 1도 간격을 분리시켜 판처럼 다루니 색상이 비슷해도 경계부의 색상값이 많이 달라지기 때문에 차이가 잘 보인다.

 

단, 더 이상 색상값을 추출해서 특정 지점의 수치를 알 수는 없다. 그리고 온도는 실제로 연속적이지만 매 1도씩을 크게 분리시켰으므로 어떤 지역은 실제보다 차이를 더 크게 느낄 수 있다.

차이를 부각시켜 인지를 쉽게 할 수 있게 된 대신, 본래 그대로의 현상은 다소 왜곡된 셈이라 하겠다.

 

그나저나 영상을 보고 있으니 테크노 음악이라도 배경에 깔려야 할 것 같다.

 

 

---------------------------------------------------------

2021.10.13 04:00 추가

 

이번에는 데이터 항목 중 "미세먼지 보정"값을 사용해서 시각화했다.

등치선의 간격은 5 ㎍/㎥ 

그라데이션 계열을 살짝 조정했다.

50이상은 흰 색 계열, 70 이상은 검은녹색.