├── LICENSE
├── .gitignore
├── README.md
└── mbtilesToPngs.py
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Stu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
106 | # mbtile files
107 | 16/
108 | *.mbtiles
109 |
110 | # os files
111 | .DS_Store
112 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mbtilesToPngs
2 |
3 | This python program takes __rasterised__ .mbtiles files and extracts the images and places them into the correct directory structure `/${z}/${x}/${y}.png` that mimicks OpenStreetMap URI tile structure. This is to allow for offline use of OpenStreetMaps, and I will be using [Flutter_Map](https://github.com/apptreesoftware/flutter_map) along with my own images for offline maps.
4 |
5 | ## What is .mbtiles?
6 |
7 | In short, .mbtiles is a SQLite database file, with tile data stored as binary objects [(BLOBS)](https://en.wikipedia.org/wiki/Binary_large_object) in the database. There are two main formats these BLOBS can be for .mbtiles:
8 |
9 | 1. PBF for vectored tiles (think mathematical points, lines, polygons)
10 | 2. PNG for rasterised tiles (images).
11 |
12 | This script only extracts the data from within the .mbtiles (it does not convert the data). This script only works for **Rasterised** .mbtiles that are already in .PNG format.
13 |
14 | ## Where to get .mbtiles?
15 |
16 | OpenStreetMap says:
17 | > Apart from very limited testing purposes, you should not use the tiles supplied by OpenStreetMap.org itself. OpenStreetMap is a volunteer-run non-profit body and cannot supply tiles for large-scale commercial use. Rather, you should use a third party provider that makes tiles from OSM data, or generate your own.
18 |
19 | As far as I see, OpenStreetMap only offers MBTiles in [PBF format](https://docs.safe.com/fme/html/FME_Desktop_Documentation/FME_ReadersWriters/osmpbf/osmpbf.htm) which is a vectored based format. This script will not work with this format.
20 |
21 | There are various ways to download .mbtiles in PNG format, below is a OSX method:
22 |
23 | [A Guide to generate your own](https://tilemill-project.github.io/tilemill/docs/guides/osm-bright-mac-quickstart/)
24 |
25 | This guide will go through the steps for compiling map data, downloading a specific area, customise the map, then export to mbtile. Warning! For Mac, when you install TileMill.app, it currently fails to start. Go into *TileMill* > *Updates Preference* > *check Install Developer Builds*.
26 |
27 | ## How to use
28 |
29 | Make sure you have python3 install.
30 | ```bash
31 | python3 mbtilesToPngs.py -i path/to/.mbtiles
32 | ```
33 |
34 | For example:
35 | > python3 sqliteReader.py -i ./OSMBright.mbtiles
36 |
37 | ## My map says file not found! Why are the filenames incorrect?
38 |
39 | [Read Here](http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/)
40 | [And here](https://alastaira.wordpress.com/2011/07/06/converting-tms-tile-coordinates-to-googlebingosm-tile-coordinates/)
41 | I've added option to convert TMS Tile Coordinates to Google/Bing/OSM Tile Coordinates. Just use `-tms` when running.
42 |
43 | ## Links
44 |
45 | [MBTiles](https://wiki.openstreetmap.org/wiki/MBTiles)
46 | [Tiles](https://wiki.openstreetmap.org/wiki/Tiles)
47 |
48 | ## License
49 |
50 | This project is licensed under the [MIT License](LICENSE.md)
51 |
52 | ## Acknowledgments
53 |
54 | Thank you © OpenStreetMap (and) contributors, ODbL for the data!
55 | Thank you AppTreeSoftware for [Flutter_Map](https://github.com/apptreesoftware/flutter_map)
56 |
--------------------------------------------------------------------------------
/mbtilesToPngs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import sqlite3
3 | from sqlite3 import Error
4 | import errno
5 | import os
6 | import sys
7 | import getopt
8 | import time
9 |
10 |
11 | def create_connection(db_file):
12 | """ create a database connection to the SQLite database
13 | specified by the db_file
14 | :param db_file: database file
15 | :return: Connection object or None
16 | """
17 | try:
18 | conn = sqlite3.connect(db_file)
19 | return conn
20 | except Error as e:
21 | print(e)
22 |
23 | return None
24 |
25 |
26 | def select_all_tiles(conn, tms, databaseName):
27 | cur = conn.cursor()
28 | cur.execute("SELECT * FROM map")
29 |
30 | rows = cur.fetchall()
31 | assets = {}
32 | print("Map count: " + str(len(rows)))
33 | print("TMS: " + str(tms))
34 | for row in rows:
35 | image = get_image(conn, row[3])
36 |
37 | # for printing
38 | dir = str(row[0]) + "/" + str(row[1])
39 | assets[dir] = dir
40 |
41 | # tsm check
42 | yValue = row[2]
43 | if(tms):
44 | ymax = 1 << row[0]
45 | yValue = ymax - row[2] - 1
46 |
47 | # generate png
48 | blob_to_file(
49 | databaseName,
50 | str(row[0]), # {z}
51 | str(row[1]), # {x}
52 | str(yValue), # {y}
53 | image, # data
54 | )
55 | for asset in assets:
56 | print ('- assets/map/' + databaseName + '/' + assets[asset] + "/")
57 |
58 |
59 | def get_image(conn, id):
60 | cur = conn.cursor()
61 | cur.execute("SELECT * FROM images WHERE tile_id = ?", [id])
62 |
63 | row = cur.fetchone()
64 | return(row[0])
65 |
66 |
67 | def mkdir_p(path):
68 | try:
69 | os.makedirs(path)
70 | except OSError as exc: # Python >2.5
71 | if exc.errno == errno.EEXIST and os.path.isdir(path):
72 | pass
73 | else:
74 | raise
75 |
76 |
77 | def blob_to_file(dir0, dir1, dir2, dir3, ablob):
78 | directory = os.path.join(os.path.expanduser('~'),
79 | "Desktop", dir0, "map", dir1, dir2)
80 | if not os.path.exists(directory):
81 | os.makedirs(directory)
82 | # mkdir_p("~/Desktop/" + dir0) # database
83 | # mkdir_p("~/Desktop/" + dir0 + "/map") # map
84 | # mkdir_p("~/Desktop/" + dir0 + "/map/" + dir1 + "/" + dir2) # zoom
85 | # mkdir_p("/" + dir0 + "/map/" + dir1 +
86 | # "/" + dir2 + "/" + dir3) # column
87 | filename = os.path.join(directory, (dir3 + ".png"))
88 | with open(filename, 'wb') as output_file:
89 | output_file.write(ablob)
90 |
91 |
92 | def update_tms_row_to_zxy(conn):
93 | cur = conn.cursor()
94 | cur.execute("SELECT * FROM map")
95 | rows = cur.fetchall()
96 | for row in rows:
97 | ymax = 1 << row[0]
98 | yValue = ymax - row[2] - 1
99 | cur.execute("UPDATE map SET tile_row = ? WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?",
100 | (yValue, row[0], row[1], row[2]))
101 | conn.commit()
102 |
103 |
104 | def begin_convertion(database, tms):
105 | # create a database connection
106 | conn = sqlite3.connect(database)
107 | if database.__contains__("/"):
108 | database = database.split("/")[-1]
109 | with conn:
110 | print("Processing mbtiles..\n***********\nIf you find your y coordinates (filename.png) are incorrect, use the -tsm option\n***********\n")
111 | select_all_tiles(conn, tms, database.split(".")[0])
112 | if tms:
113 | print("Do you want to update the mbtiles database to zxy index?\n(y/n)")
114 | strInput = input()
115 | if "y" in strInput:
116 | update_tms_row_to_zxy(conn)
117 | conn.close()
118 |
119 |
120 | def main(argv):
121 | inputDir = ''
122 | tms = False
123 | try:
124 | opts, args = getopt.getopt(argv, "hi:o:tms", ["ifile="])
125 | except getopt.GetoptError:
126 | print ('mbtilesToPngs.py -i (.mbtiles only)')
127 | sys.exit(2)
128 | for opt, arg in opts:
129 | if opt == '-h':
130 | print ('mbtilesToPngs.py -i (.mbtiles only)')
131 | sys.exit()
132 | elif opt in ("-i", "--ifile"):
133 | inputDir = str(arg)
134 | if not os.path.isfile(inputDir):
135 | print (inputDir, " file not found")
136 | sys.exit()
137 | elif opt in ("-tms"):
138 | print("-tsm checked")
139 | tms = True
140 | if inputDir == '':
141 | print ('mbtilesToPngs.py -i (.mbtiles only)')
142 | sys.exit(2)
143 | start = time.time()
144 | begin_convertion(inputDir, tms)
145 | end = time.time()
146 | print("Time taken to complete: ", str(round((end - start), 2)), "s")
147 |
148 |
149 | if __name__ == "__main__":
150 | main(sys.argv[1:])
151 |
--------------------------------------------------------------------------------