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
How to create an Angular library How to create an Angular library 08/02/2017

Having a light and clean distribution AND running tests on Travis, showing demo on Github Pages, ...

Elections.js génère des cartes pour vous Elections.js génère des cartes pour vous 30/03/2015

Le travail sur les cartes pour les élections départementales a débouché sur la création d'un ...

Bien gérer le passage au développement front end Bien gérer le passage au développement front end 21/02/2017

Adaptation des équipes à de nouveaux enjeux.

Faire une boucle for dans un template Angular 2 Faire une boucle for dans un template Angular 2 11/07/2016

Quand on n'a pas d'itérable sous la main

Retour sur le petit déjeuner "Quel framework JS pour 2017 ?" Retour sur le petit déjeuner "Quel framework JS pour 2017 ?" 09/12/2016

Si vous n'avez pas pu assister à notre session toulousaine ou à notre session nantaise, voici la ...