KOSMIndoorMap

maploader.cpp
1 /*
2  SPDX-FileCopyrightText: 2020 Volker Krause <[email protected]>
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 
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 
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 
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 
99 void MapLoader::loadForCoordinate(double lat, double lon)
100 {
101  loadForCoordinate(lat, lon, {});
102 }
103 
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 
121 {
122  return std::move(d->m_data);
123 }
124 
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 
140 void MapLoader::downloadFinished()
141 {
142  if (d->m_tileCache.pendingDownloads() > 0) {
143  return;
144  }
145  loadTiles();
146 }
147 
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 
216 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 
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 
232 bool MapLoader::isLoading() const
233 {
234  return d->m_tileCache.pendingDownloads() > 0;
235 }
236 
237 bool MapLoader::hasError() const
238 {
239  return !d->m_errorMessage.isEmpty();
240 }
241 
242 QString MapLoader::errorMessage() const
243 {
244  return d->m_errorMessage;
245 }
OSM-based multi-floor indoor maps for buildings.
QString errorString() const const
virtual QString fileName() const const override
int size() const const
Coordinate, stored as 1e7 * degree to avoid floating point precision issues, and offset to unsigned v...
Definition: datatypes.h:37
QUrl fromUserInput(const QString &userInput)
Holds OSM elements produced by a parser prior to merging into OSM::DataSet.
uchar * map(qint64 offset, qint64 size, QFileDevice::MemoryMapFlags flags)
Raw OSM map data, separated by levels.
Definition: mapdata.h:59
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
Q_INVOKABLE void loadForCoordinate(double lat, double lon)
Load map for the given coordinates.
Definition: maploader.cpp:99
virtual bool open(QIODevice::OpenMode mode) override
QString toLocalFile() const const
qreal x() const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
virtual qint64 size() const const override
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
Parser of .osm.pbf files.
Definition: osmpbfparser.h:24
void parse(const uint8_t *data, std::size_t len)
Parse the given binary content.
Definition: o5mparser.cpp:52
void setMergeBuffer(OSM::DataSetMergeBuffer *buffer)
Sets a merge buffer.
Definition: o5mparser.cpp:47
Zero-copy parser of O5M binary files.
Definition: o5mparser.h:26
MapData && takeData()
Take out the completely loaded result.
Definition: maploader.cpp:120
A set of nodes, ways and relations.
Definition: datatypes.h:283
qreal y() const const
Q_INVOKABLE void loadFromFile(const QString &fileName)
Load a single O5M or OSM PBF file.
Definition: maploader.cpp:68
Bounding box, ie.
Definition: datatypes.h:95
QueuedConnection
void parse(const uint8_t *data, std::size_t len)
Parse the given binary content.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
qint64 elapsed() const const
Q_EMITQ_EMIT
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Oct 23 2021 23:03:45 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.