[토이 프로젝트] 내가 사는 지역에 대한 차트 만들기

기운찬곰·2023년 8월 28일
1

토이 프로젝트

목록 보기
3/3
post-thumbnail

Overview

일주일동안 한 가지 주제로 목표를 세우고, 공부하고, 프로젝트를 진행해보면서 결과를 내기로 했었죠...? 이번 일주일 동안은 차트에 대해 공부했습니다. 저번 글을 통해 Chart.js에서는 차트가 canvas를 사용해 만들어진다는 것을 알 수 있었습니다.

이번 시간에는 그 이후에 Canvas API를 공부한 내용과 Geo 차트에 대해 알아보고, Geo 포맷에 대해 알아보고, 실제 GeoJSON 포맷을 커스텀해보면서 내가 사는 지역에 대한 차트까지 만든 내용을 정리해보려고 합니다.


Canvas API

MDN 소개

참고 : https://developer.mozilla.org/ko/docs/Web/API/Canvas_API

Canvas API는 자바스크립트와 HTML canvas 엘리먼트를 통해 그래픽을 그리기위한 수단을 제공합니다. 무엇보다 애니메이션, 게임 그래픽, 데이터 시각화, 사진 조작 및 실시간 비디오 처리를 위해 사용됩니다.

Canvas API는 주로 2D 그래픽에 중점을 두고 있습니다. WebGL API 또한 canvas 엘리먼트를 사용하며, 하드웨어 가속 2D 및 3D 그래픽을 그립니다.

기본 예시

기본 html 파일을 만듭니다. 안에는 canvas 엘리먼트가 있습니다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <canvas id="canvas"></canvas>
    <script src="main.js"></script>
  </body>
</html>

HTMLCanvasElement.getContext() 메서드를 통해 엘리먼트의 컨텍스트(렌더링될 그리기의 대상)를 얻습니다. 실제 그리기는 CanvasRenderingContext2D 인터페이스를 사용해 수행됩니다.

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d"); 

ctx.fillStyle = "green";
ctx.fillRect(10, 10, 150, 100);

fillStyle 프로퍼티는 사각형을 초록색으로 만듭니다. fillRect() 메서드는 좌측 상단 코너를 (10, 10) 위치에 놓으며, 너비를 150, 높이를 100으로 지정합니다. 결과는 아래와 같습니다.

참고 : https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D

디버깅을 해보면 ctx가 CanvasRenderingContext2D 인 것을 알 수 있습니다. 이 안에는 도형, 텍스트, 이미지 및 기타 객체를 그리는 데 사용되는 프로퍼티와 메서드가 존재합니다. 인터페이스의 속성 및 방법은 위 페이지의 참조 섹션에 설명되어 있습니다.

캔버스 API는 매우 강력하지만 항상 사용이 간단하지는 않습니다. 아래에 나열된 라이브러리는 캔버스 기반 프로젝트를 더 빠르고 쉽게 만들 수 있습니다. 몇 개는 저도 들어보긴 한 라이브러리네요.

  • Fabric.js
  • Konva.js
  • Phaser
  • p5.js

Canvas tutorial

참고 : https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial

이 자습서에서는 canvas 요소를 사용하여 2D 그래픽을 그리는 방법을 기본적으로 설명합니다. 제공된 예제는 canvas 로 무엇을 할 수 있는지에 대한 몇 가지 명확한 아이디어를 제공하고 자신만의 콘텐츠를 만드는 데 사용할 수 있는 코드 조각을 제공합니다.

✍️ 생각보다 볼만 하니 Canvas를 처음 다루시는 분이라면 꼭 한번 보시길 바랍니다. 여기서는 전부 다루는 대신 꼭 필요한 부분만 설명하고 넘어가도록 하겠습니다.

1. Basic usage

width 및 height 속성을 지정하지 않으면 캔버스의 처음 너비는 300 픽셀이고 높이는 150 픽셀입니다. 요소는 CSS에 의해 임의로 크기를 정할 수 있지만 렌더링하는 동안 이미지는 레이아웃 크기에 맞게 크기가 조정됩니다. CSS 크기 지정이 초기 캔버스의 비율을 고려하지 않으면 왜곡되어 나타납니다. 따라서 CSS 대신 <canvas> 속성에서 width 및 height 속성을 명시적으로 지정하는 것을 추천합니다.

<canvas id="tutorial" width="150" height="150"></canvas>

canvas 요소는 인터넷 익스플로러 9 이하의 버전이나 텍스트기반 브라우저 등과 같은, 캔버스를 지원하지 않는 오래된 브라우저들을 위한 대체 컨텐츠를 정의하기 쉽습니다. 여러분은 그러한 브라우저들을 위한 대체 컨텐츠를 제공해야 합니다. 예를 들어, 캔버스 내용에 대한 텍스트 설명을 제공하거나 동적으로 렌더링 된 내용의 정적 이미지를 제공 할 수 있습니다.

<canvas id="stockGraph" width="150" height="150">
  current stock price: $3.15 +0.15
</canvas>

<canvas id="clock" width="150" height="150">
  <img src="images/clock.png" width="150" height="150" alt="" />
</canvas>

canvas 엘리먼트는 고정 크기의 드로잉 영역을 생성하고 하나 이상의 렌더링 컨텍스(rendering contexts)를 노출하여, 출력할 컨텐츠를 생성하고 다루게 됩니다.

var canvas = document.getElementById("tutorial");
var ctx = canvas.getContext("2d");

// checking for support (필요시 사용)
if (canvas.getContext) {
  const ctx = canvas.getContext("2d");
  // drawing code here
} else {
  // canvas-unsupported code here
}

간단하게 두 개의 직사각형을 그린 간단한 예제를 보도록하겠습니다. 그 중 하나는 투명도(alpha transparency)를 가집니다.

ctx.fillStyle = "rgb(200, 0, 0)";
ctx.fillRect(10, 10, 50, 50);

ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.fillRect(30, 30, 50, 50);

2. Drawing shapes with canvas

우리가 그림을 그리기 시작하기 전에, 우리는 캔버스 격자 또는 좌표 공간에 대해 이야기해야 합니다. 일반적으로 그리드의 1단위는 캔버스의 1픽셀에 해당합니다. 이 그리드의 원점은 좌표 (0,0)의 왼쪽 상단 모서리에 위치합니다. 모든 요소는 이 원점에 상대적으로 배치됩니다.

따라서 파란색 사각형의 왼쪽 상단 모서리의 위치는 왼쪽에서 x 픽셀이 되고 위쪽에서 y 픽셀이 좌표 (x,y)가 됩니다.

Drawing rectangles

SVG와는 다르게, canvas는 직사각형과 경로(선으로 연결된 점들의 목록)라는 두 개의 원시 형태만을 지원합니다. 다른 모든 도형은 하나 이상의 경로를 결합하여 작성해야 합니다.

먼저 직사각형을 살펴봅시다. 캔버스에 직사각형을 그리는 세 가지 함수가 있습니다:

  • fillRect(x, y, width, height) : 색칠된 직사각형을 그립니다.
  • strokeRect(x, y, width, height) : 직사각형 윤곽선을 그립니다.
  • clearRect(x, y, width, height) : 특정 부분을 지우는 직사각형이며, 이 지워진 부분은 완전히 투명해집니다.

각각의 세 함수는 모두 같은 변수를 가집니다. x와 y는 캔버스의 좌측상단에서 사각형의 (원점으로부터 상대적인) 위치를 뜻하며, width 와 height는 사각형의 크기를 뜻하게 됩니다.

function draw() {
  const canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    const ctx = canvas.getContext("2d");

    ctx.fillRect(25, 25, 100, 100);
    ctx.clearRect(45, 45, 60, 60);
    ctx.strokeRect(50, 50, 50, 50);
  }
}

fillRect() 함수는 가로 세로 100 픽셀 사이즈의 검정 사각형을 그립니다. 이후 clearRect() 함수가 60x60 픽셀의 사각형 크기로 도형 중앙을 지우게 되고, strokeRect()은 이 빈 사각형 공간 안에 50x50 픽셀 사이즈의 윤곽선만 있는 사각형을 만들게 됩니다.

참고로, rect() 함수도 있습니다. 근데 이 메서드는 직접 렌더링하지 않습니다. fill()이나 stroke()를 별도로 사용해야 됩니다. 그니까 rect + fill = fillRect인 셈인듯 하네요.

Drawing paths

경로(path)는 곡선 또는 곡선이 아닌 선의 세그먼트로 연결된 점의 목록으로, 폭과 색상이 서로 다를 수 있습니다. 경로를 사용하여 모양을 만들려면 몇 가지 추가 단계를 수행합니다:

  1. 경로를 생성합니다.
  2. 그런 다음 드로잉 명령을 사용하여 경로를 그립니다.
  3. 경로를 생성한 후, 경로를 스트로크하거나 채워서 렌더링할 수 있습니다.

다음은 위의 단계들을 실행하기 위해 사용되는 함수입니다:

  • beginPath() : 새로운 경로를 만듭니다. 경로가 생성됐다면, 이후 그리기 명령들은 경로를 구성하고 만드는데 사용하게 됩니다.
  • Path methods : 물체를 구성할 때 필요한 여러 경로를 설정하는데 사용하는 함수입니다. ex) lineTo, arc 등
  • closePath() : 현재 하위 경로의 시작 부분과 연결된 직선을 추가합니다.
  • stroke() : 윤곽선을 이용하여 도형을 그립니다.
  • fill() : 경로의 내부를 채워서 내부가 채워진 도형을 그립니다.

경로를 만들기 위한 첫번째 단계는 beginPath() 메소드를 사용하는 것 입니다. 내부적으로, 경로는 도형을 이루는 하위경로(선, 아치 등)들의 집합으로 이루어져있습니다. 이 메소드가 호출될 때 마다, 하위 경로의 모음은 초기화됩니다.

두번째 단계는 실제로 경로가 그려지는 위치를 설정하는 메소드를 호출하는 것 입니다.

세번째 단계는 선택사항으로 closePath() 메소드를 호출하는 것 입니다. 이 메소드는 현재 점 위치와 시작점 위치를 직선으로 이어서 도형을 닫습니다. 참고로, fill() 메소드 호출 시, 열린 도형은 자동으로 닫히게 되므로 closePath()메소드를 호출하지 않아도 됩니다. 이것은 stroke() 메소드에는 적용되지 않습니다.

예를 들어, 하나의 두 삼각형 (윤곽선 삼각형, 색칠된 삼각형)을 그리기 위한 코드는 다음과 같습니다.

function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    // Filled triangle
    ctx.beginPath();
    ctx.moveTo(25, 25);
    ctx.lineTo(105, 25);
    ctx.lineTo(25, 105);
    ctx.fill();

    // Stroked triangle
    ctx.beginPath();
    ctx.moveTo(125, 125);
    ctx.lineTo(125, 45);
    ctx.lineTo(45, 125);
    ctx.closePath();
    ctx.stroke();
  }
}

beginPath를 먼저하고 moveTo로 시작좌표를 움직인다음, lineTo로 선을 긋고 있습니다. 그리고 마지막으로 fill을 하면 시작 좌표와 자동으로 닫히게 됩니다. 두번째 삼각형을 그릴때는 다시 beginPath로 초기화를 해주고 똑같이 반복하면 됩니다. 대신 여기서는 stroke를 사용하므로 closePath를 해줘야 합니다. (감이 잡히죠?)

호(arc)

직선이 있으면 곡선도 있어야겠죠? 호나 원을 그리기위해서는 arc() 혹은 arcTo() 메소드를 사용합니다.

  • arc(x, y, radius, startAngle, endAngle, anticlockwise) : (x, y) 위치에 원점을 두면서, 반지름 r을 가지고, startAngle 에서 시작하여 endAngle 에서 끝나며 주어진 anticlockwise 방향으로 향하는 (기본값은 시계방향 회전) 호를 그리게 됩니다.
  • arcTo(x1, y1, x2, y2, radius) : 주어진 제어점들과 반지름으로 호를 그리고, 이전 점과 직선으로 연결합니다.

arc 함수에서 각도는 각이 아닌 라디안 값을 사용합니다. 각도를 라디안으로 바꾸려면 다음의 자바스크립트(JavaScript) 코드를 사용하실 수 있습니다: radians = (Math.PI/180)*degrees.

function arcDraw() {
  var canvas = document.getElementById("canvas3");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    for (var i = 0; i < 4; i++) {
      for (var j = 0; j < 3; j++) {
        ctx.beginPath();
        var x = 25 + j * 50; // x coordinate
        var y = 25 + i * 50; // y coordinate
        var radius = 20; // Arc radius
        var startAngle = 0; // Starting point on circle
        var endAngle = Math.PI + (Math.PI * j) / 2; // End point on circle
        var anticlockwise = i % 2 == 0 ? false : true; // clockwise or anticlockwise

        ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

        if (i > 1) {
          ctx.fill();
        } else {
          ctx.stroke();
        }
      }
    }
  }
}

보면 1~2행은 stroke, 3~4행은 fill로 되어있습니다. 1, 3행은 anticlockwise false이므로 반시계 방향이고요. 2, 4행은 시계 방향입니다. 그리고 endAngle은 180도, 270도, 360도인 거 같습니다. 파이가 180도니까요. 따라서 결과는 다음과 같습니다.

좀 더 복잡한 곡선 - 베지어(Bezier) 곡선과 이차(Quadratic )곡선은 생략하겠습니다. 이를 이용하면 그럴듯한 하트나 말풍선 모형 등을 만들 수 있습니다.

유틸리티 함수 - 곡선이 있는 rectangle

곡선이 있는 rectangle은 자주 사용될 거 같습니다. 이런 것들을 유틸리티 함수로 놓고 사용하면 편하겠죠? 사용해야 할 코드의 양과 복잡함을 줄여주는데 도움을 줍니다.

보니까 line그리고 arcTo로 곡선그리고, 다시 라인 그리고, 곡선 그리고를 반복하는군요.

// A utility function to draw a rectangle with rounded corners.

function roundedRect(x, y, width, height, radius) {
  var canvas = document.getElementById("canvas4");
  var ctx = canvas.getContext("2d");

  ctx.beginPath();
  ctx.moveTo(x, y + radius);
  ctx.lineTo(x, y + height - radius);
  ctx.arcTo(x, y + height, x + radius, y + height, radius);
  ctx.lineTo(x + width - radius, y + height);
  ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
  ctx.lineTo(x + width, y + radius);
  ctx.arcTo(x + width, y, x + width - radius, y, radius);
  ctx.lineTo(x + radius, y);
  ctx.arcTo(x, y, x, y + radius, radius);
  ctx.stroke();
}

roundedRect(12, 12, 150, 150, 15);
roundedRect(19, 19, 150, 150, 9);
roundedRect(53, 53, 49, 33, 10);

나머지 챕터에 대해 간략한 설명

1장과 2장이 가장 핵심이라고 볼 수 있고 나머지는 필요시에 보면 될 듯합니다. 간단하게 정리만 하겠습니다.

  • 3장. Drawing shapes with canvas: 색을 칠하는 방법에 대해 배웁니다. 선의 스타일, 그라디언트, 패턴, 그림자 등에 대해서도 배웁니다.
  • 4장. Drawing text: 텍스트 그리기, 텍스트 스타일 적용하기에 대해 배웁니다. 이를 이용하면 그럴듯한 (광고)효과도 줄 수 있겠네요.
  • 5장. Using Images: 캔버스에 배경 이미지 추가하는 법, 이미지 타일링, 이미지 잘라내서 프레임을 적용해서 액자처럼 만들기, Art gallery 를 만들어볼 수도 있습니다.
  • ★ 6장. Transformations: 여기서는 다소 중요한 상태의 저장과 복원이라는 개념을 다룹니다. Canvas 상태는 스택(stack)에 쌓입니다. 복잡한 그림을 그리려고 한다면 반드시 있어야 합니다. translating, rotating, scale에 대해 배웁니다.
  • 7장. Compositing and clipping: 합성과 잘라내기에 대해 배웁니다. 기존 이미지를 별 모양으로 잘라낼 수도 있습니다.
  • ★ 8장. Basic animations: 애니메이션도 만들 수 있습니다. 기본 애니메이션 단계에 대해 배우고, 간단한 실습을 통해 지구가 공전하는 애니메이션을 만들 수 있습니다. 그 외 시계 애니메이션, 파노라마 사진 애니메이션(이거 괜찮아보임), 마우스에 따라 움직이는 애니메이션을 만들 수 있습니다.
  • 9장. Advanced animations: 애니메이션을 더 발전시키기 위해 몇가지 물리 동작을 추가해봅니다. 공을 만들고 속도, 가속도.. 를 적용해봅니다.
  • 10장. Pixel manipulation with canvas: 사진을 픽셀 조작을 통해 color picker를 만들 수도 있고, 사진 grayscale, inverted와 같은 효과도 줄 수 있습니다. 그 외 Zooming도 있습니다.

나만의 차트 만들기 - Geo 차트

어떤 커스텀 차트를 만들면 좋을까?

사실 이번 프로젝트 목표는 나만의 차트(커스텀 차트) 만들기입니다. 이를 위해 Chart.js도 분석하고 Canvas API도 공부했죠. 하지만 이미 기존 차트는 잘 되어있어서 굳이... 라는 생각이 들었습니다.

참고 : https://github.com/nhn/tui.chart
참고 : https://spicy-lace-142.notion.site/Han-Jung-c43f4bcd2b3f4b3d85b93aee41c5e098

그 와중에 TOAST UI 에서는 기존 SVG를 Canvas로 마이그레이션 하는 작업을 했더군요. 은근 NHN이 기술 트렌드를 잘 따라가는거 같습니다. 나중에 여기 코드도 분석해보면 좋을거 같네요. 아무튼!! 그래서 어떤 차트를 만들지 고민하다가 geo 라는게 눈에 띄었습니다.

참고 : https://github.com/chartjs/awesome#charts
참고 : https://github.com/sgratzl/chartjs-chart-geo

Chart.js에서는 별도로 geo 차트가 있었습니다. 하지만 예시는 전세계랑 미국 지도 뿐이였죠. 그게 조금 아쉽더군요. 우리나라 지도는 어떻게 적용해야 할까요? 그래서 찾아봤습니다. (아. 참고로 여기서는 SVG를 사용하지는 않지만 SVG 로 만드는 것도 가능합니다 - 참고)

GeoJSON 포맷

참고 : https://ko.wikipedia.org/wiki/GeoJSON

이를 위해서는 먼저 GeoJSON, TopoJSON 포맷 부터 알아야 했습니다. 이게 보니까 조금 전문적인 영역이더군요. 😂

GeoJSON는 위치정보를 갖는 점을 기반으로 체계적으로 지형을 표현하기 위해 설계된 개방형 공개 표준 형식입니다. 이것은 JSON인 자바스크립트 Object Notation을 사용하는 파일 포맷입니다. 지리좌표계의 점을 기반으로 Geocoding된 지형지물(주소 및 위치), 라인스트링 또는 폴리라인, 다각형 및 이러한 유형의 여러 부분으로 구성된 모음을 특징으로 합니다. 산악 등반이나 마운틴 바이크를 위한 루트 및 길안내 자료등으로 사용할 수 있습니다. (오호 솔깃...!)

GeoJSON 형식은 공식 표준 조직이 아니라 국제 인터넷 표준화 기구 산하 워킹그룹에 의해 작성되고 유지된다는 점에서 다른 GIS 표준과 다르지만 XML을 기반으로 한 GPX와 함께 사실상 표준처럼 사용됩니다. GeoJSON의 주목할만한 계열은 TopoJSON 입니다.

다음은 말레이시아, 싱가포르등이 위치해 있는 자바 해의 말레이 제도에서의 가상의 GeoJson 표현입니다. http://geojson.io/ 에서 확인 가능합니다.

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [102.0, 0.5]
      },
      "properties": {
        "prop0": "value0"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "LineString",
        "coordinates": [
          [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]
        ]
      },
      "properties": {
        "prop0": "value0",
        "prop1": 0.0
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
            [100.0, 1.0], [100.0, 0.0]
          ]
        ]
      },
      "properties": {
        "prop0": "value0",
        "prop1": { "this": "that" }
      }
    }
  ]
}

기본 포맷은 type, geometry, properties 3가지로 구성됩니다. 그리고 geometry에는 7개 타입이 있습니다. coordinates는 [경도, 위도]를 나타내는 점입니다. 추가 설명은 다음을 참고하시길 바랍니다.

참고 : https://happy-chipmunk.tistory.com/108
참고2 : https://wallacearchivemain.gatsbyjs.io/blog/geojson/

아무튼 이를 이용하면 네이버 지도 위에 이런 재미난 것도 할 수 있습니다. (네이버 API 참고)

TopoJSON 포맷

참고 : https://ko.wikipedia.org/wiki/GeoJSON#TopoJSON_스키마
참고 : https://github.com/topojson/topojson/blob/master/README.md
참고 : https://life-of-panda.tistory.com/11

GeoJSON의 확장 포맷이 바로 TopoJSON 입니다. TopoJSON에서 기하학적 구조는 분리되는 대신 arc라는 공유되는 선 세그먼트에서 함께 연결합니다. 예를 들어, 캘리포니아와 네바다의 공유 경계선은 두 주 모두에 대해 중복되지 않고 단 한 번만 표시되는 방식입니다. 따라서, TopoJSON은 중복성을 제거하여 관련 기하학적 구조를 동일한 파일에 효율적으로 저장할 수 있도록 합니다.

단순하지만 유효하고 완전한 topojson 파일의 'Polygon', ' LineString ', 'Point' ,'arc' 및 'properties'는 다음과 같이 정의할 수 있습니다.

{
  "type":"Topology",
  "transform":{
    "scale": [1,1],
    "translate": [0,0]
  },
  "objects":{
    "two-squares":{
      "type": "GeometryCollection",
      "geometries":[
        {"type": "Polygon", "arcs":[[0,1]],"properties": {"name": "Left_Polygon" }},
        {"type": "Polygon", "arcs":[[2,-1]],"properties": {"name": "Right_Polygon" }}
      ]
    },
    "one-line": {
      "type":"GeometryCollection",
      "geometries":[
        {"type": "LineString", "arcs": [3],"properties":{"name":"Under_LineString"}}
      ]
    },
    "two-places":{
      "type":"GeometryCollection",
      "geometries":[
        {"type":"Point","coordinates":[0,0],"properties":{"name":"Origine_Point"}},
        {"type":"Point","coordinates":[0,-1],"properties":{"name":"Under_Point"}}
      ]
    }
  },
  "arcs": [
    [[1,2],[0,-2]],
    [[1,0],[-1,0],[0,2],[1,0]],
    [[1,2],[1,0],[0,-2],[-1,0]],
    [[0,-1],[2,0]]
  ]
}

뭔가 느낌상 arcs는 공유되는 자원인거 같고, geometries 안에 arcs에서 가져다가 사용하는 느낌... 인 듯 합니다.

우리나라 GeoJSON 포맷은 어떻게 구할까

우리나라 차트를 보여주고 싶다면 우리나라 GeoJSON 포맷이 있어야 될 것입니다. 혹은 더 나아가서 제가 사는 시흥시만 보여주고 싶다면 또 그에 따른 GeoJSON 포맷이 있어야 합니다. 이걸 어떻게 구할까요? 이미 깃허브에 있습니다. ㅎㅎ

참고 : https://github.com/vuski/admdongkor
참고 2: https://business.juso.go.kr/addrlink/adresInfoProvd/guidance/provdAdresInfo.do (여기서 '구역의 도형'을 신청하여 받으면 됩니다.)

저는 신청하는게 귀찮아서 깃허브에서 ver20230701 를 다운받아 사용하기로 했습니다. 33.2 MB 라는 꽤 큰 용량이더군요. 다운로드 했으니 봐야겠죠? 근데 어디서 봐야될까요...ㅠㅠ

찾아보니 kepler 서비스 라는 곳에서 간단하게 무료로 볼 수 있었습니다. 오오!! 모든 지역정보까지 다 나눠놓은것을 볼 수 있습니다.

그리고 데이터를 살펴보니 여러가지 정보가 있었습니다. 그 중에서 adm_cd, adm_cd2를 중점적으로 볼 필요가 있습니다.

  • 속성의 adm_cd 는 통계청에서 사용하는 7자리의 [한국행정구역분류코드]입니다. (행정동)
  • 속성의 adm_cd2 는 행정안전부 사용하는 10자리의 [행정기관코드]입니다. (법정동)

저는 오늘 동이 2개라는 사실을 처음 알게되었습니다. ㅋㅋㅋㅋ 간단하게 의미를 살펴보면 다음과 같습니다.

  • 법정동 : 전통적인 지역 이름입니다. 지번주소에 적는 동은 법정동입니다. 도로명주소를 적을때 괄호 안에 병기하는 동명도 법정동입니다.
  • 행정동 : 법정동대로 행정을 처리하려면 문제가 발생하는데, 어떠한 동은 인구나 면적이 너무 작고, 어떠한 동은 너무 커서 행정 수요에 맞는 행정이 불가능해집니다. 이러한 문제를 막기위해 생긴 제도가 행정동입니다.

예를 들어, 서울특별시 중구 명동은 법정동으로보면 무교동, 다동, 태평로1가, 을지로1가, 을지로2가, 남대문로1가, 삼각동, 수하동, 장교동,… 등 16개로 잘게 쪼개져있는데, 도심 공동화로 인구는 점점 줄어들어 행정수요가 줄었기 때문에 이들을 모두 포함하는 '명동'이라는 하나의 행정동으로 운영하고 명동 주민센터 하나만 설치되어있다. - 나무위키 참고

근데... 자료를 찾다보니 법정동코드목록조회는 쉽게 찾을 수 있었습니다. 하지만 행정동코드는 자료 찾기가 좀 힘들더군요. 결국 최신 자료를 못찾았습니다. 그래서 그냥 저는 법정동을 사용하기로 했습니다. 다른 곳에서도 법정동을 사용하는거 같고요.

✍️ 참고 : http://www.gisdeveloper.co.kr/?p=2332 (여기 시도, 시군구, 읍면동 기준으로 만든 자료가 있습니다.)

내가 사는 '시' 에 대한 GeoJSON 포맷 구하기

전체 GeoJSON 포맷을 구했지만 아직 제가 사는 시흥시에 대한 GeoJSON 포맷은 구하지 못했습니다. 이를 구하기 위해서는 필터링을 해야 됩니다. 하지만 이상하게 kepler 서비스에서 adm_cd2 타입을 time으로 인식하더군요. adm_cd는 int 타입이면서... 어째서 이런일이.. 결국 방법을 찾을 수 없었습니다.

참고 : https://medium.com/@masoolsa7/행정구역-경계-topojson-파일-만들기-392800d0bc0b
참고 2 : https://blog.naver.com/flower756/222393681058

대신 QGIS 라는 오픈 소스 프로그램에 대해 알게되었습니다. 근데 문제는 좀 무겁다는 것과 (설치하면 3GB...), 다운로드 시간이 좀 걸립니다. 그리고 Mac에서는 이 프로그램을 악성 프로그램인지 판단할 수 없으므로 안 열립니다. 설정을 통해 열 수 있긴 한데 살짝 꺼림직한 측면도 있습니다.

아무튼 별수 없으니 QGIS를 설치하고 열어봤습니다. 잘 되더군요.

여기서 표현식 필터링을 통해 시흥시에 해당하는 동 영역만 불러오도록 해줍니다.

시흥시 선택이 잘되었습니다. 이제 선택된 영역을 내보내기를 해줍니다.

여기서 주의할 점은 파일 이름만 작성하고 내보내려고 하면 “Failed to create GeoJSON datasource:” 에러를 볼 수 있습니다. 이는 파일 쓰기 권한이 없어서 그런건데, 파일 저장하려는 위치를 봤더니 루트였습니다. 위치를 수정해줬더니 성공적으로 저장되었습니다.

그리고 나서 https://mapshaper.org 사이트에 접속해서 만든 파일을 올리고 import 해줍니다. 이를 topojson로 export 해줍니다.

TopoJSON + Chart.js 시각화

Chart.js Geo 를 이용해 위에서 만든 시흥시 TopoJSON를 차트로 보여주는 예시입니다. 그냥 하면 재미없으니 시흥시 법정동별 인구수 자료를 찾아서 TopoJSON properties 속성에 추가해주었습니다.

        {
          "arcs": [[42, -41, 43, -34, 44, 45]],
          "type": "Polygon",
          "properties": {
            "adm_nm": "경기도 시흥시 군자동",
            "adm_cd": "3115068",
            "adm_cd2": "4139058100",
            "sgg": "41390",
            "sido": "41",
            "sidonm": "경기도",
            "temp": "시흥시 군자동",
            "sggnm": "시흥시",
            "adm_cd8": "31150680",
            "population": 9267
          }
        },
import Chart from "chart.js/auto";
import {
  topojson,
  ChoroplethController,
  ProjectionScale,
  ColorScale,
  GeoFeature,
} from "chartjs-chart-geo";

export default async function () {
  Chart.register(ChoroplethController, ProjectionScale, ColorScale, GeoFeature);

  const ctx = document.getElementById("myChart")! as HTMLCanvasElement;

  fetch("siheung.json")
    .then((r) => r.json())
    .then((data) => {
      const nation = topojson.feature(data, data.objects.siheung).features[0];
      const states = topojson.feature(data, data.objects.siheung).features;

      const chart = new Chart(ctx, {
        type: "choropleth",
        data: {
          labels: states.map((d) => d.properties.adm_nm),
          datasets: [
            {
              label: "States",
              outline: nation,
              data: states.map((d) => ({
                feature: d,
                value: d.properties.population,
              })),
            },
          ],
        },
        options: {
          showOutline: false,
          showGraticule: false,
          plugins: {
            legend: {
              display: false,
            },
          },
          scales: {
            projection: {
              axis: "x",
              projection: "mercator", // 메르카토르 투영법 설정 : 구형인 지구를 평면으로 표현하는 방법
              projectionScale: 1, // 스케일 배율 조절하는게 아닌가?
              projectionOffset: [50, -150], // 원점 조절
              padding: 210, // 차트의 크기 조절?
            },
          },
        },
      });
    });
}

드디어 완성입니다. 월곶동 인구수는 생각보다 얼마 안되었군요. 만명은 넘을 줄 알았더니... 확실히 시흥시는 여기 따로 저기 따로 인거 같습니다. 정왕동 중심 생활권, 신천-대야-은행 중심 생활권, 그 외 나머지 지역들... ㅋㅋㅋ


마치면서

GeoJSON, TopoJSON 이라는 포맷을 배우고 이를 조작해서 제가 사는 지역에 대한 차트를 시각화 해봤다는게 너무 재미있었습니다. 새로운 사실에 대해 알게 되는 즐거움? 그리고 이를 통해 많은 다양한 것들을 해볼 수 있을 거 같다는 기대감도 들었습니다.


참고 자료

profile
배움을 좋아합니다. 새로운 것을 좋아합니다.

0개의 댓글