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: