Take Google Maps Offline

So I bought a Nokia N800, initially so I can work on a mobile version of allurstuff, but also I’m curious if I can get android working on it… But while I’m waiting for FedEx to deliver, I decided that it needed a way to access Google Maps even without an internet connection.

And so was born ogmaps. It’s a fairly simple python script that downloads all the HTML/Javascript/image files used by gmaps, and modifies them to run right off your hard drive. (or flash drive, whatever) It then looks up whatever location you give it, and caches all the surrounding map files. (within reason….grabs about 5-10mb of data for each location you give it)

You don’t need a handheld to use it either… It’ll work wherever you have python and firefox. (I haven’t tried it with IE yet, and likely won’t – get a better browser!)

Anyway, tell me what you think. :)

derek

21 Responses to “Take Google Maps Offline”

  1. derek Says:

    argh. all it pulls up is a white page on the n800’s browser. where’s an embedded version of firebug when you need it?

  2. Tobias Says:

    In case your not notified. I posted a bug about me getting a white page (or rather a “loading” page). This is with Opera (same thing in Nokia tablets) but it’s also the same in Firefox.
    I added some Opera JS error output.

  3. milmomma Says:

    I am running it on a mac with both firefox and safari. I am also getting the dreaded white page

  4. dave Says:

    Installed today from scratch. First time install. I am not a ‘power user.’ I had to install svn – edited my .profile – finally got ogmaps to connect and download all the relevant map files from google.

    rebooted macintosh (10.4) and unplugged network cable. Opened up ogmap.html and got a gray page with ‘Loading….’ in the upper left hand side.

    If I connect network cable and open ogmap.html it connects to google maps.

  5. dave Says:

    I made an error in my earlier comment – ogmap.html does NOT connect to google maps is network cable is connected.

  6. dave Says:

    Same problem with IE on Windows XP.

  7. yvan Says:

    15 days passed and I’m curious to know if you were able to do what you wanted to do on the N800? What happened?

  8. egiron Says:

    I did the same but I got a gray “Loading page” too, the mapfiles and tiles didn’t load. It seems your program does not replace correctly the urls or something like that because it try to connect to google.

  9. derek Says:

    hmm….yeah, looks like one of their updates broke ogmaps. will try to figure out a fix this weekend.

  10. Jake Says:

    Derek,
    I’m very interested in your fix if you come by one. Thanks for putting together such a slick program; here’s hoping it works out!

    P.S. Any chance for Windows binaries? Pretty please?

  11. Iain Says:

    Just downloaded from svn today, April 7th 2008. Having the same problem as others with the tiles not loading. Do you have any ETA for the update? I would love to be able to give this a try!

  12. Wafflematt Says:

    Downloaded today, and I get a javascript error when running it (gapp is not defined).
    Map tile data did download.

    Posted a bug on google code.

    I’d like this to work…

  13. blur Says:

    if the tiles not downloaded. try change the line 87 and 88 of ogmaps.py from v=w2.63 to v=w2.75.

  14. blur Says:


    #!/usr/bin/env python

    # OGMaps
    # Copyright (C) 2007 Derek Anderson
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of the GNU General Public License as published by
    # the Free Software Foundation; either version 2 of the License, or
    # (at your option) any later version.
    #
    # This program is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License along
    # with this program; if not, write to the Free Software Foundation, Inc.,
    # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

    import math, os, re, sys, urllib

    RUN_FROM_DIR = os.path.abspath(os.path.dirname(sys.argv[0])) + '/'
    DEFAULT_MAX_SPAN = 12

    from BeautifulSoup import BeautifulSoup
    import openanything

    def download_if_dne(href, filename):
    if os.path.isfile(filename):
    # print 'already downloaded:', href
    return False
    else:
    try:
    if href[0:7]!='http://' : href = 'http://maps.google.com'+href
    print 'downloading:', href
    oa = openanything.fetch(href)
    if oa['status']==200:
    file = open( filename, 'wb' )
    file.write( oa['data'] )
    file.close()
    return True
    except KeyboardInterrupt:
    raise
    except:
    print '\tdownload failed -', sys.exc_info()[0]
    return False

    def hide_if_found(o):
    if o:
    o['style'] = 'display:none;'

    def get_tile_coords(lat, lng, zl):
    tile_width = 256
    max_zoom_level = 17

    map_size = 2**((max_zoom_level + (math.log(tile_width)/math.log(2)))-zl)
    lng_degrees = abs(-180 - lng)
    lng_ppd = map_size / 360
    lng_ppd_radians = map_size/(2*math.pi)
    e = math.sin(lat*(1/180.*math.pi))
    if e>0.9999: e=0.9999
    if e max_span:
    x1f = (x1+x2)/2-max_span/2; x2f = (x1+x2)/2+max_span/2
    else:
    x1f, x2f = x1, x2
    if y2-y1 > max_span:
    y1f = (y1+y2)/2-max_span/2; y2f = (y1+y2)/2+max_span/2
    else:
    y1f, y2f = y1, y2

    # print 'x1,x2,y1,y2', x1f,x2f,y1f,y2f
    for x in range(x1f,x2f+1):
    for y in range(y1f,y2f+1):
    href = 'http://mt%i.google.com/mt?n=404&v=w2.75&x=%i&y=%i&zoom=%i' % ((x+y)%4,x,y,zl)
    filename = 'mt_n=404&v=w2.75&x=%i&y=%i&zoom=%i' % (x,y,zl)
    download_if_dne( href, os.path.join(RUN_FROM_DIR, 'data', 'tiles', filename) )

    def download(location=None, max_span=DEFAULT_MAX_SPAN):

    # init dirs
    for dir in [ 'data', os.path.join('data','tiles') ]:
    if not os.path.isdir(dir):
    os.mkdir(dir)

    # download the base page
    if location:
    print 'downloading the following location:', location
    oa = openanything.fetch( 'http://maps.google.com/maps?q='+urllib.quote_plus(location) )
    else:
    print 'downloading the default world map'
    oa = openanything.fetch('http://maps.google.com')
    if oa['status']!=200:
    print 'error connecting to http://maps.google.com - aborting'
    return
    html = oa['data']

    # find our loc,lat,lng
    p = re.compile('laddr:"([^"]+)"')
    m = p.search(html)
    if m:
    location = m.group(1)
    print '\tlocation =',location
    else:
    if location:
    print '\tlocation not found - aborting'
    return
    p = re.compile('center:{lat:([0-9.-]+),lng:([0-9.-]+)}')
    m = p.search(html)
    if m:
    lat, lng = float(m.group(1)), float(m.group(2))
    else:
    lat, lng = 37.0625,-95.677068
    print '\tlatitude, longitude = %f, %f' % (lat, lng)

    # find our zoom level
    p = re.compile('span:{lat:([0-9.]+),lng:([0-9.]+)}')
    m = p.search(html)
    if m:
    span_lat, span_lng = float(m.group(1)), float(m.group(2))
    else:
    span_lat, span_lng = 32, 64
    print '\tspan-latitude, span-longitude = %f, %f' % (span_lat, span_lng)

    mapfiles = 'http://www.google.com/intl/en_All/mapfiles/117/maps2'

    # perform some base transformations
    html = html.replace(' ', '') # beautifulsoup doesn't like this char
    html = html.replace('window.document.title = vPage.title;', 'window.document.title = "Offline Google Maps - http://code.google.com/p/ogmaps/";')
    html = html.replace('http://mt0.google.com/mt?', 'data\\x2ftiles\\x2fmt?')
    html = html.replace('http://mt1.google.com/mt?', 'data\\x2ftiles\\x2fmt?')
    html = html.replace('http://mt2.google.com/mt?', 'data\\x2ftiles\\x2fmt?')
    html = html.replace('http://mt3.google.com/mt?', 'data\\x2ftiles\\x2fmt?')
    html = html.replace('body{margin-top: 3px;margin-bottom: 0;margin-left: 8px;}', 'body{margin:0px;}')
    html = html.replace('#map {left: 20em;margin-left: 8px;margin-right: 20em;', '#map {')
    html = html.replace('var height = getWindowHeight() - offsetTop - 10;', 'var height = getWindowHeight() - offsetTop;')

    # get our kitchen
    soup = BeautifulSoup(html)

    hide_if_found( soup.find('div', attrs={'id':'header'}) )
    hide_if_found( soup.find('div', attrs={'id':'guser'}) )
    hide_if_found( soup.find('div', attrs={'id':'gbar'}) )
    hide_if_found( soup.find('div', attrs={'id':'gbh'}) )
    hide_if_found( soup.find('div', attrs={'id':'hp'}) )
    hide_if_found( soup.find('div', attrs={'id':'panel'}) )
    hide_if_found( soup.find('a', attrs={'id':'paneltoggle'}) )

    o = soup.find('div', attrs={'id':'actions'})
    if o:
    o['style'] = 'display:none;'

    # get main.js and transmogrify
    if not os.path.isfile(os.path.join(RUN_FROM_DIR, 'data', 'main.js')):
    print 'downloading:', mapfiles+'/main.js'
    oa = openanything.fetch(mapfiles+'/main.js')
    js = oa['data']
    js = js.replace('function rf(a,b){','function rf(a,b){b = b.replace("tiles/mt?","tiles/mt_");')
    js = js.replace('mb("/maps/gen_204?ev=failed_tile&cad="+f)','mb("data/transparent.png")')
    js = js.replace('document.body.style[Nk]=$(0);document.body.style[Fe]=$(8)','')
    js = js.replace('this.u.id="hmtctl";','this.u.id="hmtctl";this.u.style.display="none";')
    file = open( os.path.join(RUN_FROM_DIR, 'data', 'main.js'), 'w' )
    file.write( js )
    file.close()

    # get mod_cb.js and transmogrify
    if not os.path.isfile(os.path.join(RUN_FROM_DIR, 'data', 'mod_cb.js')):
    print 'downloading:', mapfiles+'/mod_cb.js'
    oa = openanything.fetch(mapfiles+'/mod_cb.js')
    js = oa['data']
    js = js.replace('/mapfiles/cb','data')
    js = js.replace('c.id="cbcontrol";','c.id="cbcontrol";c.style.display="none;";')
    file = open( os.path.join(RUN_FROM_DIR, 'data', 'mod_cb.js'), 'w' )
    file.write( js )
    file.close()

    # get mod_traffic_app.js and transmogrify
    if not os.path.isfile(os.path.join(RUN_FROM_DIR, 'data', 'mod_traffic_app.js')):
    print 'downloading:', mapfiles+'/mod_traffic_app.js'
    oa = openanything.fetch(mapfiles+'/mod_traffic_app.js')
    js = oa['data']
    js = js.replace('/maps/tldata','data/tldata')
    file = open( os.path.join(RUN_FROM_DIR, 'data', 'mod_traffic_app.js'), 'w' )
    file.write( js )
    file.close()

    # get mod_ms.js and transmogrify
    if not os.path.isfile(os.path.join(RUN_FROM_DIR, 'data', 'mod_ms.js')):
    print 'downloading:', mapfiles+'/mod_ms.js'
    oa = openanything.fetch(mapfiles+'/mod_ms.js')
    js = oa['data']
    js = js.replace('http://maps.google.com','data')
    js = js.replace('/mapfiles','')
    file = open( os.path.join(RUN_FROM_DIR, 'data', 'mod_ms.js'), 'w' )
    file.write( js )
    file.close()

    # get other scripts
    scripts = [ 'mod_mymaps.js', 'mod_mpl_host.js', 'mod_kml.js', 'mod_le.js', 'mod_lyrsctrl.js','mod_trends.js', 'mod_infowindow.js']
    for s in scripts:
    download_if_dne( mapfiles+'/'+s, os.path.join(RUN_FROM_DIR, 'data', s) )

    # get linked scripts
    for tag in soup.findAll('link'):
    try:
    href = tag['href']
    filename = href.split('/')[-1]
    download_if_dne( href, os.path.join(RUN_FROM_DIR, 'data', filename) )
    tag['href'] = 'data/'+filename
    except:
    print 'error:', tag

    # get all static images
    for tag in soup.findAll('img'):
    try:
    src = tag['src']
    filename = src.split('/')[-1]
    download_if_dne( src, os.path.join(RUN_FROM_DIR, 'data', filename) )
    tag['src'] = 'data/'+filename
    except:
    # print 'error:', tag
    pass

    # get other misc files
    download_if_dne( 'http://www.google.com/mapfiles/cb/bounds_cippppt.txt', os.path.join(RUN_FROM_DIR, 'data', 'bounds_cippppt.txt') )
    download_if_dne( 'http://maps.google.com/maps/tldata?tldtype=1&hl=en&country=us&callback=_xdc_._1f9onnphn', os.path.join(RUN_FROM_DIR, 'data', 'tldata') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/arrow-white.png', os.path.join(RUN_FROM_DIR, 'data', 'arrow-white.png') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/arrow.png', os.path.join(RUN_FROM_DIR, 'data', 'arrow.png') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/lmc.png', os.path.join(RUN_FROM_DIR, 'data', 'lmc.png') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/lmc-bottom.png', os.path.join(RUN_FROM_DIR, 'data', 'lmc-bottom.png') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/slider.png', os.path.join(RUN_FROM_DIR, 'data', 'slider.png') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/scale.png', os.path.join(RUN_FROM_DIR, 'data', 'scale.png') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/arrowtransparent.png', os.path.join(RUN_FROM_DIR, 'data', 'arrowtransparent.png') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/overcontract.gif', os.path.join(RUN_FROM_DIR, 'data', 'overcontract.gif') )
    download_if_dne( 'http://maps.google.com/mapfiles/etna.jpg', os.path.join(RUN_FROM_DIR, 'data', 'etna.jpg') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/drag_cross_67_16.png', os.path.join(RUN_FROM_DIR, 'data', 'drag_cross_67_16.png') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/iws2.png', os.path.join(RUN_FROM_DIR, 'data', 'iws2.png') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/iw2.png', os.path.join(RUN_FROM_DIR, 'data', 'iw2.png') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/iw_close.gif', os.path.join(RUN_FROM_DIR, 'data', 'iw_close.gif') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/iw_plus.gif', os.path.join(RUN_FROM_DIR, 'data', 'iw_plus.gif') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/iw_fullscreen.gif', os.path.join(RUN_FROM_DIR, 'data', 'iw_fullscreen.gif') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/iw_minus.gif', os.path.join(RUN_FROM_DIR, 'data', 'iw_minus.gif') )
    download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/transparent.gif', os.path.join(RUN_FROM_DIR, 'data', 'transparent.gif') )
    # download_if_dne( 'http://www.google.com/intl/en_All/mapfiles/', os.path.join(RUN_FROM_DIR, 'data', '') )

    # some post transformations, then write to disk
    html = soup.prettify()
    html = html.replace(mapfiles, 'data')
    html = html.replace('http://www.google.com/intl/en_All/mapfiles', 'data')
    html = html + 'div.contextmenu {display:none;}'

    html = html.replace('/intl/en_ALL/mapfiles/117/maps2','./data')
    html = html.replace('/intl/en_ALL/mapfiles/','./data/')
    html = html.replace('_mStaticPath+\'/cb','_mStaticPath+\'/')

    file = open( os.path.join(RUN_FROM_DIR, 'ogmap.html'), 'w')
    file.write( html )
    file.close()
    #print html

    # get map data
    for zl in range(17,0,-1):
    get_map_data( get_tile_coords( lat-span_lat, lng-span_lng, zl ), get_tile_coords( lat+span_lat, lng+span_lng, zl ), zl, max_span )

    print '\nyour offline google map is ready at:', RUN_FROM_DIR+'ogmap.html'

    if __name__ == "__main__":
    max_span = DEFAULT_MAX_SPAN
    locations = []
    if len(sys.argv)>1:
    for arg in sys.argv[1:]:
    if arg.startswith('--'):
    if arg.startswith('--max-span='):
    max_span = int(arg[11:])
    print 'using max_span =', max_span
    else:
    locations.append(arg)

    if not len(locations):
    download(None, max_span)
    else:
    for location in locations:
    download(location, max_span)

    this is my modified version to avoid those errors.
    no error any more, and tiles downloaded.

    but still nothing on the map.

  15. FreakyT Says:

    I tried it, most of the images failed to download (blur’s fix managed to get it to download the tiles, but that didn’t help), and I also get a “gapp is not defined” error. Any chance of a fix?

  16. Lode Says:

    I also got the ‘gapp is not defined’ error. The images do work if you use this patch:

    Index: ogmaps.py
    ===================================================================
    — ogmaps.py (revision 8) +++ ogmaps.py (working copy)
    @@ -31,6 +31,9 @@
    # print ‘already downloaded:’, href
    return False
    else:
    + if not href.startswith(‘http://’):
    + href = ‘http://maps.google.com’ + href
    + print ‘Fixed url :’, href
    try:
    print ‘downloading:’, href
    oa = openanything.fetch(href)
    @@ -84,8 +87,8 @@
    # print ‘x1,x2,y1,y2′, x1f,x2f,y1f,y2f
    for x in range(x1f,x2f+1):
    for y in range(y1f,y2f+1):
    - href = ‘http://mt%i.google.com/mt?n=404&v=w2.63&x=%i&y=%i&zoom=%i’ % ((x+y)%4,x,y,zl)
    - filename = ‘mt_n=404&v=w2.63&x=%i&y=%i&zoom=%i’ % (x,y,zl)
    + href = ‘http://mt%i.google.com/mt?n=404&v=w2t.75&x=%i&y=%i&zoom=%i’ % ((x+y)%4,x,y,zl)
    + filename = ‘mt_n=404&v=w2t.75&s=Galileo&x=%i&y=%i&zoom=%i’ % (x,y,zl)
    download_if_dne( href, os.path.join(RUN_FROM_DIR, ‘data’, ’tiles’, filename) )

  17. captainjc Says:

    current version is 2.80. that gets it to download the tiles, but still won’t display. would be great if someone would update this to work with the new google maps code.

  18. ermes Says:

    HI! I used this script but I’ve had some problems with the parameter v!! when google upgrade the version of the api I must upgrade the link used by the script. do you know if is it possible to control the version in a changelog??

    sorry for my english!

  19. Corey Says:

    I downloaded it and it runs perfectly, and outputs the html page for “moab, ut” When I open it in FF I get only the default zoom level, with no options to zoom in or out. However, in the /data folder, I see the zoom level tiles. Am I missing something?

  20. Erik Chavez Says:

    Downloaded today Subversion for Mac, I am using a Macbook. I had no problems with subversion or the tile downloads, but when I opened the ogmap.html I noticed under safari that ogmaps/maps/gen_204?imp=slm has a “file doesn’t exist” under the activity. I get a blank page also.

    Tried opening with Firefox, but I only see a level 0 map of my downloaded files, links are not enabled under the zoom option.
    Has anyone figured this out yet?

  21. Satya Says:

    derek,

    I need your help, i need to develop same tool in Java with some other customizations.

    Big question for me is, how did you develop this tool? it seems like you know in and out of the google map implementation.
    Where did you get this information? do you have any documents/article on this?

    Can you please pass that information to me?? my email id: satya.office@gmail.com

    – Satya

Leave a Reply


<Kered.org>   © Copyright 2000-2005 by Derek Anderson
Get Firefox