4

Background: I've produced a 1-terapixel rendering of the Mandelbrot Set and am using LeafletJS to zoom and pan around in it interactively. It works great. But since the Mandelbrot Set is symmetric along the real axis, I'm currently using twice as many tile images as necessary.

Question: How can I hook into LeafletJS's display-time code (using some callback?) so that whenever a tile is loaded via HTTP, it either passes through unchanged or is flipped vertically? This would allow me to reduce the data by many tens of gigabytes on higher zoom levels.

Example: Here are four tiles from zoom level 1 (shown here separated by one pixel). I'd like to throw away the bottom two tile images and load them instead as vertically-flipped versions of the top two tiles. Can this be done on-the-fly with LeafletJS?

Zoom Level 1 tiles

More concretely: If I know zoom level z and tile coordinates x,y, I'd like to flip the tile vertically at load-time whenever y is less than 2^(z–1). For instance, at zoom level z=10, I'd like to flip the tiles vertically for all y < 512.

I imagine the answer is going to involve something like setting the transform, -moz-transform, -o-transform, and -webkit-transform properties of the <img> tag to scaleY(-1) and maybe filter and -ms-filter to FlipV, but I don't know where/how to define these in a LeafletJS context.

Todd Lehman
  • 2,880
  • 1
  • 26
  • 32

1 Answers1

2

You would just need to modify the y number of bottom tiles in L.TileLayer._loadTile method, before it gets applied on the image URL.

As for flipping the image itself, unfortunately we cannot use classes because a transform property is already applied by Leaflet directly on the tiles (images), so it overrides any transform in class. Then we have to append any transform, -moz-transform etc. on the tile.style.

L.HalfTileLayer = L.TileLayer.extend({
    _loadTile: function (tile, tilePoint) {

        tile._layer  = this;
        tile.onload  = this._tileOnLoad;
        tile.onerror = this._tileOnError;

        this._adjustTilePoint(tilePoint);

        //////////////////
        var limit = Math.pow(2, tilePoint.z - 1),
            y = tilePoint.y;

        if (y >= limit) { // modify for bottom tiles, i.e. higher y
            tilePoint.y = 2 * limit - y - 1; // y starts at 0
            tile.style.transform += " scaleY(-1)"; // append
            // apply more transforms for cross-browser
        }
        /////////////////

        tile.src     = this.getTileUrl(tilePoint);

        this.fire('tileloadstart', {
            tile: tile,
            url: tile.src
        });
    }
});

(new L.HalfTileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png')).addTo(map);

Demo: http://jsfiddle.net/ve2huzxw/73/

Note that in the default configuration, y = 0 is the top, y = 2^z - 1 is the bottom.

ghybs
  • 47,565
  • 6
  • 74
  • 99
  • Most excellent. Although this feels like a hack (I guess it is, as you've pointed out), it does seem to work beautifully in your example. I'm going to incorporate this into my code later tonight and then very likely mark this as the accepted answer. I wonder if it will play well with `unloadInvisibleTiles` and `reuseTiles`? (If not, I'll have to add an assertion that those aren't set.) – Todd Lehman Dec 01 '15 at 23:36
  • And for this, I'll also set `tms` to `false` in my `TileLayer` options. Thanks for pointing that out. I can't remember why I set it to `true` in the first place; I can't think of a compelling reason to stray from the defaults for the tile numbering. – Todd Lehman Dec 01 '15 at 23:40
  • You could keep `tms` at true if desired (to avoid having to rename your tiles for example). You would simply adapt the condition and computation of new y value. – ghybs Dec 02 '15 at 02:12