Accueil / Blog / Métier / 2013 / Elegant overlapping with photographic layers in Leaflet

Elegant overlapping with photographic layers in Leaflet

Par Eric Brehault publié 30/10/2013
How to display a secondary layer behind a photographic layer outside the covered area.

The problem

When we convert aerial photography into tiles in order to display it with Leaflet, the result might be frustating because the borders are usally quite ugly:

Ugly bounds

Ideally, we would like to keep the photo inside the covered area, and display another layer outside instead of those big black and white blocks.

Using PNG would allow us to use transparency, but unfortunately, our tiles are in JPEG format because JPEG are much smaller (about 5x) than PNG for photos.

Our solution

Here is the solution we have implemented to solve this problem.

Process the borders

First, we need to convert each JPG tile intersecting the border into a nicely cropped PNG. A small Python script can do that:

  • we use landez to read the tiles of all the zoom levels,
  • we use Shapely to check if its bounding box intersects the border,
  • if it does, we open the image with PIL, and we clean up all the pixels belonging to the black or white blocks,
  • and we store them as PNG (with transparency) instead of the original JPG.

Fallback to PNG

Our tile url scheme is similar to 'http://{s}.tiles.vuduciel.loire-atlantique.fr/ortho-2012/{z}/{x}/{y}.jpg'
So Leaflet will just try to load .jpg images.
How to make sure Leaflet will load the PNG tile when the expected JPG is missing?
It can be done quite easily by overloading L.Tilelayer:
  L.FallbackTileLayer = L.TileLayer.extend({

    _tileOnError: function () {
      var layer = this._layer;

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

      var newUrl;
      if(this.src.indexOf(".jpg")>0) {
          newUrl = this.src.replace("jpg", "png");
      } else {
        newUrl = layer.options.errorTileUrl;
      }
      if (newUrl) {
        this.src = newUrl;
      }

      layer._tileLoaded();
      
    }
  });
Basically, when Leaflet will try to load a missing .jpg, it will call the _tileOnError method. Everytime it happens, we just replace the file extension to .png.
So now we will load .jpg everywhere (which is good for network performances) and .png on the borders so we can benefit from the transparency and display a secondary layer behind our main photo layer.
And here is what we get:
Cropped borders

Avoid loading hidden tiles

When the user is displaying an area which is not intersecting the border, Leaflet will load all the secondary layer tile but they are hidden by the photo tiles.

How to avoid that easily?

We know that if we are loading PNG instead of JPEG, it means we are near the border. So, we can set a simple flag in the _tileOnError method and use this flag to decide if we need to load the secondary layer or not:

  L.FallbackTileLayer = L.TileLayer.extend({

    _tileOnError: function () {
      var layer = this._layer;

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

      var newUrl;
      if(this.src.indexOf(".jpg")>0) {
        layer._limit = true;
        newUrl = this.src.replace("jpg", "png");
      } else {
        newUrl = layer.options.errorTileUrl;
      }
      if (newUrl) {
        this.src = newUrl;
      }

      layer._tileLoaded();
      
    },

    reachLimit: function() {
       return this._limit;
    }
  });

var main = new L.FallbackTileLayer('http://{s}.tiles.vuduciel.loire-atlantique.fr/ortho-2012/{z}/{x}/{y}.jpg').addTo(map);
main.on('loading', function() {
    main._limit = false;
});
main.on('load', function() {
    // do not display external layers if not near limit
    if(main.reachLimit()) {
        map.addLayer(secondary);
    } else {
        map.removeLayer(secondary);
    }
});

Real example

You can see a real example of this approach here:

http://vuduciel.loire-atlantique.fr/#18/47.50319/-2.44619

http://vuduciel.loire-atlantique.fr/#12/47.5101/-2.2688

ABONNEZ-VOUS À LA NEWSLETTER !
Voir aussi
Comment mettre en place Angular Universal Comment mettre en place Angular Universal 29/06/2017

Toutes les étapes détaillées et expliquées. Les pièges à éviter.

How to setup Angular Universal How to setup Angular Universal 29/06/2017

Step by step explanation. All the pitfalls to avoid.

SEO : indexing a JavaScript application SEO : indexing a JavaScript application 29/06/2017

How to produce a server-side rendered version of your web application.

SEO : indexer une application Javascript SEO : indexer une application Javascript 29/06/2017

Comment utiliser le code de votre application pour un rendu server-side.

Découverte de React Native Découverte de React Native 18/04/2016

Présentation de React Native, quelles sont ses possibilités et peut-on l'utiliser en prod ?