This page demonstrates how to use Microsoft Research's (MSR) MapCruncher to generate custom tile sets for use by the Google Map API. The licence for MSR MapCruncher only permits the use of generated tiles for non-commercial use. You will find MapCruncher at . It will produce tile sets from various formats of source image, including PDF.

Bill Chadwick October 2007

*NEW July 08* - Google Tile Hosting

Please see Step 7 of this article for details of how to host custom map tile sets on Google servers using the Google App Engine.


Here is a sample map that has been scanned from a book (with consent of the author).


The next step is to tidy up your source map with an image editing program. Its best to rotate your map so that North points straight up the screen. If you want your map background to be transparent, use png format and select the transparent colour as the background. If your map is diagrammatic, like mine, rather than photographic, its best to reduce it to 256 colours. Here is the sample map above after it has been tidied up, yellow works well as on overlay on the satellite map.


Start up MapCruncher and use the File menu - New Mashup option . Next use the File menu - Add Source Map option and load your tidied map.

You MapCruncher window should now look something like this. MapCruncher uses pink for transparent portions of your source image.

What you do next is set the source map cross hairs to several points on your map and the cross hairs on the Virtual Earth (VE) map to the corresponding points on the ground. For each correspondence, press the Add button on the Correspondences tab to add correspondence Pins. Its best to use about 6 Pins and include a Pin near each corner of your map. At this stage your map and the VE map are not synchronised and you pan and zoom them independently.

Now I have deliberately made this example difficult by using a cave! Only the entrance sticks a chance of being visible on the map. The next image shows MapCruncher after I have added the entrance marker. I happen to know that the cave entrance is under this particular tree.

I then proceed to add some further correspondences, one at each corner of the source map. The Pins are very carefully placed exactly on the corners of my source map but only roughly placed on the VE map.

Now save your mashup (as a Yum file), close MapCruncher and proceed to the next step.


This step is optional but necessary if you can't set all your correspondences well using MapCruncher. We need to work out the real world coordinates of all the Pins on your source map in WGS84 Lat/Lon. To do this you need to know the size of your image in pixels and the scale of your map in say metres per pixel. From one known location on your map, the cave entrance in my case, we can then work out the coordinates of each corner  (having previously rotated the map so that North is straight up). 

For my sample map I get a set of UK Ordnance Survey grid coordinates which I can convert to Lat/Lon. I use a Spread Sheet for this, here is the sheet for the current example.

Entrance Eastings Entrance Northings
353120 151310
Image Pixel Width Image Pixel Height
3643 4565
Entrance Pixel X Entrance Pixel Y
2676 1704
Scale Bar Feet Scale Bar Pixels
500 568
Scale Bar Metres
Metres Per Pixel
Top Left East 352402.0028
Top Left North 151767.2
Top Right East 353379.4556
Top Right North 151767.2
Bottom Left East 352402.0028
Bottom Left North 150542.3655
Bottom Right East 353379.4556
Bottom Right North 150542.3655
Top Left Lat 51.26301069
Top Left Lon -2.683591542
Top Right Lat 51.26309161
Top Right Lon -2.669589905
Bottom Left Lat 51.25199624
Bottom Left Lon -2.683428205
Bottom Right Lat 51.25207713
Bottom Right Lon -2.669429913
Entrance Lat 51.25896125
Entrance Lon -2.673241677

To do the conversion for the UK you can use the coordinate converter on the OS Web Site at, or even just use the status bar on my page at

Having got good accurate Lat/Lon for our reference points we now edit them in to MapCruncher's Yum file.

Open the Yum file with your favourite text editor. For each Pin you will find a bit of XML like this

  <PositionAssociation pinId="0" associationName="Pin0">
            <LatLonZoom zoom="9">
                <LatLon lat="0.9998779296875" lon="0" />
            <LatLonZoom zoom="9">
                <LatLon lat="51.26301069" lon="-2.683591542" />

For each Pin, edit the values highlighted in red to the computed values, then save the Yum file. Of course if you have good WGS84 registration coordinates for your map from a GIS system, (e.g. an ESRI world file, .jgw, .pgw etc) you can use those figures.

Finally reopen the Yum file with MapCruncher and press the 'Lock' button. You should get something like this.

Ideally for a map on a similar scale to mine your error will be under 50cm (around two pixels at Google zoom level 19).


Now select the Source Info tab and set the 'Maximum (Closest) Zoom' to the maximum zoom in you want for your map. For my example I use 19. If you have used a transparent png as your source map, leave the Transparency tab alone, otherwise use it to select colours to make transparent. Next press the Render button to get the Render dialogue. On this, select an output folder and then press the 'Start Estimating' button. Now wait whilst MapCruncher does its clever stuff.

You will find your output tile set in a folder called 'Layer_NewLayer' in your selected output folder something like this

To save server space and improve rendering speed its best to work with 8 bit / 256 colour png tiles wherever possible. Its also worth running pngcrunch,  over the output tile set to shrink the tiles to a minimum size.


Very happily, Google Maps and MS Virtual Earth use the same map projection and tiling scheme. Maps start with one 256x256 pixel tile at zoom 0 with Lat/Lon 0,0 in its centre, -90 Latitude at the bottom, +90 at the top, -180 Longitude at the left and +180 Longitude at the right. At zoom 1 the world is divided into four tiles, then 16 at zoom 2 etc.

The MapCruncher tile file names have one digit for each zoom level and use a quadtree format with the digits 0,1,2,3 ( see ). It turns out to be a simple matter to convert the x,y and zoom of a call on the GMap getTileUrl function to a filename in this quadtree format. Here is the Java Script code that does it.

function TileToQuadKey ( x, y, zoom){
    var quad = "";
    for (var i = zoom; i > 0; i--){
        var mask = 1 << (i - 1);
        var cell = 0;
        if ((x & mask) != 0)
        if ((y & mask) != 0)
            cell += 2;
        quad += cell;
    return quad;

Then for your GTileLayer use  something like this, of course replacing the URL with your own. 

myLayer[0].getTileUrl = function (a,b) { 
return  "" + TileToQuadKey(a.x,a.y,b) + ".png"; 

If your tiles are transparent PNGs then also include code like this.

 if(navigator.userAgent.indexOf("MSIE") == -1) 
    myLayer[0].isPng = function() {return true;};

To add your own map type use code like this for a map with tiles between zoom levels 12 and 19.

 var caveLayer = new GTileLayer(new GCopyrightCollection(''),12,19); 
 caveLayer.getTileUrl = caveTiles; 
 caveLayer.getCopyright = function(a,b) {return "Irwin & Jarrett, BDCC, MCG, Bill Chadwick 2007";}; 
 caveLayer.isPng = function() {return true;};

 var caveMap = new GMapType([caveLayer], G_SATELLITE_MAP.getProjection(), "Caves",{errorMessage:"no caves here"}); 
 caveMap.getTextColor = function() {return "#0000FF";}; 


To make a hybrid with your map type and one of the existing maps, the satellite map in this example, use code like this.

 var caveHybridLayer = new Array();
 caveHybridLayer[0] = G_SATELLITE_MAP.getTileLayers()[0];
 caveHybridLayer[1] = new GTileLayer(new GCopyrightCollection('') , 12, 19);
 caveHybridLayer[1].getTileUrl = caveTiles;
 caveHybridLayer[1].getCopyright = function(a,b) {return "Irwin & Jarrett, BDCC, MCG, Bill Chadwick 2007";};
 caveHybridLayer[1].getOpacity = function () {return 0.5;};//of the non transparent part cave
 caveHybridLayer[1].isPng = function() {return true;};

 var caveSatMap = new GMapType(caveHybridLayer, G_SATELLITE_MAP.getProjection(), 'Caves/Sat',{errorMessage:""}); 
 caveSatMap.getTextColor = function() {return "#FFFFFF";};


isPng should return true if you want to use transparent PNGs. With MSIE 6 you can only have transparency or opacity control, not both. So if you want want to control opacity with MSIE 6 you should make isPng return false. At less than 100% opacity, MSIE 7 will put a small black halo around the opaque part of your transparent overlay. As ever, Firefox does it better.


You need to upload your tiles to a folder on your web site in my case. If you have crunched more than one map you may want to omit some of the low zoom tiles that cover the same area on the ground. Finally here is the finished online map. Drag the slider by the Caves/Sat map type button to adjust the cave overlay opacity on the Caves/Sat hybrid map. See this page's code (which works fine for Firefox and MSIE7) for more details.


You can use a free Google App Engine account to host your tile sets. Currently you can get three App Engine applications, each with 500Mb of storage, per developer (Gmail address). Each application is limited to 1000 files and no more than 1Mb per file, see here for more details. To sign up for an account, you need a website that you can upload to, e.g. a Google Pages site and a mobile phone to receive a verification text on. You will need to download Python and the App Engine SDK via here.

Having got your account set up, you need to create an Application and so get an App-Id. Then in one folder, create the following files and folders:

tiles A folder containing your map tiles (up to 999)

App.yaml A file containing the following text (where myapp is your own App-Id)

  application: myapp
 version: 1
 runtime: python
 api_version: 1
 - url: /tiles
  static_dir: tiles
 - url: /.*
A file containing the following text (where myapp is your own App Id). This is Python script - correct identation is vital.

import wsgiref.handlers
from google.appengine.ext

import webapp import os
from google.appengine.ext.webapp import template

class MainPage(webapp.RequestHandler):
  def get(self):
    template_values = { }
    path = os.path.join(os.path.dirname(__file__), 'MyMap.html')

def main():
  application = webapp.WSGIApplication( [('/', MainPage),],debug=True)

if __name__ == "__main__": main()

MyMap.html a test page for your map. If you are using Google's free AppSpot hosting then use a url like for your map tiles and of course register for a Google Maps API key to use in MyMap.html .

To recap, you should have a tiles folder, and files named App.yaml, MyMap.Py and MyMap.html. You are now ready to upload all of these using the AppEngine SDK upload utility. Simply open a cmd window, navigate to your folder and enter " update ./ ". If all goes well, you should be able to view your test map at You can of course use your AppSpot hosted tiles from a map hosted anywhere on the web. The big advantage of AppEngine over GooglePages is the bulk upload facility. Only a single file at a time can be uploaded to GooglePages. Here is an AppSpot hosting of a cave survey.

For more samples of custom maps and overlays visit this map of Mendip caves. For more of my Google Maps API demos please visit