OpenStreetMap 데이터 모델 — 폴리곤이라는 타입이 없다

면이 분명히 있는데, 면이라는 타입은 없다
GeoJSON에는
Polygon이 있습니다. Shapefile에도 있고요. 그런데 전 세계에서 가장 큰 지도 데이터베이스를 열어 보면, 그런 타입이 아예 없습니다. 한강도, 여의도공원도, 서울시청 건물도 분명 지도 위에서 면으로 칠해져 있는데요. 그럼 이것들의 정체는 대체 뭘까요?
지난 KML·GML·TopoJSON 편으로 데이터 포맷(Phase 3) 을 마무리했습니다. 이제 어떤 파일을 받아도 정체를 알아볼 수 있게 됐죠. 그런데 곰곰이 생각해 보면, 우리는 아직 가장 중요한 질문을 건너뛰었습니다. 그 파일에 담을 데이터 자체는 대체 어디서 나오나요?
전 세계 도로망, 건물, 상점, 공원, 행정경계 — 이 어마어마한 것들의 출처는 상당 부분 하나로 수렴합니다. OpenStreetMap(OSM) 이에요. 그리고 OSM을 쓰려면, 먼저 이 프로젝트가 세상을 담는 이상하리만치 단순한 방식부터 넘어야 합니다.
숫자로 먼저 보는 규모
감을 잡고 갑시다. 이 글을 쓰는 시점(2026년 8월 7일) 기준 OSM 데이터베이스에 들어 있는 것들입니다.
| 개수 | |
|---|---|
| Node(점) | 10,785,701,672개 |
| Way(선) | 1,209,587,117개 |
| Relation(묶음) | 14,623,859개 |
| 가입자 | 10,706,630명 |
노드가 107억 개. 지구 인구보다 많습니다. 그런데 이 전부가 자원봉사자들이 손으로 찍은 것이에요.
시작은 초라했습니다. 2004년, 유니버시티 칼리지 런던에 다니던 스티브 코스트라는 학생이 화가 났어요. 영국 국립지리원(Ordnance Survey)이 세금으로 만든 훌륭한 지도 데이터를 갖고 있으면서 자유롭게 풀지 않았거든요. "그럼 내가 만들지 뭐." 그해 8월 9일 도메인을 등록하고, 12월 10일 GPS 수신기를 들고 런던 리젠트 파크를 자전거로 한 바퀴 돌아 첫 데이터를 올립니다.
공원 한 바퀴에서 시작한 게 107억 개가 됐습니다. 심지어 지금은 구글·마이크로소프트·애플·아마존조차 OSM 데이터를 참조하는 처지예요. 자원봉사자들의 취미가 상용 지도의 원재료가 된 셈이죠.
타입이 딱 셋뿐이다
이 107억 개를 담는 데이터 모델을 볼 차례입니다. 마음의 준비를 하세요. 타입이 세 개입니다.
- Node — 점 하나
- Way — 점들을 순서대로 이은 것
- Relation — 여러 개를 묶은 것
끝입니다. 우리가 GeoJSON에서 봤던 목록과 나란히 놓아 볼까요.
| GeoJSON | OSM |
|---|---|
| Point | Node |
| LineString | Way |
| Polygon | ??? |
| MultiPoint | (Relation) |
| MultiLineString | (Relation) |
| MultiPolygon | (Relation) |
| GeometryCollection | (Relation) |
Polygon 자리가 비어 있죠. 오타가 아닙니다. 정말로 없어요. 이 빈칸의 정체가 오늘 글의 절반입니다.
Node — 점, 그런데 97%는 아무 의미가 없다
가장 밑바닥부터. Node는 위도·경도 한 쌍을 가진 점입니다. OSM에서 좌표를 직접 가진 건 오직 Node뿐이에요. Way도 Relation도 좌표가 없습니다. 오로지 Node를 가리킬 뿐이죠. 이 사실을 붙잡고 가면 나머지가 전부 쉬워집니다.
좌표는 소수점 7자리로 저장됩니다. 1e-7도, 적도 기준 약 1.1cm예요. 내부적으로는 실수가 아니라 32비트 정수로 담깁니다(자릿수를 고정하면 정수로 눌러 담을 수 있죠). 이 정수 저장 방식은 다음 편에서 파일 크기를 좌우하는 결정적 트릭으로 다시 등장합니다.
<node id="1234567890" lat="37.5663174" lon="126.9779451">
<tag k="amenity" v="post_box"/>
</node>
여기까진 평범합니다. 그런데 재미있는 통계가 하나 있어요. 107억 개 노드 중 태그가 하나라도 붙은 건 2억 9,320만 개 — 전체의 2.71% 뿐입니다.
나머지 97.3%는 아무 의미도 없는 좌표 부스러기라는 뜻이에요. 우체통도 아니고 신호등도 아니고, 그냥 좌표 하나. 왜 이런 게 100억 개나 있을까요?
Node에 역할이 두 개이기 때문입니다.
- ① 그 자체로 의미 있는 것 — 우체통, 신호등, 벤치, 편의점. 태그가 붙습니다. (2.71%)
- ② 선을 그리기 위한 재료 — 도로가 꺾이는 지점, 건물 모서리. 태그가 없습니다. (97.3%)
도로 하나를 그리려면 꺾이는 지점마다 점이 필요하고, 그 점들 하나하나가 전부 Node로 저장됩니다. 그래서 노드 수가 폭발하는 거예요. OSM의 노드 대부분은 "장소"가 아니라 "기하학의 부품"입니다.
Way — 선인데 면도 된다
Way는 Node들의 순서 있는 목록입니다. 좌표는 없고, Node의 id만 순서대로 나열해요.
<way id="8888888">
<nd ref="1001"/>
<nd ref="1002"/>
<nd ref="1003"/>
<tag k="highway" v="residential"/>
<tag k="name" v="테헤란로"/>
</way>
<nd ref="...">가 Node를 가리키는 참조입니다. "순서 있는" 이 중요해요 — 순서가 뒤집히면 일방통행 방향이 반대가 됩니다.
Way에는 상한이 하나 있습니다. 노드를 1개에서 2,000개까지 담을 수 있어요. 이 2,000이라는 숫자를 기억해 두세요. 조금 뒤에 아주 성가신 문제를 일으킵니다.
그리고 여기서 오늘의 핵심이 나옵니다. 첫 번째 Node와 마지막 Node가 같으면 그 Way는 제자리로 돌아와 고리처럼 닫힙니다. 이걸 닫힌 선(closed way) 이라고 불러요.
닫혀 있으면 면 아닌가요?
가장 자연스러운 반응이 이겁니다. "한 바퀴 돌아서 닫혔으면 그게 곧 면 아니야?" 아닙니다. 회전교차로를 떠올려 보세요.
회전교차로의 도로는 분명히 한 바퀴 돌아 제자리로 옵니다. 완벽한 닫힌 선이죠. 그런데 이걸 면으로 칠하면 어떻게 될까요? 가운데 화단까지 전부 아스팔트가 됩니다. 실제 도로는 고리 그 자체일 뿐, 안쪽 화단은 도로가 아닌데도요.
울타리도 마찬가지예요. 마당을 빙 둘러싼 울타리는 닫힌 선이지만, 울타리는 테두리지 마당이 아닙니다. 마당을 울타리 색으로 칠하면 곤란하죠.
반대로 건물은요? 건물 외곽선을 따라 닫힌 선을 그렸다면, 이건 안쪽까지 전부 건물입니다. 반드시 칠해야 해요.
정리하면 닫힌 선에는 두 종류가 있습니다.
| 실체는 | 예 | |
|---|---|---|
| 테두리만 | 고리 그 자체 | 회전교차로, 울타리, 운동장 트랙 |
| 안쪽까지 | 고리가 감싼 영역 | 건물, 호수, 공원 |
문제는 이겁니다. 회전교차로의 고리와 건물의 외곽선은, 좌표만 놓고 보면 구별이 안 됩니다. 둘 다 그냥 "제자리로 돌아온 점들의 목록"이에요. 하나는 테두리만이 실체이고 다른 하나는 안쪽까지가 실체인데, 기하는 그 차이를 전혀 담고 있지 않습니다.
폴리곤이 없다 — 면은 '태그로 선언'된다
그럼 이 둘을 어떻게 구분할까요? OSM의 답은 이렇습니다.
면인지 아닌지는 기하가 아니라 태그가 결정한다.
OSM 위키의 표현을 그대로 옮기면, "닫힌 선이 면인지 여부는 그 태그로부터 추론된다(inferred from its tags)." 새 타입을 만드는 대신, "이건 면이야"라고 선언하는 태그를 붙이기로 한 겁니다.
방법은 두 가지예요.
① 명시적으로 선언한다.
<tag k="area" v="yes"/> <!-- 이건 면이다 -->
<tag k="area" v="no"/> <!-- 이건 면이 아니다 -->
② 암묵적으로 통한다. 매번 area=yes를 붙이긴 번거로우니, "이 태그가 붙었으면 당연히 면"이라는 관습이 생겼습니다.
| 태그 | 닫혀 있으면 |
|---|---|
building=yes | 면 (건물에 구멍 뚫린 선은 없으니까) |
landuse=forest | 면 (숲은 넓이가 있는 것) |
natural=water | 면 (호수) |
highway=residential | 선 (도로가 한 바퀴 돌면 순환도로) |
barrier=fence | 선 (마당을 둘러싼 울타리) |
그래서 생기는 모호함
이 방식은 당연히 회색지대를 만듭니다. 대표 사례가 highway=pedestrian 이에요.
- 그냥 닫혀 있으면 → 보행자 전용 "도로"가 한 바퀴 도는 것
area=yes가 붙으면 → 보행자 "광장"
같은 태그, 같은 모양인데 area=yes 한 줄로 완전히 다른 것이 됩니다. natural=tree_row(가로수 줄)도 마찬가지예요. 닫혀 있어도 그건 나무가 둘러싼 고리지 숲이 아닙니다.
💡 실무 함정. "어떤 태그를 면으로 칠 것인가" 목록은 표준 스펙이 아니라 각 도구의 구현 사항입니다. JOSM, iD 에디터, Overpass API가 저마다 자기 목록을 들고 있어요. 그래서 같은 데이터가 도구마다 다르게 보일 수 있습니다. 어떤 렌더러에선 칠해지고 어떤 렌더러에선 선으로만 나오는 상황, OSM 데이터를 다루다 보면 반드시 만납니다.
Relation — 구멍, 그리고 묶음
Node와 Way만으론 안 되는 게 있습니다. 구멍 뚫린 면이요.
호수를 품은 공원을 생각해 보세요. 바깥 경계 하나, 안쪽 호수 하나. 닫힌 선 하나로는 표현할 방법이 없습니다. 여기서 세 번째 타입이 나옵니다.
Relation은 다른 요소들을 묶고, 각각에 역할(role)을 붙인 것입니다. 가장 흔한 게 type=multipolygon이에요.
<relation id="777777">
<member type="way" ref="101" role="outer"/> <!-- 공원 바깥 경계 -->
<member type="way" ref="102" role="inner"/> <!-- 안쪽 호수 = 구멍 -->
<tag k="type" v="multipolygon"/>
<tag k="leisure" v="park"/>
</relation>
여기서 <member>가 처음 나왔는데, 이건 Way 자체가 아닙니다. 좌표가 하나도 없죠. "누구를, 무슨 역할로 이 묶음에 넣는다"는 참조 한 줄일 뿐이에요.
<member type="way" ref="101" role="outer"/>
└─종류─┘ └─id─┘ └──역할──┘
즉 "id가 101인 Way를, 바깥 경계 역할로 이 relation에 포함한다" 는 뜻입니다. 실제 Way 101은 파일 어딘가에 <way id="101">...</way>로 따로 존재해요.
눈치채셨을 수도 있는데, 이건 Way 안에서 봤던 <nd ref="1001"/> 과 똑같은 구조입니다. 딱 하나가 다르죠.
Way의 <nd> | Relation의 <member> | |
|---|---|---|
| 가리키는 대상 | 항상 Node | Node·Way·Relation 아무거나 |
type 속성 | 없음 | 있음 |
| 구분 | 순서만 의미 있음 | role로 역할 구분 |
<nd>에 type이 없는 이유가 이걸로 설명됩니다. Way는 Node밖에 담을 수 없으니 종류를 밝힐 필요가 없어요. 반면 Relation은 아무거나 담을 수 있어서 매번 "이번엔 Way다"라고 적어 줘야 합니다.
그래서 앞서 말한 "좌표를 가진 건 Node뿐" 이 여기까지 이어집니다. Relation은 Way를 가리키고, Way는 Node를 가리키고, 좌표는 사슬 맨 끝의 Node에만 있어요. 공원 하나를 화면에 그리려면 이 사슬을 두 칸 타고 내려가야 합니다.
역할로 돌아오면, role="outer"가 바깥이고 role="inner"가 구멍입니다. 규칙이 하나 있어요. 의미를 나타내는 태그는 Relation에 붙이고, 멤버로 들어간 outer Way에는 태그를 붙이지 않습니다. 안 그러면 "공원"이 두 개로 세어지니까요.
Relation이 필요한 상황이 하나 더 있습니다. 아까 그 2,000개 제한 기억하시죠? 나라 경계나 큰 강 같은 건 점 2,000개로는 어림도 없어요. 그래서 여러 Way로 쪼개 놓고 Relation으로 이어 붙입니다. 멤버는 최대 32,000개까지 넣을 수 있어요.
그리고 Relation은 폴리곤 전용이 아닙니다. "관련 있는 것들의 묶음" 이면 뭐든 됩니다.
type=route— 버스 노선 하나를 이루는 도로 구간 전부type=restriction— "이 도로에서 저 도로로 좌회전 금지"type=boundary— 행정경계
특히 restriction을 눈여겨보세요. 도로망을 그래프로 바꿔 길찾기를 구현할 때, 이 회전 제한을 무시하면 자동차더러 불법 좌회전을 하라는 경로가 나옵니다. Phase 8(라우팅) 에서 정면으로 만날 녀석이에요.
태그 — 스키마가 없는 스키마
이제 마지막 조각입니다. Node·Way·Relation은 뼈대일 뿐이고, "이게 무엇인가"는 전부 태그가 담당합니다.
태그의 규칙은 놀랄 만큼 적습니다.
- 키(key)와 값(value) 로 이루어진 문자열 쌍
- 각각 최대 255자 유니코드
- 한 요소 안에서 키는 중복 불가
이게 전부입니다. 어떤 키를 써야 하는지에 대한 강제 규칙이 없어요. 데이터베이스는 여러분이 내가만든키=아무값을 넣어도 순순히 받아 줍니다.
그 결과가 이겁니다. 실제로 쓰이고 있는 서로 다른 키가 113,052종. (2026년 8월 6일 기준, 서로 다른 태그 조합은 1억 9,500만 종)
일반적인 데이터베이스 테이블 컬럼이 스무 개쯤이라는 걸 떠올리면, 십만 개짜리 스키마라는 게 얼마나 기이한지 감이 옵니다. 물론 실제로는 소수가 압도적으로 쓰여요.
| 키 | 사용 횟수 |
|---|---|
building | 7억 396만 |
source | 3억 1,159만 |
highway | 2억 9,937만 |
addr:housenumber | 1억 8,255만 |
name | 1억 1,512만 |
그리고 키 하나만 들여다봐도 사정은 비슷합니다. highway 키 하나에 값이 582종이에요.
residential 6,970만 (주택가 도로)
service 6,540만 (주차장 진입로, 골목)
footway 3,220만 (인도)
... 나머지 579종
이게 바로 스키마가 아니라 민속 분류(folksonomy) 라고 불리는 이유입니다. highway=residential과 highway=service의 경계는 어떤 표준 문서가 정한 게 아니라, 위키 문서와 제안·투표를 거친 커뮤니티의 합의일 뿐이에요. 지키지 않아도 데이터베이스는 막지 않습니다.
💡 Shapefile과 정반대죠. Shapefile은 컬럼명이 10자를 넘으면 잘려 나갈 만큼 스키마가 빡빡했습니다. OSM은 그 반대 극단으로 갔어요.
그럼 다들 제멋대로 쓰면 어떡하죠?
당연히 나오는 질문입니다. 누군가 전기차 충전소를 amenity=charging_station이라고 쓸 때, 다른 사람이 station_charging이나 charging-station이라고 쓰면요? 막을 방법이 없잖아요.
맞습니다. 그리고 실제로 그렇게 들어 있습니다. amenity 키에서 "charg"가 들어간 값을 전부 뽑아 봤더니 36종이 나왔어요.
| 값 | 개수 |
|---|---|
charging_station | 197,147 |
charge_point | 10 |
charging_point | 5 |
charging center | 3 |
charging-station (하이픈) | 1 |
charging station (공백) | 1 |
devoce_charging_station (오타) | 1 |
borne de recharge (프랑스어) | 1 |
오타도, 하이픈도, 공백도, 아예 프랑스어로 쓴 사람도 있습니다. 걱정하신 일이 문자 그대로 벌어져 있어요.
그런데 숫자를 보세요. 표준이 19만 7천 개, 나머지 변종은 전부 합쳐 서른 개 남짓입니다. 비율로 99.99%. 이 모델은 혼돈을 막지 못합니다. 대신 혼돈이 통계적으로 무의미해지게 만들어요. 그 쏠림을 만드는 장치가 넷입니다.
① 대부분의 사람은 태그를 타이핑하지 않습니다. 이게 제일 큽니다. osm.org에서 "편집"을 누르면 뜨는 iD 에디터는 "전기차 충전소"라는 항목을 목록에서 고르게 하고, 뒤에서 알아서 amenity=charging_station을 붙여 줍니다. 손으로 직접 치는 소수만 오타를 낼 수 있어요. 변종이 하나같이 1개씩인 이유가 이겁니다.
② 비표준 태그를 쓰면 본인이 손해입니다. charging-station으로 넣으면 지도에 아무것도 안 그려집니다. 렌더러가 모르는 값이니까요. 검색도 안 되고 내비 앱도 못 찾습니다. 애써 찍었는데 안 보이니 결국 고치게 돼요.
③ taginfo가 사실상의 심판입니다. 새 태그를 쓰기 전에 다들 taginfo에서 "이거 보통 뭐라고 쓰지?"를 찾아봅니다. 많이 쓰인 게 표준이 되고, 표준이라서 더 많이 쓰이는 자기강화 루프죠.
④ 위키 제안·투표가 프리셋으로 이어집니다. 새 개념은 위키에 제안을 올려 논의·투표를 거치고, 통과하면 문서화된 뒤 에디터 프리셋에 들어갑니다. 그럼 ①로 돌아가 자동으로 퍼져요.
정리하면 이 설계의 정체는 이렇습니다.
제약을 데이터베이스가 아니라 생태계가 건다. DB는 뭐든 받아 주지만, 표준을 쓰지 않으면 아무에게도 안 보이니 결국 표준을 쓰게 된다.
그래도 안 풀린 싸움은 있다
정직하게 말하면 수렴이 일어나지 않은 곳도 있습니다. 대표적인 게 보행로예요.
highway=footway 3,225만
highway=path 1,639만
둘 다 "사람이 걷는 길"인데 경계가 모호해서 십 년 넘게 논쟁 중입니다. 나라마다 관습도 갈려요. 여기선 승자가 안 나왔고, 그래서 보행로를 다루는 쿼리는 둘 다 잡아야 합니다.
💡 그래서 실무 규칙 하나. OSM 쿼리를 짜기 전엔 taginfo에서 그 태그의 실제 사용량을 먼저 확인하세요. 위키 문서에 뭐라고 쓰여 있든, 데이터에 실제로 많이 들어 있는 값이 진짜 표준입니다. 같은 이유로 한국에서 잘 돌던 쿼리가 다른 나라 데이터에선 안 맞을 수 있어요. 나라마다 태깅 관습이 다르거든요.
왜 이 '부족한' 모델이 20년을 버텼나
자, 여기까지 보면 OSM 스키마는 좀 모자라 보입니다. 폴리곤 타입도 없고, 스키마도 없고, 면인지 선인지 태그를 봐야 알고. 왜 이렇게 만들었을까요?
답은 이겁니다. OSM은 "데이터를 저장하는 포맷"이 아니라 "수백만 명이 함께 고치는 시스템"으로 설계됐기 때문입니다.
이 관점으로 다시 보면 전부 말이 됩니다.
① 경계를 복제하지 않고 공유한다.
GeoJSON으로 강남구와 서초구를 그린다고 해 봅시다. 두 구가 맞닿은 경계선의 좌표가 양쪽 폴리곤에 각각 복사됩니다. 한쪽을 수정하면 다른 쪽과 어긋나 틈이 벌어지죠.
OSM은 다릅니다. 두 구가 같은 Way를, 그 Way는 같은 Node를 공유합니다. 노드 하나를 옮기면 양쪽 경계가 동시에 움직여요. 틈이 생길 수가 없습니다. 지난 편의 TopoJSON이 "경계를 공유해서 용량을 줄인다"고 했던 그 아이디어를, OSM은 데이터 모델 자체에 박아 넣은 겁니다.
② 새로운 개념이 나와도 마이그레이션이 없다.
스키마가 고정된 DB에서 "전기차 충전소"를 새로 다루려면 컬럼을 추가하고 마이그레이션을 돌려야 합니다. OSM은요? 그냥 amenity=charging_station이라고 쓰기 시작하면 됩니다. 앞서 봤듯 초반엔 표기가 갈리지만, 위키 합의와 에디터 프리셋을 거치며 하나로 수렴하죠. 20년간 세상에 없던 것들이 계속 생겨났는데도 스키마를 한 번도 갈아엎지 않은 이유가 이겁니다.
③ 수정의 단위가 아주 작다.
편집이 "노드 하나 이동" 단위로 쪼개집니다. 지구 반대편 두 사람이 동시에 작업해도 충돌할 일이 거의 없어요. 폴리곤을 통째로 주고받는 모델이었다면 100만 명의 동시 편집은 불가능했을 겁니다.
대가는 분명합니다. 쓰는 쪽은 편했지만, 읽는 쪽이 고생합니다. 지도를 그리려면 Way가 참조하는 Node를 전부 찾아 좌표를 조립하고, 태그를 보고 면인지 판단하고, Relation을 풀어 구멍을 뚫어야 하죠. 그래서 osm2pgsql·osmium 같은 변환 도구가 존재합니다. OSM의 모델을 우리에게 익숙한 폴리곤 세상으로 번역해 주는 게 그들의 일이에요.
손으로 만져보기 — Overpass로 서울 편의점 세기
이론은 충분합니다. 진짜로 확인해 봅시다.
Overpass API는 OSM 데이터베이스에 읽기 전용 질문을 던지는 서비스입니다. 전체를 내려받지 않고 "서울에 있는 편의점만" 같은 조건으로 뽑아올 수 있어요. 문법(Overpass QL)은 처음엔 낯설지만, 필요한 건 네 조각뿐입니다.
[out:json][timeout:90]; ① 설정: JSON으로, 90초까지
nwr["shop"="convenience"] ② 무엇을: 태그가 이런 것
(37.42,126.76,37.70,127.18); ③ 어디서: 이 사각형 안 (남,서,북,동)
out count; ④ 어떻게: 개수만
②의 nwr 이 핵심입니다. node·way·relation 셋 다 찾으라는 뜻이에요. 왜 셋 다 뒤져야 하는지는 결과가 말해 줍니다.
돌려 보죠. 별도 도구 없이 curl 한 줄이면 됩니다. 위 코드블록의 ①②③④ 는 설명용 주석이니 빼고, 세 줄만 보냅니다.
curl -s https://overpass-api.de/api/interpreter \
--data-urlencode 'data=[out:json][timeout:90];
nwr["shop"="convenience"](37.42,126.76,37.70,127.18);
out count;'
-d 가 아니라 --data-urlencode 인 게 중요합니다. 쿼리에 [ ] " ; ( ) 와 줄바꿈이 잔뜩 들어 있어서 URL 인코딩이 필요한데, 이 옵션이 알아서 처리해 줘요. data= 까지가 필드 이름이고 그 뒤 전체가 인코딩 대상입니다.
💡 Windows PowerShell이라면 두 가지를 바꿔야 합니다.
curl은Invoke-WebRequest의 별칭이라 이 옵션을 모르니curl.exe로 부르고, 여러 줄 쿼리는 here-string에 담으세요.powershell$q = @' [out:json][timeout:90]; nwr["shop"="convenience"](37.42,126.76,37.70,127.18); out count; '@ curl.exe -s https://overpass-api.de/api/interpreter --data-urlencode "data=$q"
결과입니다(2026년 8월 7일 측정 — OSM은 매일 바뀌니 여러분이 실행하면 숫자가 조금 다를 겁니다).
{
"type": "count",
"tags": {
"total": "7100",
"nodes": "6986",
"ways": "114",
"relations": "0"
}
}
서울 편의점 7,100개. 그런데 자세히 보세요. 6,986개는 Node인데, 114개는 Way입니다.
같은 편의점인데 왜 어떤 건 점이고 어떤 건 면일까요? 그 114개 중 하나를 직접 열어 봤습니다.
way 187181257
shop=convenience
building=yes ← 여기!
name=사자가 군것질할 때
name:en=Convenience Store at the Academic Information Center & Library
building=yes. 답이 나왔네요. 누군가 건물 외곽선을 그리고, 그 폴리곤에 shop=convenience를 같이 붙인 겁니다. "이 건물이 곧 편의점"이라는 뜻이죠. 확인한 Way 3개가 전부 그랬습니다.
이게 오늘 배운 모든 게 한 화면에 모인 장면입니다. OSM에는 "편의점 레이어"도 "포인트 테이블"도 없어요. 어떤 매퍼는 점을 찍었고 어떤 매퍼는 건물을 그렸을 뿐이며, 둘 다 똑같이 유효합니다.
💡 그래서 이런 함정이 생깁니다. 무심코
node["shop"="convenience"]라고만 쓰면 114개가 조용히 사라집니다. 에러도 경고도 없어요. 그냥 결과가 조금 적을 뿐이라 알아채기도 어렵습니다. OSM에 질의할 땐nwr을 기본으로 두세요.
면의 중심점만 필요하다면 출력을 바꾸면 됩니다.
[out:json][timeout:90];
nwr["shop"="convenience"](37.42,126.76,37.70,127.18);
out center;
out center;는 Way와 Relation에 대해 중심 좌표를 계산해서 함께 돌려줍니다. 점이든 면이든 똑같이 마커로 찍고 싶을 때 쓰는 방법이에요. 참고로 out center 5; 처럼 뒤에 숫자를 붙이면 5개만 받습니다. 7천 개를 전부 받으면 응답이 수 MB라, 처음 둘러볼 땐 개수를 제한하는 편이 낫습니다.
⚠️ JSON 대신 HTML 에러가 돌아온다면 쿼리를 의심하기 전에 이 문구부터 찾아보세요.
textError: runtime error: ... The server is probably too busy to handle your request.문법 오류가 아니라 공개 서버 과부하입니다. 무료로 열어 둔 자원이라 자주 붐벼요. 잠시 후 재시도하거나, 엔드포인트 주소만 미러로 바꾸면 됩니다 —
https://overpass.kumi.systems/api/interpreter또는https://overpass.private.coffee/api/interpreter.
💡 사각형 대신 행정구역으로 자르고 싶다면
area를 씁니다. 다만 이름으로 지역을 먼저 찾아야 해서 눈에 띄게 느려집니다.textarea["name"="서울특별시"]["admin_level"="4"]->.seoul; nwr["shop"="convenience"](area.seoul);
명령줄이 부담스럽다면 Overpass Turbo(overpass-turbo.eu)를 열어 보세요. 브라우저에서 쿼리를 쓰고 실행하면 결과가 바로 지도 위에 그려집니다. 태그를 이것저것 바꿔 보며 OSM에 뭐가 들어 있는지 감을 잡기에 가장 빠른 길이에요.
💡 참고로 OSM 데이터는 ODbL 라이선스입니다(2012년 9월 12일 전환). 무료로 쓸 수 있지만 출처를 표시해야 하고, OSM 데이터를 가공해 배포할 땐 같은 조건으로 공개해야 하는 조항이 있어요. 지도를 서비스에 올릴 계획이라면 한 번은 읽어 볼 값어치가 있습니다.
정리 — 한 문장으로
OSM은 지구 전체를 Node·Way·Relation 딱 셋으로 그린다. 폴리곤 타입이 따로 없어서 면인지 선인지는 태그가 결정하는데, 이 '부족함'은 실수가 아니라 수백만 명이 경계를 공유하며 동시에 편집하기 위한 설계다.
| OSM | GeoJSON | |
|---|---|---|
| 타입 | Node · Way · Relation | Point · LineString · Polygon … |
| 면 표현 | 태그로 선언 (area=yes 등) | Polygon 타입 |
| 좌표 | Node만 가진다 | 각 도형이 직접 가진다 |
| 맞닿은 경계 | Node를 공유 | 각자 복제 |
| 속성 | 자유 태그 (키 113,052종) | properties 자유 JSON |
| 최적화 대상 | 협업 편집 | 교환·전송 |
세 타입과 태그, 그리고 "면은 태그가 결정한다"는 규칙 — 이 넷만 쥐고 있으면 앞으로 만날 OSM 기반 도구들이 훨씬 순해집니다. 뒤에서 다룰 pgRouting은 Way와 Node를 그대로 그래프의 간선·정점으로 쓰고, Lanelet2(자율주행 HD맵)는 아예 OSM XML 위에 차선 개념을 얹은 포맷이에요.
그런데 아직 현실적인 문제가 남았습니다. Overpass는 편의점 몇천 개엔 좋지만, 한국 전체 도로망처럼 큰 걸 통째로 원한다면요? 107억 개 노드를 매번 API로 긁을 순 없잖아요.
다음 편에서 그 답을 봅니다. .osm.pbf — 한국 전체 지도를 파일 하나에 담는 포맷입니다. Protocol Buffer 위에 "이전 점과의 차이만 기록하는" 영리한 트릭이 얹혀 있는데, FlatGeobuf·GeoParquet 때처럼 바이트 단위로 뜯어보고, osmium으로 원하는 지역만 잘라내 손에 쥐어 보겠습니다.
참고
- https://wiki.openstreetmap.org/wiki/Elements
- https://wiki.openstreetmap.org/wiki/Node
- https://wiki.openstreetmap.org/wiki/Way
- https://wiki.openstreetmap.org/wiki/Relation
- https://wiki.openstreetmap.org/wiki/Relation:multipolygon
- https://wiki.openstreetmap.org/wiki/Area
- https://wiki.openstreetmap.org/wiki/Tags
- https://wiki.openstreetmap.org/wiki/API_v0.6
- https://wiki.openstreetmap.org/wiki/History_of_OpenStreetMap
- https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL
- https://taginfo.openstreetmap.org/
- https://planet.openstreetmap.org/statistics/data_stats.html
- https://overpass-turbo.eu/
- https://www.openstreetmap.org/copyright