1

The leaflet documentation provides an insightful tutorial to work with non-geographical image, but it is based on imageOverlay.

const imageSize = {
  width: 2315,
  height: 2315,
}
const maxZoom = 12
const minZoom = 8
const toLatLng = (x, y) => L.CRS.Simple.pointToLatLng(new L.Point(x, y), maxZoom);
const bounds = [
  toLatLng(0, 0),
  toLatLng(imageSize.width, imageSize.height),
];

var viewer = L.map('viewer', {
  crs: L.CRS.Simple,
  maxBounds: bounds,
  minZoom,
  maxZoom,
  zoomSnap: 0,
}).fitBounds(bounds);
L.imageOverlay('https://leafletjs.com/examples/crs-simple/uqm_map_full.png', bounds).addTo(viewer);
#viewer {
  width: 100vw;
  height: 100vh;
  background: none;
}
<link href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" rel="stylesheet"/>
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
<div id="viewer"/>

How can we transpose this example with tileLayer to display a tiled image based on Deep Zoom Image for instance.

srjjio
  • 930
  • 1
  • 8
  • 16
  • https://leafletjs.com/plugins.html#non-map-base-layers – IvanSanchez Dec 20 '22 at 18:57
  • Thank you @IvanSanchez for sharing the link. Note that most plugins don't seem to be actively maintained. Also, I think it's worth demonstrating that a little extra code is enough to make it work with vanilla Leaflet. – srjjio Dec 20 '22 at 22:25

1 Answers1

1

Here an example using the Duomo dataset from OpenSeadragon.

const source = {
  "Image": {
    "xmlns": "http://schemas.microsoft.com/deepzoom/2008",
    "Url": "https://openseadragon.github.io/example-images/duomo/duomo_files",
    "Format": "jpg",
    "Overlap": 1,
    "TileSize": 254,
    "Size": {
      "Width":  13920,
      "Height": 10200,
    },
  },
};

const maxZoom = Math.ceil(Math.log2(Math.max(source.Image.Size.Width, source.Image.Size.Height)));
const minZoom = Math.ceil(Math.log2(source.Image.TileSize + 2 * source.Image.Overlap));
const toLatLng = (x, y) => L.CRS.Simple.pointToLatLng(new L.Point(x, y), maxZoom);
const bounds = [
  toLatLng(0, 0),
  toLatLng(source.Image.Size.Width, source.Image.Size.Height),
];

const viewer = L.map("viewer", {
  crs: L.CRS.Simple,
  maxBounds: bounds,
  minZoom,
  maxZoom,
  zoomSnap: 0,
}).fitBounds(bounds);
L.tileLayer(`${source.Image.Url}/{z}/{x}_{y}.${source.Image.Format}`, {
  noWrap: true,  // do not query tiles outside the bounds
  bounds: bounds,
}).addTo(viewer);
#viewer {
  width: 100vw;
  height: 100vh;
  background: none;
}

img.leaflet-tile {
  height: auto !important;
  width: auto !important;
}
<link href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" rel="stylesheet"/>
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
<div id="viewer"/>

According to Deep Zoom Image documentation, large scale images are represented by a pyramid. Each resolution of the pyramid is equivalent to a level. Levels are counted from the 1x1 pixel resolution as level 0, then resolution is doubled for each following level. Therefore we can compute the resolution from the level: resolution = 2 ^ level and infer the level required to display a specific resolution: level = ceil(log2(resolution)).

In our example, we use the full image resolution to compute the maximum zoom level and the tile resolution to compute the minimum zoom level.

For the edges of the image, Deep Zoom Image returns non square tiles with a different size. This is why we need to override leaflet-tile class properties to prevent leaflet from distorting the image.

Note: the Duomo dataset seems to contain non-edge tiles with invalid size (255px instead of 256px), this is why a white line could appear for some zoom levels. But we don't expect this to happen for a dataset with only valid tile sizes.

srjjio
  • 930
  • 1
  • 8
  • 16