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








December 6th, 2007 at 3:07 am
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?
December 11th, 2007 at 12:31 am
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.
December 12th, 2007 at 4:07 pm
I am running it on a mac with both firefox and safari. I am also getting the dreaded white page
December 14th, 2007 at 4:16 pm
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.
December 14th, 2007 at 4:45 pm
I made an error in my earlier comment – ogmap.html does NOT connect to google maps is network cable is connected.
December 14th, 2007 at 7:59 pm
Same problem with IE on Windows XP.
December 23rd, 2007 at 2:52 am
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?
February 14th, 2008 at 4:02 pm
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.
February 21st, 2008 at 10:50 am
hmm….yeah, looks like one of their updates broke ogmaps. will try to figure out a fix this weekend.
February 21st, 2008 at 5:41 pm
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?
April 7th, 2008 at 12:11 pm
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!
June 8th, 2008 at 9:45 pm
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…
June 27th, 2008 at 1:11 am
if the tiles not downloaded. try change the line 87 and 88 of ogmaps.py from v=w2.63 to v=w2.75.
June 27th, 2008 at 3:05 am
#!/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.
July 7th, 2008 at 1:52 pm
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?
July 17th, 2008 at 11:24 am
I also got the ‘gapp is not defined’ error. The images do work if you use this patch:
Index: ogmaps.py
+++ ogmaps.py (working copy)
===================================================================
— ogmaps.py (revision
@@ -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) )
July 31st, 2008 at 6:44 pm
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.
January 29th, 2009 at 6:49 pm
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!
February 22nd, 2009 at 3:18 pm
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?
June 19th, 2009 at 1:41 pm
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?
September 2nd, 2009 at 6:26 am
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