Apr 02

downloading google maps tiles using modestmaps

Tag: codedenis @ 1:14 pm

ModestMaps is one the nicest mapping API out there: it just feels right. I especially love the python version, which I used to download some Google Maps tiles (anyone remember gMerge?). The following script gets a Google Maps permalink and downloads the tiles all the way down to the maximum zoom level. You may need to alter the file retrieval and naming logic. The beauty here lies in the fact that ModestMaps allows you to download any layer from many APIs (Google, Virtual Earth, Yahoo Maps and OpenStreetMaps) by changing the Google.AerialProvider() call.

import sys, urllib2, re,os, Image, httplib
 
def parselink(link):
 qs = link[link.index('?')+1:]
 vals = [v.split('=') for v in qs.split('&')]
 params = {}
 for v in vals:
  params[v[0]] = v[1]
 zoom = int(params['z'] or '0')
 center = map(float, params['ll'].split(','))
 span = map(float, params['spn'].split(','))
 ne, sw = list(center), list(center)
 ne[0] += span[0]
 ne[1] += span[1]
 sw[0] -= span[0]
 sw[1] -= span[1]
 return ne, sw, zoom
 
def fetch(pt):
 if pt.zoom > Core.Coordinate.MAX_ZOOM:
  return
 
 if download(pt):
  z = pt.zoomBy(1)
  fetch(z)
  fetch(z.right())
  fetch(z.down())
  fetch(z.down().right())
 
def download(pt):
  url = layer.getTileUrls(pt)[0]
  try:
   os.makedirs("tiles/%d/%d" % (pt.zoom, pt.row))
  except: pass
  fn = "tiles/%d/%d/%d_%d.jpg" % (pt.zoom, pt.row, pt.row, pt.column)
  if os.path.exists(fn):
   try:
    m = Image.open(fn)
    a,b,c,d = m.getbbox()
    return d > 1
   except:
    return False
  print url
  try:
   # should use urllib2 to fake user agent here
   data = urllib.urlopen(url).read()
   out = open(fn, 'w')
   out.write(data)
   out.close()
   return True
  except urllib2.HTTPError, e:
   if e.code == 404:
    #empty tile
    out = Image.new('RGB', (1,1))
    out.save(fn)
    return False
 
  #should not reach this point. add better error handling
  sys.exit(-1)
 
if __name__ == "__main__":
 sys.path.append('trunk/py/')
 from ModestMaps import *
 
 layer = Google.AerialProvider()
 bbox = parselink('http://maps.google.com/maps?f=q&hl=en&geocode=&q=new++york,+ny&ie=UTF8&ll=40.75506,-73.969917&spn=0.100907,0.197926&t=h&z=13&iwloc=addr')
 ne = layer.locationCoordinate(Geo.Location(bbox[0][0], bbox[0][1])).zoomTo(bbox[-1]).container()
 sw = layer.locationCoordinate(Geo.Location(bbox[1][0], bbox[1][1])).zoomTo(bbox[-1]).container()
 cur = ne.copy()
 while cur.row <= sw.row:
   while cur.column >= sw.column:
    fetch(cur.copy())
    cur = cur.left()
   cur.column = ne.column
   cur = cur.down()

Or get the file here

Bookmark this page on: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us
  • Facebook
  • Google
  • Reddit
  • StumbleUpon
  • Technorati

6 Responses to “downloading google maps tiles using modestmaps”

  1. Leonel says:

    Hi Denis, I discovered ModestMaps reading your blog. And I’m interested on using your script above, but I’m also new at Python. I didn’t have trouble using ModestMaps with Flash, but I think it would be also nice to download the tiles for my city and serve them locally while testing. Could you explain if this script runs on a Web Sever or on a python console application and where do I need to place this file in relation to the modestmaps api.

    Thanks for your help,

  2. denis says:

    Hello Leonel,
    Try this:
    Create a new directory (let’s say its mm). Go in that directory. Checkout ModestMaps from SVN (using svn checkout http://modestmaps.mapstraction.com/svn/trunk )
    Run the script above from the mm directory (using the python shell will work). You may want to edit the main (the code in if __name__ == “__main__”) to get different areas.
    The sys.path.append call is used to tell Python to look for modules in trunk/py. You can change it to your ModestMaps python path if it’s somewhere else.
    Tiles will be stored in the tiles folder (located in the same path from which you started the script)
    Let me know if it helps,
    Denis

  3. Leonel says:

    Thanks Denis, for other users. I downloaded Python (2.5) from python.org. Then I downloaded PIL (Python Image Library) from pythonware.com. Installed both and change the sys.path.append parameter to the full path of the py API directory from ModestMaps. This last thing, I guess if for Windows.

    The script successfully runs but the image downloaded are “corrupted” many colors appear but no map. Maybe Google did something. I uploaded a sample tile here http://www.leonelgalan.com/mm.jpg.
    Thank you for your help.

  4. denis says:

    Did you try using another provider (Yahoo Maps for example)? Just have to change Google.AerialProvider() to Yahoo.AerialProvider()

    Let me know
    Denis

  5. Leonel says:

    Yahoo and Microsoft both download images but, they can’t be open with any program. Win XP “Windows Picture Viewer” displays “No Preview Available”.

    And when trying with Google Road Provider: I get this links on Python Shell:

    http://mt3.google.com/mt?n=404&v=w2.69&x=2038&y=3757&zoom=4
    http://mt3.google.com/mt?n=404&v=w2.69&x=4076&y=7514&zoom=3
    http://mt0.google.com/mt?n=404&v=w2.69&x=8152&y=15028&zoom=2
    http://mt0.google.com/mt?n=404&v=w2.69&x=16304&y=30056&zoom=1
    http://mt0.google.com/mt?n=404&v=w2.69&x=32608&y=60112&zoom=0
    http://mt2.google.com/mt?n=404&v=w2.69&x=65216&y=120224&zoom=-1
    http://mt2.google.com/mt?n=404&v=w2.69&x=130432&y=240448&zoom=-2

    The ones with a positive zoom open with QuickTime on my browser (not a standard behavior for jpg), but the ones with a negative zoom give a 404 error.

    I really appreciate all your help. I hope this troubleshooting helps other readers as well.

  6. denis says:

    Few things:
    Don’t know if wordpress escaped the urls correctly but there as HTML entities in your url (replace the &amp; for &)
    Zoom levels range from 0 to ~20 (depending on the layer you’re using)
    High-res imagery is not available everywhere so you might get 404’s for unpopulated areas.

Leave a Reply