Published on 26 February 2014

Creating your own Google Maps tiles in OSX

Have you ever thought of creating your own custom map and displaying it using Google Maps? Well, I did and I want to share the experience. The goal was to be able to use an old map to display a kayak trip I did some time ago. I put together a whole website around that trip, MenorKayak.

Menorkayak map

Menorca Island

To generate the tiles, I originally used a high resolution image from the Institut Cartogràfic i Geològic de Catalunya. They have a very nice collection of old maps available here that I totally recommend checking it out if you're into this sort of stuff.

Installing GDAL

GDAL is a very cool library for raster geospatial data formats. More info available here. You can download the complete framework from http://www.kyngchaos.com/software:frameworks#gdal_complete

Add GDAL to your PATH:

echo 'export PATH=/Library/Frameworks/GDAL.framework/Programs:$PATH' >> ~/.bash_profile

Ensure it's working by checking the output of GDAL to your PATH:

gdal-config --version

By the time I did it the version was 1.10.1

Getting your area template

Now you have to choose the area where you want to overlay your own map. Go to http://www.maptiler.org/photoshop-google-maps-overlay-tiles and follow steps 1 and 2. No need to follow the rests of steps mentioned there.

After this you're gonna need the corner coordinates of the selected area. To find them out, in that very same page, open your Javascript console and execute:

map.getBounds().toString()

Now you have the lat/lng bounds of the current map. In my case:

"((39.61168293107564, 3.6198320749999766), (40.28546166826872, 4.498738324999977))"

With these two pair of coordinates (SW and NE) you can already figure out the four corners:

3.6198320749999766, 40.28546166826872
4.498738324999977, 40.28546166826872
3.6198320749999766, 39.61168293107564
4.498738324999977, 39.61168293107564

In my case the downloaded image looked like:

Menorca Island

Bounding box area

Overlaying images

Now you need to use Photoshop or any other similar software to overlay your own map and the downloaded image from the previous step. Open your custom image, add the downloaded Google Map image as a layer and use the opacity controls to adjust them. Once you're done save the result, JPG or PNG depending if you need transparency or not. I used JPG because I didn't need any transparency. If you're using a weather map or similar then go for PNG option.

Generating the tiles

Now that you have your own custom image you're ready to use GDAL to create the map tiles. Tiles are small images of 256x256 used by Google Maps.

Check your custom image:

gdalinfo yourimage.jpg

Output:

Driver: JPEG/JPEG JFIF
Files: yourimage.jpg
Size is 5604, 5624
Coordinate System is 
Metadata:
  EXIF_BitsPerSample=8 8 8
  EXIF_ColorSpace=65535
  EXIF_Compression=1
  EXIF_DateTime=2014:01:09 14:34:21
  EXIF_Make=Metis Systems srl
  EXIF_Model=Metis DRS 2A0 - CCD 14.4K - Firmware : 0001.01
  EXIF_Orientation=1
  EXIF_PhotometricInterpretation=2
  EXIF_PixelXDimension=5604
  EXIF_PixelYDimension=5624
  EXIF_PlanarConfiguration=1
  EXIF_ResolutionUnit=2
  EXIF_SamplesPerPixel=3
  EXIF_Software=Adobe Photoshop CS3 Macintosh
  EXIF_XResolution=(300)
  EXIF_YResolution=(300)
Image Structure Metadata:
  COMPRESSION=JPEG
  INTERLEAVE=PIXEL
  SOURCE_COLOR_SPACE=YCbCr
Corner Coordinates:
Upper Left  (    0.0,    0.0)
Lower Left  (    0.0, 5624.0)
Upper Right ( 5604.0,    0.0)
Lower Right ( 5604.0, 5624.0)
Center      ( 2802.0, 2812.0)
Band 1 Block=5604x1 Type=Byte, ColorInterp=Red
  Overviews: 2802x2812, 1401x1406, 701x703
  Image Structure Metadata:
    COMPRESSION=JPEG
Band 2 Block=5604x1 Type=Byte, ColorInterp=Green
  Overviews: 2802x2812, 1401x1406, 701x703
  Image Structure Metadata:
    COMPRESSION=JPEG
Band 3 Block=5604x1 Type=Byte, ColorInterp=Blue
  Overviews: 2802x2812, 1401x1406, 701x703
  Image Structure Metadata:
    COMPRESSION=JPEG

Now we need to create a VRT file using the command gdal_translate. Remember to change the coordinates with your own corner coordinates.

gdal_translate -of VRT -a_srs EPSG:4326 -gcp 0 0 3.6198320749999766 40.28546171792043 -gcp 5604 0 4.498738324999977 40.28546171792043 -gcp 0 5624 3.6198320749999766 39.61168298121881 -gcp 5604 5624 4.498738324999977 39.61168298121881 yourimage.jpg output.vrt

Now do the WARP:

gdalwarp -of VRT -t_srs EPSG:4326 output.vrt new_output.vrt

Generate the tiles ready to use with Google Maps:

gdal2tiles.py -p geodetic new_output.vrt

In your directory you'll see a bunch of folders and some auto-generated HTML files. These are the tiles folders and a couple of examples using Google Maps and Open Layers. The Google Maps example might not work properly because the generated code uses an old version of the Google Maps Javascript API. A Google Maps JS example that works:

$(document).ready(function() {

    var map;

    function initialize() {
      var mapMinZoom = 8;
      var mapMaxZoom = 13;
      var center = new google.maps.LatLng(39.94940191877162, 4.059285199999977);
      var myOptions = {
            zoom: 10,
            center: center,
            mapTypeId: google.maps.MapTypeId.SATELLITE,
            mapTypeControl: false,
            streetViewControl: false
      };

      map = new google.maps.Map(document.getElementById('small-map'), myOptions);
      //Custom tiles overlay
      var mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(39.6116914406, 3.619832075), new google.maps.LatLng(40.2854617179, 4.49872734429));
      var imageMapType = new google.maps.ImageMapType({
        getTileUrl: function(coord, zoom, ownerDocument) {
          var zfactor = Math.pow(2,zoom);

          if ((zoom < mapMinZoom) || (zoom > mapMaxZoom)) {
              return '/images/none.png';
          }
          var ymax = 1 << zoom;
          var y = ymax - coord.y -1;
          var tileBounds = new google.maps.LatLngBounds(
              map.getProjection().fromPointToLatLng( new google.maps.Point( ((coord.x)*256) / zfactor, ((coord.y+1)*256)/ zfactor ) ),
              map.getProjection().fromPointToLatLng( new google.maps.Point( ((coord.x+1)*256)/ zfactor, ((coord.y)*256 )/ zfactor ) )
          );
          if (mapBounds.intersects(tileBounds)) {
              return '/path_to_tiles/'+zoom+"/"+coord.x+"/"+y+".png";
          } else {
              return '/images/none.png';
          }
        },
        tileSize: new google.maps.Size(256, 256),
        maxZoom: mapMinZoom,
        minZoom: mapMaxZoom,
        opacity:1
      });

      map.overlayMapTypes.push(imageMapType);
    }

    google.maps.event.addDomListener(window, 'load', initialize);

});

Remember to set the path to your tiles folder or might not work properly.

And that's all! Now you should be able to generate your own custom map/layers and create some impressive stuff with Google Maps or another Map Server ;)

SOURCES: