File indexing completed on 2024-05-26 04:45:27

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <config-kosmindoormap.h>
0008 
0009 #include "maploader.h"
0010 #include "boundarysearch_p.h"
0011 #include "logging.h"
0012 #include "mapdata.h"
0013 #include "marblegeometryassembler_p.h"
0014 #include "tilecache_p.h"
0015 
0016 #include <osm/datatypes.h>
0017 #include <osm/datasetmergebuffer.h>
0018 #include <osm/element.h>
0019 #include <osm/o5mparser.h>
0020 #include <osm/io.h>
0021 
0022 #include <QDateTime>
0023 #include <QElapsedTimer>
0024 #include <QFile>
0025 #include <QRect>
0026 #include <QUrl>
0027 
0028 enum {
0029     TileZoomLevel = 17
0030 };
0031 
0032 inline void initResources()  // needs to be outside of a namespace
0033 {
0034 #if !BUILD_TOOLS_ONLY
0035     Q_INIT_RESOURCE(assets);
0036 #endif
0037 }
0038 
0039 namespace KOSMIndoorMap {
0040 class MapLoaderPrivate {
0041 public:
0042     OSM::DataSet m_dataSet;
0043     OSM::DataSetMergeBuffer m_mergeBuffer;
0044     MarbleGeometryAssembler m_marbleMerger;
0045     MapData m_data;
0046     TileCache m_tileCache;
0047     OSM::BoundingBox m_tileBbox;
0048     QRect m_loadedTiles;
0049     std::vector<Tile> m_pendingTiles;
0050     std::unique_ptr<BoundarySearch> m_boundarySearcher;
0051     QDateTime m_ttl;
0052 
0053     QString m_errorMessage;
0054 };
0055 }
0056 
0057 using namespace KOSMIndoorMap;
0058 
0059 MapLoader::MapLoader(QObject *parent)
0060     : QObject(parent)
0061     , d(new MapLoaderPrivate)
0062 {
0063     initResources();
0064     connect(&d->m_tileCache, &TileCache::tileLoaded, this, &MapLoader::downloadFinished);
0065     connect(&d->m_tileCache, &TileCache::tileError, this, &MapLoader::downloadFailed);
0066     d->m_tileCache.expire();
0067 }
0068 
0069 MapLoader::~MapLoader() = default;
0070 
0071 void MapLoader::loadFromFile(const QString &fileName)
0072 {
0073     QElapsedTimer loadTime;
0074     loadTime.start();
0075 
0076     d->m_errorMessage.clear();
0077     QFile f(fileName.contains(QLatin1Char(':')) ? QUrl::fromUserInput(fileName).toLocalFile() : fileName);
0078     if (!f.open(QFile::ReadOnly)) {
0079         qCritical() << f.fileName() << f.errorString();
0080         return;
0081     }
0082     const auto data = f.map(0, f.size());
0083 
0084     OSM::DataSet ds;
0085     auto reader = OSM::IO::readerForFileName(fileName, &ds);
0086     if (!reader) {
0087         qCWarning(Log) << "no file reader for" << fileName;
0088         return;
0089     }
0090     reader->read(data, f.size());
0091     d->m_data = MapData();
0092     d->m_data.setDataSet(std::move(ds));
0093     qCDebug(Log) << "o5m loading took" << loadTime.elapsed() << "ms";
0094     Q_EMIT done();
0095 }
0096 
0097 void MapLoader::loadForCoordinate(double lat, double lon)
0098 {
0099     loadForCoordinate(lat, lon, {});
0100 }
0101 
0102 void MapLoader::loadForCoordinate(double lat, double lon, const QDateTime &ttl)
0103 {
0104     d->m_ttl = ttl;
0105     d->m_tileBbox = {};
0106     d->m_pendingTiles.clear();
0107     d->m_boundarySearcher = std::make_unique<BoundarySearch>();
0108     d->m_boundarySearcher->init(OSM::Coordinate(lat, lon));
0109     d->m_errorMessage.clear();
0110     d->m_marbleMerger.setDataSet(&d->m_dataSet);
0111     d->m_data = MapData();
0112 
0113     auto tile = Tile::fromCoordinate(lat, lon, TileZoomLevel);
0114     d->m_loadedTiles = QRect(tile.x, tile.y, 1, 1);
0115     d->m_pendingTiles.push_back(std::move(tile));
0116     downloadTiles();
0117 }
0118 
0119 void MapLoader::loadForBoundingBox(OSM::BoundingBox box)
0120 {
0121     d->m_ttl = {};
0122     d->m_tileBbox = box;
0123     d->m_pendingTiles.clear();
0124     d->m_errorMessage.clear();
0125     d->m_marbleMerger.setDataSet(&d->m_dataSet);
0126     d->m_data = MapData();
0127 
0128     const auto topLeftTile = Tile::fromCoordinate(box.min.latF(), box.min.lonF(), TileZoomLevel);
0129     const auto bottomRightTile = Tile::fromCoordinate(box.max.latF(), box.max.lonF(), TileZoomLevel);
0130     for (auto x = topLeftTile.x; x <= bottomRightTile.x; ++x) {
0131         for (auto y = bottomRightTile.y; y <= topLeftTile.y; ++y) {
0132             d->m_pendingTiles.push_back(makeTile(x, y));
0133         }
0134     }
0135     downloadTiles();
0136 }
0137 
0138 void MapLoader::loadForTile(Tile tile)
0139 {
0140     d->m_ttl = {};
0141     d->m_tileBbox = tile.boundingBox();
0142     d->m_pendingTiles.clear();
0143     d->m_errorMessage.clear();
0144     d->m_marbleMerger.setDataSet(&d->m_dataSet);
0145     d->m_data = MapData();
0146 
0147     if (tile.z >= TileZoomLevel) {
0148         d->m_pendingTiles.push_back(std::move(tile));
0149     } else {
0150         const auto start = tile.topLeftAtZ(TileZoomLevel);
0151         const auto end = tile.bottomRightAtZ(TileZoomLevel);
0152         for (auto x = start.x; x <= end.x; ++x) {
0153             for (auto y = start.y; y <= end.y; ++y) {
0154                 d->m_pendingTiles.push_back(makeTile(x, y));
0155             }
0156         }
0157     }
0158 
0159     downloadTiles();
0160 }
0161 
0162 MapData&& MapLoader::takeData()
0163 {
0164     return std::move(d->m_data);
0165 }
0166 
0167 void MapLoader::downloadTiles()
0168 {
0169     for (const auto &tile : d->m_pendingTiles) {
0170         d->m_tileCache.ensureCached(tile);
0171     }
0172     if (d->m_tileCache.pendingDownloads() == 0) {
0173         // still go through the event loop when having everything cached already
0174         // this makes outside behavior more identical in both cases, and avoids
0175         // signal connection races etc.
0176         QMetaObject::invokeMethod(this, &MapLoader::loadTiles, Qt::QueuedConnection);
0177     } else {
0178         Q_EMIT isLoadingChanged();
0179     }
0180 }
0181 
0182 void MapLoader::downloadFinished()
0183 {
0184     if (d->m_tileCache.pendingDownloads() > 0) {
0185         return;
0186     }
0187     loadTiles();
0188 }
0189 
0190 void MapLoader::loadTiles()
0191 {
0192     QElapsedTimer loadTime;
0193     loadTime.start();
0194 
0195     OSM::O5mParser p(&d->m_dataSet);
0196     p.setMergeBuffer(&d->m_mergeBuffer);
0197     for (const auto &tile : d->m_pendingTiles) {
0198         const auto fileName = d->m_tileCache.cachedTile(tile);
0199         qCDebug(Log) << "loading tile" << fileName;
0200         QFile f(fileName);
0201         if (!f.open(QFile::ReadOnly)) {
0202             qWarning() << f.fileName() << f.errorString();
0203             break;
0204         }
0205         const auto data = f.map(0, f.size());
0206         p.read(data, f.size());
0207         d->m_marbleMerger.merge(&d->m_mergeBuffer);
0208 
0209         d->m_tileBbox = OSM::unite(d->m_tileBbox, tile.boundingBox());
0210     }
0211     d->m_pendingTiles.clear();
0212 
0213     if (d->m_boundarySearcher) {
0214         const auto bbox = d->m_boundarySearcher->boundingBox(d->m_dataSet);
0215         qCDebug(Log) << "needed bbox:" << bbox << "got:" << d->m_tileBbox << d->m_loadedTiles;
0216 
0217         // expand left and right
0218         if (bbox.min.longitude < d->m_tileBbox.min.longitude) {
0219             d->m_loadedTiles.setLeft(d->m_loadedTiles.left() - 1);
0220             for (int y = d->m_loadedTiles.top(); y <= d->m_loadedTiles.bottom(); ++y) {
0221                 d->m_pendingTiles.push_back(makeTile(d->m_loadedTiles.left(), y));
0222             }
0223         }
0224         if (bbox.max.longitude > d->m_tileBbox.max.longitude) {
0225             d->m_loadedTiles.setRight(d->m_loadedTiles.right() + 1);
0226             for (int y = d->m_loadedTiles.top(); y <= d->m_loadedTiles.bottom(); ++y) {
0227                 d->m_pendingTiles.push_back(makeTile(d->m_loadedTiles.right(), y));
0228             }
0229         }
0230 
0231         // expand top/bottom: note that geographics and slippy map tile coordinates have a different understanding on what is "top"
0232         if (bbox.max.latitude > d->m_tileBbox.max.latitude) {
0233             d->m_loadedTiles.setTop(d->m_loadedTiles.top() - 1);
0234             for (int x = d->m_loadedTiles.left(); x <= d->m_loadedTiles.right(); ++x) {
0235                 d->m_pendingTiles.push_back(makeTile(x, d->m_loadedTiles.top()));
0236             }
0237         }
0238         if (bbox.min.latitude < d->m_tileBbox.min.latitude) {
0239             d->m_loadedTiles.setBottom(d->m_loadedTiles.bottom() + 1);
0240             for (int x = d->m_loadedTiles.left(); x <= d->m_loadedTiles.right(); ++x) {
0241                 d->m_pendingTiles.push_back(makeTile(x, d->m_loadedTiles.bottom()));
0242             }
0243         }
0244 
0245         if (!d->m_pendingTiles.empty()) {
0246             downloadTiles();
0247             return;
0248         }
0249         d->m_data.setBoundingBox(bbox);
0250     }
0251 
0252     d->m_marbleMerger.finalize();
0253     d->m_data.setDataSet(std::move(d->m_dataSet));
0254     d->m_boundarySearcher.reset();
0255 
0256     qCDebug(Log) << "o5m loading took" << loadTime.elapsed() << "ms";
0257     Q_EMIT isLoadingChanged();
0258     Q_EMIT done();
0259 }
0260 
0261 Tile MapLoader::makeTile(uint32_t x, uint32_t y) const
0262 {
0263     auto tile = Tile(x, y, TileZoomLevel);
0264     tile.ttl = d->m_ttl;
0265     return tile;
0266 }
0267 
0268 void MapLoader::downloadFailed(Tile tile, const QString& errorMessage)
0269 {
0270     Q_UNUSED(tile);
0271     d->m_errorMessage = errorMessage;
0272     d->m_tileCache.cancelPending();
0273     Q_EMIT isLoadingChanged();
0274     Q_EMIT done();
0275 }
0276 
0277 bool MapLoader::isLoading() const
0278 {
0279     return d->m_tileCache.pendingDownloads() > 0;
0280 }
0281 
0282 bool MapLoader::hasError() const
0283 {
0284     return !d->m_errorMessage.isEmpty();
0285 }
0286 
0287 QString MapLoader::errorMessage() const
0288 {
0289     return d->m_errorMessage;
0290 }
0291 
0292 #include "moc_maploader.cpp"