6
votes

Im using leaflet to create a photo map, with my own tiles, which works as expected.

Im trying to work out how I can prevent the zoom from following this Quadtree type pattern:

  • Zoom Level 0 - Entire map width = 256px;
  • Zoom Level 1 - Entire map width = 512px;
  • Zoom Level 2 - Entire map width = 1024px;
  • And so on...

I would like to be able to zoom in say increments of 25% or 100px.

An example of 100px increments:

  • Zoom Level 0 - Entire map width = 200px;
  • Zoom Level 1 - Entire map width = 300px;
  • Zoom Level 2 - Entire map width = 400px;
  • And so on...

Question: What is the logic for doing this? If it is at all possible?


My reason for wanting to do this is so that my photo map (which doesnt wrap like a normal map) can be more responsive and fit the users screen size nicely.

I made a demonstration of my issue which can be seen here

1

1 Answers

8
votes

The short answer is that you can only show zoom levels for which you have pre-rendered tiles. Leaflet won't create intermediary zoom levels for you.

The long answer is that in order to use do this, you need to define your own CRS scale method and pass it to your map, for example:

L.CRS.CustomZoom = L.extend({}, L.CRS.Simple, {
    scale: function (zoom) {
        // This method should return the tile grid size
        // (which is always square) for a specific zoom
        // We want 0 = 200px = 2 tiles @ 100x100px,
        // 1 = 300px = 3 tiles @ 100x100px, etc.
        // Ie.: (200 + zoom*100)/100 => 2 + zoom

        return 2 + zoom;
    }
});

var map = L.map('map', { crs: L.CRS.CustomZoom }).setView([0, 0], 0);

In this example, I've extended L.CRS.Simple, but you can of course extend any CRS from the API you'd like, or even create your own from scratch.

Using a zoom factor which results in a map pixel size that is not a multiple of your tilesize, means your right/bottom edge tiles will only be partially filled with map data. This can be fixed by making the non-map part of such tiles 100% transparent (or same the colour as your background).

However, it is, in my opinion, a much better idea to set the tilesize to match the lowest common denominator, in this case 100px. Remember to reflect this by using the tileSize option in your tile layer. And, of course, you will need to re-render your image into 100x100 pixels tiles instead of the 256x256 tiles you are using currently.

One caveat, the current version of LeafletJS (0.5) has a bug that prevents a custom scale() method from working, due to the TileLayer class being hardcoded to use power-of-2 zoom scaling. However, the change you need to do is minor and hopefully this will be addressed in a future release of Leaflet. Simply change TileLayer._getWrapTileNum() from:

_getWrapTileNum: function () {
    // TODO refactor, limit is not valid for non-standard projections
    return Math.pow(2, this._getZoomForUrl());
},

To:

_getWrapTileNum: function () {
    return this._map.options.crs.scale(this._getZoomForUrl());
},