I currently have an interactive map with multiple layers based on the WMS protocol, which has been working fairly well. I use GeoServer to serve the layers and OpenLayers for the front-end. My problem has been that the tiles have been loading very slowly for large data-sets. I decided to use GWC, which is built into GeoServer, to seed the tiles to improve performance. All my layers are in EPSG:27700 and it turns out this CRS does not play nicely with GWC as I found out in my previous question, How to use geowebcache seeded tiles with a custom projection (EPSG: 27700) in openlayers.

I decided to use WMTS, which has solved the problem I was having in the previous question, however I have noticed that there is no GetFeatureInfo call with WMTS as with WMS. I have found some mention of using the WMTS REST API to get feature information, however I am having a lot of trouble finding a good example of how this is done.

Does anyone know of a good resource or have a code example to help with my problem?

  • 1
    could you not use a plain WMS getFeatureInfo request?
    – Ian Turton
    Commented May 24, 2018 at 12:30

2 Answers 2


Is it possible to do a GetFeatureInfo request using WMTS?

It depends on what is supported by your service implementation, but in theory it is possible, by which I mean the OpenGIS® Web Map Tile Service Implementation Standard (OGC 07-057r7) defines such an operation:

7.3.2 GetFeatureInfo operation (optional in procedure oriented architectural style)

The GetFeatureInfo operation in procedure oriented architectural style allows WMTS clients to request information at a particular position of a particular tile for a particular queryable layer. A layer is queryable if the Contents section of the ServiceMetadata document specifies one or more InfoFormats for this layer.

NOTE 1 This criterion is different from the one used in WMS. In WMTS, the queryable property of WMS has been substituted by the presence or absence of an InfoFormat element in the ServiceMetadata document.

The GetFeatureInfo operation is designed to provide clients of a WMTS with more information about features rendered in a previously returned tile. The canonical use case for GetFeatureInfo is that a user chooses a pixel (I,J) on a particular tile at which the user would like to obtain more information. Because the WMTS protocol is stateless, the GetFeatureInfo request indicates to the WMTS server what tile the user is viewing by including the original GetTile request parameters but modifying the request value to 'GetFeatureInfo' and adding the pixel offset parameters. From the spatial context information (TileRow, TileCol and TileMatrixSet), along with the I,J position the user requested, the WMTS can return additional information about that position. The other GetTile parameters (e.g., Style) may play a role in the server's decision as to what information to return.


If your server only supports the OGC Web Map Tile Service (WMTS) Simple Profile (13-082r2) then I think there is no operation defined.


OGC Web Map Tile Service (WMTS) Simple Profile

OpenGIS Web Map Tile Service Implementation Standard


So I ended up going Ian Turton's suggestion, which is to use a plain WMS getfeatureinfo request and overlay it on the WMTS layer, here is the code for anyone needing a similar solution:

<!DOCTYPE html>
    <title>WMTS Layer with WMS getfeatureinfo</title>
    <link rel="stylesheet" href="https://openlayers.org/en/v4.6.5/css/ol.css" type="text/css">
    <!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
    <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
    <script src="https://openlayers.org/en/v4.6.5/build/ol.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.4.4/proj4.js"></script>
    <div id="map" class="map"></div>
    <div id="info">&nbsp;</div>
      proj4.defs('EPSG:27700','+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs');
      const proj27700 = ol.proj.get('EPSG:27700');
      const center_point = [394108.6719, 331807.5317];

      var wmsSource = new ol.source.TileWMS({
        url: 'http://localhost:8080/geoserver/ui/wms',
        params: {'LAYERS': 'Flood 3'},
        serverType: 'geoserver',
        crossOrigin: 'anonymous'

      var parser = new ol.format.WMTSCapabilities();
      var map;

      fetch('http://localhost:8080/geoserver/ui/gwc/service/wmts?request=getCapabilities').then(function(response) {
        return response.text();
      }).then(function(text) {
        var result = parser.read(text);
        var options = ol.source.WMTS.optionsFromCapabilities(result, {
          layer: 'Flood 3',
          matrixSet: 'EPSG:27700',
          crossOrigin: 'anonymous'

        var layers = [
          new ol.layer.Tile({
            opacity: 1,
            source: new ol.source.XYZ({
              attributions: 'Tiles © <a href="https://services.arcgisonline.com/ArcGIS/' +
              'rest/services/World_Imagery/MapServer">ArcGIS</a> 2018',
            url: 'https://server.arcgisonline.com/ArcGIS/rest/services/' +
            crossOrigin: 'anonymous'
          new ol.layer.Tile({
            opacity: 1,
            source: new ol.source.WMTS((options))

        var map = new ol.Map({
          projection: proj27700,
          layers: layers,
          target: 'map',
          view: new ol.View({
            projection: proj27700,
            center: center_point,
            zoom: 6

        map.on('singleclick', function(evt) {
          document.getElementById('info').innerHTML = '';
          var viewResolution = /** @type {number} */ (map.getView().getResolution());
          var url = wmsSource.getGetFeatureInfoUrl(
              evt.coordinate, viewResolution, 'EPSG:27700',
              {'INFO_FORMAT': 'text/html'});
          if (url) {
            document.getElementById('info').innerHTML =
                '<iframe seamless src="' + url + '"></iframe>';

        map.on('pointermove', function(evt) {
          if (evt.dragging) {
          var pixel = map.getEventPixel(evt.originalEvent);
          var hit = map.forEachLayerAtPixel(pixel, function() {
            return true;
          map.getTargetElement().style.cursor = hit ? 'pointer' : '';

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.