summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis Nienhüser <nienhueser@kde.org>2016-07-31 08:50:58 (GMT)
committerDennis Nienhüser <nienhueser@kde.org>2016-07-31 08:50:58 (GMT)
commit0696a4c5d3b665895b0c2c6db28b822bbd290b4d (patch)
tree9011263e7ba63e65bbfc03fb97e63d1e03740761
parenta03f06194b8686361a2a01e5b8747b316c831ef8 (diff)
Add a tool to convert z/x/y tile directories to a .mbtile database
-rw-r--r--tools/CMakeLists.txt1
-rw-r--r--tools/mbtile-import/CMakeLists.txt12
-rw-r--r--tools/mbtile-import/MbTileWriter.cpp145
-rw-r--r--tools/mbtile-import/MbTileWriter.h43
-rw-r--r--tools/mbtile-import/mbtile-import.cpp126
5 files changed, 327 insertions, 0 deletions
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index a0c7134..2053eed 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -8,6 +8,7 @@ add_subdirectory( dso2kml )
add_subdirectory( iau2kml )
add_subdirectory( kml2cache )
add_subdirectory( kml2kml )
+add_subdirectory( mbtile-import )
add_subdirectory( osm-simplify )
add_subdirectory( poly2kml )
add_subdirectory( pnt2svg )
diff --git a/tools/mbtile-import/CMakeLists.txt b/tools/mbtile-import/CMakeLists.txt
new file mode 100644
index 0000000..b4fc383
--- /dev/null
+++ b/tools/mbtile-import/CMakeLists.txt
@@ -0,0 +1,12 @@
+SET (TARGET mbtile-import)
+PROJECT (${TARGET})
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+set( ${TARGET}_SRC mbtile-import.cpp MbTileWriter.cpp)
+add_executable( ${TARGET} ${${TARGET}_SRC} )
+
+target_link_libraries( ${TARGET} Qt5::Sql marblewidget-qt5 )
diff --git a/tools/mbtile-import/MbTileWriter.cpp b/tools/mbtile-import/MbTileWriter.cpp
new file mode 100644
index 0000000..c9112d9
--- /dev/null
+++ b/tools/mbtile-import/MbTileWriter.cpp
@@ -0,0 +1,145 @@
+//
+// This file is part of the Marble Virtual Globe.
+//
+// This program is free software licensed under the GNU LGPL. You can
+// find a copy of this license in LICENSE.txt in the top directory of
+// the source code.
+//
+// Copyright 2016 Dennis Nienhüser <nienhueser@kde.org>
+//
+
+#include "MbTileWriter.h"
+
+#include <QDebug>
+#include <QSqlDatabase>
+#include <QSqlQuery>
+#include <QSqlError>
+
+#include <iostream>
+
+namespace Marble
+{
+
+MbTileWriter::MbTileWriter(const QString &filename, const QString &extension) :
+ m_overwriteTiles(true),
+ m_reportProgress(true)
+{
+ bool const exists = QFileInfo(filename).exists();
+
+ QSqlDatabase database = QSqlDatabase::addDatabase( "QSQLITE" );
+ database.setDatabaseName( filename );
+ if ( !database.open() ) {
+ qCritical() << "Failed to connect to database";
+ return;
+ }
+
+ if (!exists) {
+ execQuery("PRAGMA application_id = 0x4d504258"); // MBTiles tileset, see https://www.sqlite.org/src/artifact?ci=trunk&filename=magic.txt
+ execQuery("CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob);");
+ execQuery("CREATE UNIQUE INDEX tile_index ON tiles(zoom_level, tile_column, tile_row);");
+
+ execQuery("CREATE TABLE metadata (name text, value text);");
+ setMetaData("name", "Marble Vector OSM");
+ setMetaData("type", "baselayer");
+ setMetaData("version", "1.0");
+ setMetaData("description", "A global roadmap created by the OpenStreetMap (OSM) project");
+ setMetaData("format", extension);
+ setMetaData("attribution", "Data from <a href=\"http://openstreetmap.org/\">OpenStreetMap</a> and <a href=\"http://www.naturalearthdata.com/\">Natural Earth</a> contributors");
+ }
+ execQuery("BEGIN TRANSACTION");
+}
+
+MbTileWriter::~MbTileWriter()
+{
+ execQuery("END TRANSACTION");
+ if (m_reportProgress) {
+ std::cout << std::endl;
+ }
+}
+
+void MbTileWriter::setOverwriteTiles(bool overwrite)
+{
+ m_overwriteTiles = overwrite;
+}
+
+void MbTileWriter::setReportProgress(bool report)
+{
+ m_reportProgress = report;
+}
+
+void MbTileWriter::addTile(const QFileInfo &file, qint32 x, qint32 y, qint32 z)
+{
+ if (!m_overwriteTiles && haveTile(x, y, z)) {
+ if (m_reportProgress) {
+ std::cout << "Skipping existing " << z << '/' << x << '/' << y << '\r';
+ std::cout.flush();
+ }
+ return;
+ }
+
+ if (m_reportProgress) {
+ std::cout << "Adding " << z << '/' << x << '/' << y << '\r';
+ std::cout.flush();
+ }
+
+ QFile tileContent(file.absoluteFilePath());
+ tileContent.open(QFile::ReadOnly);
+ QSqlQuery query;
+ query.prepare( "INSERT OR REPLACE INTO tiles"
+ " (zoom_level, tile_column, tile_row, tile_data)"
+ " VALUES (?, ?, ?, ?)" );
+ query.addBindValue(z);
+ query.addBindValue(x);
+ query.addBindValue(y);
+ query.addBindValue(tileContent.readAll());
+ execQuery(query);
+}
+
+bool MbTileWriter::haveTile(qint32 x, qint32 y, qint32 z) const
+{
+ QSqlQuery query;
+ query.prepare( "SELECT EXISTS(SELECT 1 FROM tiles"
+ " WHERE zoom_level=? AND tile_column=? AND tile_row=?);");
+ query.addBindValue(z);
+ query.addBindValue(x);
+ query.addBindValue(y);
+ query.exec();
+ if (query.lastError().isValid()) {
+ qCritical() << "Problems occurred when executing the query" << query.executedQuery();
+ qCritical() << "SQL error: " << query.lastError();
+ } else {
+ if (query.next()) {
+ return query.value(0).toBool();
+ }
+ }
+ return false;
+}
+
+void MbTileWriter::execQuery( const QString &query ) const
+{
+ QSqlQuery sqlQuery( query );
+ if ( sqlQuery.lastError().isValid() ) {
+ qCritical() << "Problems occurred when executing the query" << query;
+ qCritical() << "SQL error: " << sqlQuery.lastError();
+ }
+}
+
+void MbTileWriter::execQuery( QSqlQuery &query ) const
+{
+ query.exec();
+ if ( query.lastError().isValid() ) {
+ qCritical() << "Problems occurred when executing the query" << query.executedQuery();
+ qCritical() << "SQL error: " << query.lastError();
+ }
+}
+
+void MbTileWriter::setMetaData(const QString &name, const QString &value)
+{
+ QSqlQuery query;
+ query.prepare("INSERT INTO metadata (name, value) VALUES (?, ?)");
+ query.addBindValue(name);
+ query.addBindValue(value);
+ execQuery(query);
+}
+
+}
diff --git a/tools/mbtile-import/MbTileWriter.h b/tools/mbtile-import/MbTileWriter.h
new file mode 100644
index 0000000..39e10f3
--- /dev/null
+++ b/tools/mbtile-import/MbTileWriter.h
@@ -0,0 +1,43 @@
+//
+// This file is part of the Marble Virtual Globe.
+//
+// This program is free software licensed under the GNU LGPL. You can
+// find a copy of this license in LICENSE.txt in the top directory of
+// the source code.
+//
+// Copyright 2016 Dennis Nienhüser <nienhueser@kde.org>
+//
+
+#ifndef MARBLE_MBTILEWRITER_H
+#define MARBLE_MBTILEWRITER_H
+
+#include <QSqlQuery>
+#include <QFileInfo>
+
+namespace Marble
+{
+
+class MbTileWriter
+{
+public:
+ explicit MbTileWriter(const QString &filename, const QString &extension="o5m");
+ ~MbTileWriter();
+
+ void setOverwriteTiles(bool overwrite);
+ void setReportProgress(bool report);
+
+ void addTile(const QFileInfo &file, qint32 x, qint32 y, qint32 z);
+
+private:
+ bool haveTile(qint32 x, qint32 y, qint32 z) const;
+ void execQuery(const QString &query) const;
+ void execQuery(QSqlQuery &query) const;
+ void setMetaData(const QString &name, const QString &value);
+
+ bool m_overwriteTiles;
+ bool m_reportProgress;
+};
+
+}
+
+#endif
diff --git a/tools/mbtile-import/mbtile-import.cpp b/tools/mbtile-import/mbtile-import.cpp
new file mode 100644
index 0000000..b848a8a
--- /dev/null
+++ b/tools/mbtile-import/mbtile-import.cpp
@@ -0,0 +1,126 @@
+//
+// This file is part of the Marble Virtual Globe.
+//
+// This program is free software licensed under the GNU LGPL. You can
+// find a copy of this license in LICENSE.txt in the top directory of
+// the source code.
+//
+// Copyright 2016 Dennis Nienhüser <nienhueser@kde.org>
+//
+
+#include "MbTileWriter.h"
+
+#include <QCoreApplication>
+#include <QApplication>
+#include <QCommandLineParser>
+#include <QFileInfo>
+#include <QFile>
+#include <QDir>
+#include <QDirIterator>
+#include <QSqlDatabase>
+#include <QSqlQuery>
+#include <QSqlError>
+#include <QDebug>
+
+using namespace std;
+using namespace Marble;
+
+void importTiles(const QString &tileDirectory, MbTileWriter &tileWriter, const QPair<int, int> &tileLevels)
+{
+ QString const extension = "o5m";
+ QDir tileDir(tileDirectory);
+ auto const strip = 1+tileDir.absolutePath().size();
+ foreach(const auto &entryInfo, tileDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
+ bool isNumber;
+ int const z = entryInfo.baseName().toInt(&isNumber);
+ if (isNumber && tileLevels.first <= z && z <= tileLevels.second) {
+ QDirIterator tileIter(entryInfo.absoluteFilePath(), QDirIterator::Subdirectories);
+ for (; tileIter.hasNext(); tileIter.next()) {
+ auto tileInfo = tileIter.fileInfo();
+ if (!tileInfo.isFile() || tileInfo.completeSuffix() != extension) {
+ continue;
+ }
+
+ QString const tileId = tileInfo.absoluteFilePath().mid(strip);
+ QStringList const tileEntries = tileId.split('/');
+ if (tileEntries.size() == 3) {
+ int const x = tileEntries[1].toInt(&isNumber);
+ if (isNumber && x >= 0) {
+ int const y = tileInfo.baseName().toInt(&isNumber);
+ if (isNumber && y >= 0) {
+ tileWriter.addTile(tileInfo, x, y, z);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+int main(int argc, char** argv)
+{
+ QCoreApplication app(argc, argv);
+ QCoreApplication::setApplicationName("mbtile-import");
+ QCoreApplication::setApplicationVersion("0.1");
+
+ QCommandLineParser parser;
+ parser.setApplicationDescription("Import tiles from a z/x/y.ext directory structure to a .mbtiles SQLite database.");
+ auto const helpOption = parser.addHelpOption();
+ auto const versionOption = parser.addVersionOption();
+ parser.addPositionalArgument("directory", "Directory with tiles in z/x/y.ext structure");
+ parser.addPositionalArgument("output", "Destination MBTile database");
+
+ parser.addOptions({
+ {{"o", "overwrite"}, "Overwrite existing tiles in the database"},
+ {{"q", "quiet"}, "No progress report to stdout"},
+ {{"t", "tilelevels"}, "Restrict tile levels to <tilelevels>", "tilelevels", "0-20"},
+ });
+
+ if (!parser.parse(QCoreApplication::arguments())) {
+ qDebug() << parser.errorText();
+ parser.showHelp(2);
+ } else if (parser.isSet(helpOption)) {
+ parser.showHelp(0);
+ } else if (parser.isSet(versionOption)) {
+ parser.showVersion();
+ return 0;
+ }
+
+ const QStringList positionalArguments = parser.positionalArguments();
+ if (positionalArguments.size() != 2) {
+ parser.showHelp(positionalArguments.size() == 0 ? 0 : 1);
+ }
+
+ QString const tileDirectory = parser.positionalArguments()[0];
+ if (!QFileInfo(tileDirectory).isDir()) {
+ qDebug() << tileDirectory << "is not a directory";
+ parser.showHelp(3);
+ }
+
+ QStringList const tileLevels = parser.value("tilelevels").split('-');
+ QPair<int, int> tileLevelRange = QPair<int, int>(0, 20);
+ bool haveValidRange = false;
+ if (tileLevels.size() == 2) {
+ bool ok;
+ tileLevelRange.first = tileLevels[0].toInt(&ok);
+ if (ok) {
+ tileLevelRange.second = tileLevels[1].toInt(&ok);
+ if (ok) {
+ haveValidRange = tileLevelRange.first >= 0 && tileLevelRange.first <= tileLevelRange.second && tileLevelRange.second <= 30;
+ }
+ }
+ }
+
+ if (!haveValidRange) {
+ qDebug() << "Cannot parse tile level range. Expecting format 'minLevel-maxLevel', e.g. '3-7'.";
+ return 4;
+ }
+
+ QString const mbTilesFile = parser.positionalArguments()[1];
+ MbTileWriter tileWriter(mbTilesFile);
+ tileWriter.setOverwriteTiles(parser.isSet("overwrite"));
+ tileWriter.setReportProgress(!parser.isSet("quiet"));
+
+ importTiles(tileDirectory, tileWriter, tileLevelRange);
+ return 0;
+}