QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
OsmParserThread.cc
Go to the documentation of this file.
1#include "OsmParserThread.h"
2
3#include "QGCGeo.h"
5
6#include <QtCore/QDir>
7#include <QtCore/QFileInfo>
8#include <QtCore/QThread>
9
10#include <osmium/handler.hpp>
11#include <osmium/io/reader.hpp>
12#include <osmium/io/xml_input.hpp>
13#include <osmium/visitor.hpp>
14
15#include <cmath>
16#include <exception>
17#include <limits>
18#include <utility>
19
20QGC_LOGGING_CATEGORY(OsmParserThreadLog, "Viewer3d.OsmParserThread")
21
22// ============================================================================
23// OsmBuildingHandler — libosmium streaming handler
24// ============================================================================
25
26class OsmBuildingHandler : public osmium::handler::Handler
27{
28public:
29 OsmBuildingHandler(const QGeoCoordinate &gpsRef,
30 const QStringList &singleStorey,
31 const QStringList &doubleStoreyLeisure)
32 : _gpsRef(gpsRef)
33 , _singleStorey(singleStorey)
34 , _doubleStoreyLeisure(doubleStoreyLeisure)
35 {}
36
37 void node(const osmium::Node &node)
38 {
39 const int64_t nodeId = node.id();
40 if (nodeId <= 0) {
41 return;
42 }
43
44 QGeoCoordinate coord;
45 coord.setLatitude(node.location().lat());
46 coord.setLongitude(node.location().lon());
47 coord.setAltitude(0);
48
49 nodes.insert(static_cast<uint64_t>(nodeId), coord);
50 }
51
52 void way(const osmium::Way &way)
53 {
54 const int64_t wayId = way.id();
55 if (wayId == 0) {
56 return;
57 }
58
60 std::vector<QGeoCoordinate> gpsPoints;
61 std::vector<QVector2D> localPoints;
62 double lonMax = -1e10, lonMin = 1e10;
63 double latMax = -1e10, latMin = 1e10;
64 double xMax = -1e10, xMin = 1e10;
65 double yMax = -1e10, yMin = 1e10;
66
67 for (const auto &nr : way.nodes()) {
68 const int64_t refId = nr.ref();
69 if (refId <= 0) {
70 continue;
71 }
72
73 auto it = nodes.constFind(static_cast<uint64_t>(refId));
74 if (it == nodes.constEnd()) {
75 continue;
76 }
77
78 const QGeoCoordinate &gpsCoord = it.value();
79 gpsPoints.push_back(gpsCoord);
80 const QVector3D localPt = QGCGeo::convertGpsToEnu(gpsCoord, _gpsRef);
81 localPoints.push_back(QVector2D(localPt.x(), localPt.y()));
82
83 xMax = std::fmax(xMax, localPt.x());
84 yMax = std::fmax(yMax, localPt.y());
85 xMin = std::fmin(xMin, localPt.x());
86 yMin = std::fmin(yMin, localPt.y());
87
88 lonMax = std::fmax(lonMax, gpsCoord.longitude());
89 latMax = std::fmax(latMax, gpsCoord.latitude());
90 lonMin = std::fmin(lonMin, gpsCoord.longitude());
91 latMin = std::fmin(latMin, gpsCoord.latitude());
92 }
93
94 for (const auto &tag : way.tags()) {
95 const QString key = QString::fromUtf8(tag.key());
96 if (key == QStringLiteral("building:levels")) {
97 building.levels = QString::fromUtf8(tag.value()).toFloat();
98 } else if (key == QStringLiteral("height")) {
99 building.height = QString::fromUtf8(tag.value()).toFloat();
100 } else if (key == QStringLiteral("building") && building.levels == 0 && building.height == 0) {
101 const QString value = QString::fromUtf8(tag.value());
102 if (_singleStorey.contains(value)) {
103 building.levels = 1;
104 } else {
105 building.levels = 2;
106 }
107 } else if (key == QStringLiteral("leisure") && building.levels == 0 && building.height == 0) {
108 const QString value = QString::fromUtf8(tag.value());
109 if (_doubleStoreyLeisure.contains(value)) {
110 building.levels = 2;
111 }
112 }
113 }
114
115 if (gpsPoints.size() > 2) {
116 if (building.levels > 0 || building.height > 0) {
117 coordMin.setLatitude(std::fmin(coordMin.latitude(), latMin));
118 coordMin.setLongitude(std::fmin(coordMin.longitude(), lonMin));
119 coordMax.setLatitude(std::fmax(coordMax.latitude(), latMax));
120 coordMax.setLongitude(std::fmax(coordMax.longitude(), lonMax));
121 }
122 building.points_gps = gpsPoints;
123 building.points_local = localPoints;
124 building.bb_max = QVector2D(xMax, yMax);
125 building.bb_min = QVector2D(xMin, yMin);
126 buildings.insert(static_cast<uint64_t>(wayId), building);
127 }
128 }
129
130 void relation(const osmium::Relation &relation)
131 {
132 const int64_t relationId = relation.id();
133 if (relationId == 0) {
134 return;
135 }
136
138 std::vector<int64_t> idsToRemove;
139 bool isBuilding = false;
140 bool isMultipolygon = false;
141
142 for (const auto &member : relation.members()) {
143 if (member.type() == osmium::item_type::way) {
144 const int64_t refId = member.ref();
145 const QString role = QString::fromUtf8(member.role());
146 auto bldItem = buildings.find(static_cast<uint64_t>(refId));
147 if (bldItem != buildings.end()) {
148 building.append(bldItem.value().points_local, role == QStringLiteral("inner"));
149 building.append(bldItem.value().points_gps, role == QStringLiteral("inner"));
150 building.levels = std::fmax(building.levels, bldItem.value().levels);
151 building.height = std::fmax(building.height, bldItem.value().height);
152
153 building.bb_max[0] = std::fmax(building.bb_max[0], bldItem.value().bb_max[0]);
154 building.bb_max[1] = std::fmax(building.bb_max[1], bldItem.value().bb_max[1]);
155 building.bb_min[0] = std::fmin(building.bb_min[0], bldItem.value().bb_min[0]);
156 building.bb_min[1] = std::fmin(building.bb_min[1], bldItem.value().bb_min[1]);
157 idsToRemove.push_back(refId);
158 }
159 }
160 }
161
162 for (const auto &tag : relation.tags()) {
163 const QString key = QString::fromUtf8(tag.key());
164 if (key == QStringLiteral("type")) {
165 if (QString::fromUtf8(tag.value()) == QStringLiteral("multipolygon")) {
166 isMultipolygon = true;
167 }
168 } else if (key == QStringLiteral("building")) {
169 isBuilding = true;
170 }
171 }
172
173 if (isBuilding) {
174 if (building.height == 0) {
175 building.levels = (building.levels == 0) ? 2 : building.levels;
176 }
177 }
178
179 if (isMultipolygon && !idsToRemove.empty()) {
180 for (int64_t id : idsToRemove) {
181 buildings.remove(static_cast<uint64_t>(id));
182 }
183 buildings.insert(static_cast<uint64_t>(idsToRemove[0]), building);
184 }
185 }
186
187 QMap<uint64_t, QGeoCoordinate> nodes;
188 QMap<uint64_t, OsmParserThread::BuildingType_t> buildings;
189 QGeoCoordinate coordMin;
190 QGeoCoordinate coordMax;
191
192private:
193 const QGeoCoordinate &_gpsRef;
194 const QStringList &_singleStorey;
195 const QStringList &_doubleStoreyLeisure;
196};
197
198// ============================================================================
199// BuildingType_t helpers
200// ============================================================================
201
202void OsmParserThread::BuildingType_t::append(const std::vector<QGeoCoordinate> &newPoints, bool isInner)
203{
204 auto &target = isInner ? points_gps_inner : points_gps;
205 target.insert(target.end(), newPoints.begin(), newPoints.end());
206}
207
208void OsmParserThread::BuildingType_t::append(const std::vector<QVector2D> &newPoints, bool isInner)
209{
210 auto &target = isInner ? points_local_inner : points_local;
211 target.insert(target.end(), newPoints.begin(), newPoints.end());
212}
213
214// ============================================================================
215// OsmParserThread
216// ============================================================================
217
219 : QObject{nullptr}
220 , _workerThread(new QThread())
221{
222 connect(this, &OsmParserThread::startThread, this, &OsmParserThread::_parseOsmFile);
223
224 this->moveToThread(_workerThread);
225 _workerThread->start();
226}
227
229{
230 _workerThread->quit();
231 _workerThread->wait();
232 delete _workerThread;
233}
234
235void OsmParserThread::start(const QString &filePath)
236{
237 emit startThread(filePath);
238}
239
240void OsmParserThread::_parseOsmFile(const QString &filePath)
241{
242 _mapNodes.clear();
243 _mapBuildings.clear();
244
245 if (filePath.isEmpty()) {
246 if (_mapLoadedFlag) {
247 qCDebug(OsmParserThreadLog, "The 3D View has been cleared!");
248 } else {
249 qCDebug(OsmParserThreadLog, "No OSM File is selected!");
250 }
251 _mapLoadedFlag = false;
252 return;
253 }
254
255 _mapLoadedFlag = false;
256
257 QString resolvedPath = filePath;
258#ifdef Q_OS_UNIX
259 if (!QDir::isAbsolutePath(resolvedPath)) {
260 resolvedPath = QStringLiteral("/") + filePath;
261 }
262#endif
263
264 QFileInfo fileInfo(resolvedPath);
265 if (!fileInfo.exists() || !fileInfo.isFile()) {
266 qCDebug(OsmParserThreadLog) << "OSM file does not exist:" << resolvedPath;
267 emit fileParsed(false);
268 return;
269 }
270 const QString suffix = fileInfo.suffix().toLower();
271 if (suffix != QStringLiteral("osm") && suffix != QStringLiteral("xml")) {
272 qCDebug(OsmParserThreadLog) << "Invalid file extension:" << suffix;
273 emit fileParsed(false);
274 return;
275 }
276
277 try {
278 osmium::io::File inputFile{resolvedPath.toStdString()};
279 osmium::io::Reader reader{inputFile, osmium::osm_entity_bits::all};
280
281 const auto &header = reader.header();
282 bool hasHeaderBounds = !header.boxes().empty();
283 if (hasHeaderBounds) {
284 const auto &box = header.boxes().front();
285 _coordinateMin = QGeoCoordinate(box.bottom_left().lat(), box.bottom_left().lon(), 0);
286 _coordinateMax = QGeoCoordinate(box.top_right().lat(), box.top_right().lon(), 0);
287 _gpsRefPoint = QGeoCoordinate(
288 0.5 * (_coordinateMin.latitude() + _coordinateMax.latitude()),
289 0.5 * (_coordinateMin.longitude() + _coordinateMax.longitude()), 0);
290 }
291
292 OsmBuildingHandler handler(_gpsRefPoint, _singleStoreyBuildings, _doubleStoreyLeisure);
293 handler.coordMin = _coordinateMin;
294 handler.coordMax = _coordinateMax;
295 osmium::apply(reader, handler);
296 reader.close();
297
298 _mapNodes = std::move(handler.nodes);
299 _mapBuildings = std::move(handler.buildings);
300 if (!hasHeaderBounds) {
301 // Some libosmium builds do not expose bounds in header for valid .osm files.
302 if (_mapNodes.isEmpty()) {
303 emit fileParsed(false);
304 return;
305 }
306
307 double minLat = std::numeric_limits<double>::max();
308 double minLon = std::numeric_limits<double>::max();
309 double maxLat = std::numeric_limits<double>::lowest();
310 double maxLon = std::numeric_limits<double>::lowest();
311
312 for (auto it = _mapNodes.cbegin(); it != _mapNodes.cend(); ++it) {
313 minLat = std::fmin(minLat, it.value().latitude());
314 minLon = std::fmin(minLon, it.value().longitude());
315 maxLat = std::fmax(maxLat, it.value().latitude());
316 maxLon = std::fmax(maxLon, it.value().longitude());
317 }
318
319 _coordinateMin = QGeoCoordinate(minLat, minLon, 0);
320 _coordinateMax = QGeoCoordinate(maxLat, maxLon, 0);
321 _gpsRefPoint = QGeoCoordinate(0.5 * (minLat + maxLat), 0.5 * (minLon + maxLon), 0);
322
323 // Building local coordinates were computed before fallback bounds were known.
324 // Recompute with the finalized reference point for consistent geometry.
325 for (auto it = _mapBuildings.begin(); it != _mapBuildings.end(); ++it) {
326 BuildingType_t &building = it.value();
327 building.points_local.clear();
328 building.points_local_inner.clear();
329
330 building.bb_max = QVector2D(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest());
331 building.bb_min = QVector2D(std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
332
333 for (const QGeoCoordinate &gpsCoord : building.points_gps) {
334 const QVector3D localPt = QGCGeo::convertGpsToEnu(gpsCoord, _gpsRefPoint);
335 const QVector2D local2D(localPt.x(), localPt.y());
336 building.points_local.push_back(local2D);
337
338 building.bb_max[0] = std::fmax(building.bb_max[0], local2D.x());
339 building.bb_max[1] = std::fmax(building.bb_max[1], local2D.y());
340 building.bb_min[0] = std::fmin(building.bb_min[0], local2D.x());
341 building.bb_min[1] = std::fmin(building.bb_min[1], local2D.y());
342 }
343
344 for (const QGeoCoordinate &gpsCoord : building.points_gps_inner) {
345 const QVector3D localPt = QGCGeo::convertGpsToEnu(gpsCoord, _gpsRefPoint);
346 const QVector2D local2D(localPt.x(), localPt.y());
347 building.points_local_inner.push_back(local2D);
348
349 building.bb_max[0] = std::fmax(building.bb_max[0], local2D.x());
350 building.bb_max[1] = std::fmax(building.bb_max[1], local2D.y());
351 building.bb_min[0] = std::fmin(building.bb_min[0], local2D.x());
352 building.bb_min[1] = std::fmin(building.bb_min[1], local2D.y());
353 }
354 }
355 } else {
356 _coordinateMin = handler.coordMin;
357 _coordinateMax = handler.coordMax;
358 }
359
360 _mapLoadedFlag = true;
361 emit fileParsed(true);
362 } catch (const std::exception &e) {
363 qCDebug(OsmParserThreadLog) << "OSM parse error:" << e.what();
364 emit fileParsed(false);
365 }
366}
Geographic coordinate conversion utilities using GeographicLib.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void way(const osmium::Way &way)
OsmBuildingHandler(const QGeoCoordinate &gpsRef, const QStringList &singleStorey, const QStringList &doubleStoreyLeisure)
QMap< uint64_t, OsmParserThread::BuildingType_t > buildings
QMap< uint64_t, QGeoCoordinate > nodes
void node(const osmium::Node &node)
QGeoCoordinate coordMin
QGeoCoordinate coordMax
void relation(const osmium::Relation &relation)
OsmParserThread(QObject *parent=nullptr)
void fileParsed(bool isValid)
void start(const QString &filePath)
void startThread(const QString &filePath)
QVector3D convertGpsToEnu(const QGeoCoordinate &coord, const QGeoCoordinate &ref)
Definition QGCGeo.cc:80
void append(const std::vector< QGeoCoordinate > &newPoints, bool isInner)
std::vector< QGeoCoordinate > points_gps
std::vector< QVector2D > points_local
std::vector< QGeoCoordinate > points_gps_inner