Hack 68. Automatically Cut and Name Custom Map Tiles
Make time to really enjoy a cup of coffee.
Google Maps are made of dozens to thousands of tile images, depending on the zoom level. At the distant zoom levels you only need a few images to cover a large area. For example, the NYC Subway Map uses 49 images to cover the greater metropolitan area at zoom level 5. But at the closer zoom levels, thousands of images are required. It takes more than 3500 images to cover the NYC metropolitan area at zoom level 2. "How Big Is the World?" [Hack #16] discusses the amount of disk space needed to tile the entire planet. Figure 7-21 shows a custom tile at zoom level 5, while Figure 7-22 shows part of the same area at zoom level 2.
Creating and then uniquely naming each of these images for even a small part of our world would be a daunting task if you had to do it by hand. That's why we use a script to do this. The following hack will show you how to configure a batch processing script to use with Photoshop 7 or CS that will carve all the tiles you need from one big image into hundreds of smaller GIFs and name them exactly as you need them named.
7.8.1. The Google Map Tile Structure
Before we set up and use our script we need to know a few things about Google's tiles. As we learned in "Serve Custom Map Imagery" [Hack #67], each 256 X 256pixel tile has a longitude, a latitude, and a zoom value. These are represented in the URL by the x, y, and zoom values.
Figure 7-21. A custom tile at zoom level 5
Figure 7-22. A custom tile at zoom level 2
Here is what a tile path from Google looks like:
http://mt.google.com/mt?v=w2.4&x=1206&y=1539&zoom=5
7.8.2. Figuring Out the Values for Your Tiles
So how do you get the x, y, and zoom values? Fortunately you don't have to get all the x, y, and z values for all your images. We only need to get a set of values for one tile and from there we can derive all the other values we need.
We start with a limited area for our map because we assume you don't want to remap the whole world, but just a small corner of it. All we need then is to get the x, y, and z value of the upper-left corner tile of our map area and know how many tiles wide and high our map area is. For simplicity, our example map is square so that the area width and height are the same, but you could tweak this script to work with rectangular areas. Once we know the upper-left corner tile values, our batch processing script will calculate the rest for us.
Unfortunately getting just these corner tile values is not easy on our own. Fortunately again there is a web site that will help do this for us. The site at http://www.onnyturf.com/google/latlontotile.html has a set of tools that will help us figure out this information for the upper-left corner tile values. Figure 7-23 illustrates the process of converting lat/long to tile row and column.
Figure 7-23. Converting latitude and longitude to tile row and column
Using the map on the right side of this page you can find the center of your map area. When you click there the web page will get the latitude and longitude for the center of your map. You can also set the zoom level and tile width here. When you press submit, the web site will return Google's tiles that make up your requested map area. For our batch processing script, you will want to do this for your furthest zoom level. You'll probably have to experiment a little until you get the right values that match your map area. For my NYC Subway map, the furthest zoom level is 5 and my tile width is 7, as shown in Figure 7-24. Your maximum zoom level will probably be somewhere between 5 and 8.
Figure 7-24. Finding the upper-left corner for a given latitude/longitude
To get the values for the upper-left corner tile, we now just right-click on that tile and select View Image. This will load the image into the browser window, and now we can see our x, y, and zoom values in the image URL:
http://mt.google.com/mt?v=w2.4&x=1206&y=1539&zoom=5
Whew. So there it is. Now we know our start values for this example are:
x = 1206 y = 1539 zoom = 5 tile width = 7
7.8.3. Configuring the Script
The batch processing script provided is too long to go through in detail here. This hack focuses on showing you how to configure it for your map area so you can make your tiles.
To use the script provided you must enter some custom values, including the ones we just got for our upper-lefthand tile. The values you must customize come at the beginning of the script.
7.8.3.1. ZoomLevel.
Last things first. The first variable you encounter sets the Zoom Level you want to generate tiles for. To create your custom map you will probably want tiles at various levels. For each level you want to create tiles for, you must change this value and rerun the batch processing script on your master art. Here it is set to 3, but you will change this every time you want to run this script for another level.
var ZoomLevel = 3;
The rest of the variables you must customize you will only customize once for your map area. Let's go through them one by one.
7.8.3.2. FolderPath.
This next one sets the path to where your tiles will be saved. This path is for use on a PC. The path on the Mac will be slightly different. This sample path uses a separate folder for each zoom level, and those folders are in a folder GoogleMap that sits on the user desktop. You will have to precreate your zoom folders, but the path to those folders is dynamically generated in this example.
var FolderPath = "~/Desktop/GoogleMap/Zoom"+ZoomLevel+"Maps/";
7.8.3.3. Furthest zoom upper-left corner values.
Lastly, we put our upper-left corner values to work. These are the originating values, off of which the batch processing script will figure out the values for all the tiles you are creating.
var OrgX = 1204; //<-- the X value var OrgY = 1536; //<-- the Y value var OrgZoomLevel = 5; //<-- the zoom level var OrgTileWidth = 7; //<-- the number of tiles wide your full map area is var OrgTileHeight = 7; //<-- the number of tiles high your full map area is
That's all you have to configure! Here is how the whole thing looks with our customized values at the top:
/* Tile Carver for Photoshop 7 and CS Created by Will James http://onNYTurf.com */ //**** CUSTOMIZE THE FOLLOWING VARIABLES FOR YOUR MAP AREA **** var ZoomLevel = 3; var FolderPath = "~/Desktop/GoogleMap/Zoom"+ZoomLevel+"Maps/"; var OrgX = 1204; var OrgY = 1536; var OrgZoomLevel = 5; var OrgTileWidth = 6; var OrgTileHeight = 6; //**** EVERYTHING BEYOND THIS POINT SHOULD NOT BE TOUCHED //**** UNLESS YOU KNOW WHAT YOU ARE DOING!!! **** var StartX = OrgX * (OrgZoomLevel - MakeZoomLevel) * 2; var StartY = OrgY * (OrgZoomLevel - MakeZoomLevel) * 2; var xTiles = OrgTileWidth * (OrgZoomLevel - MakeZoomLevel) * 2; var yTiles = OrgTileHeight * (OrgZoomLevel - MakeZoomLevel) * 2; var PixelWidth = 256; var PixelHeight = 256; var TotalTiles = (xTiles)*(yTiles); preferences.rulerUnits = Units.PIXELS; var xm = 0; var ym = 0; var TileX = StartX; var TileY = StartY; for (n=1; n
At http://mapki.com/index.php?title=Automatic_Tile_Cutter, you can get copies of this script. Save your script as TileCutterNamer.js, as it is a JavaScript file. Put the script somewhere easy to find, because next you are going to open it from Photoshop.
7.8.4. Running Our Script in Photoshop
We are going to cut our tiles from one big image. For the NYC Subway map, I created my map art in Adobe Illustrator and then imported it into Photoshop. As you create your custom map, an important thing to keep in mind is to know how wide and high to make your map art. I started in Illustrator with the dimensions for the furthest zoom level; then, when I open the art in Photoshop, I tell Photoshop to create a bitmap to match whichever zoom level I am working on.
We can figure out how wide and high our art must be by multiplying the total number of tiles across and down by the individual tile width of 256 pixels. For my furthest zoom, the width and height of the subway map ends up being 1,792 pixels: 7 tiles wide by 256 pixels. But in our example batch processing script, we are making tiles for zoom level 3; 1,792 X 1,792 pixels is going to be too small an image to carve all our tiles from.
So how do we get the right width and height for other zoom levels? Every zoom level is twice the height and width of its previous zoom level. So, if our first zoom level was 1,792 pixels wide, we end up with a matrix like Table 7-1.
Zoom level |
Pixel width |
---|---|
5 |
1,792 |
4 |
3,584 |
3 |
7,168 |
2 |
14,336 |
To batch process tiles for zoom level 3 then, I start with a file 7,168 pixels wide. By the way, this is 28 tiles across. We know that because 7,168 pixels divided by 256 pixels is 28. For a square map area 28 pixels wide by 28 pixels high, we are then making 784 tiles! Thank goodness this script also names these tiles for us!
Getting back to our batch processor, I open my Illustrator art into Photoshop at 7,168 X 7,168 pixels, as shown in Figure 7-25.
Figure 7-25. Photoshop's File Import dialog
Once the file is rasterized, we run the JavaScript by going to File
Figure 7-26. The list of completed custom map tiles
The only thing left to do then is upload your new zoom level to your maptiles/ folder on your server and then go and check out your new map in your web page!
7.8.5. Hacking the Hack
This batch processing script is written to produce GIFs. It could also be written to produce JPGs or PNGs. You will have to customize the settings in this part of the script:
//Set path to file and file name saveFile = new File(FolderPath + TileX+ "_" + TileY+ "_" + ZoomLevel + ".gif"); //Set save options gifSaveOptions = new GIFSaveOptions(); gifSaveOptions.colors = 64; gifSaveOptions.dither = Dither.NONE; gifSaveOptions.matte = MatteType.NONE; gifSaveOptions.preserveExactColors = 0; gifSaveOptions.transparency = 0; gifSaveOptions.interlaced = 0;
7.8.6. See Also
- "Add Your Own Custom Map" [Hack #66]
- "Serve Custom Map Imagery" [Hack #67]
Will James