2019년, 데이터 만들기
2019년, 그러니까 7년 전에 '새로 만드는 수도권 지하철은 시간걸를 얼마나 단축시킬까' 라는 제목으로 글을 쓴 적이 있다.
새로 만드는 수도권 지하철은 시간거리를 얼마나 단축시킬까
글 : 김승범 서울을 포함한 수도권은 지하철 건설이 한창이다. GTX 같은 급행철도나 경전철을 포함하여 새로 만드는 노선이 열 개도 넘는다. 이렇게 만들어지는 지하철은 서울과 수도권의 시간거
www.vw-lab.com
기본 노선도는 공공 데이터를 받아서 시작했는데, 미래 노선도는 역을 하나하나 선로를 그린 후 처음 개통한 날짜를 조사해서 구축했다. 물론 기본 노선도에도 처음 개통 날짜가 없었으므로 조사 시간이 한참 걸렸다.
그래서 완성한 뒤 QGIS에서 찍었던 스냅샷을 연결한 것이 아래의 gif다.

네트워크가 실제로 작동하기 위해서는 환승 노선들이 모두 연결되어야 한다. 노선별로 환승역을 분리해서 구분한 후 Transfer 레이어를 별도로 만들고 관리했다. 환승 시간은 youtube에 철도덕후들의 노고로 업로드 된 환승 영상을 참고했다. 환승 후 탑승 시간은 평균 배차간격의 절반을 넣었다. 그렇게 네트워크를 구축해서 SSSP(Single Source Shortest Path)탐색을 했다.
2019년 당시 미래에 계획된 노선들이 다수 있었는데, GTX-A 같은 경우 일부 개통되었지만, 대부분의 노선이 예정보다 많이 밀렸다.
당시에 이 노선도를 시각화 했었는데, 자바스크립트 개발을 별로 하지 않았을 때라 좀 투박했다. c++에서 opengl 다루던 문법으로 간단히 webgl로 포팅하려고 무턱대고 시작 했는데, 생각보다 안 되는게 많아서 고생했던 기억이 난다.
Isochrone map of Seoul Subway
vuski.github.io
그 이후로도 연관된 일을 하느라 지하철 네트워크는 계속 업데이트 했었는데, 웹으로 다시 구현할 시간이 없어서 투박한 시각화는 9년째 방치되어 있었다. 그러던 중 드디어 클로드의 도움으로 전면 리뉴얼 할 수 있었다.
다시 데이터
물론 데이터부터 다시 다듬어야 한다.
그 동안 실현되지 않았던 강북횡단선, 면목선 등은 개통시기를 그냥 2050년으로 일괄 밀어넣어놓고, 거의 확정적으로 예정된 노선들까지만 앞으로 당겨서 개통 날짜를 배치했다.
1. GTX-A의 서울~수서역 연결(2026.07) 및 삼성역 개통(2028.08)
2. 위례선 트램 개통(2026.12)
3. 동북선(2027.11)
4. 신안산선 개통(2028.12)
그리고 몇 년 후 예정된 노선들도 넣었다. 날짜는 적당히 뉴스에서 검색되는대로
5. GTX-B(2031.07)
6. GTX-C(2032.12)
데이터를 만들고 웹에서 확인하다가 잘못된 것들을 발견하고 다시 수정하는 일을 열 번 넘게 반복했는데, 파이썬 노트북으로 데이터 수정시 프론트엔드에서 필요한 파일들 목록을 생산해내는 절차를 자동화 시켜놓았다. 원본은 geoparquet와 parquet 파일로 관리했는데, 간단한 것들은 그냥 클로드에게 프롬프트로 주문해도 고쳐준다. 편리한 세상이 되었다.
OSM 보행 네트워크

왼쪽이 이번의 시각화, 오른쪽이 예전 시각화다. 예전에는 지하철 노선도만 넣어서 들로네 삼각화로 공간을 쪼개다 보니 영역 분할이 너무 거칠게 되었다. 그래서 이번에는 OSM에서 보행 네트워크를 넣어 지하철 노선도와 결합시켰다. 각 역에서 가까운 보행 네트워크에 수선을 내리고 승하차가 되도록 연결했다.
우선 OSM에서 보행 가능한 도로만 추출한 후 지하철과 관련있는 영역으로 crop했다.

그런데 문제는 OSM의 보행 네트워크가 너무 자세하다는 것.

44만개 노드가 추출되었는데 웹에서 로드하기에는 너무 부담스러운 용량이다.
이번의 경우는 골목의 자세한 길은 필요가 없었다.지하철 역에서 수선을 내릴 수 있고, 영역 곳곳에 닿도록 말단의 네트워크만 유지할 수 있으면 되었다. 그래서 이런 경우 가장 적합한 네트워크 분석 지수인 betweeness 를 계산해서 필터링하기로 했다. 존재하는 노드들의 모든 최단 경로쌍을 계산한 뒤 최단 경로가 통과하는 횟수들을 각 링크에 누적시킨다. 도로 네트워크에서는 간선도로들이 자연스럽게 많은 값으로 남고 골목길은 적게 통과하므로 작은 값이 남는다.
모든 쌍의 최단 경로쌍을 무작정 계산하려면 꽤 많은 시간이 소요되지만, 각 점에서 dijkstra로 SSSP를 전체 범위로 한번 돌린 결과에서 betweeness를 뽑아낼 수 있으므로 멀티 코어로 8분 정도의 계산을 거쳐 결과값을 얻었다. 물론 아무리 멀티 코어라도 44만개를 무턱대고 돌리면 꽤 많은 시간이 나오므로 numba 등으로 알고리즘을 최적화 해주고 도로망 네트워크 자원을 계속 공유해서 쓰는 등 약간의 신경을 써주어야 빠른 탐색이 나온다. 결과적으로 계산해보면 1초에 900개 정도의 SSSP를 돌린 셈이다.
아래는 계산 결과를 색상으로 다르게 했다.

아래 그림처럼 5백만 이상인 값들만 취하고, island 처럼 다른 곳과 연결 안되는 곳들은 간단히 제거해주었다.

22만개 정도, 즉 절반으로 줄여서 지하철과 결합시켰다.
자원접근성 분석
리뉴얼 하는 김에 자원 접근성도 계산해서 넣어보기로 했다.
통계청 SGIS에서 2023년 인구 100m 격자와 종사자수 100m 격자를 받았다. 각각의 등시선 안에 들어오는 격자들을 계산해서 누적시키면 된다.
등시선 생성과 관련된 내용은 아래의 글에 자세히 적어놓은 바 있다.
실시간 반응형 등시선도(Realtime Interactive Isochrone Map) 그리기
글 : 김승범 몇 단계를 거쳐 를 그린 과정을 설명하려고 한다. 마우스를 지도 위에서 움직이면 해당 지점을 중심으로 다른 곳들이 시간적으로 얼마만큼 걸리는지를 보여주는 지도다. 등시선도(is
www.vw-lab.com
자원 접근성은 아래와 같이 구한다. 왼쪽은 간단히 그린 등시선과 그 안에 인구 격자가 하나 있다고 가정한 것. 오른쪽은 그 격자가 들로네 삼각화 된 삼각형 안에 있을 때 무게중심 선형비례로 계산된다는 것을 설명하고 있다. 즉, 저 격자 안에 100명이 있다면, 그 100명은 31.5분 소요 시간에 도달할 수 있는 자원이 된다.

수도권 전역의 인구와 종사자 접근성 격자들을 대상으로 각 역에서 시간대별로 접근 가능한 수를 각각 구했다. 예를 들어 영등포 역에서 30분 안에 10만명의 종사자 수 격자에 접근 가능하고, 서울역에서 30분 안에 20만명의 종사자 수 격자에 접근 가능하다고 계산될 수 있다. 이렇게 800여개 역에 대해서 모두 계산을 한 뒤 정렬하면, 어떤 역에서 종사자 수 분포에 정해진 시간 안에 가장 많이 접근 가능한지 알 수 있다. 즉 종사자 접근성 순위는 직장 접근성이 좋은 거주지 순위로 해석할 수 있다.
반면, 인구 접근성 순위는 해당 역으로 사람들이 접근하기 좋은 상업적지 순위로 해석 가능하다. 역 방향 탐색 시간이 동일하다고 가정한 것인데, 각 인구 격자(거주지 격자)에서 설정한 시간 내에 그 역에 접근 가능한 수를 구하는 것. 예를 들어 "강남역에 30분 동안 100만명이 접근 가능하다"라는 계산을 해서 정렬하는 방식이다.
이렇게 미리 15, 30, 45, ... 180 분까지 15분 단위 x 20여개 시점 x 800여개 역 을 대상으로 사전 계산을 해서 1.6MB 정도의 parquet파일로 만들어 로딩했다. 모든 경우의 계산 시간은 1분 남짓이지만, 프론트엔드에서 싱글 코어로 선택한 시점과 시간에 대해서 800여개 역의 접근성을 실시간 계산하려면 그래도 몇 2~3초 걸릴 수 있기 때문이다.
계산한 순위는 아래처럼 볼 수 있다.

프론트엔드 기본 구성
완성된 프론트엔드 화면은 아래와 같다.

기본적으로 역을 클릭해서 선택하거나 검색창을 통해 선택할 수 있다.
선택할 경우 지정해놓은 탐색 시간만큼 등시선이 확장된다. 일종의 효과로서 바탕에 깔린 보행 네트워크가 탐색 시간에 맞춰서 퍼져나가도록 했다. 등시선과 보행 네트워크 모두 shader에서 작동하기 때문에 적은 비용으로 한 프레임 안에서 렌더링할 수 있다.
MapLibre에 전체적인 컨트롤러와 프레임 구성 권한을 주고 나머지는 MapLibre의 렌러 패스 안에 넣었다.
Deck.gl 은 overlay 방식으로 maplibre에 넣었고, 노선도와 각 역을 렌러딩한다.
각 역의 이름과 툴팁은 html 태그로 표현했다. 출발역을 선택한 후 다른 역들을 선택하면 그 역까지의 최단경로가 애니메이션으로 표시되는데, 그 부분은 svg로 넣었다.
보통 이렇게 지도와 다른 요소들을 함께 그릴 때, 지도를 zoom/pan 하게 되면 다른 요소들이 한 두 프레임 늦게 뒤따라 오는 시각적 이격 현상이 발생한다. 반드시 MapLibre 의 'render' 이벤트(또는 custom layer 의 render())에 그리기 콜백을 등록하는 방식으로 그려야 지도와 다른 표현 요소들이 착 달라붙어 움직이게 할 수 있다. 지도가 한 프레임 안에서 카메라 매트릭스를 갱신할 때, 그와 같은 사이클에 다른 요소들도 같이 그려지도록 해야 한다. React state·useEffect·map.on('move') 로 setState 를 거치는 경로는 한 프레임 늦어지므로 피해야 한다. 한 프레임이 늦어지면 아까 말했던 지도와 그 위의 요소들이 따로따로 움직이는 현상을 마주하게 된다.
역명 표시

각 역의 소요시간들을 어떻게 하면 너무 복잡하지 않게 보여줄지 고민했다.
일단 툴팁 형식으로 하얀 바탕에 강조해서 보여주는 역과, 테두리 없이 보여주는 역들의 두 종류로 구분했다.
사용자의 환경, 즉 모니터 크기에 따라 표현할 수 있는 정보량이 다를 수 있으므로 강조해서 보여주는 역이 무엇이 될지는 사용자가 선택할 수 있게 했다.
'끄기'는 선택한 역만 툴팁으로 보여준다. 화면을 확대하면 각 역의 도달시간이 보인다. 최대 탐색 시간 안에서만 소요시간을 보여준다.
'주요역'은 서울역 등 주요한 역과 각 역의 종점들이 드러나도록 수작업으로 추가했다. 역시 확대하면 모든 역의 도달시간을 볼 수 있다.
'환승'은 모든 환승역을 툴팁으로 표현한다. 그렇지만 화면을 축소하면 '주요역'만 드러나게 했다. 어차피 툴팁들이 겹쳐서 잘 보이지 않기 때문이다. 이 경우에도 확대하면 모든 역의 도달시간을 볼 수 있다.
역간 최단경로 탐색

등시선을 그리고 각 역에 도달 시간을 표시했지만, 사용자 입장에서는 그러한 정보로 부족할 수 있다.
그래서 출발역을 선택한 상태에서 각 역에 마우스를 올려놓으면 전체 탐색의 결과값을 바탕으로 마우스를 올린 역의 경로까지만 추출해서 보여주었다. 어찌보면 불필요한 작업일 수 있지만, 사용자 입장에서는 어떻게 해서 그만큼의 소요시간이 걸렸는지 이해할 수 있다. 데이터 시각화의 목적 중 하나는 사용자를 이해시키고 수긍하게 만드는 것이기도 하다. 단순히 결과만 보여주는 것보다 이러한 추가적 설명 작업을 넣음으로써 경험하는 사람의 신뢰를 얻을 수 있다.
중간 역의 툴팁들은 서로 겹치지 않게 하기 위해 약간의 장치들을 두었다.
우선 출발역, 도착역, 중간 환승역 중 환승 직후 역을 전체 경로 배열에서 앵커로 박아넣어 표시했다. '환승 직후 역'이란 1호선 신도림역에서 2호선 신도림으로 환승할 경우, 2호선 신도림을 의미한다.
그리고 직전에 표현된 역과 앵커들과의 간격을 비교하여 가로 110px, 세로 50px 중 어느 하나의 간격을 넘어야만 표시하도록 했다. 픽셀 기준이므로 화면을 확대하게 되면 거의 모든 역의 소요시간 툴팁들을 볼 수 있다.
등시선 표시

등시선은 최초 로딩시 30분을 기본값으로 두었다.
180분으로 설정할 경우, 어느 역에서 출발해도 180분이면 대부분의 지역을 커버하므로 등시선을 움직여도 화면에 변화가 적어보인다. 물론 색상이 다른 등시선으로 구분이 되어있기는 하지만, 그래도 탐색 시간을 줄여서 확인할 때 좀 더 변화가 눈에 잘 들어온다. 등시선이 그려진 부분과 그려지지 않은 부분들로 도달 가능 영역의 차이를 더 쉽게 인지할 수 있기 때문이다.
그리고 시각화와는 별개로, 수도권 네트워크에서 접근성을 파악할 때 얼마만큼의 도달 시간을 기준으로 삼을 것인가에 대해 생각해볼 수 있다.
통상적으로 수도권 안에서 door to door 기준으로 1시간 이동이면 길다는 생각이 든다. 2024년 서울 시민의 평균 통근시간이 35분인데, 그게 door to door 기준이므로 실제 지하철 탑승 시간은 30분 이내일 것이다. 물론 경기도에서 서울로 출퇴근 하는 사람들은 35분의 두 배 가까이 지하철에서 보내기도 하지만, 일단 처음 탐색은 '직장에서 지하철로만 30분 거리 안쪽은 어느 정도의 영역일지' 질문을 던지는 것에서 시작할 수 있다고 생각했다.
시점의 변화
원본 네트워크에는 역별로 자세한 개통 시기가 기록되어 있다. 그렇지만 모든 시점들을 끊어서 만들면 너무 많아지므로 몇몇 시점들을 통합해서 구분했다. 시점 슬라이더를 움직이면 과거 시점의 지하철 노선도와 선택한 역 기준의 등시선. 그리고 접근성 순위가 어떻게 변해왔는지 확인할 수 있다.

위의 그림은 접근 영역의 확장을 시각적으로 강조하기 위해 탐색 시간을 90분으로 충분히 설정했다.
그런데 아마도 더 궁금한 것은 미래 시점일 것 같다.

신안산선 개통 시기부터는 시점은 불확실하지만 GTX-B, C 모두 착공을 했으므로 6~7년이면 완공되는 것으로 간주하고 현재 알려진 개통 시기들을 넣었다.
GTX 들은 확실히 수도권 접근성의 지형도들을 크게 변화시킨다. 물론 이 네트워크 망은 지하철 역을 시작점으로 두고 있으므로 탑승할때 지하 50m의 대심도까지 접근하는 시간은 포함되어 있지 않다. 따라서 집에서 출발해서 GTX를 탑승할 경우 기존 지하철처럼 2분 정도면 역 입구에서 승강장까지 도달할 수 있는 경우보다는 탑승 저항이 클 것 같다.
환승 저항(환승시간+대기시간)은 넣어놓았다.
만든 과정의 설명은 여기까지. 다음 편에서는 시점을 변화시켜보면서 실제로 어떻게 접근성의 지형도가 변했는지 살펴보기로 하자.
수도권지하철소요시간
수도권 지하철·광역철도 네트워크에서 임의의 역을 선택해 도달 시간을 등시선으로 시각화합니다. 1974년부터 미래 개통 예정 노선까지 시점별 네트워크 변화를 탐색할 수 있습니다.
seoulsubway.vw-lab.com
'Function' 카테고리의 다른 글
| admdongkor 라이브러리 구축기 (1) | 2026.04.27 |
|---|---|
| Flowring : 인구 이동 시각화와 행정구역 데이터의 시계열 정합성 (0) | 2026.04.03 |
| 1.7MB에 전국 100m 격자 인구 데이터 넣기 (0) | 2026.03.27 |
| Roaring Bitmap을 사용한 로컬데이터(localdata.kr) 브라우징 최적화 (1) | 2026.03.23 |
| OpenStreetMap과 GTFS 데이터를 이용한 도시 자원 접근성 분석 방법 (2) | 2025.03.27 |