KOSMIndoorMap

maploader.cpp
1/*
2 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include <config-kosmindoormap.h>
8
9#include "maploader.h"
10#include "boundarysearch_p.h"
11#include "logging.h"
12#include "mapdata.h"
13#include "marblegeometryassembler_p.h"
14#include "tilecache_p.h"
15
16#include <osm/datatypes.h>
17#include <osm/datasetmergebuffer.h>
18#include <osm/element.h>
19#include <osm/o5mparser.h>
20#include <osm/io.h>
21
22#include <QDateTime>
23#include <QElapsedTimer>
24#include <QFile>
25#include <QRect>
26#include <QUrl>
27
28enum {
29 TileZoomLevel = 17
30};
31
32inline void initResources() // needs to be outside of a namespace
33{
34#if !BUILD_TOOLS_ONLY
35 Q_INIT_RESOURCE(assets);
36#endif
37}
38
39namespace KOSMIndoorMap {
40class MapLoaderPrivate {
41public:
42 OSM::DataSet m_dataSet;
43 OSM::DataSetMergeBuffer m_mergeBuffer;
44 MarbleGeometryAssembler m_marbleMerger;
45 MapData m_data;
46 TileCache m_tileCache;
47 OSM::BoundingBox m_tileBbox;
48 QRect m_loadedTiles;
49 std::vector<Tile> m_pendingTiles;
50 std::unique_ptr<BoundarySearch> m_boundarySearcher;
51 QDateTime m_ttl;
52
53 QString m_errorMessage;
54};
55}
56
57using namespace KOSMIndoorMap;
58
59MapLoader::MapLoader(QObject *parent)
60 : QObject(parent)
61 , d(new MapLoaderPrivate)
62{
63 initResources();
64 connect(&d->m_tileCache, &TileCache::tileLoaded, this, &MapLoader::downloadFinished);
65 connect(&d->m_tileCache, &TileCache::tileError, this, &MapLoader::downloadFailed);
66 d->m_tileCache.expire();
67}
68
69MapLoader::~MapLoader() = default;
70
71void MapLoader::loadFromFile(const QString &fileName)
72{
73 QElapsedTimer loadTime;
74 loadTime.start();
75
76 d->m_errorMessage.clear();
77 QFile f(fileName.contains(QLatin1Char(':')) ? QUrl::fromUserInput(fileName).toLocalFile() : fileName);
78 if (!f.open(QFile::ReadOnly)) {
79 qCritical() << f.fileName() << f.errorString();
80 return;
81 }
82 const auto data = f.map(0, f.size());
83
84 OSM::DataSet ds;
85 auto reader = OSM::IO::readerForFileName(fileName, &ds);
86 if (!reader) {
87 qCWarning(Log) << "no file reader for" << fileName;
88 return;
89 }
90 reader->read(data, f.size());
91 d->m_data = MapData();
92 d->m_data.setDataSet(std::move(ds));
93 qCDebug(Log) << "o5m loading took" << loadTime.elapsed() << "ms";
94 Q_EMIT done();
95}
96
97void MapLoader::loadForCoordinate(double lat, double lon)
98{
99 loadForCoordinate(lat, lon, {});
100}
101
102void MapLoader::loadForCoordinate(double lat, double lon, const QDateTime &ttl)
103{
104 d->m_ttl = ttl;
105 d->m_tileBbox = {};
106 d->m_pendingTiles.clear();
107 d->m_boundarySearcher = std::make_unique<BoundarySearch>();
108 d->m_boundarySearcher->init(OSM::Coordinate(lat, lon));
109 d->m_errorMessage.clear();
110 d->m_marbleMerger.setDataSet(&d->m_dataSet);
111 d->m_data = MapData();
112
113 auto tile = Tile::fromCoordinate(lat, lon, TileZoomLevel);
114 d->m_loadedTiles = QRect(tile.x, tile.y, 1, 1);
115 d->m_pendingTiles.push_back(std::move(tile));
116 downloadTiles();
117}
118
120{
121 d->m_ttl = {};
122 d->m_tileBbox = box;
123 d->m_pendingTiles.clear();
124 d->m_errorMessage.clear();
125 d->m_marbleMerger.setDataSet(&d->m_dataSet);
126 d->m_data = MapData();
127
128 const auto topLeftTile = Tile::fromCoordinate(box.min.latF(), box.min.lonF(), TileZoomLevel);
129 const auto bottomRightTile = Tile::fromCoordinate(box.max.latF(), box.max.lonF(), TileZoomLevel);
130 for (auto x = topLeftTile.x; x <= bottomRightTile.x; ++x) {
131 for (auto y = bottomRightTile.y; y <= topLeftTile.y; ++y) {
132 d->m_pendingTiles.push_back(makeTile(x, y));
133 }
134 }
135 downloadTiles();
136}
137
139{
140 d->m_ttl = {};
141 d->m_tileBbox = tile.boundingBox();
142 d->m_pendingTiles.clear();
143 d->m_errorMessage.clear();
144 d->m_marbleMerger.setDataSet(&d->m_dataSet);
145 d->m_data = MapData();
146
147 if (tile.z >= TileZoomLevel) {
148 d->m_pendingTiles.push_back(std::move(tile));
149 } else {
150 const auto start = tile.topLeftAtZ(TileZoomLevel);
151 const auto end = tile.bottomRightAtZ(TileZoomLevel);
152 for (auto x = start.x; x <= end.x; ++x) {
153 for (auto y = start.y; y <= end.y; ++y) {
154 d->m_pendingTiles.push_back(makeTile(x, y));
155 }
156 }
157 }
158
159 downloadTiles();
160}
161
163{
164 return std::move(d->m_data);
165}
166
167void MapLoader::downloadTiles()
168{
169 for (const auto &tile : d->m_pendingTiles) {
170 d->m_tileCache.ensureCached(tile);
171 }
172 if (d->m_tileCache.pendingDownloads() == 0) {
173 // still go through the event loop when having everything cached already
174 // this makes outside behavior more identical in both cases, and avoids
175 // signal connection races etc.
176 QMetaObject::invokeMethod(this, &MapLoader::loadTiles, Qt::QueuedConnection);
177 } else {
178 Q_EMIT isLoadingChanged();
179 }
180}
181
182void MapLoader::downloadFinished()
183{
184 if (d->m_tileCache.pendingDownloads() > 0) {
185 return;
186 }
187 loadTiles();
188}
189
190void MapLoader::loadTiles()
191{
192 QElapsedTimer loadTime;
193 loadTime.start();
194
195 OSM::O5mParser p(&d->m_dataSet);
196 p.setMergeBuffer(&d->m_mergeBuffer);
197 for (const auto &tile : d->m_pendingTiles) {
198 const auto fileName = d->m_tileCache.cachedTile(tile);
199 qCDebug(Log) << "loading tile" << fileName;
200 QFile f(fileName);
201 if (!f.open(QFile::ReadOnly)) {
202 qWarning() << f.fileName() << f.errorString();
203 break;
204 }
205 const auto data = f.map(0, f.size());
206 p.read(data, f.size());
207 d->m_marbleMerger.merge(&d->m_mergeBuffer);
208
209 d->m_tileBbox = OSM::unite(d->m_tileBbox, tile.boundingBox());
210 }
211 d->m_pendingTiles.clear();
212
213 if (d->m_boundarySearcher) {
214 const auto bbox = d->m_boundarySearcher->boundingBox(d->m_dataSet);
215 qCDebug(Log) << "needed bbox:" << bbox << "got:" << d->m_tileBbox << d->m_loadedTiles;
216
217 // expand left and right
218 if (bbox.min.longitude < d->m_tileBbox.min.longitude) {
219 d->m_loadedTiles.setLeft(d->m_loadedTiles.left() - 1);
220 for (int y = d->m_loadedTiles.top(); y <= d->m_loadedTiles.bottom(); ++y) {
221 d->m_pendingTiles.push_back(makeTile(d->m_loadedTiles.left(), y));
222 }
223 }
224 if (bbox.max.longitude > d->m_tileBbox.max.longitude) {
225 d->m_loadedTiles.setRight(d->m_loadedTiles.right() + 1);
226 for (int y = d->m_loadedTiles.top(); y <= d->m_loadedTiles.bottom(); ++y) {
227 d->m_pendingTiles.push_back(makeTile(d->m_loadedTiles.right(), y));
228 }
229 }
230
231 // expand top/bottom: note that geographics and slippy map tile coordinates have a different understanding on what is "top"
232 if (bbox.max.latitude > d->m_tileBbox.max.latitude) {
233 d->m_loadedTiles.setTop(d->m_loadedTiles.top() - 1);
234 for (int x = d->m_loadedTiles.left(); x <= d->m_loadedTiles.right(); ++x) {
235 d->m_pendingTiles.push_back(makeTile(x, d->m_loadedTiles.top()));
236 }
237 }
238 if (bbox.min.latitude < d->m_tileBbox.min.latitude) {
239 d->m_loadedTiles.setBottom(d->m_loadedTiles.bottom() + 1);
240 for (int x = d->m_loadedTiles.left(); x <= d->m_loadedTiles.right(); ++x) {
241 d->m_pendingTiles.push_back(makeTile(x, d->m_loadedTiles.bottom()));
242 }
243 }
244
245 if (!d->m_pendingTiles.empty()) {
246 downloadTiles();
247 return;
248 }
249 d->m_data.setBoundingBox(bbox);
250 }
251
252 d->m_marbleMerger.finalize();
253 d->m_data.setDataSet(std::move(d->m_dataSet));
254 d->m_boundarySearcher.reset();
255
256 qCDebug(Log) << "o5m loading took" << loadTime.elapsed() << "ms";
257 Q_EMIT isLoadingChanged();
258 Q_EMIT done();
259}
260
261Tile MapLoader::makeTile(uint32_t x, uint32_t y) const
262{
263 auto tile = Tile(x, y, TileZoomLevel);
264 tile.ttl = d->m_ttl;
265 return tile;
266}
267
268void MapLoader::downloadFailed(Tile tile, const QString& errorMessage)
269{
270 Q_UNUSED(tile);
271 d->m_errorMessage = errorMessage;
272 d->m_tileCache.cancelPending();
273 Q_EMIT isLoadingChanged();
274 Q_EMIT done();
275}
276
277bool MapLoader::isLoading() const
278{
279 return d->m_tileCache.pendingDownloads() > 0;
280}
281
282bool MapLoader::hasError() const
283{
284 return !d->m_errorMessage.isEmpty();
285}
286
287QString MapLoader::errorMessage() const
288{
289 return d->m_errorMessage;
290}
291
292#include "moc_maploader.cpp"
Raw OSM map data, separated by levels.
Definition mapdata.h:60
Q_INVOKABLE void loadFromFile(const QString &fileName)
Load a single O5M or OSM PBF file.
Definition maploader.cpp:71
void done()
Emitted when the requested data has been loaded.
bool isLoading
Indicates we are downloading content.
Definition maploader.h:32
void loadForBoundingBox(OSM::BoundingBox box)
Load map data for the given bounding box, without applying the boundary search.
Q_INVOKABLE void loadForCoordinate(double lat, double lon)
Load map for the given coordinates.
Definition maploader.cpp:97
MapData && takeData()
Take out the completely loaded result.
void loadForTile(Tile tile)
Load map data for the given tile.
Bounding box, ie.
Definition datatypes.h:95
Coordinate, stored as 1e7 * degree to avoid floating point precision issues, and offset to unsigned v...
Definition datatypes.h:37
Holds OSM elements produced by a parser prior to merging into OSM::DataSet.
A set of nodes, ways and relations.
Definition datatypes.h:340
Zero-copy parser of O5M binary files.
Definition o5mparser.h:28
Q_SCRIPTABLE Q_NOREPLY void start()
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
OSM-based multi-floor indoor maps for buildings.
KOSM_EXPORT std::unique_ptr< AbstractReader > readerForFileName(QStringView fileName, OSM::DataSet *dataSet)
Returns a suitable reader for the given file name.
Definition io.cpp:40
qint64 elapsed() const const
virtual QString fileName() const const override
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_EMITQ_EMIT
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QueuedConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QUrl fromUserInput(const QString &userInput, const QString &workingDirectory, UserInputResolutionOptions options)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.