shp 파일 하나만 보내주세요 — 30년 묵은 표준, Shapefile의 사연

9

shp 파일 하나만 보내주세요 — 30년 묵은 표준, Shapefile의 사연 이미지

"shp 파일 하나만 보내주세요"라고 했는데

동료에게 행정구역 데이터를 부탁해 .shp 파일 하나를 받았습니다. QGIS에 끌어다 놓으니 — 안 열립니다. 어찌어찌 열려도 셋 중 하나죠. 속성 테이블이 텅 비어 있거나, 데이터가 아프리카 앞바다 어딘가에 뚝 떨어져 있거나, 한글 이름이 전부 ??? 로 깨져 있거나. 당신이 뭘 잘못한 게 아닙니다. 애초에 "Shapefile"은 파일 하나가 아니거든요.

지난 벡터와 래스터 편에서, 벡터 데이터는 점·선·면의 좌표로 저장된다고 했습니다. 그러면 그 좌표는 실제 파일 안에 어떤 모습으로 담겨 있을까요? Phase 3의 첫 주자는 이 바닥의 터줏대감입니다. 1990년대에 태어나 30년이 지난 지금도 도무지 죽지 않는 포맷, Shapefile이죠.

30년 전에 태어난 표준 — 왜 아직도 안 죽나

Shapefile은 1990년대 초 ESRI가 자사 제품(ArcView)을 위해 만든 포맷입니다. 결정적이었던 건 1998년, ESRI가 그 스펙을 백서로 공개한 일이에요. 누구나 읽고 구현할 수 있게 되자 모든 GIS 도구가 이걸 지원하기 시작했고, 그렇게 사실상의 표준(de facto standard) 이 됐습니다.

문제는 그 뒤로 30년이 흘렀다는 겁니다. 훨씬 나은 후계자들이 줄줄이 나왔는데도(GeoPackage, FlatGeobuf, GeoParquet — 다 뒤에서 만납니다), Shapefile은 여전히 살아 있어요. 이유는 단순합니다.

💡 팩스가 아직 안 죽는 이유와 똑같습니다. 기술이 훌륭해서가 아니라, 모두가 갖고 있어서 죽지 못하는 것. Shapefile은 GIS 세계의 팩스예요.

하나가 아니다 — 파일 묶음의 정체

이제 핵심입니다. 하나의 Shapefile은 최소 3개, 보통 4~5개 파일이 모인 한 세트입니다. 이 파일들은 이름(확장자를 뗀 부분)이 똑같아야 하고, 같은 폴더에 함께 있어야 해요. seoul.shp, seoul.dbf, seoul.prj… 이런 식으로요.

각각이 맡은 역할은 이렇습니다.

확장자담는 것없으면 벌어지는 일
.shp도형 그 자체 (점·선·면의 좌표)아무것도 없음. 본체가 없다
.shx인덱스 (n번째 도형이 몇 바이트째인지)특정 도형을 빨리 찾지 못함
.dbf속성 테이블 (이름·인구·면적 등)도형은 뜨는데 속성이 텅 빔
.prj좌표계 (WKT 형식)이게 5179인지 4326인지 아무도 모름
.cpg인코딩 (CP949·UTF-8 등)한글이 깨짐

앞의 세 개(.shp·.shx·.dbf)가 스펙상 필수고, 뒤의 두 개(.prj·.cpg)는 "선택"이라고 되어 있지만 — 현실에선 없으면 반드시 사고가 납니다. 사실상 필수예요. 이 밖에 .sbn·.sbx(ESRI 공간 인덱스), .qix(QGIS 인덱스) 같은 곁다리가 딸려오기도 하는데, 이건 없어도 그만입니다.

여기서 뭔가 이상하지 않나요? 도형과 속성이 서로 다른 파일에 따로 산다는 것. 게다가 좌표계와 인코딩마저 각자 딴 파일에 적혀 있죠. 그러니 이 중 하나만 빠져도 데이터는 반쪽이 됩니다.

💡 그래서 Shapefile은 언제나 zip으로 묶어서 주고받습니다. .shp 하나만 메신저로 툭 보내는 순간 사고가 시작되는 거예요. 누가 "shp 파일 주세요"라고 하면, 마음속으로 "폴더째 주세요"로 번역해 들으세요.

shp 파일 하나만 보내주세요 — 30년 묵은 표준, Shapefile의 사연 이미지

도입부 3대 사고, 범인은 누구였나

이제 맨 처음의 세 가지 증상을 다시 봅시다. 범인이 전부 드러났습니다.

① 속성 테이블이 텅 비었다.dbf가 없습니다. 도형(.shp)만 왔으니 모양은 그려지는데, 그게 어느 동네인지 알려줄 표가 통째로 빠진 거죠.

② 데이터가 아프리카 앞바다에 있다.prj가 없습니다. 이게 제일 극적이에요. .prj가 없으면 프로그램은 좌표계를 모른 채, 흔히 "위경도겠거니" 하고 가정해버립니다. 그런데 그 숫자가 실은 EPSG:5179의 미터 좌표라면? (953000, 1950000) 같은 값이 경도 95만 도로 읽히면서 지도 밖으로 튕겨 나가거나, 좌표가 0 근처로 뭉개져 위경도 (0, 0) — 기니만 앞바다, 이른바 Null Island 에 착륙합니다.

눈치채셨겠지만 이건 좌표 변환 편에서 만난 ST_SetSRID 사고와 정확히 같은 사고입니다. 그때는 코드에서 라벨을 잘못 붙여 벌어졌다면, 지금은 라벨 파일 자체가 없어서 벌어질 뿐이죠. 같은 함정이 층만 바꿔 되살아난 겁니다.

③ 한글이 ???로 깨졌다.cpg가 없거나 내용이 틀렸습니다. 속성 테이블의 글자를 어떤 인코딩으로 읽어야 하는지 알 수 없으니, 프로그램이 제멋대로 찍다가 깨지는 거예요.

증상범인처방
속성이 텅 빔.dbf 실종원본 폴더·zip을 통째로 다시 받기
엉뚱한 위치(0,0 근처).prj 실종원본 좌표계를 물어 -s_srs로 직접 지정
한글이 ???·.cpg 실종·불일치인코딩(대개 CP949)을 직접 지정

화석이 된 제약들 — 오래됨의 대가

파일이 여러 개인 것만으로 끝이 아닙니다. Shapefile에는 1990년대에 굳어버린 제약들이 화석처럼 박혀 있어요. 유별난 건 이 제약 대부분이 .dbf이라는 점입니다. 속성을 담는 이 파일이 사실 1980년대 데이터베이스 포맷인 dBASE를 그대로 가져다 쓴 것이거든요.

컬럼명 10자 제한. 필드 이름이 10자를 넘으면 잘립니다. apartment_nameapartment_가 되죠. 더 지독한 건, 잘린 이름끼리 겹칠 때예요. building_areabuilding_addr가 나란히 building_a가 되면, 프로그램이 알아서 buildin_1·buildin_2 식으로 개명해버립니다. 이쯤 되면 어떤 컬럼이 원래 뭐였는지 미궁에 빠지죠.

인코딩 지옥. dBASE는 인코딩이라는 개념이 부실합니다. 한국 공공데이터는 대개 CP949(EUC-KR) 로 인코딩돼 있고 요즘 것은 UTF-8인데, .cpg가 없거나 틀리면 즉시 한글이 깨집니다. 그래서 GDAL 같은 도구는 SHAPE_ENCODING이라는 별도 설정으로 인코딩을 강제 지정하는 탈출구를 마련해 뒀어요.

2GB의 천장. .shp.dbf는 파일 안의 위치를 32비트로 기록합니다. 그래서 파일 하나가 약 2GB를 넘길 수 없어요. 전국 건물처럼 덩치 큰 데이터를 담으려 하면 이 천장에 머리를 부딪칩니다.

그 외에도 자잘한 제약이 줄줄이 딸려옵니다.

제약내용
컬럼명 10자넘으면 잘리고, 겹치면 자동 개명
텍스트 254자문자 필드의 최대 길이
시간 타입 없음날짜(date)는 있지만 시각까지 담는 타입이 없음
NULL 표현 애매"빈 값"과 "0"이 잘 구분되지 않음
한 파일 = 한 도형 타입점·선·면을 한 파일에 섞을 수 없음
약 2GB파일당 크기 상한

💡 오해하지 마세요. 이건 버그가 아니라 1990년대 설계의 화석입니다. 그 시절엔 2GB가 사실상 무한대였고, 컬럼명 10자면 충분했으며, 한글 인코딩은 지구 반대편 남의 나라 얘기였으니까요. Shapefile이 나쁜 게 아니라, 너무 오래 살아남은 게 문제입니다.

실무에선 — 받는 즉시 갈아탄다

그래서 현업의 요령은 명확합니다. Shapefile로 받되, 그걸로 작업하지는 않는다. 받자마자 다루기 편한 포맷(GeoJSON·GeoPackage·PostGIS)으로 변환해두고, 원본 zip은 그대로 보관하는 거죠.

변환의 표준 도구는 GDAL의 ogr2ogr입니다. 핵심은 인코딩과 좌표계를 손으로 명시해주는 것이에요.

bash
# CP949로 쓰인 EPSG:5179 Shapefile을 → UTF-8 GeoJSON(EPSG:4326)으로
ogr2ogr -f GeoJSON seoul.geojson seoul.shp \
  --config SHAPE_ENCODING CP949 \
  -s_srs EPSG:5179 \
  -t_srs EPSG:4326

--config SHAPE_ENCODING CP949가 한글 깨짐을 막고, -s_srs(원본 좌표계)와 -t_srs(목표 좌표계)가 아프리카행을 막습니다. 특히 -s_srs를 굳이 적어주는 이유는, .prj가 없거나 믿을 수 없을 때를 대비해서예요. 좌표 변환 편의 결론 — "원본 EPSG를 정확히 알려주는 것이 관건" — 이 여기서도 그대로 통합니다.

PostGIS로 바로 밀어 넣는 shp2pgsql 같은 길도 있는데, 그건 데이터베이스를 다루는 Phase 6에서 제대로 만납니다.

💡 재미있는 사실 하나 — switchfromshapefile.org 라는 사이트가 진짜로 있습니다. "제발 Shapefile 좀 그만 쓰자"고 조목조목 설득하는 페이지죠. 그만큼 전 세계의 많은 사람이 이 포맷에 데였다는 뜻이기도 하고요.

정리 — 한 문장으로

Shapefile은 파일 하나가 아니라 한 묶음이다. .shp는 도형, .dbf는 속성, .prj는 좌표계, .cpg는 인코딩 — 하나라도 빠지면 데이터는 조용히 망가진다.

확장자역할빠지면
.shp도형(좌표)본체가 없음
.shx인덱스접근이 느려짐
.dbf속성 테이블속성이 텅 빔
.prj좌표계지도 밖·바다로 날아감
.cpg인코딩한글이 깨짐

30년을 버틴 포맷의 사연을 봤으니, 이제 그 반대편이 궁금해집니다. 파일을 다섯 개로 쪼개고, 컬럼명을 10자로 자르고, 한글을 깨뜨리는 이 무거운 유산 대신 — 웹은 완전히 다른 길을 갔습니다. 텍스트 한 덩어리면 끝나는, 브라우저가 그대로 읽을 수 있는 포맷. 다음 편은 웹 표준이 된 GeoJSON입니다. 좌표 순서가 왜 하필 [경도, 위도]인지, 그리고 이 편리한 포맷이 파일이 커지는 순간 왜 무너지는지를 파헤칩니다.

참고