question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

_globalTileRange is not recalculated on getTileUrl()

See original GitHub issue

How to reproduce

  • Leaflet version I’m using: 1.3.1
  • Browser (with version) I’m using: Chrome 65
  • OS/Platform (with version) I’m using: macOS High Sierra
  • Create simple tile TMS layer
  • Run getTileUrl() for different zoom levels

What behaviour I’m expecting and which behaviour I’m seeing

Expecting: correct tile URLs

Observed behaviour: invalid tile URLs

Reason: please consider following piece of code from Leaflet with comment:

getTileUrl: function (coords) {
    ...
    if (this._map && !this._map.options.crs.infinite) {

        // Following line of code is invalid - the _globalTileRange is updated only when
        // _setView() is called, which does not happen when calling getTileUrl()
        // for different tile layers
	var invertedY = this._globalTileRange.max.y - coords.y;

	if (this.options.tms) {
	    data['y'] = invertedY;
        }
	data['-y'] = invertedY;
    }

    return template(this._url, extend(data, this.options));
}

The bug happens only for TMS layers, as only TMS layers go into the if (this.options.tms) { } condition. The problem is that URLs of new tiles with zoom level, that is different from the current one (like in example, we are going from zoomMin=10 to zoomMax=12), are not calculated correctly because of the irrelevant _globalTileRange.

The _globalTileRange needs to be somehow updated if the coords.z parameter of the getTileUrl() is different from the internal value of zoom, I think.

Minimal example reproducing the issue

https://jsbin.com/payowah/edit?html,console,output

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:7 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
sashukcommented, Jul 9, 2018

@ghybs Thank you very much for the reply!

Indeed, using the L.Util.template() (assuming that coordinates are already known) really solved the issue! Final code:

/**
 * Generates all URL for fetching the underlying tile set for specified boundary
 * 
 * @param {*} map Leaflet map instance
 * @param {*} bounds bounds object
 * @param {*} tileLayer tile layer
 * @param {*} minZoom minimum map zoom
 * @param {*} maxZoom maximum map zoom
 */
getTileUrls (map, bounds, tileLayer, minZoom, maxZoom) {
    if (!tileLayer) throw new Error('Tile layer is undefined');
    let urls = [];

    console.log(`Getting all tiles for specified boundary from ${minZoom} to ${maxZoom} zoom`);
    for (let localZoom = minZoom; localZoom <= maxZoom; localZoom++) {
        let min = map.project(bounds.getNorthWest(), localZoom).divideBy(256).floor();
        let max = map.project(bounds.getSouthEast(), localZoom).divideBy(256).floor();
        const max_y = (Math.pow(2, localZoom) - 1);

        for (let i = min.x; i <= max.x; i++) {
            for (let j = min.y; j <= max.y; j++) {
                let coords = new L.Point(i, j);
                coords.z = localZoom;

                // Check if layer is WMS or TMS, tileLayer.options.tms is used as an example
                if (tileLayer.options.tms) {
                    coords.y = max_y - coords.y;
                }

                let url = L.Util.template(tileLayer._url, coords);
                urls.push(url);
            }
        }
    }

    console.log(`Total tile URLs: ${urls.length}`);
    return urls;
};
1reaction
ghybscommented, Jul 8, 2018

Hi,

Unfortunately, the getTileUrl method, while its name might be a bit misleading, is not meant to be used externally to generate URL from arbitrary coords input. As stated in the docs (emphasis mine):

Called only internally, returns the URL for a tile given its coordinates. Classes extending TileLayer can override this function to provide custom tile URL naming schemes.

As you noticed, the z (zoom) property is discarded and only the current Tile Layer zoom (_tileZoom) is used instead. I guess it is so because of the _globalTileRange that is also dependent on zoom. While this scheme may be discussed, I do not have the history behind it to be able to determine whether this is for a reason or if it is unnecessarily complicated.

But in your case, you really do not need to use getTileUrl at all: given that you already know the coords of the tiles you want to fetch, you only need the y reversal to fit your TMS scheme, then apply those coordinates to your URL template:

function myGetTileUrl(coords) {
  // Determine the max Y for the given zoom. You might want to cache this value.
  var max_y_for_z = Math.pow(2, coords.z) - 1;
  // Now reverse the y coordinate.
  coords.y = max_y_for_z - coords.y;
  return L.Util.template(urlTemplate, coords);
}

Updated JSBin: https://jsbin.com/faditemita/1/edit?html,console,output

Read more comments on GitHub >

github_iconTop Results From Across the Web

No results found

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found