File indexing completed on 2024-03-24 15:23:45

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2016 David Kolozsvari <freedawson@gmail.com>
0004 // SPDX-FileCopyrightText: 2016 Dennis Nienhüser <nienhueser@kde.org>
0005 //
0006 
0007 #include "GeoDataDocumentWriter.h"
0008 #include "MarbleModel.h"
0009 #include "ParsingRunnerManager.h"
0010 #include "GeoDataGeometry.h"
0011 #include "GeoDataPlacemark.h"
0012 #include "GeoDataPolygon.h"
0013 #include "GeoDataLatLonAltBox.h"
0014 #include "TileId.h"
0015 #include "MarbleDirs.h"
0016 #ifdef STATIC_BUILD
0017 #include "src/plugins/runner/osm/translators/O5mWriter.h"
0018 #endif
0019 
0020 #include <QApplication>
0021 #include <QCommandLineParser>
0022 #include <QDebug>
0023 #include <QFileInfo>
0024 #include <QDir>
0025 #include <QString>
0026 #include <QUrl>
0027 
0028 #include "VectorClipper.h"
0029 #include "NodeReducer.h"
0030 #include "TileIterator.h"
0031 #include "TileDirectory.h"
0032 #include "SpellChecker.h"
0033 
0034 #ifdef STATIC_BUILD
0035 #include <QtPlugin>
0036 Q_IMPORT_PLUGIN(OsmPlugin)
0037 Q_IMPORT_PLUGIN(ShpPlugin)
0038 #endif
0039 
0040 #include <iostream>
0041 
0042 using namespace Marble;
0043 
0044 static QString tileFileName(const QCommandLineParser &parser, int x, int y, int zoomLevel)
0045 {
0046     QString const extension = parser.value("extension");
0047     QString const output = parser.isSet("development") ? QString("%1/maps/earth/vectorosm-dev").arg(MarbleDirs::localPath()) : parser.value("output");
0048     QString const outputDir = QString("%1/%2/%3").arg(output).arg(zoomLevel).arg(x);
0049     QString const outputFile = QString("%1/%2.%3").arg(outputDir).arg(y).arg(extension);
0050     return outputFile;
0051 }
0052 
0053 bool writeTile(GeoDataDocument* tile, const QString &outputFile)
0054 {
0055     QDir().mkpath(QFileInfo(outputFile).path());
0056     if (!GeoDataDocumentWriter::write(outputFile, *tile)) {
0057         qWarning() << "Could not write the file " << outputFile;
0058         return false;
0059     }
0060     return true;
0061 }
0062 
0063 int main(int argc, char *argv[])
0064 {
0065     QCoreApplication app(argc, argv);
0066 
0067     QCoreApplication::setApplicationName("marble-vectorosm-tilecreator");
0068     QCoreApplication::setApplicationVersion("0.1");
0069 
0070     QCommandLineParser parser;
0071     parser.setApplicationDescription("A tool for Marble, which is used to reduce the details of osm maps.");
0072     parser.addHelpOption();
0073     parser.addVersionOption();
0074     parser.addPositionalArgument("input", "The input .osm or .shp file.");
0075 
0076     parser.addOptions({
0077                           {{"t", "osmconvert"}, "Tile data using osmconvert."},
0078                           {"conflict-resolution", "How to deal with existing tiles: overwrite, skip or merge", "mode", "overwrite"},
0079                           {{"c", "cache-directory"}, "Directory for temporary data.", "cache", "cache"},
0080                           {{"s", "spellcheck"}, "Use this geonames.org cities file for spell-checking city names", "spellcheck"},
0081                           {"verbose", "Increase amount of shell output information"},
0082                           {{"d", "development"}, "Use local development vector osm map theme as output storage"},
0083                           {{"z", "zoom-level"}, "Zoom level according to which OSM information has to be processed.", "levels", "11,13,15,17"},
0084                           {{"o", "output"}, "Output file or directory", "output", QString("%1/maps/earth/vectorosm").arg(MarbleDirs::localPath())},
0085                           {{"e", "extension"}, "Output file type: o5m (default), osm or kml", "file extension", "o5m"}
0086                       });
0087 
0088     // Process the actual command line arguments given by the user
0089     parser.process(app);
0090 
0091     const QStringList args = parser.positionalArguments();
0092     if (args.isEmpty()) {
0093         parser.showHelp();
0094         return 0;
0095     }
0096     // input is args.at(0), output is args.at(1)
0097 
0098     QString const extension = parser.value("extension");
0099     QString inputFileName = args.at(0);
0100     auto const levels = parser.value("zoom-level").split(',');
0101     QVector<unsigned int> zoomLevels;
0102     int maxZoomLevel = 0;
0103     for(auto const &level: levels) {
0104         int const zoomLevel = level.toInt();
0105         maxZoomLevel = qMax(zoomLevel, maxZoomLevel);
0106         zoomLevels << zoomLevel;
0107     }
0108 
0109     if (zoomLevels.isEmpty()) {
0110         parser.showHelp(1);
0111         return 1;
0112     }
0113 
0114     // work around MARBLE_ADD_WRITER not working for static builds
0115 #ifdef STATIC_BUILD
0116     GeoDataDocumentWriter::registerWriter(new O5mWriter, QStringLiteral("o5m"));
0117 #endif
0118 
0119     bool const overwriteTiles = parser.value("conflict-resolution") == "overwrite";
0120 
0121     MarbleModel model;
0122     ParsingRunnerManager manager(model.pluginManager());
0123     QString const cacheDirectory = parser.value("cache-directory");
0124     QDir().mkpath(cacheDirectory);
0125     if (!QFileInfo(cacheDirectory).isWritable()) {
0126         qWarning() << "Cannot write to cache directory" << cacheDirectory;
0127         parser.showHelp(1);
0128     }
0129 
0130     if (*zoomLevels.cbegin() <= 9) {
0131         auto map = TileDirectory::open(inputFileName, manager);
0132         VectorClipper processor(map.data(), maxZoomLevel);
0133         GeoDataLatLonBox world(85.0, -85.0, 180.0, -180.0, GeoDataCoordinates::Degree);
0134         if (parser.isSet("spellcheck")) {
0135             SpellChecker spellChecker(parser.value("spellcheck"));
0136             spellChecker.setVerbose(parser.isSet("verbose"));
0137             spellChecker.correctPlaceLabels(map.data()->placemarkList());
0138         }
0139         for(auto zoomLevel: zoomLevels) {
0140             TileIterator iter(world, zoomLevel);
0141             qint64 count = 0;
0142             qint64 const total = iter.total();
0143             for(auto const &tileId: iter) {
0144                 ++count;
0145                 QString const filename = tileFileName(parser, tileId.x(), tileId.y(), zoomLevel);
0146                 if (!overwriteTiles && QFileInfo(filename).exists()) {
0147                     continue;
0148                 }
0149                 GeoDataDocument* tile = processor.clipTo(zoomLevel, tileId.x(), tileId.y());
0150                 if (!tile->isEmpty()) {
0151                     NodeReducer nodeReducer(tile, TileId(0, zoomLevel, tileId.x(), tileId.y()));
0152                     if (!writeTile(tile, filename)) {
0153                         return 4;
0154                     }
0155                     TileDirectory::printProgress(count / double(total));
0156                     std::cout << " Tile " << count << "/" << total << " (" << tile->name().toStdString() << ") done.";
0157                     double const reduction = nodeReducer.removedNodes() / qMax(1.0, double(nodeReducer.remainingNodes() + nodeReducer.removedNodes()));
0158                     std::cout << " Node reduction: " << qRound(reduction * 100.0) << "%";
0159                 } else {
0160                     TileDirectory::printProgress(count / double(total));
0161                     std::cout << " Skipping empty tile " << count << "/" << total << " (" << tile->name().toStdString() << ").";
0162                 }
0163                 std::cout << std::string(20, ' ') << '\r';
0164                 std::cout.flush();
0165                 delete tile;
0166             }
0167         }
0168     } else {
0169         qWarning() << "high zoom level tiles cannot be created with this tool!";
0170         return 1;
0171     }
0172 
0173     return 0;
0174 }