blog.itcode.devblog.itcode.dev

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.

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.
RWB0104
@RWBwritten at 2022-03-21 14:51:45
A Guide for Developers Traveling Through OpenLayers

시리즈 모아보기

A Guide for Developers Traveling Through OpenLayers

11 / 23

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}
NameDescriptionValue
keyIssued API Key
layerRequested map typeBase, gray, midnight, Hybrid, Satellite
tileMatrixMap level6 ~ 18 (gray, midnight) / 6 ~ 19 (Base, Hybrid, Satellite)
tileRowy coordinate
tileColx coordinate
tileTypeExtensionjpeg (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.

NameTypeDefaultDescription
attributionsol/source/Source-AttributionLike | undefinedAttribution text (bottom-right of map)
attributionsCollapsiblebooleantrueWhether attribution text is collapsible
cacheSizenumber | undefinedTile cache size
crossOriginstring | nullanonymousCORS attribute
imageSmoothingbooleantrueDeprecated attribute. Whether to use interpolation
interpolatebooleantrueWhether to use interpolation
opaquebooleantrueWhether it is opaque
projectionol/proj-ProjectionLikeEPSG:3857Coordinate system
reprojectionErrorThresholdnumber0.5Maximum reprojection error in pixels (0 ~ 1)
maxZoomnumber42Maximum zoom level. No data is shown beyond the specified zoom level
minZoomnumber0Minimum zoom level. No data is shown below the specified zoom level
maxResolutionnumber | undefined0Resolution at zoom level 0 (not applied if tileGrid is set)
tileGridol/tilegrid/TileGrid-TileGrid | undefined0Tile grid
tileLoadFunctionol/Tile-LoadFunction | undefinedTile load function
tilePixelRationumber1Tile pixel ratio (if the value is 2, the tile pixel size is 512x512)
tileSizenumber | ol/size-Size[ 256, 256 ]Tile size (not applied if tileGrid is set)
tileUrlFunctionol/Tile-UrlFunction | undefinedURL return function
urlstring | undefinedURL pattern. Must include {x}, {y} or {-y}, {z}
urlsArray<string> | undefinedArray of URL patterns
wrapXbooleantrueWhether to wrap horizontally
transitionnumber250Rendering output animation duration
zDirectionol/array-NearestDirectionFunction | number0Whether 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
});
NameTypeDefaultDescription
classNamestringol-layerClass name
opacitynumber1Opacity (0 ~ 1)
visiblebooleantrueWhether visible
extentol/extent-Extent | undefinedThe rendering extent of the layer. Data is not displayed beyond this range
zIndexnumber | undefinedPriority (higher is shown on top)
minResolutionnumber | undefinedMinimum display resolution
maxResolutionnumber | undefinedMaximum display resolution
minZoomnumber | undefinedMinimum display zoom level
maxZoomnumber | undefinedMaximum display zoom level
preloadnumber0Preload low-resolution tiles up to the specified level (0 means unused)
sourceol/source/Tile-TileSource | undefinedThe layer's source
mapol/PluggableMap-PluggableMap | undefinedUse this layer as an overlay in the specified Map object
useInterimTilesOnErrorbooleantrueWhether to use interim tiles on error
propertiesobject | undefinedArbitrary 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.

NameTypeDefaultDescription
centerol/coordinate-Coordinate | undefinedThe center of the map
constrainRotationboolean | numbertrueWhether rotation is constrained. If a number, indicates the number of allowed rotation steps (if 0: 90, 180, 270, 360)
enableRotationbooleantrueWhether rotation is enabled
extentol/extent-Extent | undefinedThe map's viewing extent. Cannot go outside the specified range
constrainOnlyCenterbooleanfalseIf true, the extent restriction applies only to the View's center, not to the whole extent
smoothExtentConstraintbooleantrueWhether the View can slightly exceed the extent range
maxResolutionnumber | undefinedMaximum viewing resolution. Cannot zoom in beyond the specified resolution.
minResolutionnumber | undefinedMinimum viewing resolution. Cannot zoom out beyond the specified resolution.
maxZoomnumber28Maximum viewing zoom level. Cannot zoom in beyond the specified zoom level.
minZoomnumber0Minimum viewing zoom level. Cannot zoom out beyond the specified zoom level.
multiWorldbooleanfalseWhether multiple worlds are used
constrainResolutionbooleanfalseWhether only integer zoom levels are allowed
smoothResolutionConstraintbooleantrueWhether to use loose zoom in/out rules
showFullExtentbooleanfalseWhether to display the entire configured extent
projectionol/proj-ProjectionLikeEPSG:3857Coordinate system
resolutionnumber | undefinedInitial resolution
resolutionsArray<number> | undefinedList of available resolutions (descending order). max/minResolution, max/minZoom, zoomFactor options are ignored
rotationnumber0Default rotation value
zoomnumber | undefinedDefault zoom level
zoomFactornumber2Zoom factor
paddingArray<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
});
NameTypeDefaultDescription
controlsol/Collection-Collection<ol/control/Control-Control> | Array<ol/control/Control-Control> | undefinedol/control/defaultsThe map's control object
pixelRationumberwindow.devicePixelRatioDevice pixel ratio
interactionsol/Collection-Collection<ol/interaction/Interaction-Interaction> | Array<ol/interaction/Interaction-Interaction> | undefined
keyboardEventTargetHTMLElement | Document | string | undefinedTarget element for keyboard events
layersArray<ol/layer/Base-BaseLayer> | ol/Collection-Collection<ol/layer/Base-BaseLayer> | ol/layer/Group-LayerGroup | undefinedList of layers. The later in the array, the higher the priority
maxTilesLoadingnumber16Maximum number of tiles that can load simultaneously
moveTolerancenumber1Minimum pixels the mouse must move to be recognized as a map move event
overlaysol/Collection-Collection<ol/Overlay-Overlay> | Array<ol/Overlay-Overlay> | undefinedThe map's overlay object
targetHTMLElement | string | undefinedThe DOM or DOM id where the map is displayed
viewol/View-View | Promise<ol/View-View> | undefinedThe 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.

# GIS# OpenLayers
ship
blog.itcode.dev

Notes from the π-th Alpaca

7.0.1
Developed by RWB since 2021.057th upgraded at 2026.08