3

I'm trying to load tiles from different subdomains depending on my current viewZoom level (i. e. all possible zoom steps). The resolutions for each step are calculated from the user's screen size. They might have identical gridZoom but different URLs.

viewZoom zoom   subdomain
0        1.0    subdomain1
1        1.2    subdomain1
2        1.4    subdomain2
3        1.6    subdomain3

For simplicity, I'm giving the actual zoom values for each viewZoom step instead of the resolutions. Therefore, gridZoom = Math.round(zoom). This is the {z} value that is loaded from my tile source.

I've tried to calculate the URL from which the tiles are loaded depending on the current viewZoom level using tileUrlFunction:

var tilesource = new XYZ({
// ...
    tileUrlFunction: function(tileCoord) {
      var z = tileCoord[0];
      var x = tileCoord[1];
      var y = tileCoord[2];
      var viewZoom = map.getView().getZoom();

      if(viewZoom <= 1) {
       path = 'subdomain1';
      } else if (viewZoom <= 2) {
       path = 'subdomain2';
      } else {
       path = 'subdomain3';
      }

      return 'http://mytileserver.net/' + path + '/' + z + '/' + x + '/' + y + '.png';
  }
}

With this approach, sometimes tiles from previous subdomains will be shown, probably due to caching. I've tried to force a reload whenever the subdomain changes:

map.getView().on('change:resolution', refreshMap);

function refreshMap() {
  var newZoom = map.getView().getZoom();

  var old_subdomain = getSubdomain(currZoom);
  var new_subdomain = getSubdomain(newZoom);

  if(old_subdomain != new_subdomain) {
    tilesource.refresh();
    tilesource.setTileUrlFunction(tilesource.getTileUrlFunction());
  }
  currZoom = newZoom;
}

In this case, no tiles from wrong subdomains will be loaded. However, I'm experiencing several issues with this approach:

  • If I zoom out, in some cases, there won't be any tiles at all for some areas. I guess, it's the same problem with caching again.
  • The switch between subdomains is very abrupt. Unlike "normal" zoom operations, old tiles are deleted before new ones are loaded. I would rather like to have a smooth transition with old tiles to be removed only when the new tiles are ready.

If I'm using refresh on every viewZoom change, the first problem will be gone. However, in that case I'm losing all the beneficial effects of caching and the second problem becomes even more severe, occuring on any zoom operation.

I hope there is a solution for smooth loading of tiles from different sources depending on the viewZoom.

3
  • You assume that problems are "probably due to caching". Since tile source url is different, that can't be so. I would say problems comes from timing when zoom is happening. In your tile url function your are using map.getView().getZoom() to get current view zoom. At a certain point during zoom execution this changes, but you cannot be sure when this happens.
    – TomazicM
    Dec 15 '19 at 11:40
  • @TomazicM: I was assuming this because I saw tiles from the old source when the switch should have already occured. I'm now trying to get the behaviour I'm hoping for with setUrl. However, I don't get the smooth behaviour that is shown here. I'm using a custom XYZ source with a custom projection (non-geographic). I wonder if that might actually be the problem? Dec 16 '19 at 13:35
  • Example you are referring to does not change source on zoom change event, so there is no problem with timing.
    – TomazicM
    Dec 16 '19 at 13:43
2

You could try setting fake tile sizes which give extra zoom levels in the tile grid to match the view resolutions

This example simply multiplies by 1.2, 1.4 and 1.6 and rounds

new TileGrid({
  resolutions: [1000, 256*1000/307, 256*1000/359, 256*1000/410, 500, 250,
  tileSizes:   [ 256, 307,          359,          410,          256, 256,
  ....
});

    tileUrlFunction: function(tileCoord) {
      var z = tileCoord[0];
      var x = tileCoord[1];
      var y = tileCoord[2];

      if(z <= 1) {
       path = 'subdomain1';
      } else if (viewZoom <= 2) {
       path = 'subdomain2';
      } else {
       path = 'subdomain3';
      }
      z = Math.max(0, z-3);

      return 'http://mytileserver.net/' + path + '/' + z + '/' + x + '/' + y + '.png';
  }

Here is a more refined working example https://codesandbox.io/s/simple-bg7d4

2
  • If you link your code example from the GitHub issue in your answer, I would gladly mark it as accepted. Jan 5 '20 at 12:08
  • I was at first a bit sceptical about this approach but it works very nicely (as can be seen in the codebox example). For anyone interested, there's more information on this in the GitHub issue linked above. Jan 8 '20 at 16:39
0

I've found a solution which is a bit better than my first approach. I'm using several layers with overlapping minZoom and maxZoom. On overlapping zoom levels, opacity and zIndex are changed programmatically to bring the right layer into the foreground (see suggestion here). This does always load the correct tiles and when they're loaded fast enough, transitions are smooth.

// Tile layers for different subdomains
var tilelayer1 = new TileLayer({
      name: 'layer1',
      source: new XYZ({
        // ...
        url: 'http://myurl/subdomain1/{z}/{x}/{y}.png'
      }),
      maxZoom: 2,   // overlapping with tilelayer2 at viewZoom == 2
                    // fade out at viewZoom == 2

      opacity: 1,   // initially visible (start layer) and in foreground
      zIndex: 2,
      preload: Infinity
});

var tilelayer2 = new TileLayer({
      name: 'layer2',
      source: new XYZ({
        // ...
        url: 'http://myurl/subdomain2/{z}/{x}/{y}.png'
      }),
      minZoom: 1,   // preload at viewZoom == 1
                    // make visible at viewZoom == 2
      maxZoom: 3,   // fade out at viewZoom == 3

      opacity: 0,   // initially invisible and in background
      zIndex: 1,

      preload: Infinity    // preload even earlier??
});

var tilelayer3 = new TileLayer({
      name: 'layer3',
      source: new XYZ({
        // ...
        url: 'http://myurl/subdomain3/{z}/{x}/{y}.png'
      }),
      minZoom: 2
      opacity: 0,
      zIndex: 1,
      preload: Infinity
});

// Layer group containing all the tile layers
var layerGroup = new LayerGroup({
  layers: [
    tilelayer1,
    tilelayer2,
    tilelayer3
  ]
});

var currZoom = map.getView().getZoom();
map.on('moveend', layerTransition);

// For overlapping zoom ranges, fade in layer of the new subdomain and make old layer invisible
// using my own functions
function layerTransition() {
  var newZoom = map.getView().getZoom();

// if zoom has changed  
if (currZoom != newZoom) {

    // Get entire layerGroup and layer corresponding to current viewZoom
     var layerGroup = custom_getLayerGroup();
     var selectedLayer = custom_getSelectedLayer(newZoom);

    // change zIndex of selected layer to 2, change zIndex of other layers to 1
     custom_bringSelectedLayerToFront(selectedLayer);

    // gradually change opacity for the selected layer from 0 to 1
    // when timer has ended, change opacity for all other layers to 0
     custom_fadeLayers(selectedLayer, layerGroup, duration, steps)

   currZoom = newZoom;
  }
}

However, this does not really replicate the behaviour of the tileUrlFunction which I wish to achieve.

  • Preload for different subdomains works only where minZoom/maxZoom levels overlap.
  • If gridZoom is different between different subdomains (which might be the case), preload comes at the cost at loading two layers instead of one, even though only one is visible at a time. I'd rather show the old tiles resized until the new tiles are loaded (i. e. the default zoom behaviour).
  • If new tiles aren't loaded fast enough or the users is zooming too fast (fast increment by more than one), old tiles might still be removed before new tiles are loaded.

I'd be happy if anyone has got a better solution.

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.