1 /*
2     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "maploader.h"
8 #include "boundarysearch_p.h"
9 #include "logging.h"
10 #include "mapdata.h"
11 #include "marblegeometryassembler_p.h"
12 #include "tilecache_p.h"
13 
14 #include <osm/datatypes.h>
15 #include <osm/datasetmergebuffer.h>
16 #include <osm/element.h>
17 #include <osm/o5mparser.h>
18 #include <osm/osmpbfparser.h>
19 #include <osm/xmlparser.h>
20 
21 #include <QDateTime>
22 #include <QElapsedTimer>
23 #include <QFile>
24 #include <QRect>
25 #include <QUrl>
26 
27 enum {
28     TileZoomLevel = 17
29 };
30 
initResources()31 inline void initResources()  // needs to be outside of a namespace
32 {
33     Q_INIT_RESOURCE(assets);
34 }
35 
36 namespace KOSMIndoorMap {
37 class MapLoaderPrivate {
38 public:
39     OSM::DataSet m_dataSet;
40     OSM::DataSetMergeBuffer m_mergeBuffer;
41     MarbleGeometryAssembler m_marbleMerger;
42     MapData m_data;
43     TileCache m_tileCache;
44     OSM::BoundingBox m_tileBbox;
45     QRect m_loadedTiles;
46     std::vector<Tile> m_pendingTiles;
47     BoundarySearch m_boundarySearcher;
48     QDateTime m_ttl;
49 
50     QString m_errorMessage;
51 };
52 }
53 
54 using namespace KOSMIndoorMap;
55 
MapLoader(QObject * parent)56 MapLoader::MapLoader(QObject *parent)
57     : QObject(parent)
58     , d(new MapLoaderPrivate)
59 {
60     initResources();
61     connect(&d->m_tileCache, &TileCache::tileLoaded, this, &MapLoader::downloadFinished);
62     connect(&d->m_tileCache, &TileCache::tileError, this, &MapLoader::downloadFailed);
63     d->m_tileCache.expire();
64 }
65 
66 MapLoader::~MapLoader() = default;
67 
loadFromFile(const QString & fileName)68 void MapLoader::loadFromFile(const QString &fileName)
69 {
70     QElapsedTimer loadTime;
71     loadTime.start();
72 
73     d->m_errorMessage.clear();
74     QFile f(fileName.contains(QLatin1Char(':')) ? QUrl::fromUserInput(fileName).toLocalFile() : fileName);
75     if (!f.open(QFile::ReadOnly)) {
76         qCritical() << f.fileName() << f.errorString();
77         return;
78     }
79     const auto data = f.map(0, f.size());
80 
81     OSM::DataSet ds;
82     if (fileName.endsWith(QLatin1String(".osm.pbf"))) {
83         OSM::OsmPbfParser p(&ds);
84         p.parse(data, f.size());
85     } else if (fileName.endsWith(QLatin1String(".osm"))) {
86         qDebug() << fileName << f.pos() <<f.size();
87         OSM::XmlParser p(&ds);
88         p.parse(&f);
89     } else {
90         OSM::O5mParser p(&ds);
91         p.parse(data, f.size());
92     }
93     d->m_data = MapData();
94     d->m_data.setDataSet(std::move(ds));
95     qCDebug(Log) << "o5m loading took" << loadTime.elapsed() << "ms";
96     Q_EMIT done();
97 }
98 
loadForCoordinate(double lat,double lon)99 void MapLoader::loadForCoordinate(double lat, double lon)
100 {
101     loadForCoordinate(lat, lon, {});
102 }
103 
loadForCoordinate(double lat,double lon,const QDateTime & ttl)104 void MapLoader::loadForCoordinate(double lat, double lon, const QDateTime &ttl)
105 {
106     d->m_ttl = ttl;
107     d->m_tileBbox = {};
108     d->m_pendingTiles.clear();
109     d->m_boundarySearcher.init(OSM::Coordinate(lat, lon));
110     d->m_errorMessage.clear();
111     d->m_marbleMerger.setDataSet(&d->m_dataSet);
112     d->m_data = MapData();
113 
114     auto tile = Tile::fromCoordinate(lat, lon, TileZoomLevel);
115     d->m_loadedTiles = QRect(tile.x, tile.y, 1, 1);
116     d->m_pendingTiles.push_back(std::move(tile));
117     downloadTiles();
118 }
119 
takeData()120 MapData&& MapLoader::takeData()
121 {
122     return std::move(d->m_data);
123 }
124 
downloadTiles()125 void MapLoader::downloadTiles()
126 {
127     for (const auto &tile : d->m_pendingTiles) {
128         d->m_tileCache.ensureCached(tile);
129     }
130     if (d->m_tileCache.pendingDownloads() == 0) {
131         // still go through the event loop when having everything cached already
132         // this makes outside behavior more identical in both cases, and avoids
133         // signal connection races etc.
134         QMetaObject::invokeMethod(this, &MapLoader::loadTiles, Qt::QueuedConnection);
135     } else {
136         Q_EMIT isLoadingChanged();
137     }
138 }
139 
downloadFinished()140 void MapLoader::downloadFinished()
141 {
142     if (d->m_tileCache.pendingDownloads() > 0) {
143         return;
144     }
145     loadTiles();
146 }
147 
loadTiles()148 void MapLoader::loadTiles()
149 {
150     QElapsedTimer loadTime;
151     loadTime.start();
152 
153     OSM::O5mParser p(&d->m_dataSet);
154     p.setMergeBuffer(&d->m_mergeBuffer);
155     for (const auto &tile : d->m_pendingTiles) {
156         const auto fileName = d->m_tileCache.cachedTile(tile);
157         qCDebug(Log) << "loading tile" << fileName;
158         QFile f(fileName);
159         if (!f.open(QFile::ReadOnly)) {
160             qWarning() << f.fileName() << f.errorString();
161             break;
162         }
163         const auto data = f.map(0, f.size());
164         p.parse(data, f.size());
165         d->m_marbleMerger.merge(&d->m_mergeBuffer);
166 
167         d->m_tileBbox = OSM::unite(d->m_tileBbox, tile.boundingBox());
168     }
169     d->m_pendingTiles.clear();
170 
171     const auto bbox = d->m_boundarySearcher.boundingBox(d->m_dataSet);
172     qCDebug(Log) << "needed bbox:" << bbox << "got:" << d->m_tileBbox << d->m_loadedTiles;
173 
174     // expand left and right
175     if (bbox.min.longitude < d->m_tileBbox.min.longitude) {
176         d->m_loadedTiles.setLeft(d->m_loadedTiles.left() - 1);
177         for (int y = d->m_loadedTiles.top(); y <= d->m_loadedTiles.bottom(); ++y) {
178             d->m_pendingTiles.push_back(makeTile(d->m_loadedTiles.left(), y));
179         }
180     }
181     if (bbox.max.longitude > d->m_tileBbox.max.longitude) {
182         d->m_loadedTiles.setRight(d->m_loadedTiles.right() + 1);
183         for (int y = d->m_loadedTiles.top(); y <= d->m_loadedTiles.bottom(); ++y) {
184             d->m_pendingTiles.push_back(makeTile(d->m_loadedTiles.right(), y));
185         }
186     }
187 
188     // expand top/bottom: note that geographics and slippy map tile coordinates have a different understanding on what is "top"
189     if (bbox.max.latitude > d->m_tileBbox.max.latitude) {
190         d->m_loadedTiles.setTop(d->m_loadedTiles.top() - 1);
191         for (int x = d->m_loadedTiles.left(); x <= d->m_loadedTiles.right(); ++x) {
192             d->m_pendingTiles.push_back(makeTile(x, d->m_loadedTiles.top()));
193         }
194     }
195     if (bbox.min.latitude < d->m_tileBbox.min.latitude) {
196         d->m_loadedTiles.setBottom(d->m_loadedTiles.bottom() + 1);
197         for (int x = d->m_loadedTiles.left(); x <= d->m_loadedTiles.right(); ++x) {
198             d->m_pendingTiles.push_back(makeTile(x, d->m_loadedTiles.bottom()));
199         }
200     }
201 
202     if (!d->m_pendingTiles.empty()) {
203         downloadTiles();
204         return;
205     }
206 
207     d->m_marbleMerger.finalize();
208     d->m_data.setDataSet(std::move(d->m_dataSet));
209     d->m_data.setBoundingBox(bbox);
210 
211     qCDebug(Log) << "o5m loading took" << loadTime.elapsed() << "ms";
212     Q_EMIT isLoadingChanged();
213     Q_EMIT done();
214 }
215 
makeTile(uint32_t x,uint32_t y) const216 Tile MapLoader::makeTile(uint32_t x, uint32_t y) const
217 {
218     auto tile = Tile(x, y, TileZoomLevel);
219     tile.ttl = d->m_ttl;
220     return tile;
221 }
222 
downloadFailed(Tile tile,const QString & errorMessage)223 void MapLoader::downloadFailed(Tile tile, const QString& errorMessage)
224 {
225     Q_UNUSED(tile);
226     d->m_errorMessage = errorMessage;
227     d->m_tileCache.cancelPending();
228     Q_EMIT isLoadingChanged();
229     Q_EMIT done();
230 }
231 
isLoading() const232 bool MapLoader::isLoading() const
233 {
234     return d->m_tileCache.pendingDownloads() > 0;
235 }
236 
hasError() const237 bool MapLoader::hasError() const
238 {
239     return !d->m_errorMessage.isEmpty();
240 }
241 
errorMessage() const242 QString MapLoader::errorMessage() const
243 {
244     return d->m_errorMessage;
245 }
246