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