A Guide for Developers Traveling Through OpenLayers - 11. Creating a VWorld Map
A Guide for Developers Traveling Through OpenLayers - 11. Creating a VWorld Map
In the previous chapter, we displayed a map through OSM. However, OSM's map has an undeniable drawback: its lack of detail.
Even though OSM has the great advantage of being an easy-to-use world map, that one drawback makes it difficult to use as a map for domestic Korean services.
In other words, to use it for something more substantial than an example, a map specialized for Korean geography is needed.
Something worth considering here is the VWorld map. VWorld is a map provided by the Spatial Information Industry Promotion Agency, a national institution. Since it's a map provided by a national institution, it is more specialized for Korean geography than OSM.
VWorld provides an API service, and since it provides base maps in URL form, it has good compatibility with OpenLayers.
VWorld provides several types of base maps, so you can use it as needed.
- Base map: The map we commonly use
- Blank map: A black-and-white version of the general map
- Night map: A map using a dark color scheme for higher visibility at night
- Satellite map: A photographic map
- Hybrid map: An extension map that emphasizes only roads and place names. Difficult to use on its own.
In this chapter, we'll put VWorld up on a map using OpenLayers.
You need to sign up for membership to get an API key. Visit the link above and sign up.
You can apply for an API from the [Authentication Key] - [Issue Authentication Key] menu. The key is issued immediately upon application.
Fill in the required fields and apply.
For [VWorld Utilization API], [WMTS/TMS API] must be checked.
The key issued initially is a development key, valid for 3 months, and can be extended up to 3 times.
If you want a stable service, you can apply for a production key. There's a review period of up to about 10 days. It seems that sites like example pages don't get issued one.
The production key isn't unlimited either — it needs to be renewed once a year, and it seems a re-review is performed every time a renewal is requested.
VWorld can be requested as follows.
TXT
http://api.vworld.kr/req/wmts/1.0.0/{key}/{layer}/{tileMatrix}/{tileRow}/{tileCol}.{tileType}
| Name | Description | Value |
|---|---|---|
| key | Issued API Key | |
| layer | Requested map type | Base, gray, midnight, Hybrid, Satellite |
| tileMatrix | Map level | 6 ~ 18 (gray, midnight) / 6 ~ 19 (Base, Hybrid, Satellite) |
| tileRow | y coordinate | |
| tileCol | x coordinate | |
| tileType | Extension | jpeg (Satellite) / png (other) |
For detailed information, check the VWorld WMTS/TMS API Reference.
For OSM, OpenLayers itself provided an object called OSM, so it was very easy to use, but for VWorld, you need to construct the source directly.
You can create a VWorld source through the XYZ object.
TYPESCRIPT
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' });
The above URL is a URL for calling VWorld's base map.
| Name | Type | Default | Description |
|---|---|---|---|
| attributions | ol/source/Source-AttributionLike | undefined | Attribution text (bottom-right of map) | |
| attributionsCollapsible | boolean | true | Whether attribution text is collapsible |
| cacheSize | number | undefined | Tile cache size | |
| crossOrigin | string | null | anonymous | CORS attribute |
| boolean | true | Deprecated attribute. Whether to use interpolation | |
| interpolate | boolean | true | Whether to use interpolation |
| opaque | boolean | true | Whether it is opaque |
| projection | ol/proj-ProjectionLike | EPSG:3857 | Coordinate system |
| reprojectionErrorThreshold | number | 0.5 | Maximum reprojection error in pixels (0 ~ 1) |
| maxZoom | number | 42 | Maximum zoom level. No data is shown beyond the specified zoom level |
| minZoom | number | 0 | Minimum zoom level. No data is shown below the specified zoom level |
| maxResolution | number | undefined | 0 | Resolution at zoom level 0 (not applied if tileGrid is set) |
| tileGrid | ol/tilegrid/TileGrid-TileGrid | undefined | 0 | Tile grid |
| tileLoadFunction | ol/Tile-LoadFunction | undefined | Tile load function | |
| tilePixelRatio | number | 1 | Tile pixel ratio (if the value is 2, the tile pixel size is 512x512) |
| tileSize | number | ol/size-Size | [ 256, 256 ] | Tile size (not applied if tileGrid is set) |
| tileUrlFunction | ol/Tile-UrlFunction | undefined | URL return function | |
| url | string | undefined | URL pattern. Must include {x}, {y} or {-y}, {z} | |
| urls | Array<string> | undefined | Array of URL patterns | |
| wrapX | boolean | true | Whether to wrap horizontally |
| transition | number | 250 | Rendering output animation duration |
| zDirection | ol/array-NearestDirectionFunction | number | 0 | Whether to use a higher or lower tile when the zoom level is a real number (e.g. 12.552) |
You may not need the other options, but the url or urls, tileUrlFunction that provide the location of the resource must be specified.
For url, you can enter literal patterns such as {x}, {y}, {-y}, {z} in the URL, which automatically reflect the current map's x, y, z coordinates in the URL.
For other dynamic URL generation needs, it's better to use tileUrlFunction.
For other options and methods you can use, check ol/source/XYZ.
As a side note, the OSM object covered in the previous chapter is also an object implemented by extending the XYZ object.
Create a Layer object to hold the XYZ Source. This Layer will display the VWorld map through the assigned XYZ Source.
TYPESCRIPT
import TileLayer from 'ol/layer/Tile'; import XYZ from 'ol/source/XYZ'; // Base map 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 }); // Blank map 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 }); // Night map 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 }); // Hybrid map 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 }); // Satellite map 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 | Class name |
| opacity | number | 1 | Opacity (0 ~ 1) |
| visible | boolean | true | Whether visible |
| extent | ol/extent-Extent | undefined | The rendering extent of the layer. Data is not displayed beyond this range | |
| zIndex | number | undefined | Priority (higher is shown on top) | |
| minResolution | number | undefined | Minimum display resolution | |
| maxResolution | number | undefined | Maximum display resolution | |
| minZoom | number | undefined | Minimum display zoom level | |
| maxZoom | number | undefined | Maximum display zoom level | |
| preload | number | 0 | Preload low-resolution tiles up to the specified level (0 means unused) |
| source | ol/source/Tile-TileSource | undefined | The layer's source | |
| map | ol/PluggableMap-PluggableMap | undefined | Use this layer as an overlay in the specified Map object | |
| useInterimTilesOnError | boolean | true | Whether to use interim tiles on error |
| properties | object | undefined | Arbitrary attributes. Can be manipulated with get(), set() |
You can create a tile layer via the TileLayer object.
The source option is required; if this option is left empty, nothing appears on the layer, making the layer meaningless.
properties allows you to specify arbitrary attributes of the layer. Assigning a unique identifier to the layer as above helps with managing the layer, since it becomes troublesome to extract a layer from the Map object without a unique identifier.
For other options and methods you can use, check ol/layer/Tile.
TYPESCRIPT
import View from 'ol/View'; const view = new View({ projection: 'EPSG:3857', center: [ 14135490.777017945, 4518386.883679577 ], zoom: 17 });
[ 14135490.777017945, 4518386.883679577 ] is the coordinates of Seoul City Hall expressed in EPSG:3857.
| Name | Type | Default | Description |
|---|---|---|---|
| center | ol/coordinate-Coordinate | undefined | The center of the map | |
| constrainRotation | boolean | number | true | Whether rotation is constrained. If a number, indicates the number of allowed rotation steps (if 0: 90, 180, 270, 360) |
| enableRotation | boolean | true | Whether rotation is enabled |
| extent | ol/extent-Extent | undefined | The map's viewing extent. Cannot go outside the specified range | |
| constrainOnlyCenter | boolean | false | If true, the extent restriction applies only to the View's center, not to the whole extent |
| smoothExtentConstraint | boolean | true | Whether the View can slightly exceed the extent range |
| maxResolution | number | undefined | Maximum viewing resolution. Cannot zoom in beyond the specified resolution. | |
| minResolution | number | undefined | Minimum viewing resolution. Cannot zoom out beyond the specified resolution. | |
| maxZoom | number | 28 | Maximum viewing zoom level. Cannot zoom in beyond the specified zoom level. |
| minZoom | number | 0 | Minimum viewing zoom level. Cannot zoom out beyond the specified zoom level. |
| multiWorld | boolean | false | Whether multiple worlds are used |
| constrainResolution | boolean | false | Whether only integer zoom levels are allowed |
| smoothResolutionConstraint | boolean | true | Whether to use loose zoom in/out rules |
| showFullExtent | boolean | false | Whether to display the entire configured extent |
| projection | ol/proj-ProjectionLike | EPSG:3857 | Coordinate system |
| resolution | number | undefined | Initial resolution | |
| resolutions | Array<number> | undefined | List of available resolutions (descending order). max/minResolution, max/minZoom, zoomFactor options are ignored | |
| rotation | number | 0 | Default rotation value |
| zoom | number | undefined | Default zoom level | |
| zoomFactor | number | 2 | Zoom factor |
| padding | Array<number> | [ 0, 0, 0, 0 ] | Padding |
You can declare the map's viewing information through the View object.
For the smoothResolutionConstraint option, for example, let's assume the map's size is width: 120px, height: 80px. With the default value false, the map can zoom in up to 80px at most.
However, if true, the map can zoom in up to 120px at most. In other words, this specifies whether the zoom criterion of the map is based on the shortest length or the longest length.
Create the Map object that assembles all the information to build a map.
TYPESCRIPT
import Map from 'ol/Map'; import View from 'ol/View'; import TileLayer from 'ol/layer/Tile'; import XYZ from 'ol/source/XYZ'; // Base map 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 }); // Hybrid map 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 | The map's control object |
| pixelRatio | number | window.devicePixelRatio | Device pixel ratio |
| interactions | ol/Collection-Collection<ol/interaction/Interaction-Interaction> | Array<ol/interaction/Interaction-Interaction> | undefined | ||
| keyboardEventTarget | HTMLElement | Document | string | undefined | Target element for keyboard events | |
| layers | Array<ol/layer/Base-BaseLayer> | ol/Collection-Collection<ol/layer/Base-BaseLayer> | ol/layer/Group-LayerGroup | undefined | List of layers. The later in the array, the higher the priority | |
| maxTilesLoading | number | 16 | Maximum number of tiles that can load simultaneously |
| moveTolerance | number | 1 | Minimum pixels the mouse must move to be recognized as a map move event |
| overlays | ol/Collection-Collection<ol/Overlay-Overlay> | Array<ol/Overlay-Overlay> | undefined | The map's overlay object | |
| target | HTMLElement | string | undefined | The DOM or DOM id where the map is displayed | |
| view | ol/View-View | Promise<ol/View-View> | undefined | The map's view object |
Assign the objects declared so far to the Map object. The declared map is displayed in the DOM specified in target.
target: map means the map is displayed in the DOM whose id is map. It doesn't have to be an id — you can also assign an HTMLElement.
You can check an example implementing this at OpenLayers6 Sandbox - VWorld.
You can check 4 types of base maps, along with the hybrid map, which is an extension map. From the panel in the top-left, you can change the base map or toggle the extension map On/Off.
Compared to OSM, you can confirm that it provides various types of maps while also being a map optimized for Korea.
