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"