OpenStreetMap logo OpenStreetMap

Tile Stitching...

Posted by mkarau on 24 October 2013 in English.

Below is a shell script using graphicsmagick (can be easily adapted to imagemagick by simply removing gm from the two image-generation lines below) for stitching together downloaded tile sets into one large PNG. It is tested on MacOS 10.8.5 with graphicsmagick installed via homebrew and should work on most Linux distros. This script is adapted from a script posted by HannesHH.

The script can be useful when producing large printed maps at a zoom level unavailable from most online services. I use JTileDownloader to grab tiles. Be sure to obey the terms of use of your tile server (or bake your own).

#!/bin/bash
# Original credit to OSM user HannesHH
# Updated by OSM user mkarau
# will search for tiles in your current directory ($z/$x/$y.png) and stitch them all together
# will fail horribly if you do not have a rectangular area with tiles
# this is super i/o intense if you use it for a larger amount of tiles so do not do that
# that is because i am lazy and just needed something for small areas
# uses graphicsmagick's gm tool, just remove "gm" if you use imagemagick
# needs bash 3 i guess
# pass the wanted zoomlevel as argument, eg "bash ./tilestitch.sh 14"

blankFileName="./blank.png"
stitchedFileName="./stitchedtiles.png"
#montageOptions="-verbose +frame +shadow +label"
montageOptions="+frame +shadow +label"

runScript=0

if [ $# -gt 0 ];
then
	zoomlevel=$1
	if [ ${zoomlevel} -ge 0 ];
	then
		if [ ${zoomlevel} -le 18 ];
		then
			if [ -d "${zoomlevel}" ];
			then
				runScript=1
			fi
		fi
	fi
fi


if [ ${runScript} -le 0 ];
then
	echo "usage: $0 ZOOMLEVEL"
	echo "ZOOMLEVEL: 0-18"
	echo "Files must be stored in the directory structure below"
	echo "./ZOOMLEVEL/XXXXXX/YYYYYY.png"
	exit	
fi

rows=$(ls -1 $zoomlevel/*/*.png | grep -Eo '/.*/' | sort| uniq | wc -l)
rows=$(echo ${rows} | tr -d ' ')

columns=$(ls -1 $zoomlevel/*/*.png | grep -Eo '/[^/]*png' | sort | uniq | wc -l)
columns=$(echo ${columns} | tr -d ' ')

min_x=$(ls -1 $zoomlevel/*/*.png | grep -Eo '/.*/' | sed 's/\///g' | sort -n  | head -1)
max_x=$(ls -1 $zoomlevel/*/*.png | grep -Eo '/.*/' | sed 's/\///g' | sort -n | tail -1)
min_y=$(ls -1 $zoomlevel/*/*.png | grep -Eo '[^/]*png' | sed 's/\.png//g' | sort -n | head -1)
max_y=$(ls -1 $zoomlevel/*/*.png | grep -Eo '[^/]*png' | sed 's/\.png//g' | sort -n | tail -1)
# so sorry...

echo "zoomlevel: $zoomlevel"
echo "rows: $rows"
echo "columns: $columns"
totalTiles=$(( rows * columns ))
echo "Total Tiles: ${totalTiles}"

echo "min_x: $min_x"
echo "max_x: $max_x"
echo "min_y: $min_y"
echo "max_y: $max_y"

missingTiles=0
tileNo=0
xIndex=0
yIndex=0

echo -n "Ordering filenames for stitching "
# gm montage needs them ordered by column ($y).
for y in $(eval echo {${min_y}..${max_y}})
do
	for x in $(eval echo {${min_x}..${max_x}})
	do	
		xIndex=$((x - min_x))
		yIndex=$((y - min_y))
		tileNo=$(( ((yIndex) * (rows)) + (xIndex) ))
#		echo "Count: ${tileNo} Missing: ${missingTiles} X: ${x}  Y: ${y} xIndx: ${xIndex} yIndx: ${yIndex}"	
		if [ -f $zoomlevel/$x/$y.png ];
		then
			filenames="${filenames} ${zoomlevel}/${x}/${y}.png"
			echo -n "."
		else
			echo -n "B"
#			echo "$zoomlevel/$x/$y.png does not exist, using blank tile"
			filenames="${filenames} ${blankFileName}"
			missingTiles=$(( missingTiles + 1 ))			
		fi
	done
done
echo " DONE"

if [ ${missingTiles} -gt 0 ];
then
	echo -n "Creating blank tile: "
	gm convert -format png -geometry 256x256+0+0 -colorspace RGB xc:none ${blankFileName}
	echo "DONE"
fi

echo -n "Creating Montage (${stitchedFileName})... "
tileSize="${rows}x${columns}"
gmCommand="gm montage ${montageOptions} -tile ${tileSize} -geometry 256x256+0+0 ${filenames} ${stitchedFileName}"
#echo "${gmCommand}"
${gmCommand}
echo "DONE"


if [ ${missingTiles} -gt 0 ];
then
	if [ -f ${blankFileName} ];
	then
		echo -n "Removing blank tile (${blankFileName}): "
		rm ${blankFileName}
		echo "DONE"
	fi
fi
Location: Musaffah Industrial Area, Musaffah, Abu Dhabi, Abu Dhabi Emirate, United Arab Emirates

Discussion

Comment from 5m4u9 on 30 October 2013 at 06:45

Works great on Slackware / Linux and imagemagick.

Thanks!

Comment from aharvey on 31 October 2013 at 09:44

I also have a similar script in Perl at https://gist.github.com/andrewharvey/3736925

It will take a lat/lon and zoom and image width/height download tiles and paste these together into a single map.

But if your script works for you, no reason not to use it!

Comment from mkarau on 1 November 2013 at 06:54

Thanks for the link, aharvey. This works well for me in conjunction with a tile downloader - nice to see you’ve wrapped the process all together.

Comment from Harry Wood on 20 November 2013 at 12:42

I created a wiki page, mainly so I could make this findable in the Tile Stitching category. Quite a few different tools do this. Maybe we should make a more general page ‘Tile Stitching’

Comment from kannix on 18 April 2014 at 15:44

Thanks a lot, just what I needed :-)

Comment from kannix on 20 April 2014 at 07:29

Now I understand “will fail on non rectangular area” ;-)

I am fiddling around seacharts (OpenSeaMap): OSeaM-tiles are non existing in case there is nothing to show…. Could you extend your script, checking for missing y-directories (and missing x-files in missing y-directories)?

Thanks a lot, Dirk http://maps.grade.de/

P.S. https://code.google.com/p/gmapcatcher is a great tool, fetching tiles along a path. Perfect small footprint downloader in conjunction with your script :-)

Log in to leave a comment