File indexing completed on 2024-05-12 04:42:19

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <KOSMIndoorMap/MapData>
0008 #include <KOSMIndoorMap/MapLoader>
0009 #include <loader/tilecache_p.h>
0010 
0011 #include <osm/datatypes.h>
0012 #include <osm/io.h>
0013 
0014 #include <QCommandLineParser>
0015 #include <QCoreApplication>
0016 #include <QDebug>
0017 #include <QFile>
0018 #include <QtPlugin>
0019 
0020 #if HAVE_OSM_PBF_SUPPORT
0021 Q_IMPORT_PLUGIN(OSM_PbfIOPlugin)
0022 #endif
0023 Q_IMPORT_PLUGIN(OSM_XmlIOPlugin)
0024 
0025 using namespace KOSMIndoorMap;
0026 
0027 static void filterByBbox(OSM::DataSet &dataSet, OSM::BoundingBox bbox)
0028 {
0029     dataSet.relations.erase(std::remove_if(dataSet.relations.begin(), dataSet.relations.end(), [bbox](const auto &rel) {
0030         return !OSM::intersects(rel.bbox, bbox);
0031     }), dataSet.relations.end());
0032     dataSet.ways.erase(std::remove_if(dataSet.ways.begin(), dataSet.ways.end(), [bbox](const auto &way) {
0033         return !OSM::intersects(way.bbox, bbox);
0034     }), dataSet.ways.end());
0035     dataSet.nodes.erase(std::remove_if(dataSet.nodes.begin(), dataSet.nodes.end(), [bbox](const auto &nd) {
0036         return !OSM::contains(bbox, nd.coordinate);
0037     }), dataSet.nodes.end());
0038 }
0039 
0040 template <typename Elem>
0041 static bool containsElement(const std::vector<Elem> &elems, OSM::Id id)
0042 {
0043     const auto it = std::lower_bound(elems.begin(), elems.end(), id, [](const Elem &lhs, OSM::Id rhs) { return lhs.id < rhs; });
0044     return it != elems.end() && (*it).id == id;
0045 }
0046 
0047 static void purgeDanglingReferences(OSM::DataSet &dataSet)
0048 {
0049     for (auto &rel : dataSet.relations) {
0050         rel.members.erase(std::remove_if(rel.members.begin(), rel.members.end(), [&dataSet](const auto &mem) {
0051             switch (mem.type()) {
0052                 case OSM::Type::Null:
0053                     Q_UNREACHABLE();
0054                 case OSM::Type::Node:
0055                     return !containsElement(dataSet.nodes, mem.id);
0056                 case OSM::Type::Way:
0057                     return !containsElement(dataSet.ways, mem.id);
0058                 case OSM::Type::Relation:
0059                     return !containsElement(dataSet.relations, mem.id);
0060             }
0061             return false;
0062         }), rel.members.end());
0063     }
0064 }
0065 
0066 int main(int argc, char **argv)
0067 {
0068     QCoreApplication app(argc, argv);
0069     QCommandLineParser parser;
0070     parser.addHelpOption();
0071     QCommandLineOption bboxOpt({QStringLiteral("b"), QStringLiteral("bbox")}, QStringLiteral("bounding box to download"), QStringLiteral("minlat,minlon,maxlat,maxlon"));
0072     parser.addOption(bboxOpt);
0073     QCommandLineOption clipOpt({QStringLiteral("c"), QStringLiteral("clip")}, QStringLiteral("clip to bounding box"));
0074     parser.addOption(clipOpt);
0075     QCommandLineOption outOpt({QStringLiteral("o"), QStringLiteral("out")}, QStringLiteral("output file"), QStringLiteral("file"));
0076     parser.addOption(outOpt);
0077     QCommandLineOption pointOpt({QStringLiteral("p"), QStringLiteral("point")}, QStringLiteral("download area around point"), QStringLiteral("lat,lon"));
0078     parser.addOption(pointOpt);
0079     QCommandLineOption tileOpt({QStringLiteral("t"), QStringLiteral("tile")}, QStringLiteral("download tile"), QStringLiteral("z/x/y"));
0080     parser.addOption(tileOpt);
0081     parser.process(app);
0082 
0083     if ((!parser.isSet(bboxOpt) && !parser.isSet(pointOpt) && !parser.isSet(tileOpt)) || !parser.isSet(outOpt)) {
0084         parser.showHelp(1);
0085         return 1;
0086     }
0087 
0088     OSM::BoundingBox bbox;
0089     MapLoader loader;
0090     if (parser.isSet(bboxOpt)) {
0091         const auto coords = QStringView(parser.value(bboxOpt)).split(QLatin1Char(','));
0092         if (coords.size() == 4) {
0093             bbox.min = OSM::Coordinate(coords[0].toDouble(), coords[1].toDouble());
0094             bbox.max = OSM::Coordinate(coords[2].toDouble(), coords[3].toDouble());
0095         }
0096         if (!bbox.isValid()) {
0097             qCritical() << "Invalid bounding box!";
0098             return 1;
0099         }
0100     }
0101 
0102     if (parser.isSet(pointOpt)) {
0103         const auto coords = QStringView(parser.value(pointOpt)).split(QLatin1Char(','));
0104         if (coords.size() != 2) {
0105             qCritical() << "Invalid coordinate!";
0106             return 1;
0107         }
0108         OSM::Coordinate coord{coords[0].toDouble(), coords[1].toDouble()};
0109         loader.loadForCoordinate(coord.latF(), coord.lonF());
0110     } else if (parser.isSet(tileOpt)) {
0111         const auto coords = QStringView(parser.value(tileOpt)).split(QLatin1Char('/'));
0112         if (coords.size() != 3) {
0113             qCritical() << "Invalid tile!";
0114             return 1;
0115         }
0116         Tile t(coords[1].toUInt(), coords[2].toUInt(), coords[0].toUInt());
0117         loader.loadForTile(t);
0118     } else if (parser.isSet(bboxOpt)) {
0119         loader.loadForBoundingBox(bbox);
0120     }
0121 
0122     QObject::connect(&loader, &MapLoader::done, &app, &QCoreApplication::quit);
0123     QCoreApplication::exec();
0124     auto data = loader.takeData();
0125 
0126     if (parser.isSet(clipOpt) && parser.isSet(bboxOpt)) {
0127         filterByBbox(data.dataSet(), bbox);
0128         purgeDanglingReferences(data.dataSet());
0129     }
0130 
0131     QFile f(parser.value(outOpt));
0132     if (!f.open(QFile::WriteOnly)) {
0133         qCritical() << f.errorString();
0134         return 1;
0135     }
0136     auto writer = OSM::IO::writerForFileName(f.fileName());
0137     if (!writer) {
0138         qCritical() << "no file writer for requested format:" << f.fileName();
0139         return 1;
0140     }
0141     writer->write(data.dataSet(), &f);
0142     return 0;
0143 }