blog.itcode.devblog.itcode.dev

A Guide for Developers Traveling Through OpenLayers - 19. Attaching a Popup to WMS

When you click a marker or object displayed on the map, a popup shows detailed information about that object. In this chapter, we'll display a popup on a WFS map to show a marker's detailed information.

A Guide for Developers Traveling Through OpenLayers - 19. Attaching a Popup to WMS

When you click a marker or object displayed on the map, a popup shows detailed information about that object. In this chapter, we'll display a popup on a WFS map to show a marker's detailed information.
RWB0104
@RWBwritten at 2022-05-28 11:55:23
A Guide for Developers Traveling Through OpenLayers

시리즈 모아보기

A Guide for Developers Traveling Through OpenLayers

19 / 23

Can a popup be shown for an image-based map like WMS, rather than an object-based map like WFS?

On the surface, it seems impossible. In the case of WFS, since the script itself holds the spatial information, we were able to make use of it appropriately to display the desired information. But in the case of WMS, the foundation itself is an image, so its usefulness as analyzable data is very limited.

In other words, a computer can't directly know which objects this single image contains, or how many.

So is it impossible for a WMS map to show a popup?




Something worth considering here is a protocol called GetFeatureInfo. GetFeatureInfo provides information about the Features at a position calculated from the current map's BBOX, the clicked coordinate, and the pixel value on the map.

In other words, by clicking on the map image, we can find out whether a Feature exists and extract that Feature's information. We can use this to implement a popup.


We use the WMS map covered in Chapter 1 as-is.

OpenLayers can display any HTML tag on top of the map through the Overlay object. Although OpenLayers handles the behavior, it isn't an object rendered onto the canvas; it outputs an actual DOM element.

By default, Overlay follows the position clicked on the map. In other words, if you display an Overlay at a specific position on the map and then move the map, the position isn't fixed — it follows the marker. Handling this kind of behavior directly in the DOM would require quite cumbersome handling. Thanks to this characteristic, it's a very good fit for popups.



TXT

GET https://example.com/geoserver/wms?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetFeatureInfo&FORMAT=image%2Fpng&TRANSPARENT=true&QUERY_LAYERS=test:building&layers=buld_sejong&exceptions=application%2Fjson&INFO_FORMAT=application%2Fjson&I=221&J=178&WIDTH=256&HEIGHT=256&CRS=EPSG%3A3857&STYLES=&BBOX=14169590.555392835%2C4366694.551875548%2C14169896.303505976%2C4367000.299988689
ParameterExampleRequireDescription
serviceWMS (fixed)YService name
version1.3.0 (fixed), 1.1.1, 1.1.0, 1.0.0YVersion
requestGetFeatureInfo (fixed)YRequest name
layersrepo_name:layer_nameYLayer name (separate multiple with commas)
stylesstyle1Style name to apply (meaningless for GetFeatureInfo)
crs(or srs)EPSG:4326Reference coordinate system (if empty, uses the layer's default CRS)
bboxxmin,ymin,xmax,ymaxx_{min},y_{min},x_{max},y_{max}YImage extent coordinates
width256YImage width
height256YImage height
query_layersrepo_name:layer_nameYAdditional target layer name (separate multiple with commas)
info_formatapplication/vnd.ogc.se_xml (default)Response format
feature_count1 (default)Maximum number of objects to retrieve
x(or i)225YPixel x value on the map
y(or j)156YPixel y value on the map
exceptionsapplication/vnd.ogc.se_xml (default)Exception response format

GetFeatureInfo, like GetImage, requires quite a lot of parameters. Since GetFeatureInfo itself is an API that returns the marker information at the clicked location, you must provide the clicked position on the map in the form of x, y. In many ways it has a fair number of tricky parameters.

Fortunately, just like with GetImage, OpenLayers provides an object that generates the GetFeatureInfo URL, so you can use it to call it easily. This method is described in the section on handling the Overlay's events. For now, just know that it can be called this way as a plain URL.



Let's create the Overlay object directly.

ParameterTypeDefaultDescription
idnumber | string | undefinedOverlay ID
Used when calling the ol/Map-Map#getOverlayById method
elementHTMLElement | undefinedThe target Element for the overlay
offsetArray<number>[ 0, 0 ]Offset (px) for the overlay's display
[ x, y ], where x is horizontal and y is vertical.
Larger values move it right and down, respectively.
positionol/events/condition-Condition | undefinedDisplay position of the overlay
positioningol/OverlayPositioningtop-leftPlacement anchor of the overlay
Values such as bottom-left, top-right, etc.
stopEventbooleantrueWhether to stop event propagation
If true, placed in a DOM with the class ol-overlay container-stopevent
If false, placed in a DOM with the class specified in the className property
insertFirstbooleantrueWhether to insert the overlay first or append it to the element
autoPanol/Overlay-PanIntoViewOptions | booleanfalseWhen setPosition is called on the overlay, automatically pans the map so the overlay is fully visible
autoPanAnimationol/Overlay-PanOptionsAnimation settings for the pan triggered by autoPan.
Ignored if the autoPan setting is not an object but a boolean
autoPanMarginnumber20Margin between the overlay and the map edge during the pan triggered by autoPan
Ignored if the autoPan setting is not an object but a boolean
autoPanOptionsol/Overlay-PanIntoViewOptions | undefinedOptions for autoPan
Takes precedence over autoPanAnimation and autoPanMargin
classNamestringol-overlay-container ol-selectableCSS class value

The way to create one is as follows.

HTML

<div id="map-popup"></div>

Enter the DOM element to use for the popup. Use the id or class attribute appropriately to identify the DOM. The position of the tag within the HTML code doesn't matter much.

TYPESCRIPT

import { Overlay } from 'ol';

const popup = document.getElementById('map-popup') as HTMLElement | null;

const overlay = new Overlay({
	id: 'popup',
	element: popup || undefined,
	positioning: 'center-center',
	autoPan: {
		animation: {
			duration: 250
		}
	}
});

Once you assign the tag and configure it appropriately, you can create the Overlay object.

The complete information for Overlay can be found in the official documentation.



Let's apply the created Overlay to the Map.

TYPESCRIPT

import { Map } from 'ol';

const map = new Map({
	// ...
	overlays: [ overlay ]
	// ...
});

You can apply it as shown above. You can register Overlays in the form of an array.

TYPESCRIPT

// Add an overlay
map.addOverlay();

// Return an overlay by ID
map.getOverlayById();

// Return the list of overlays
map.getOverlays();

// Remove an overlay
map.removeOverlay();

The related methods are as above. They can be called on the created Map object.



From here on, the approach differs a bit between WFS and WMS. In the case of WFS, we could access the GetFeature information directly in the script and immediately show the Feature's information.

However, as mentioned repeatedly, WMS's GetImage renders and returns an image based on the spatial information, so there is a limit to how directly it can show Feature information.

By using GetFeatureInfo on click, we can retrieve the Feature data at the clicked location, so let's make use of that.

TYPESCRIPT

import ImageLayer from 'ol/layer/Image';

const layer = new TileLayer({
	source: source,
	minZoom: 15,
	properties: { name: 'wms' },
	zIndex: 5
});

Assume the WMS layer has been declared as above.

TSX

map.on('singleclick', (e) =>
{
	// Extract the layer whose WMS properties name is wms
	const wmsLayer = map.getAllLayers().filter(layer => layer.get('name') === 'wms')[0];

	// Call the WMS layer's Source
	const source: TileWMS | ImageWMS = wmsLayer.getSource();

	// Generate the GetFeatureInfo URL
	const url = source.getFeatureInfoUrl(e.coordinate, map.getView().getResolution() || 0, 'EPSG:3857', {
		QUERY_LAYERS: 'test:building',
		INFO_FORMAT: 'application/json'
	});

	// If the GetFeatureInfo URL is valid
	if (url)
	{
		const request = await fetch(url.toString(), { method: 'GET' }).catch(e => alert(e.message));

		// If the response is valid
		if (request)
		{
			// If the response is OK
			if (request.ok)
			{
				const json = await request.json();

				// If there are no objects
				if (json.features.length === 0)
				{
					overlay.setPosition(undefined);
				}

				// If there are objects
				else
				{
					// Create a Feature from the GeoJSON
					const feature = new GeoJSON().readFeature(json.features[0]);

					// Create a VectorSource with the created Feature
					const vector = new VectorSource({ features: [ feature ] });

					setPopupState(
						<ul>
							<li>{feature.getId() || ''}</li>
							<li>{feature.get('buld_nm') || <span>No name</span>}</li>
							<li>{feature.get('bul_man_no')}</li>
						</ul>
					);

					overlay.setPosition(getCenter(vector.getExtent()));
				}
			}

			// Otherwise
			else
			{
				alert(request.status);
			}
		}
	}
});

We make appropriate use of the click event as shown above.

  1. On click, call the WMS layer.
  2. Call the Source object from the WMS layer.
  3. Generate the GetFeatureInfo URL through the Source object's getFeatureInfoUrl method.
  4. Call GetFeatureInfo on GeoServer.
  5. Build a Feature from the response's GeoJSON and create a VectorSource object.
  6. Retrieve and call the desired data from the created Feature object.
  7. Calculate the data's actual position via vector.getExtent() and set it as the Overlay's position.

The logic proceeds as described above. Of course this is just one example of usage, so you can describe whatever behavior you want in the event to perform various actions.




You can check an example implementing this at OpenLayers6 Sandbox - WMS Popup.

# GIS# GeoServer# OpenLayers# WMS
ship
blog.itcode.dev

Notes from the π-th Alpaca

7.0.1
Developed by RWB since 2021.057th upgraded at 2026.08