How to make a tiled image pyramid for leaflet from a non-geographic source
Asked Answered
A

2

6

Suppose I have a non-geographic image instead of a usual map. Let's say for example an x-ray, MRI scan or microscope image and I would like to use leaflet so I could zoom-in, zoom-out and put some markers on some predetermined points.

I have read the example from Non-geographical maps but this case demonstrates the use of one single image instead of tiling. I would rather prefer tiles since my image is going to be fairly large. Is there something else that would fit a case like the one I described above please? I am looking into rastercoords but I haven't crystallized yet whether this works for any raster file of this is only for plain maps.

Artificer answered 14/7, 2018 at 20:18 Comment(2)
Please reopen the question. There is already a satisfactory answer, so the problem is clearly not perceived as "too broad". Moreover, @user894763 has an even better, basically one-command answer which they would like to post.Southwards
I had a go at editing this down to a single question, I hope it looks OK.Ruppert
S
14

Here's my experience on how to create slippy maps from sources like PDFs or high-res images or non-slippy maps. I wanted to write an article on this anyway, so let this answer be a sketch of the yet-to-be-written article.

To give you an example, here's a PDF map of European inland waterways with vector graphics and here's a slippy map of it.

Basically, the most reasonable way is to make a standard tile set and let Leaflet show it. I.e. to produce tiles sized 256x256 for each of the zoom levels.
You don't want huge images as layers as that will be to heavy for the browser. You also don't want any resizing in the browser, this will lead to poor quality.

Fortunately, creating tiles is quite easy with ImageMagick. This is how I do it.

Decide How Many Zoom Levels You Want

First, decide how many zoom levels you want. This depends on the map, from my experience you need 5-7 zoom levels at most. Let's take 5 zoom levels for example. The more levels you produce, the higher hardware requirements you will have. The approache below is probably not suitable for more that 7-8 zoom levels.

Render or Resize the Source Image

Next, render or resize your image for each of the zoom levels. You have to produce images with one of the dimensions equal to:

  • 256 pixel on level 0
  • 512 pixel on level 1
  • 1024 pixel on level 2
  • 2048 pixel on level 3
  • 4096 pixel on level 4
  • and so on.

Attention: the result of this step are huge images. Level 5 would be around 10 MB, level 6 around 20 MB, level 7 around 40 MB. Be careful try to open these images in "normal" tools.

Resizing a Normal High-Res Image

If your source is a high-res image simply use convert -resize with either x*256* or *256*x:

convert images\source.jpg -resize   x256 images\0.jpg
convert images\source.jpg -resize   x512 images\1.jpg
convert images\source.jpg -resize  x1024 images\2.jpg
convert images\source.jpg -resize  x2048 images\3.jpg
convert images\source.jpg -resize  x4096 images\4.jpg
convert images\source.jpg -resize  x8192 images\5.jpg

If you have several zoom images for different zoom levels (I guess this will be the case for the MRI scans), choose the closest-zoomed source image.

Working With Already Tiled Images

In some cases source images are already cut in tiles. This is typical in "old" map clients which you want to slippify. This is an example, tiles are called vk-X-Y.jpg and are cut with some overlapping. In this case you first have to crop the images:

magick data\vk-0-0.jpg  -crop 522x373+0x0 images\t-0-0.jpg
magick data\vk-1-0.jpg  -crop 522x373+0x0 images\t-1-0.jpg
magick data\vk-2-0.jpg  -crop 522x373+0x0 images\t-2-0.jpg
magick data\vk-3-0.jpg  -crop 522x373+0x0 images\t-3-0.jpg
magick data\vk-4-0.jpg  -crop 522x373+0x0 images\t-4-0.jpg
magick data\vk-5-0.jpg  -crop 650x373+0x0 images\t-5-0.jpg
...

To figure out crop parameters load vertically and horizontally neighbouring tiles into a graphic editor, try to match them and check the offset coordinates.

Then, when the tiles are cropped, append them to a large image:

magick images\t-0-0.jpg images\t-1-0.jpg images\t-2-0.jpg images\t-3-0.jpg images\t-4-0.jpg images\t-5-0.jpg +append images\t-0.jpg
...
magick images\t-0.jpg images\t-1.jpg images\t-2.jpg images\t-3.jpg images\t-4.jpg images\t-5.jpg images\t-6.jpg images\t-7.jpg images\t-8.jpg images\t-9.jpg images\t-10.jpg -append images\t.jpg

The result of this crop-and-append operation is a big high-res image of the map. Resize it to each of the levels as described above.

Resizing PDFs

When rendering PDFs I prefer resizing using density. To calculate density per zoom level (this is the Windows command, modify for Linux accordingly):

identify -precision 16 -format "%%[fx:((256/max(w,h))*72)]\n%%[fx:((512/max(w,h))*72)]\n%%[fx:((1024/max(w,h))*72)]\n%%[fx:((2048/max(w,h))*72)]\n%%[fx:((4096/max(w,h))*72)]" source.pdf

This gives you something like:

21.89073634204276
43.78147268408551
87.56294536817103
175.1258907363421
350.2517814726841

The magic of the (4096/max(w,h))*72 expression is simple: (target size / source size) * standard DPI.

Having densities render the images:

convert -verbose -density 21.89073634204276 source.pdf        images\0.png
convert -verbose -density 43.78147268408551 source.pdf        images\1.png
convert -verbose -density 87.56294536817103 source.pdf        images\2.png
convert -verbose -density 175.1258907363421 source.pdf        images\3.png
convert -verbose -density 350.2517814726841 source.pdf        images\4.png

This may take a lot of time on higher levels.

Cutting the Level Images in Tiles

At this point you should have one image per layer. Now we can cut them in tiles:

convert -verbose images\0.png -crop 256x256 +adjoin -background white -extent 256x256 -set filename:tile "%%[fx:floor(page.x/256)]_%%[fx:floor(page.y/256)]" +repage "tiles\0_%%[filename:tile].png"
convert -verbose images\1.png -crop 256x256 +adjoin -background white -extent 256x256 -set filename:tile "%%[fx:floor(page.x/256)]_%%[fx:floor(page.y/256)]" +repage "tiles\1_%%[filename:tile].png"
convert -verbose images\2.png -crop 256x256 +adjoin -background white -extent 256x256 -set filename:tile "%%[fx:floor(page.x/256)]_%%[fx:floor(page.y/256)]" +repage "tiles\2_%%[filename:tile].png"
convert -verbose images\3.png -crop 256x256 +adjoin -background white -extent 256x256 -set filename:tile "%%[fx:floor(page.x/256)]_%%[fx:floor(page.y/256)]" +repage "tiles\3_%%[filename:tile].png"
convert -verbose images\4.png -crop 256x256 +adjoin -background white -extent 256x256 -set filename:tile "%%[fx:floor(page.x/256)]_%%[fx:floor(page.y/256)]" +repage "tiles\4_%%[filename:tile].png"
convert -verbose images\5.png -crop 256x256 +adjoin -background white -extent 256x256 -set filename:tile "%%[fx:floor(page.x/256)]_%%[fx:floor(page.y/256)]" +repage "tiles\5_%%[filename:tile].png"

This produces files like:

  • tiles/0_0_0.png
  • tiles/1_0_0.png
  • tiles/1_0_1.png
  • tiles/1_1_0.png
  • tiles/1_1_1.png
  • etc.

This is your static pre-rendered set of 256x256-sized tiles.

Configure Leaflet

Now you only have to configure the Leaflet. Assuming the tile files are in ../tiles directory relatively to the HTML file, it's simply:

L.tileLayer('../tiles/{z}_{x}_{y}.png', {
    maxZoom: 5,
    noWrap: true,                     
    attribution: 'Some Attribution'
}).addTo(map);

If you want to set the proper initial view point, zoom/move to where you want, open the JavaScript console in your dev tools and type:

map.getCenter();
map.getZoom();

Then use the printed parameters when you initialize the map:

var map = L.map('map').setView([-26.3525, -65.0390], 3);

To add marker:

L.marker([-26.3525, -65.0390], {title: "Hi there!"}).addTo(map);

The marker will remain at the same position even when you pan or zoom.


Here's one of the projects as example:

Southwards answered 15/7, 2018 at 5:26 Comment(5)
Thanks lexicore for the time you spent with this elaborate answer. It helps a lot!Artificer
@Southwards have you tried libvips for this? It should be much faster. On this laptop, vips dzsave European_inland_waterways_-_2012.pdf[dpi=600] x --layout google makes a complete leaflet tree in about 15s and needs 1gb of memory. Introduction here jcupitt.github.io/libvips/API/current/…Ruppert
For anyone having troubles with the filenames on Ubuntu or stuff -> Replace all the %% in the convert-string with %.Trull
graphicsmagick (gm) handles huge images better than imagemagick.Merkel
github.com/danizen/campaign-map/blob/master/gentiles.py is a good way to generate all the slippy image pyramid to use for leaflet.Merkel
R
10

libvips has an operation that can make a slippy map tileset for leaflet in one command.

For example, with this PDF map of European inland waterways (thank you @lexicore!) you can enter:

vips dzsave European_inland_waterways_-_2012.pdf[dpi=600] xxx --layout google

and it'll make a directory called xxx containing all your tiles, ready to be uploaded to your server. It takes about 15 seconds (on this laptop anyway).

It's fast and needs little memory. The details vary a bit with the file format, but for many formats, it can decode the input, construct all the pyramid layers, and write the output tiles, all in parallel, and without ever needing to load the whole of the input image into memory. I regularly render pyramids of more than 300,000 x 300,000 pixels on a modest laptop.

It can do some useful filetypes as well as the usual tiff, PNG, JPG, etc., including things like SVG, FITS, DICOM and OpenSlide. It can make pyramids for deepzoom and zoomify too.

A nice feature for Windows hosts is the ability to write the tileset to a zip file rather than the filesystem. Windows is rather slow at file creation -- with a large pyramid and small tiles you can spend almost 75% of CPU time just in file create. Write to a zip file instead and you'll see perhaps a 3x speedup:

vips dzsave huge.tif xxx.zip --layout google

Plus of course the zip is simpler to upload to a server.

There's a chapter in the libvips manual introducing dzsave and showing all the options.

If you'd prefer a GUI, vipsdisp, a libvips image viewer, has a save-as option that can make image pyramids. Open the image, select save-as, and enter a filename with .dz as the suffix:

save-as pyramid

There are linux and windows binaries on the Releases page.

Ruppert answered 24/7, 2018 at 21:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.