4 # http://github.com/mapbox/mbutil
8 import sqlite3, sys, logging, time, os, re
10 from optparse import OptionParser
12 logger = logging.getLogger(__name__)
15 # Functions from mbutil for sqlite DB format and connections
19 return (2**zoom-1) - y
21 def mbtiles_setup(cur):
29 cur.execute("""create table metadata
30 (name text, value text);""")
31 cur.execute("""create unique index name on metadata (name);""")
32 cur.execute("""create unique index tile_index on tiles
33 (zoom_level, tile_column, tile_row);""")
35 def mbtiles_connect(mbtiles_file):
37 con = sqlite3.connect(mbtiles_file)
39 except Exception as e:
40 logger.error("Could not connect to database")
44 def optimize_connection(cur):
45 cur.execute("""PRAGMA synchronous=0""")
46 cur.execute("""PRAGMA locking_mode=EXCLUSIVE""")
47 cur.execute("""PRAGMA journal_mode=DELETE""")
49 def write_database(cur):
50 logger.debug('analyzing db')
51 cur.execute("""ANALYZE;""")
53 def optimize_database(cur):
54 logger.debug('cleaning db')
55 cur.execute("""VACUUM;""")
58 # End functions from mbutils
61 # Based on disk_to_mbtiles in mbutil
62 def vikcache_to_mbtiles(directory_path, mbtiles_file, **kwargs):
63 logger.debug("%s --> %s" % (directory_path, mbtiles_file))
64 con = mbtiles_connect(mbtiles_file)
66 optimize_connection(cur)
70 start_time = time.time()
72 onlydigits_re = re.compile ('^\d+$')
74 #print ('tileid ' + kwargs.get('tileid'))
75 # Need to split tDddsDdzD
76 # note zoom level can be negative hence the '-?' term
77 p = re.compile ('^t'+kwargs.get('tileid')+'s(-?\d+)z\d+$')
78 for ff in os.listdir(directory_path):
79 # Find only dirs related to this tileset
85 # For some reason Viking does '17-zoom level' - so need to reverse that
88 for r2, xs, ignore in os.walk(os.path.join(directory_path, ff)):
90 # Try to ignore any non cache directories
91 m2 = onlydigits_re.match(x);
93 #print('x:'+directory_path+'/'+ff+'/'+x)
94 for r3, ignore, ys in os.walk(os.path.join(directory_path, ff, x)):
96 # Legacy viking cache file names only made from digits
97 m3 = onlydigits_re.match(y);
99 #print('tile:'+directory_path+'/'+ff+'/'+x+'/'+y)
100 f = open(os.path.join(directory_path, ff, x, y), 'rb')
101 # Viking in xyz so always flip
102 y = flip_y(int(z), int(y))
103 cur.execute("""insert into tiles (zoom_level,
104 tile_column, tile_row, tile_data) values
106 (z, x, y, sqlite3.Binary(f.read())))
109 if (count % 100) == 0:
110 for c in msg: sys.stdout.write(chr(8))
111 msg = "%s tiles inserted (%d tiles/sec)" % (count, count / (time.time() - start_time))
112 sys.stdout.write(msg)
114 msg = "\nTotal tiles inserted %s \n" %(count)
115 sys.stdout.write(msg)
117 print ("No tiles inserted. NB This method only works with the Legacy Viking cache layout")
120 if not kwargs.get('nooptimize'):
121 sys.stdout.write("Optimizing...\n")
122 optimize_database(con)
125 def mbtiles_to_vikcache(mbtiles_file, directory_path, **kwargs):
126 logger.debug("Exporting MBTiles to disk")
127 logger.debug("%s --> %s" % (mbtiles_file, directory_path))
128 con = mbtiles_connect(mbtiles_file)
129 count = con.execute('select count(zoom_level) from tiles;').fetchone()[0]
132 base_path = directory_path
133 if not os.path.isdir(base_path):
134 os.makedirs(base_path)
136 start_time = time.time()
138 tiles = con.execute('select zoom_level, tile_column, tile_row, tile_data from tiles;')
144 # Viking in xyz so always flip
145 y = flip_y(int(z), int(y))
146 # For some reason Viking does '17-zoom level' - so need to reverse that
148 tile_dir = os.path.join(base_path, 't'+ kwargs.get('tileid') + 's' + str(vz) + 'z0', str(x))
149 if not os.path.isdir(tile_dir):
150 os.makedirs(tile_dir)
151 # NB no extension for VikCache files
152 tile = os.path.join(tile_dir,'%s' % (y))
153 # Only overwrite existing tile if specified
154 if not os.path.isfile(tile) or kwargs.get('force'):
159 if (done % 100) == 0:
160 for c in msg: sys.stdout.write(chr(8))
161 msg = "%s / %s tiles imported (%d tiles/sec)" % (done, count, done / (time.time() - start_time))
162 sys.stdout.write(msg)
164 msg = "\nTotal tiles imported %s \n" %(done)
165 sys.stdout.write(msg)
169 ## Start of code here
171 parser = OptionParser(usage="""usage: %prog -m <mode> [options] input output
173 When either the input or output refers to a Viking legacy cache ('vcl'), is it the root directory of the cache, typically ~/.viking-maps
177 Export Viking's legacy cache files of a map type to an mbtiles file:
178 $ ./viking-cache.py -m vlc2mbtiles -t 17 ~/.viking-maps OSM_Cycle.mbtiles
180 Note you can use the http://github.com/mapbox/mbutil mbutil script to further handle .mbtiles
181 such as converting it into an OSM tile layout and then pointing a new Viking Map at that location with the map type of 'On Disk OSM Layout'
183 Import from an MB Tiles file into Viking's legacy cache file layout, forcing overwrite of existing tiles:
184 $ ./viking-cache.py -m mbtiles2vlc -t 321 -f world.mbtiles ~/.viking-maps
185 NB: You'll need to a have a corresponding ~/.viking/maps.xml definition for the tileset id when it is not a built in id
188 parser.add_option('-t', '--tileid', dest='tileid',
190 help='''Tile id of Viking map cache to use (19 if not specified as this is Viking's default (MaqQuest))''',
194 parser.add_option('-n', '--nooptimize', dest='nooptimize',
196 help='''Do not attempt to optimize the mbtiles output file''',
199 parser.add_option('-f', '--force', dest='force',
201 help='''Force overwrite of existing tiles''',
204 parser.add_option('-m', '--mode', dest='mode',
206 help='''Mode of operation which must be specified. "vlc2mbtiles", "mbtiles2vlc", "vlc2osm", "osm2vlc"''',
210 (options, args) = parser.parse_args()
212 if options.__dict__.get('mode') == 'none':
213 sys.stderr.write ("\nError: Mode not specified\n")
223 if options.__dict__.get('mode') == 'vlc2mbtiles':
225 if os.path.isdir(args[0]) and not os.path.isfile(args[0]):
226 vikcache_to_mbtiles(in_fd, out_fd, **options.__dict__)
228 if options.__dict__.get('mode') == 'mbtiles2vlc':
230 if os.path.isfile(args[0]):
231 mbtiles_to_vikcache(in_fd, out_fd, **options.__dict__)