OpenLayers를 여행하는 개발자를 위한 안내서 - 11. VWorld 맵 만들기
프로젝트
⏰ 2022-03-21 14:51:45
D O W N
D O W N
이전 장에서 OSM을 통해 지도를 표시해봤다. 하지만, OSM의 지도는 디테일함이 떨어진다는 무시할 수 없는 단점이 존재한다.
OSM은 사용하기 쉬운 세계지도라는 큰 장점이 있음에도, 저 단점 하나로 인해 국내 서비스용 지도로 사용하기 어렵다.
즉, 예제 이상의 실질적인 서비스에 사용하려면 국내 지리에 특화된 지도가 필요하다.
이 때 고려해볼만한 것이 VWorld 지도다. VWorld는 국가기관인 공간정보산업진흥원에서 제공하는 지도다. 국가기관에서 제공하는 지도인 만큼. OSM보다 국내 지리에 좀 더 특화되어 있다.
VWorld는 API 서비스를 제공하며, URL의 형태로 배경지도를 제공하므로, OpenLayers와 호환성이 좋다.
VWorld는 여러 타입의 배경지도를 제공하므로, 필요에 맞게 활용할 수 있다.
이 장에서는 OpenLayers를 통해 VWorld를 지도에 띄워본다.
API키를 발급하기 위해선 회원가입이 필요하다. 위 링크에 접속하여 회원가입을 수행하자.
[인증키] - [인증키 발급] 메뉴에서 API 발급을 신청할 수 있다. 발급은 신청 즉시 이루어진다.
요구하는 항목을 작성하고 신청하면 된다.
[브이월드 활용API]는 반드시 [WMTS/TMS API]가 체크되어야 한다.
처음 지급되는 키는 개발키로, 개발키는 3개월 간 유효하며, 최대 3번 연장 가능하다.
안정적인 서비스를 원한다면 운영키 신청을 하면 된다. 최대 10일 정도의 심사기간이 있다. 예제 페이지 같은 사이트는 발급해주지 않는 것 같다.
운영키 역시 무제한은 아니고, 1년 주기로 갱신이 필요하며, 갱신을 요청할 때마다 재심사를 거치는 것 같다.
VWorld를 요청하는 방법은 아래와 같다.
TXT
1
http://api.vworld.kr/req/wmts/1.0.0/{key}/{layer}/{tileMatrix}/{tileRow}/{tileCol}.{tileType}
Name | Description | Value |
---|---|---|
key | 발급받은 API Key | |
layer | 요청 지도 타입 | Base, gray, midnight, Hybrid, Satellite |
tileMatrix | 지도 레벨 | 6 ~ 18 (gray, midnight) / 6 ~ 19 (Base, Hybrid, Satellite) |
tileRow | y좌표 | |
tileCol | x좌표 | |
tileType | 확장자 | jpeg (Satellite) / png (other) |
자세한 정보는 🔗 VWorld WMTS/TMS API 레퍼런스에서 확인할 수 있다.
OSM의 경우, OpenLayers에서 자체적으로 OSM이라는 객체를 제공했으므로 매우 쉽게 사용이 가능했지만, VWorld의 경우 직접 소스를 구성해줄 필요가 있다.
XYZ객체를 통해 VWorld 소스를 만들 수 있다.
TYPESCRIPT
1 2 3
import XYZ from 'ol/source/XYZ'; const source = new XYZ({ url: 'https://api.vworld.kr/req/wmts/1.0.0/API_KEY/Base/{z}/{y}/{x}.png' });
위 URL은 VWorld의 기본지도를 호출하는 URL이다.
Name | Type | Default | Description |
---|---|---|---|
attributions | 🔗 ol/source/Source-AttributionLike | undefined | 기여 문구 (지도 우측 하단) | |
attributionsCollapsible | boolean | true | 기여 문구 축소 여부 |
cacheSize | number | undefined | 타일 캐시 크기 | |
crossOrigin | string | null | anonymous | CORS 속성 |
boolean | true | Deprecated 속성. 보간 사용 여부 | |
interpolate | boolean | true | 보간 사용 여부 |
opaque | boolean | true | 불투명 여부 |
projection | 🔗 ol/proj-ProjectionLike | EPSG:3857 | 좌표계 |
reprojectionErrorThreshold | number | 0.5 | 최대 재투영 오류 픽셀 (0 ~ 1) |
maxZoom | number | 42 | 최대 줌 레벨. 지정된 줌레벨을 초과할 경우 데이터 미출력 |
minZoom | number | 0 | 최소 줌 레벨. 지정된 줌레벨 보다 미만일 경우 데이터 미출력 |
maxResolution | number | undefined | 0 | 줌 레벨 0의 해상도 (tileGrid가 있을 경우 미적용) |
tileGrid | 🔗 ol/tilegrid/TileGrid-TileGrid | undefined | 0 | 타일 그리드 |
tileLoadFunction | 🔗 ol/Tile-LoadFunction | undefined | 타일 로드 함수 | |
tilePixelRatio | number | 1 | 타일 픽셀 비율 (값이 2일 경우, 타일 픽셀은 512x512) |
tileSize | number | 🔗 ol/size-Size | [ 256, 256 ] | 타일 사이즈 (tileGrid가 있을 경우 미적용) |
tileUrlFunction | 🔗 ol/Tile-UrlFunction | undefined | URL 반환 함수 | |
url | string | undefined | URL 양식. {x}, {y} 혹은 {-y}, {z}를 포함해야 함 | |
urls | Array<string> | undefined | URL 양식 배열 | |
wrapX | boolean | true | 수직 감싸기 여부 |
transition | number | 250 | 렌더링 출력 애니메이션 시간 |
zDirection | 🔗 ol/array-NearestDirectionFunction | number | 0 | 줌 레벨이 실수(ex. 12.552)일 경우 더 높은 타일을 사용할지, 낮은 타일을 사용할지 여부 |
다른 옵션은 몰라도, 자원의 위치를 제공하는 url 혹은 urls, tileUrlFunction은 반드시 명시해야한다.
url의 경우 URL에 리터럴 패턴으로 {x}, {y}, {-y}, {z} 등을 입력할 수 있는데, 현재 지도의 x, y, z좌표를 URL에 자동으로 반영해준다.
그 밖의 동적인 URL 생성이 필요하다면 tileUrlFunction을 활용하는 것이 좋다.
그 외 사용할 수 있는 옵션과 메서드의 종류는 🔗 ol/source/XYZ에서 확인하자.
여담으로, 이전 장에서 다뤘던 OSM 객체 또한 XYZ 객체를 확장하여 구현한 객체다.
XYZ Source를 담을 Layer 객체를 생성한다. 이 Layer는 할당된 XYZ Source를 통해 VWorld 지도를 표출해줄 것이다.
TYPESCRIPT
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
import TileLayer from 'ol/layer/Tile'; import XYZ from 'ol/source/XYZ'; // 기본지도 const vworldBaseLayer = new TileLayer({ source: new XYZ({ url: 'https://api.vworld.kr/req/wmts/1.0.0/2AAC4DD9-4F6F-3844-A740-E2DB6BDC8CEF/Base/{z}/{y}/{x}.png' }), properties: { name: 'base-vworld-base' }, minZoom: 5, maxZoom: 19, zIndex: 2, preload: Infinity }); // 백지도 const vworldGrayLayer = new TileLayer({ source: new XYZ({ url: 'https://api.vworld.kr/req/wmts/1.0.0/2AAC4DD9-4F6F-3844-A740-E2DB6BDC8CEF/gray/{z}/{y}/{x}.png' }), properties: { name: 'base-vworld-gray' }, minZoom: 5, maxZoom: 18, zIndex: 2, preload: Infinity }); // 야간지도 const vworldMidnightLayer = new TileLayer({ source: new XYZ({ url: 'https://api.vworld.kr/req/wmts/1.0.0/2AAC4DD9-4F6F-3844-A740-E2DB6BDC8CEF/midnight/{z}/{y}/{x}.png' }), properties: { name: 'base-vworld-midnight' }, minZoom: 5, maxZoom: 18, zIndex: 2, preload: Infinity }); // 하이브리드 지도 const vworldHybridLayer = new TileLayer({ source: new XYZ({ url: 'https://api.vworld.kr/req/wmts/1.0.0/2AAC4DD9-4F6F-3844-A740-E2DB6BDC8CEF/Hybrid/{z}/{y}/{x}.png' }), properties: { name: 'ext-vworld-hybrid' }, minZoom: 5, maxZoom: 19, zIndex: 3, preload: Infinity }); // 위성지도 const vworldSatelliteLayer = new TileLayer({ source: new XYZ({ url: 'https://api.vworld.kr/req/wmts/1.0.0/2AAC4DD9-4F6F-3844-A740-E2DB6BDC8CEF/Satellite/{z}/{y}/{x}.jpeg' }), properties: { name: 'base-vworld-satellite' }, minZoom: 5, maxZoom: 19, zIndex: 2, preload: Infinity });
Name | Type | Default | Description |
---|---|---|---|
className | string | ol-layer | 클래스명 |
opacity | number | 1 | 투명도 (0 ~ 1) |
visible | boolean | true | 표시 여부 |
extent | 🔗 ol/extent-Extent | undefined | 레이어의 렌더링 범위. 해당 범위를 넘어가면 데이터를 표시하지 않음 | |
zIndex | number | undefined | 우선 순위 (높을수록 위에 표시) | |
minResolution | number | undefined | 최소 표시 해상도 | |
maxResolution | number | undefined | 최대 표시 해상도 | |
minZoom | number | undefined | 최소 표시 줌 레벨 | |
maxZoom | number | undefined | 최대 표시 줌 레벨 | |
preload | number | 0 | 지정한 레벨까지 저해상도 타일을 미리 로드 (0은 미사용) |
source | 🔗 ol/source/Tile-TileSource | undefined | 레이어의 소스 | |
map | 🔗 ol/PluggableMap-PluggableMap | undefined | 지정한 Map 객체에서 해당 레이어를 오버레이로 사용 | |
useInterimTilesOnError | boolean | true | 오류 시 중간타일 사용 여부 |
properties | object | undefined | 임의 속성. get(), set()으로 조작 가능 |
TileLayer 객체를 통해 타일 레이어를 생성할 수 있다.
옵션의 source는 필수 옵션으로, 이 옵션을 비우면 레이어에 아무 것도 뜨지 않아 레이어의 의미가 없다.
properties는 레이어의 임의의 속성을 지정할 수 있다. 위 처럼 레이어의 고유 식별자를 할당해주면, 레이어를 관리하는 데 도움이 된다. Map 객체에서 레이어를 뽑아낼 때, 고유 식별자가 없으면 곤란하기 때문.
그 외 사용할 수 있는 옵션과 메서드의 종류는 🔗 ol/layer/Tile에서 확인하자.
TYPESCRIPT
1 2 3 4 5 6 7
import View from 'ol/View'; const view = new View({ projection: 'EPSG:3857', center: [ 14135490.777017945, 4518386.883679577 ], zoom: 17 });
[ 14135490.777017945, 4518386.883679577 ]은 EPSG:3857으로 표기한 서울시청 좌표다.
Name | Type | Default | Description |
---|---|---|---|
center | 🔗 ol/coordinate-Coordinate | undefined | 지도의 중심 | |
constrainRotation | boolean | number | true | 회전 구속 여부. 숫자일 경우 회전 가능 갯수를 의미 (0일 경우, 90, 180, 270, 360) |
enableRotation | boolean | true | 회전 가능 여부 |
extent | 🔗 ol/extent-Extent | undefined | 지도의 뷰잉 범위. 지정된 범위 밖을 벗어날 수 없음 | |
constrainOnlyCenter | boolean | false | true일 경우 extent 제한이 View 중심에만 적용되며, 전체 extent에 적용되지 않음 |
smoothExtentConstraint | boolean | true | View가 extent 범위를 약간 벗어날 수 있는지 여부 |
maxResolution | number | undefined | 최대 뷰잉 해상도. 지정 해상도 이상 확대 불가능. | |
minResolution | number | undefined | 최소 뷰잉 해상도. 지정 해상도 이상 축소 불가능. | |
maxZoom | number | 28 | 최대 뷰잉 줌 레벨. 지정 줌 레벨 이상 확대 불가능. |
minZoom | number | 0 | 최소 뷰잉 줌 레벨. 지정 줌 레벨 이상 축소 불가능. |
multiWorld | boolean | false | 다중 월드 사용 여부 |
constrainResolution | boolean | false | 줌 레벨 정수만 허용 여부 |
smoothResolutionConstraint | boolean | true | 느슨한 확대/축소 규칙 사용 여부 |
showFullExtent | boolean | false | 전체 구성된 extent 표시 여부 |
projection | 🔗 ol/proj-ProjectionLike | EPSG:3857 | 좌표계 |
resolution | number | undefined | 초기 해상도 | |
resolutions | Array<number> | undefined | 사용 가능한 해상도 목록 (내림차순) max/minResolution, max/minZoom, zoomFactor 옵션이 무시됨 | |
rotation | number | 0 | 기본 회전값 |
zoom | number | undefined | 기본 줌 레벨 | |
zoomFactor | number | 2 | 줌 배율 |
padding | Array<number> | [ 0, 0, 0, 0 ] | 패딩 |
View 객체를 통해, 지도의 뷰잉 정보를 선언할 수 있다.
smoothResolutionConstraint 옵션은, 예를 들어 지도의 크기가 width: 120px, height: 80px이라고 가정하자. 기본값 false라면, 지도는 최대 80px까지 확대가 가능하다.
그러나 true라면, 지도는 최대 120px까지 확대가 가능해진다. 즉, 지도의 확대 기준을 가장 짧은 길이로 할지, 긴 길이로 할지 지정하는 것.
모든 정보를 종합하여 지도를 만드는 Map 객체를 생성한다.
TYPESCRIPT
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
import Map from 'ol/Map'; import View from 'ol/View'; import TileLayer from 'ol/layer/Tile'; import XYZ from 'ol/source/XYZ'; // 기본지도 const vworldBaseLayer = new TileLayer({ source: new XYZ({ url: 'https://api.vworld.kr/req/wmts/1.0.0/2AAC4DD9-4F6F-3844-A740-E2DB6BDC8CEF/Base/{z}/{y}/{x}.png' }), properties: { name: 'base-vworld-base' }, minZoom: 5, maxZoom: 19, zIndex: 2, preload: Infinity }); // 하이브리드 지도 const vworldHybridLayer = new TileLayer({ source: new XYZ({ url: 'https://api.vworld.kr/req/wmts/1.0.0/2AAC4DD9-4F6F-3844-A740-E2DB6BDC8CEF/Hybrid/{z}/{y}/{x}.png' }), properties: { name: 'ext-vworld-hybrid' }, minZoom: 5, maxZoom: 19, zIndex: 3, preload: Infinity }); const view = new View({ projection: 'EPSG:3857', center: [ 14135490.777017945, 4518386.883679577 ], zoom: 17 }); const map = new Map({ layers: [ vworldBaseLayer, vworldHybridLayer ], target: 'map', view: view });
Name | Type | Default | Description |
---|---|---|---|
controls | 🔗 ol/Collection-Collection<🔗 ol/control/Control-Control> | Array<🔗 ol/control/Control-Control> | undefined | 🔗 ol/control/defaults | 지도 컨트롤 객체 |
pixelRatio | number | window.devicePixelRatio | 기기 픽셀 비율 |
interactions | 🔗 ol/Collection-Collection<🔗 ol/interaction/Interaction-Interaction> | Array<🔗 ol/interaction/Interaction-Interaction> | undefined | ||
keyboardEventTarget | HTMLElement | Document | string | undefined | 키보드 이벤트 대상 요소 | |
layers | Array<🔗 ol/layer/Base-BaseLayer> | 🔗 ol/Collection-Collection<🔗 ol/layer/Base-BaseLayer> | 🔗 ol/layer/Group-LayerGroup | undefined | 레이어 목록. 배열 뒤에 있을 수록 우선순위가 높아짐 | |
maxTilesLoading | number | 16 | 동시 로드 가능한 최대 타일 수 |
moveTolerance | number | 1 | 지도 이동 이벤트로 인식하기 위해 마우스가 움직여야할 최소 픽셀 |
overlays | 🔗 ol/Collection-Collection<🔗 ol/Overlay-Overlay> | Array<🔗 ol/Overlay-Overlay> | undefined | 지도 오버레이 객체 | |
target | HTMLElement | string | undefined | 지도를 표시할 DOM 혹은 DOM 아이디 | |
view | 🔗 ol/View-View | Promise<🔗 ol/View-View> | undefined | 지도 뷰 객체 |
Map 객체에 지금까지 선언한 객체들을 할당한다. target에 지정된 DOM에 선언된 지도가 표시된다.
target: map은 아이디가 map인 DOM에 지도를 표시한다는 뜻이다. 꼭 아이디가 아니더라도 HTMLElement를 할당할 수도 있다.
🔗 OpenLayers6 Sandbox - VWorld에서 이를 구현한 예제를 확인할 수 있다.
4가지 배경 지도와, 확장용 지도인 하이브리드 지도를 확인할 수 있다. 좌측 상단의 패널에서 배경지도를 변경하거나, 확장 지도를 On/Off 할 수 있다.
OSM에 비해 다양한 타입의 지도를 제공함과 동시에, 국내에 최적화된 지도임을 확인할 수 있다.
🏷️ Related Tag