3

Let's say I have a:

var myTileLayer = L.tileLayer('https://{s}.tile.SomeMapServer.com/{z}/{x}/{y}.png');

and if some tiles fail to load, I want them to be loaded from

'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'

I have tried to achieve this using 'errorTileUrl', but that probably doesn't take "{}" placeholders.

var myTileLayer = L.tileLayer('https://{s}.tile.SomeMapServer.com/{z}/{x}/{y}.png',
{errorTileUrl: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'});
1

Leaflet has no built in functionality to specify alternative tile layer url to be used in case of tile loading error from primary url.

A look at L.GridLayer and L.TileLayer objects internals shows that individual tiles are loaded asynchronously by specifying HTML element source for each tile. To specify general alternative url to be used in the case of individual tile load error would require extensive modification of L.GridLayer and L.TileLayer objects.

There is one not very elegant, but simple solution. In the case of tile load error, internal error handling fuction _tileOnError is called to display possible error tile, specified with errorTileUrl option. One of the input parameters for this function is tile, which contains final url for the requested tile. This url can be (with some tweaking) transformed to secondary url tile, which is then used to set src attribute of tile HMTL element to secondary url. Flag (with arbitrary name) has to be set to check if also secondatry url failed.

In the example below primary url is http://{s}.tile.osm.org/.png and secondary url to be used in the case of error http://{s}.tile.openstreetmap.org/.png. L.TileLayer object is then modified by extension, where _tileOnError method is modified as described above.

When transforming original tile url to secondary url, fact that {s} is evaluated to single character has to be taken into account.

var url1p1 = 'http://{s}';
var url1p2 = '.tile.osm.org/';
var url1p3 = '.png';
var url1 = url1p1 + url1p2 + '{z}/{x}/{y}' + url1p3;

var url2p1 = 'http://{s}';
var url2p2 = '.tile.openstreetmap.org/';
var url2p3 = '.png';

L.MyTileLayer = L.TileLayer.extend({
  _tileOnError: function (done, tile, e) {
    var tileSrc = tile.src.substring(0, (url1p1.length - 2));
    tileSrc += url2p2;
    tileSrc += tile.src.substring((url1p1.length + url1p2.length - 2), (tile.src.length - url1p3.length)); 
    tileSrc += url2p3;
    if (!tile._tileError) {
      tile.src = tileSrc;
      tile._tileError = true;
      }
    else {
      tile._tileError = false;
    }
    done(e, tile);
  }
});

L.myTileLayer = function(templateUrl, options) {
  return new L.MyTileLayer(templateUrl, options);
}

var map = L.map('map').setView([37.8, -96], 4);

L.myTileLayer(url1, {maxZoom: 18}).addTo(map);

Simple test of this code is to specify invalid first url. In this case secondary url has to be used for all tiles.

Edit: After going through docs again, there is much simpler and more elegant solution. When tile load error happens, Leaflet fires tileerror event. In case of event handling function, event parameter has properties tile and coords, which can be used to fire tile load from secondary url.

Code can be much cleaner in this case. In the example below subdomain for secondary url is randomly selected from range abc.

var url1 = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var url2 = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png';
var url2s = 'abc';

var map = L.map('map').setView([37.8, -96], 4);

var myLayer = L.tileLayer(url1, {maxZoom: 18}).addTo(map);

function handleTileError(evt) {
  if (evt.tile._hasError) return;

  var si = Math.floor((Math.random() * 3));
  var tileSrc = url2.replace(/{s}/g, url2s.substring(si, si + 1));
  tileSrc = tileSrc.replace(/{x}/g, evt.coords.x);
  tileSrc = tileSrc.replace(/{y}/g, evt.coords.y);
  tileSrc = tileSrc.replace(/{z}/g, evt.coords.z);
  evt.tile._hasError = true;
  evt.tile.src = tileSrc;
};

myLayer.on('tileerror', handleTileError);
  • Cool! While not elegant, luckily simple enough for me to understand and make it work. Thanks! – SofaGuy Jan 17 at 12:27

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.