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.
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.
Share Improve this question edited Jul 23, 2018 at 15:05 jcupitt 11.2k2 gold badges27 silver badges42 bronze badges asked Jul 14, 2018 at 20:18 AenaonAenaon 3,5735 gold badges41 silver badges70 bronze badges 2- 1 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. – lexicore Commented Jul 18, 2018 at 20:12
- 1 I had a go at editing this down to a single question, I hope it looks OK. – jcupitt Commented Jul 23, 2018 at 15:06
2 Answers
Reset to default 14Here'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 0512
pixel on level 11024
pixel on level 22048
pixel on level 34096
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:
- https://github.com/highsource/unece-maps
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:
There are linux and windows binaries on the Releases page.