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

17 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.

Leave a Reply


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