2

I have a map in OpenLayers with TileWMS source that's not very reliable. At almost every full page load there are a few tiles that fail to load with status 503 - Service Unavailable. When I try to load failed tile a bit later 'manually', it loads without a problem. That lead me to the idea of reloading failed tile if first try is not successful.

I couldn't find any OpenLayers 'official' way of doing that, so I tried to achieve that using tileLoadFunction, where I initiate reload of the tile with 2 seconds delay and before reload check if tile was already loaded successfully. Reload is triggered by first assigning dummy image source for tile and then the original one. Successful loading of the tile is checked by hooking on tileloadend event and setting custom flag evt.tile._loaded = true.

Here is the relevant code:

proj4.defs('EPSG:3912',
           '+proj=tmerc +lat_0=0 +lon_0=15 +k=0.9999 +x_0=500000 +y_0=-5000000 +ellps=bessel +towgs84=682,-203,480,0,0,0,0 +units=m +no_defs');
var proj3912 = ol.proj.get('EPSG:3912');
proj3912.setExtent([374371.84, 30513.32, 624119.18, 195517.48]);

var tileGridResolutions = [1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 0.5];

var dofLayer = new ol.layer.Tile({
  source: new ol.source.TileWMS({
    url: 'https://prostor4.gov.si/ows2-m-pub/wms',
    attributions: ['<a href="http://www.gu.gov.si/">GURS</a>'],
    params: {
      'LAYERS': 'SI.GURS.ZPDZ:DOF050_D48',
      'FORMAT': 'image/png',
      'VERSION': '1.3.0'
    },
    tileGrid: new ol.tilegrid.TileGrid({
      extent: proj3912.getExtent(),
      resolutions: tileGridResolutions,
      tileSize: 256
    }),
    tileLoadFunction: tileLoadFunction,
    projection: proj3912
  }),
  title: 'Ortofoto',
  type: 'base'
});

function tileLoadFunction(imageTile, src) {
  imageTile.getImage().src = src;
  if (typeof imageTile._retry == 'undefined') {
    imageTile._retry = 0;
    imageTile._loaded = false;
  }
  if (imageTile._retry < 3) {
    imageTile._retry++;
    setTimeout(function() {
      if (!imageTile._loaded) {
        imageTile.getImage().src = 'img/empty.png';
        tileLoadFunction(imageTile, src);
      }
    }, 2000);
  }
}

dofLayer.getSource().on('tileloadend', function(evt) {
  evt.tile._loaded = true;
});

Unluckily this method works only in old venerable IE11, but not in Firefox and Chrome. There is no error, it just seems like image is not transferred from image element to canvas. Since I work in ES5 JS, I cannot debug OpenLayers internals to see where the problem is.

Is there any way to make the above method work in Firefox and Chrome or some other approach to try reloading failed TileWMS tile?

1

You would get more control loading via xhr as in https://openlayers.org/en/main/apidoc/module-ol_Tile.html#~LoadFunction and scheduling a retry from the the xhr's error/timeout handler.

No error handling should be needed once you have an object url, but to avoid memory leaks

tile.getImage().src = URL.createObjectURL(data);

should be replaced by

var url = URL.createObjectURL(data);
tile.getImage().onload = function(){
  URL.revokeObjectURL(url);
}
tile.getImage().src = url;

UPDATE

This sample code using a source very prone to errors is giving some success at a 2nd attempt after 5 seconds, while some tiles still fail.

  var map = new ol.Map({
    target: 'map',
    layers: [
      new ol.layer.Tile({
        source: new ol.source.OSM({
          url: 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png',
          maxZoom: 17,
          tileLoadFunction: function (tile, src) {
            function attemptLoad(attempt) {
              var xhr = new XMLHttpRequest();
              xhr.responseType = 'blob';
              xhr.addEventListener('load', function (evt) {
                var data = this.response;
                if (data !== undefined) {
                  var url = URL.createObjectURL(data);
                  if (attempt > 1) {
                    console.log('success at 2nd attempt ' + src);
                  }
                  tile.getImage().addEventListener('load', function () {
                    URL.revokeObjectURL(url);
                  });
                  tile.getImage().src = url;
                } else {
                  console.log('no data ' + src);
                  tile.setState(3);
                }
              });
              xhr.addEventListener('error', function () {
                if (attempt > 1) {
                  console.log('fail at 2nd attempt ' + src);
                  tile.setState(3);
                } else {
                  setTimeout(function () {
                    console.log('retry ' + src);
                    attemptLoad(attempt + 1);
                  }, 5000);
                }
              });
              xhr.open('GET', src);
              xhr.send();
            }
            attemptLoad(1);
          }
        })
      })
    ],
    view: new ol.View({
      center: [0, 0],
      zoom: 2
    })
  });
  • Thanks! I was getting close with your response to question gis.stackexchange.com/questions/364005/…, but failed to do 'mental jump' that .src can also be final content, not just url. – TomazicM Jul 18 '20 at 17:56
  • Now I bumped into famous CORS error nad have no idea of how to get rid of it. – TomazicM Jul 18 '20 at 17:57
  • You cannot make cross-origin xhr requests unless the server allows it (with a direct image load it works but you get tainted canvas). To get around it you would need a proxy on your server or localhost. – Mike Jul 18 '20 at 18:15
  • Yes, I solved CORS problem with a simple local .php proxy page I have just for this purpose: $content = file_get_contents($_GET["site"]); echo $content; – TomazicM Jul 18 '20 at 18:22
  • Unluckily this does not help in reloading failed tiles either. Normal first loading works, there is indeed greater control over error processing, but reload of failed tiles does not work. – TomazicM Jul 18 '20 at 19:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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