QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCGeo.cc
Go to the documentation of this file.
1/****************************************************************************
2 *
3 * (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 *
5 * QGroundControl is licensed according to the terms in the file
6 * COPYING.md in the root of the source code directory.
7 *
8 ****************************************************************************/
9
10#include "QGCGeo.h"
11#include "QGCLoggingCategory.h"
12
13#include <QtCore/QString>
14
15#include <cmath>
16
17#include <GeographicLib/Geocentric.hpp>
18#include <GeographicLib/Geodesic.hpp>
19#include <GeographicLib/GeodesicLine.hpp>
20#include <GeographicLib/LocalCartesian.hpp>
21#include <GeographicLib/MGRS.hpp>
22#include <GeographicLib/PolygonArea.hpp>
23#include <GeographicLib/UTMUPS.hpp>
24
25QGC_LOGGING_CATEGORY(QGCGeoLog, "Utilities.QGCGeo")
26
27namespace QGCGeo
28{
29
30// ============================================================================
31// NED (North-East-Down) Local Tangent Plane
32// ============================================================================
33
34void convertGeoToNed(const QGeoCoordinate &coord, const QGeoCoordinate &origin, double &x, double &y, double &z)
35{
36 if (coord == origin) {
37 x = y = z = 0.0;
38 return;
39 }
40
41 // Handle NaN altitude (QGeoCoordinate without altitude set)
42 const double originAlt = std::isnan(origin.altitude()) ? 0.0 : origin.altitude();
43 const double coordAlt = std::isnan(coord.altitude()) ? 0.0 : coord.altitude();
44
45 double east, north, up;
46 GeographicLib::LocalCartesian ltp(origin.latitude(), origin.longitude(), originAlt,
47 GeographicLib::Geocentric::WGS84());
48 ltp.Forward(coord.latitude(), coord.longitude(), coordAlt, east, north, up);
49
50 // Convert ENU to NED
51 x = north;
52 y = east;
53 z = -up;
54}
55
56void convertNedToGeo(double x, double y, double z, const QGeoCoordinate &origin, QGeoCoordinate &coord)
57{
58 // Convert NED to ENU
59 const double east = y;
60 const double north = x;
61 const double up = -z;
62
63 // Handle NaN altitude
64 const double originAlt = std::isnan(origin.altitude()) ? 0.0 : origin.altitude();
65
66 double lat, lon, alt;
67 GeographicLib::LocalCartesian ltp(origin.latitude(), origin.longitude(), originAlt,
68 GeographicLib::Geocentric::WGS84());
69 ltp.Reverse(east, north, up, lat, lon, alt);
70
71 coord.setLatitude(lat);
72 coord.setLongitude(lon);
73 coord.setAltitude(alt);
74}
75
76// ============================================================================
77// ENU (East-North-Up) Local Tangent Plane
78// ============================================================================
79
80QVector3D convertGpsToEnu(const QGeoCoordinate &coord, const QGeoCoordinate &ref)
81{
82 double x, y, z;
83 GeographicLib::LocalCartesian ltp(ref.latitude(), ref.longitude(), ref.altitude(),
84 GeographicLib::Geocentric::WGS84());
85 ltp.Forward(coord.latitude(), coord.longitude(), coord.altitude(), x, y, z);
86 return QVector3D(x, y, z);
87}
88
89QGeoCoordinate convertEnuToGps(const QVector3D &enu, const QGeoCoordinate &ref)
90{
91 double lat, lon, alt;
92 GeographicLib::LocalCartesian ltp(ref.latitude(), ref.longitude(), ref.altitude(),
93 GeographicLib::Geocentric::WGS84());
94 ltp.Reverse(enu.x(), enu.y(), enu.z(), lat, lon, alt);
95 return QGeoCoordinate(lat, lon, alt);
96}
97
98// ============================================================================
99// ECEF (Earth-Centered Earth-Fixed)
100// ============================================================================
101
102QVector3D convertGeodeticToEcef(const QGeoCoordinate &coord)
103{
104 double x, y, z;
105 GeographicLib::Geocentric::WGS84().Forward(coord.latitude(), coord.longitude(), coord.altitude(), x, y, z);
106 return QVector3D(x, y, z);
107}
108
109QGeoCoordinate convertEcefToGeodetic(const QVector3D &ecef)
110{
111 double lat, lon, alt;
112 GeographicLib::Geocentric::WGS84().Reverse(ecef.x(), ecef.y(), ecef.z(), lat, lon, alt);
113 return QGeoCoordinate(lat, lon, alt);
114}
115
116QVector3D convertEcefToEnu(const QVector3D &ecef, const QGeoCoordinate &ref)
117{
118 // ECEF -> Geodetic
119 double lat, lon, h;
120 GeographicLib::Geocentric::WGS84().Reverse(ecef.x(), ecef.y(), ecef.z(), lat, lon, h);
121
122 // Geodetic -> ENU
123 double x, y, z;
124 GeographicLib::LocalCartesian ltp(ref.latitude(), ref.longitude(), ref.altitude(),
125 GeographicLib::Geocentric::WGS84());
126 ltp.Forward(lat, lon, h, x, y, z);
127 return QVector3D(x, y, z);
128}
129
130QVector3D convertEnuToEcef(const QVector3D &enu, const QGeoCoordinate &ref)
131{
132 // ENU -> Geodetic
133 double lat, lon, h;
134 GeographicLib::LocalCartesian ltp(ref.latitude(), ref.longitude(), ref.altitude(),
135 GeographicLib::Geocentric::WGS84());
136 ltp.Reverse(enu.x(), enu.y(), enu.z(), lat, lon, h);
137
138 // Geodetic -> ECEF
139 double x, y, z;
140 GeographicLib::Geocentric::WGS84().Forward(lat, lon, h, x, y, z);
141 return QVector3D(x, y, z);
142}
143
144// ============================================================================
145// UTM (Universal Transverse Mercator)
146// ============================================================================
147
148int convertGeoToUTM(const QGeoCoordinate &coord, double &easting, double &northing)
149{
150 try {
151 int zone;
152 bool northp;
153 GeographicLib::UTMUPS::Forward(coord.latitude(), coord.longitude(), zone, northp, easting, northing);
154 return zone;
155 } catch (const GeographicLib::GeographicErr &e) {
156 qCDebug(QGCGeoLog) << e.what();
157 return 0;
158 }
159}
160
161bool convertUTMToGeo(double easting, double northing, int zone, bool southhemi, QGeoCoordinate &coord)
162{
163 try {
164 double lat, lon;
165 GeographicLib::UTMUPS::Reverse(zone, !southhemi, easting, northing, lat, lon);
166 coord.setLatitude(lat);
167 coord.setLongitude(lon);
168 return true;
169 } catch (const GeographicLib::GeographicErr &e) {
170 qCDebug(QGCGeoLog) << e.what();
171 return false;
172 }
173}
174
175// ============================================================================
176// MGRS (Military Grid Reference System)
177// ============================================================================
178
179QString convertGeoToMGRS(const QGeoCoordinate &coord)
180{
181 try {
182 int zone;
183 bool northp;
184 double x, y;
185 GeographicLib::UTMUPS::Forward(coord.latitude(), coord.longitude(), zone, northp, x, y);
186
187 std::string mgrs;
188 GeographicLib::MGRS::Forward(zone, northp, x, y, coord.latitude(), 5, mgrs);
189
190 // Format with spaces: "32TMT6588647092" -> "32TMT 65886 47092"
191 const QString qstr = QString::fromStdString(mgrs);
192 for (int i = qstr.length() - 1; i >= 0; i--) {
193 if (!qstr.at(i).isDigit()) {
194 const int numLen = (qstr.length() - i - 1) / 2;
195 return qstr.left(i + 1) + " " + qstr.mid(i + 1, numLen) + " " + qstr.mid(i + 1 + numLen);
196 }
197 }
198 return qstr;
199 } catch (const GeographicLib::GeographicErr &e) {
200 qCDebug(QGCGeoLog) << e.what();
201 return QString();
202 }
203}
204
205bool convertMGRSToGeo(const QString &mgrs, QGeoCoordinate &coord)
206{
207 try {
208 int zone, prec;
209 bool northp;
210 double x, y;
211 GeographicLib::MGRS::Reverse(mgrs.simplified().replace(" ", "").toStdString(), zone, northp, x, y, prec);
212
213 double lat, lon;
214 GeographicLib::UTMUPS::Reverse(zone, northp, x, y, lat, lon);
215
216 coord.setLatitude(lat);
217 coord.setLongitude(lon);
218 return true;
219 } catch (const GeographicLib::GeographicErr &e) {
220 qCDebug(QGCGeoLog) << e.what();
221 return false;
222 }
223}
224
225// ============================================================================
226// Geodesic Calculations (Great Circle on Ellipsoid)
227// ============================================================================
228
229double geodesicDistance(const QGeoCoordinate &from, const QGeoCoordinate &to)
230{
231 double distance;
232 GeographicLib::Geodesic::WGS84().Inverse(from.latitude(), from.longitude(),
233 to.latitude(), to.longitude(), distance);
234 return distance;
235}
236
237double geodesicAzimuth(const QGeoCoordinate &from, const QGeoCoordinate &to)
238{
239 double distance, azimuth1, azimuth2;
240 GeographicLib::Geodesic::WGS84().Inverse(from.latitude(), from.longitude(),
241 to.latitude(), to.longitude(), distance, azimuth1, azimuth2);
242
243 // Normalize to [0, 360)
244 if (azimuth1 < 0.0) {
245 azimuth1 += 360.0;
246 }
247 return azimuth1;
248}
249
250QGeoCoordinate geodesicDestination(const QGeoCoordinate &from, double azimuth, double distance)
251{
252 double lat, lon;
253 GeographicLib::Geodesic::WGS84().Direct(from.latitude(), from.longitude(), azimuth, distance, lat, lon);
254 return QGeoCoordinate(lat, lon, from.altitude());
255}
256
257// ============================================================================
258// Path and Polygon Calculations
259// ============================================================================
260
261double pathLength(const QList<QGeoCoordinate> &path)
262{
263 if (path.size() < 2) {
264 return 0.0;
265 }
266
267 double totalLength = 0.0;
268 for (int i = 1; i < path.size(); ++i) {
269 totalLength += geodesicDistance(path[i - 1], path[i]);
270 }
271 return totalLength;
272}
273
274double polygonArea(const QList<QGeoCoordinate> &polygon)
275{
276 if (polygon.size() < 3) {
277 return 0.0;
278 }
279
280 GeographicLib::PolygonArea poly(GeographicLib::Geodesic::WGS84());
281 for (const QGeoCoordinate &coord : polygon) {
282 poly.AddPoint(coord.latitude(), coord.longitude());
283 }
284
285 double perimeter, area;
286 poly.Compute(false, true, perimeter, area);
287 // Area sign indicates winding order (positive = counter-clockwise, negative = clockwise).
288 // Return absolute value since we only care about magnitude.
289 return qAbs(area);
290}
291
292double polygonPerimeter(const QList<QGeoCoordinate> &polygon)
293{
294 if (polygon.size() < 2) {
295 return 0.0;
296 }
297
298 GeographicLib::PolygonArea poly(GeographicLib::Geodesic::WGS84());
299 for (const QGeoCoordinate &coord : polygon) {
300 poly.AddPoint(coord.latitude(), coord.longitude());
301 }
302
303 double perimeter, area;
304 poly.Compute(false, true, perimeter, area);
305 return perimeter;
306}
307
308QList<QGeoCoordinate> interpolatePath(const QGeoCoordinate &from, const QGeoCoordinate &to, int numPoints)
309{
310 QList<QGeoCoordinate> result;
311
312 // Clamp to reasonable bounds to prevent excessive memory allocation
313 constexpr int kMaxPoints = 10000;
314 if (numPoints < 2) {
315 numPoints = 2;
316 } else if (numPoints > kMaxPoints) {
317 qCWarning(QGCGeoLog) << "interpolatePath: numPoints" << numPoints << "exceeds maximum, clamping to" << kMaxPoints;
318 numPoints = kMaxPoints;
319 }
320
321 if (from == to) {
322 for (int i = 0; i < numPoints; ++i) {
323 result.append(from);
324 }
325 return result;
326 }
327
328 // Create GeodesicLine for efficient multi-point interpolation
329 const GeographicLib::GeodesicLine line = GeographicLib::Geodesic::WGS84().InverseLine(
330 from.latitude(), from.longitude(), to.latitude(), to.longitude());
331
332 const double totalDistance = line.Distance();
333 const double altDiff = to.altitude() - from.altitude();
334
335 for (int i = 0; i < numPoints; ++i) {
336 const double fraction = static_cast<double>(i) / (numPoints - 1);
337 const double distance = totalDistance * fraction;
338
339 double lat, lon;
340 line.Position(distance, lat, lon);
341
342 const double alt = from.altitude() + altDiff * fraction;
343 result.append(QGeoCoordinate(lat, lon, alt));
344 }
345
346 return result;
347}
348
349QGeoCoordinate interpolateAtDistance(const QGeoCoordinate &from, const QGeoCoordinate &to, double distance)
350{
351 if (from == to || distance <= 0.0) {
352 return from;
353 }
354
355 const GeographicLib::GeodesicLine line = GeographicLib::Geodesic::WGS84().InverseLine(
356 from.latitude(), from.longitude(), to.latitude(), to.longitude());
357
358 const double totalDistance = line.Distance();
359
360 if (distance >= totalDistance) {
361 return to;
362 }
363
364 double lat, lon;
365 line.Position(distance, lat, lon);
366
367 // Linear altitude interpolation
368 const double fraction = distance / totalDistance;
369 const double alt = from.altitude() + (to.altitude() - from.altitude()) * fraction;
370
371 return QGeoCoordinate(lat, lon, alt);
372}
373
374} // namespace QGCGeo
Geographic coordinate conversion utilities using GeographicLib.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QList< QGeoCoordinate > interpolatePath(const QGeoCoordinate &from, const QGeoCoordinate &to, int numPoints)
Definition QGCGeo.cc:308
QVector3D convertEcefToEnu(const QVector3D &ecef, const QGeoCoordinate &ref)
Definition QGCGeo.cc:116
void convertGeoToNed(const QGeoCoordinate &coord, const QGeoCoordinate &origin, double &x, double &y, double &z)
Definition QGCGeo.cc:34
double polygonPerimeter(const QList< QGeoCoordinate > &polygon)
Definition QGCGeo.cc:292
double geodesicAzimuth(const QGeoCoordinate &from, const QGeoCoordinate &to)
Definition QGCGeo.cc:237
QVector3D convertGeodeticToEcef(const QGeoCoordinate &coord)
Definition QGCGeo.cc:102
QGeoCoordinate geodesicDestination(const QGeoCoordinate &from, double azimuth, double distance)
Definition QGCGeo.cc:250
QGeoCoordinate convertEcefToGeodetic(const QVector3D &ecef)
Definition QGCGeo.cc:109
QString convertGeoToMGRS(const QGeoCoordinate &coord)
Definition QGCGeo.cc:179
double pathLength(const QList< QGeoCoordinate > &path)
Definition QGCGeo.cc:261
double polygonArea(const QList< QGeoCoordinate > &polygon)
Definition QGCGeo.cc:274
double geodesicDistance(const QGeoCoordinate &from, const QGeoCoordinate &to)
Definition QGCGeo.cc:229
QVector3D convertEnuToEcef(const QVector3D &enu, const QGeoCoordinate &ref)
Definition QGCGeo.cc:130
int convertGeoToUTM(const QGeoCoordinate &coord, double &easting, double &northing)
Definition QGCGeo.cc:148
void convertNedToGeo(double x, double y, double z, const QGeoCoordinate &origin, QGeoCoordinate &coord)
Definition QGCGeo.cc:56
QGeoCoordinate convertEnuToGps(const QVector3D &enu, const QGeoCoordinate &ref)
Definition QGCGeo.cc:89
QVector3D convertGpsToEnu(const QGeoCoordinate &coord, const QGeoCoordinate &ref)
Definition QGCGeo.cc:80
bool convertUTMToGeo(double easting, double northing, int zone, bool southhemi, QGeoCoordinate &coord)
Definition QGCGeo.cc:161
QGeoCoordinate interpolateAtDistance(const QGeoCoordinate &from, const QGeoCoordinate &to, double distance)
Definition QGCGeo.cc:349
bool convertMGRSToGeo(const QString &mgrs, QGeoCoordinate &coord)
Definition QGCGeo.cc:205