QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgsgpslogger.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgpslogger.cpp
3 -------------------
4 begin : November 2022
5 copyright : (C) 2022 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsgpslogger.h"
17
18#include <memory>
19
20#include "gmath.h"
21#include "qgsgeometry.h"
22#include "qgsgpsconnection.h"
23#include "qgslinestring.h"
24#include "qgspolygon.h"
27#include "qgssettingstree.h"
28
29#include <QString>
30#include <QTimeZone>
31#include <QTimer>
32
33#include "moc_qgsgpslogger.cpp"
34
35using namespace Qt::StringLiterals;
36
43
45
46const QgsSettingsEntryBool *QgsGpsLogger::settingsGpsStoreAttributeInMValues = new QgsSettingsEntryBool( u"store-attribute-in-m-values"_s, QgsSettingsTree::sTreeGps, false, u"Whether GPS attributes should be stored in geometry m values"_s ) SIP_SKIP;
47
49 : QObject( parent )
50 , mWgs84CRS( QgsCoordinateReferenceSystem( u"EPSG:4326"_s ) )
51{
53
54 mLastNmeaPosition.lat = nmea_degree2radian( 0.0 );
55 mLastNmeaPosition.lon = nmea_degree2radian( 0.0 );
56
57 mAcquisitionTimer = std::make_unique<QTimer>( this );
58 mAcquisitionTimer->setSingleShot( true );
59
61
62 connect( mAcquisitionTimer.get(), &QTimer::timeout,
63 this, &QgsGpsLogger::switchAcquisition );
64}
65
70
72{
73 return mConnection;
74}
75
77{
78 if ( mConnection )
79 {
80 disconnect( mConnection, &QgsGpsConnection::stateChanged, this, &QgsGpsLogger::gpsStateChanged );
81 }
82
83 mConnection = connection;
84
85 if ( mConnection )
86 {
87 connect( mConnection, &QgsGpsConnection::stateChanged, this, &QgsGpsLogger::gpsStateChanged );
88 }
89}
90
91void QgsGpsLogger::setEllipsoid( const QString &ellipsoid )
92{
93 mDistanceCalculator.setEllipsoid( ellipsoid );
95}
96
98{
99 mTransformContext = context;
100 mDistanceCalculator.setSourceCrs( mWgs84CRS, mTransformContext );
101 emit distanceAreaChanged();
102}
103
105{
106 return mTransformContext;
107}
108
110{
111 return mDistanceCalculator;
112}
113
114QVector<QgsPoint> QgsGpsLogger::currentTrack() const
115{
116 return mCaptureListWgs84;
117}
118
120{
121 const Qgis::GeometryType geometryType = QgsWkbTypes::geometryType( type );
122 const QVector< QgsPoint > captureListWgs84 = currentTrack();
123 if ( geometryType == Qgis::GeometryType::Line && captureListWgs84.size() < 2 )
124 {
125 error = tr( "Creating a line feature requires a track with at least two vertices." );
126 return QgsGeometry();
127 }
128 else if ( geometryType == Qgis::GeometryType::Polygon && captureListWgs84.size() < 3 )
129 {
130 error = tr( "Creating a polygon feature requires a track with at least three vertices." );
131 return QgsGeometry();
132 }
133
134 const bool is3D = QgsWkbTypes::hasZ( type );
135 const bool isMeasure = QgsWkbTypes::hasM( type );
136 switch ( geometryType )
137 {
139 {
140 const QgsPointXY pointXYWgs84 = lastPosition();
141
142 QgsGeometry g;
143 if ( is3D )
144 g = QgsGeometry( new QgsPoint( pointXYWgs84.x(), pointXYWgs84.y(), lastElevation() ) );
145 else
146 g = QgsGeometry::fromPointXY( pointXYWgs84 );
147
148 if ( isMeasure )
149 g.get()->addMValue( lastMValue() );
150
151 if ( QgsWkbTypes::isMultiType( type ) )
153
154 return g;
155 }
156
159 {
160 QgsGeometry g;
161
162 auto ringWgs84 = std::make_unique<QgsLineString>( captureListWgs84 );
163 if ( !is3D )
164 ringWgs84->dropZValue();
165 if ( !isMeasure )
166 ringWgs84->dropMValue();
167
168 if ( geometryType == Qgis::GeometryType::Line )
169 {
170 g = QgsGeometry( ringWgs84.release() );
171 if ( QgsWkbTypes::isMultiType( type ) )
173 }
174 else if ( geometryType == Qgis::GeometryType::Polygon )
175 {
176 ringWgs84->close();
177 auto polygon = std::make_unique<QgsPolygon>();
178 polygon->setExteriorRing( ringWgs84.release() );
179
180 g = QgsGeometry( polygon.release() );
181
182 if ( QgsWkbTypes::isMultiType( type ) )
184 }
185 return g;
186 }
187
190 break;
191 }
192 return QgsGeometry();
193}
194
196{
197 return mLastGpsPositionWgs84;
198}
199
201{
202 return mLastElevation;
203}
204
206{
207 return mLastMValue;
208}
209
211{
213
214 const bool trackWasEmpty = mCaptureListWgs84.isEmpty();
215 mCaptureListWgs84.clear();
217 mTrackStartTime = QDateTime();
218
219 if ( !trackWasEmpty )
220 emit trackIsEmptyChanged( true );
221
222 emit trackReset();
223}
224
226{
227 int acquisitionInterval = 0;
229 {
230 acquisitionInterval = static_cast< int >( QgsGpsConnection::settingGpsAcquisitionInterval->value() );
231 mDistanceThreshold = QgsGpsConnection::settingGpsDistanceThreshold->value();
232 mApplyLeapSettings = QgsGpsConnection::settingGpsApplyLeapSecondsCorrection->value();
233 mLeapSeconds = static_cast< int >( QgsGpsConnection::settingGpsLeapSeconds->value() );
236 mOffsetFromUtc = static_cast< int >( QgsGpsConnection::settingsGpsTimeStampOffsetFromUtc->value() );
237
238 mStoreAttributeInMValues = settingsGpsStoreAttributeInMValues->value();
239 mMValueComponent = settingsGpsMValueComponent->value();
240 }
241 else
242 {
243 acquisitionInterval = QgsGpsLogger::settingsAcquisitionInterval->value();
244 mDistanceThreshold = QgsGpsLogger::settingsDistanceThreshold->value();
245 mApplyLeapSettings = QgsGpsLogger::settingsApplyLeapSeconds->value();
246 mLeapSeconds = QgsGpsLogger::settingsLeapSecondsCorrection->value();
247
248 switch ( QgsGpsLogger::settingsTimeStampFormat->value() )
249 {
250 case 0:
251 mTimeStampSpec = Qt::TimeSpec::LocalTime;
252 break;
253
254 case 1:
255 mTimeStampSpec = Qt::TimeSpec::UTC;
256 break;
257
258 case 2:
259 mTimeStampSpec = Qt::TimeSpec::TimeZone;
260 break;
261 }
262 mTimeZone = QgsGpsLogger::settingsTimestampTimeZone->value();
263 }
264
265 mAcquisitionInterval = acquisitionInterval * 1000;
266 if ( mAcquisitionTimer->isActive() )
267 mAcquisitionTimer->stop();
268 mAcquisitionEnabled = true;
269
270 switchAcquisition();
271}
272
274{
275 QVector<QgsPointXY> points;
276 QgsGeometry::convertPointList( mCaptureListWgs84, points );
277 try
278 {
279 return mDistanceCalculator.measureLine( points );
280 }
281 catch ( QgsCsException & )
282 {
283 // TODO report errors to user
284 QgsDebugError( u"An error occurred while calculating length"_s );
285 return 0;
286 }
287}
288
290{
291 if ( mCaptureListWgs84.empty() )
292 return 0;
293
294 try
295 {
296 return mDistanceCalculator.measureLine( { QgsPointXY( mCaptureListWgs84.constFirst() ), QgsPointXY( mCaptureListWgs84.constLast() )} );
297 }
298 catch ( QgsCsException & )
299 {
300 // TODO report errors to user
301 QgsDebugError( u"An error occurred while calculating length"_s );
302 return 0;
303 }
304}
305
307{
308 if ( !mConnection )
309 return QVariant();
310
311 switch ( component )
312 {
326 return mConnection->currentGPSInformation().componentValue( component );
327
329 return lastTimestamp();
330
332 return totalTrackLength();
334 return trackDistanceFromStart();
336 return trackStartTime();
338 return lastTimestamp();
339
341 try
342 {
343 return mPreviousTrackPoint.isEmpty() ? QVariant() : distanceArea().measureLine( mPreviousTrackPoint, lastPosition() );
344 }
345 catch ( QgsCsException & )
346 {
347 // TODO report errors to user
348 QgsDebugError( u"An error occurred while calculating length"_s );
349 return 0;
350 }
351
353 return mPreviousTrackPointTime.isValid() ? static_cast< double >( mPreviousTrackPointTime.msecsTo( lastTimestamp() ) ) / 1000 : QVariant();
354 }
356}
357
358void QgsGpsLogger::switchAcquisition()
359{
360 if ( mAcquisitionInterval > 0 )
361 {
362 if ( mAcquisitionEnabled )
363 mAcquisitionTimer->start( mAcquisitionInterval );
364 else
365 //wait only acquisitionInterval/10 for new valid data
366 mAcquisitionTimer->start( mAcquisitionInterval / 10 );
367 // anyway switch to enabled / disabled acquisition
368 mAcquisitionEnabled = !mAcquisitionEnabled;
369 }
370}
371
372void QgsGpsLogger::gpsStateChanged( const QgsGpsInformation &info )
373{
375 return;
376
377 const bool validFlag = info.isValid();
378 QgsPointXY newLocationWgs84;
379 nmeaPOS newNmeaPosition;
380 double newAlt = 0.0;
381 if ( validFlag )
382 {
383 newLocationWgs84 = QgsPointXY( info.longitude, info.latitude );
384 newNmeaPosition.lat = nmea_degree2radian( info.latitude );
385 newNmeaPosition.lon = nmea_degree2radian( info.longitude );
386 newAlt = info.elevation;
387
388 if ( info.utcDateTime.isValid() )
389 {
390 mLastTime = info.utcDateTime;
391 }
392
393 switch ( mMValueComponent )
394 {
407 {
408 const QVariant value = info.componentValue( mMValueComponent );
409 mLastMValue = value.isValid() ? info.componentValue( mMValueComponent ).toDouble() : std::numeric_limits< double >::quiet_NaN();
410 break;
411 }
412
414 mLastMValue = static_cast< double >( info.utcDateTime.toMSecsSinceEpoch() );
415 break;
416
424 // not possible
425 break;
426 }
427 }
428 else
429 {
430 newLocationWgs84 = mLastGpsPositionWgs84;
431 newNmeaPosition = mLastNmeaPosition;
432 newAlt = mLastElevation;
433 }
434 if ( !mAcquisitionEnabled || ( nmea_distance( &newNmeaPosition, &mLastNmeaPosition ) < mDistanceThreshold ) )
435 {
436 // do not update position if update is disabled by timer or distance is under threshold
437 newLocationWgs84 = mLastGpsPositionWgs84;
438
439 }
440 if ( validFlag && mAcquisitionEnabled )
441 {
442 // position updated by valid data, reset timer
443 switchAcquisition();
444 }
445
446 // Avoid adding track vertices when we haven't moved
447 if ( mLastGpsPositionWgs84 != newLocationWgs84 )
448 {
449 mLastGpsPositionWgs84 = newLocationWgs84;
450 mLastNmeaPosition = newNmeaPosition;
451 mLastElevation = newAlt;
452
453 if ( mAutomaticallyAddTrackVertices )
454 {
456 }
457 }
458
459 emit stateChanged( info );
460
461 mPreviousTrackPointTime = lastTimestamp();
462 mPreviousTrackPoint = mLastGpsPositionWgs84;
463}
464
466{
467 QgsPoint pointWgs84 = QgsPoint( mLastGpsPositionWgs84.x(), mLastGpsPositionWgs84.y(), mLastElevation );
468
469 if ( mStoreAttributeInMValues )
470 {
471 pointWgs84.addMValue( mLastMValue );
472 }
473
474 const bool trackWasEmpty = mCaptureListWgs84.empty();
475 mCaptureListWgs84.push_back( pointWgs84 );
476
477 emit trackVertexAdded( pointWgs84 );
478
479 if ( trackWasEmpty )
480 {
481 mTrackStartTime = lastTimestamp();
482 emit trackIsEmptyChanged( false );
483 }
484}
485
487{
488 return mAutomaticallyAddTrackVertices;
489}
490
492{
493 mAutomaticallyAddTrackVertices = enabled;
494}
495
497{
498 if ( !mLastTime.isValid() )
499 return QDateTime();
500
501 QDateTime time = mLastTime;
502
503 // Time from GPS is UTC time
504 time.setTimeSpec( Qt::UTC );
505 // Apply leap seconds correction
506 if ( mApplyLeapSettings && mLeapSeconds != 0 )
507 {
508 time = time.addSecs( mLeapSeconds );
509 }
510 // Desired format
511 if ( mTimeStampSpec != Qt::TimeSpec::OffsetFromUTC )
512 time = time.toTimeSpec( mTimeStampSpec );
513
514 if ( mTimeStampSpec == Qt::TimeSpec::TimeZone )
515 {
516#if QT_FEATURE_timezone > 0
517 // Get timezone from the combo
518 const QTimeZone destTz( mTimeZone.toUtf8() );
519 if ( destTz.isValid() )
520 {
521 time = time.toTimeZone( destTz );
522 }
523#else
524 QgsDebugError( u"Qt is built without timezone support, cannot convert GPS timestamps to specified timezone."_s );
525#endif
526 }
527 else if ( mTimeStampSpec == Qt::TimeSpec::LocalTime )
528 {
529 time = time.toLocalTime();
530 }
531 else if ( mTimeStampSpec == Qt::TimeSpec::OffsetFromUTC )
532 {
533 time = time.toOffsetFromUtc( mOffsetFromUtc );
534 }
535 else if ( mTimeStampSpec == Qt::TimeSpec::UTC )
536 {
537 // Do nothing: we are already in UTC
538 }
539
540 return time;
541}
542
544{
545 return mTrackStartTime;
546}
GpsInformationComponent
GPS information component.
Definition qgis.h:2014
@ TrackStartTime
Timestamp at start of current track (available from QgsGpsLogger class only).
Definition qgis.h:2029
@ GroundSpeed
Ground speed.
Definition qgis.h:2017
@ TrackTimeSinceLastPoint
Time since last recorded location (available from QgsGpsLogger class only).
Definition qgis.h:2032
@ Pdop
Dilution of precision.
Definition qgis.h:2021
@ TrackEndTime
Timestamp at end (current point) of current track (available from QgsGpsLogger class only).
Definition qgis.h:2030
@ Altitude
Altitude/elevation above or below the mean sea level.
Definition qgis.h:2016
@ TrackDistanceFromStart
Direct distance from first vertex in current GPS track to last vertex (available from QgsGpsLogger cl...
Definition qgis.h:2020
@ TotalTrackLength
Total distance of current GPS track (available from QgsGpsLogger class only).
Definition qgis.h:2019
@ Hdop
Horizontal dilution of precision.
Definition qgis.h:2022
@ EllipsoidAltitude
Altitude/elevation above or below the WGS-84 Earth ellipsoid.
Definition qgis.h:2034
@ Bearing
Bearing measured in degrees clockwise from true north to the direction of travel.
Definition qgis.h:2018
@ Vdop
Vertical dilution of precision.
Definition qgis.h:2023
@ GeoidalSeparation
Geoidal separation, the difference between the WGS-84 Earth ellipsoid and mean-sea-level (geoid),...
Definition qgis.h:2033
@ VerticalAccuracy
Vertical accuracy in meters.
Definition qgis.h:2025
@ Location
2D location (latitude/longitude), as a QgsPointXY value
Definition qgis.h:2015
@ TrackDistanceSinceLastPoint
Distance since last recorded location (available from QgsGpsLogger class only).
Definition qgis.h:2031
@ HorizontalAccuracy
Horizontal accuracy in meters.
Definition qgis.h:2024
@ SatellitesUsed
Count of satellites used in obtaining the fix.
Definition qgis.h:2027
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:365
@ Point
Points.
Definition qgis.h:366
@ Line
Lines.
Definition qgis.h:367
@ Polygon
Polygons.
Definition qgis.h:368
@ Unknown
Unknown types.
Definition qgis.h:369
@ Null
No geometry.
Definition qgis.h:370
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:280
virtual bool addMValue(double mValue=0)=0
Adds a measure to the geometry, initialized to a preset value.
Represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
Custom exception class for Coordinate Reference System related exceptions.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
double measureLine(const QVector< QgsPointXY > &points) const
Measures the length of a line with multiple segments.
A geometry is the spatial representation of a feature.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
bool convertToMultiType()
Converts single type geometry into multitype geometry e.g.
static void convertPointList(const QVector< QgsPointXY > &input, QgsPointSequence &output)
Upgrades a point list from QgsPointXY to QgsPoint.
Abstract base class for connections to a GPS device.
static const QgsSettingsEntryEnumFlag< Qt::TimeSpec > * settingsGpsTimeStampSpecification
Settings entry time specification for GPS time stamps.
static const QgsSettingsEntryString * settingsGpsTimeStampTimeZone
Settings entry GPS time stamp time zone.
static const QgsSettingsEntryBool * settingGpsApplyLeapSecondsCorrection
Settings entry GPS apply leap seconds correction.
static const QgsSettingsEntryInteger * settingGpsLeapSeconds
Settings entry GPS leap seconds correction amount (in seconds).
static const QgsSettingsEntryInteger * settingsGpsTimeStampOffsetFromUtc
Settings entry GPS time offset from UTC in seconds.
static const QgsSettingsEntryInteger * settingGpsAcquisitionInterval
Settings entry GPS track point acquisition interval.
static const QgsSettingsEntryDouble * settingGpsDistanceThreshold
Settings entry GPS track point distance threshold.
void stateChanged(const QgsGpsInformation &info)
Emitted whenever the GPS state is changed.
Encapsulates information relating to a GPS position fix.
bool isValid() const
Returns whether the connection information is valid.
double latitude
Latitude in decimal degrees, using the WGS84 datum.
double longitude
Longitude in decimal degrees, using the WGS84 datum.
QDateTime utcDateTime
The date and time at which this position was reported, in UTC time.
double elevation
Altitude (in meters) above or below the mean sea level.
QVariant componentValue(Qgis::GpsInformationComponent component) const
Returns the value of the corresponding GPS information component.
static const QgsSettingsEntryBool * settingsGpsStoreAttributeInMValues
Settings entry for whether storing GPS attributes as geometry M values should be enabled.
virtual void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context to be used when transforming GPS coordinates.
~QgsGpsLogger() override
void stateChanged(const QgsGpsInformation &info)
Emitted whenever the associated GPS device state is changed.
static const QgsSettingsEntryEnumFlag< Qgis::GpsInformationComponent > * settingsGpsMValueComponent
Settings entry dictating which GPS attribute should be stored in geometry M values.
double lastElevation() const
Returns the last recorded elevation the device.
static const QgsSettingsEntryInteger * settingsTimeStampFormat
QgsGpsLogger(QgsGpsConnection *connection, QObject *parent=nullptr)
Constructor for QgsGpsLogger with the specified parent object.
QgsCoordinateReferenceSystem mWgs84CRS
WGS84 coordinate reference system.
static const QgsSettingsEntryInteger * settingsLeapSecondsCorrection
int mBlockGpsStateChanged
Used to pause logging of incoming GPS messages.
static const QgsSettingsEntryBool * settingsApplyLeapSeconds
QgsGeometry currentGeometry(Qgis::WkbType type, QString &error) const
Returns the current logged GPS positions as a geometry of the specified type.
const QgsDistanceArea & distanceArea() const
Returns the distance area calculator which should be used for calculating distances associated with t...
QVector< QgsPoint > currentTrack() const
Returns the recorded points in the current track.
bool automaticallyAddTrackVertices() const
Returns true if track vertices will be automatically added whenever the GPS position is changed.
void trackIsEmptyChanged(bool isEmpty)
Emitted whenever the current track changes from being empty to non-empty or vice versa.
QDateTime trackStartTime() const
Returns the timestamp at which the current track was started.
static const QgsSettingsEntryDouble * settingsDistanceThreshold
void updateGpsSettings()
Should be called whenever the QGIS GPS settings are changed.
void trackReset()
Emitted whenever the current track is reset.
void resetTrack()
Resets the current track, discarding all recorded points.
QDateTime lastTimestamp() const
Returns the last recorded timestamp from the device.
void setAutomaticallyAddTrackVertices(bool enabled)
Sets whether track vertices will be automatically added whenever the GPS position is changed.
void addTrackVertex()
Adds a track vertex at the current GPS location.
double lastMValue() const
Returns the last recorded value corresponding to the QgsGpsLogger::settingsGpsMValueComponent setting...
double trackDistanceFromStart() const
Returns the direct length from the first vertex in the track to the last (in meters).
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context to be used when transforming GPS coordinates.
void setConnection(QgsGpsConnection *connection)
Sets the associated GPS connection.
void distanceAreaChanged()
Emitted whenever the distance area used to calculate track distances is changed.
QgsGpsConnection * connection()
Returns the associated GPS connection.
static const QgsSettingsEntryString * settingsTimestampTimeZone
double totalTrackLength() const
Returns the total length of the current digitized track (in meters).
void setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid which will be used for calculating distances in the log.
static const QgsSettingsEntryInteger * settingsAcquisitionInterval
QVariant componentValue(Qgis::GpsInformationComponent component) const
Returns the value of the corresponding GPS information component.
void trackVertexAdded(const QgsPoint &vertex)
Emitted whenever a new vertex is added to the track.
QgsPointXY lastPosition() const
Returns the last recorded position of the device.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
Definition qgspoint.cpp:575
A boolean settings entry.
A double settings entry.
A template class for enum and flag settings entry.
An integer settings entry.
A string settings entry.
static QgsSettingsTreeNode * sTreeGps
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static Q_INVOKABLE bool isMultiType(Qgis::WkbType type)
Returns true if the WKB type is a multi type.
#define BUILTIN_UNREACHABLE
Definition qgis.h:7489
#define SIP_SKIP
Definition qgis_sip.h:134
#define QgsDebugError(str)
Definition qgslogger.h:59