QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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#include "qgsgpsconnection.h"
18#include "gmath.h"
19#include "qgsgeometry.h"
20#include "qgslinestring.h"
21#include "qgspolygon.h"
24#include "qgssettingstree.h"
25
26#include <QTimer>
27#include <QTimeZone>
28
29
32const QgsSettingsEntryString *QgsGpsLogger::settingsTimestampTimeZone = new QgsSettingsEntryString( QStringLiteral( "timestampTimeZone" ), QgsSettingsTree::sTreeGps, QString() );
33const QgsSettingsEntryInteger *QgsGpsLogger::settingsTimeStampFormat = new QgsSettingsEntryInteger( QStringLiteral( "timeStampFormat" ), QgsSettingsTree::sTreeGps, Qt::LocalTime );
36
38
39const QgsSettingsEntryBool *QgsGpsLogger::settingsGpsStoreAttributeInMValues = new QgsSettingsEntryBool( QStringLiteral( "store-attribute-in-m-values" ), QgsSettingsTree::sTreeGps, false, QStringLiteral( "Whether GPS attributes should be stored in geometry m values" ) ) SIP_SKIP;
40
41QgsGpsLogger::QgsGpsLogger( QgsGpsConnection *connection, QObject *parent )
42 : QObject( parent )
43 , mWgs84CRS( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) )
44{
45 setConnection( connection );
46
47 mLastNmeaPosition.lat = nmea_degree2radian( 0.0 );
48 mLastNmeaPosition.lon = nmea_degree2radian( 0.0 );
49
50 mAcquisitionTimer = std::unique_ptr<QTimer>( new QTimer( this ) );
51 mAcquisitionTimer->setSingleShot( true );
52
53 updateGpsSettings();
54
55 connect( mAcquisitionTimer.get(), &QTimer::timeout,
56 this, &QgsGpsLogger::switchAcquisition );
57}
58
60{
61
62}
63
65{
66 return mConnection;
67}
68
70{
71 if ( mConnection )
72 {
73 disconnect( mConnection, &QgsGpsConnection::stateChanged, this, &QgsGpsLogger::gpsStateChanged );
74 }
75
76 mConnection = connection;
77
78 if ( mConnection )
79 {
80 connect( mConnection, &QgsGpsConnection::stateChanged, this, &QgsGpsLogger::gpsStateChanged );
81 }
82}
83
84void QgsGpsLogger::setEllipsoid( const QString &ellipsoid )
85{
86 mDistanceCalculator.setEllipsoid( ellipsoid );
88}
89
91{
92 mTransformContext = context;
93 mDistanceCalculator.setSourceCrs( mWgs84CRS, mTransformContext );
95}
96
98{
99 return mTransformContext;
100}
101
103{
104 return mDistanceCalculator;
105}
106
107QVector<QgsPoint> QgsGpsLogger::currentTrack() const
108{
109 return mCaptureListWgs84;
110}
111
113{
114 const Qgis::GeometryType geometryType = QgsWkbTypes::geometryType( type );
115 const QVector< QgsPoint > captureListWgs84 = currentTrack();
116 if ( geometryType == Qgis::GeometryType::Line && captureListWgs84.size() < 2 )
117 {
118 error = tr( "Creating a line feature requires a track with at least two vertices." );
119 return QgsGeometry();
120 }
121 else if ( geometryType == Qgis::GeometryType::Polygon && captureListWgs84.size() < 3 )
122 {
123 error = tr( "Creating a polygon feature requires a track with at least three vertices." );
124 return QgsGeometry();
125 }
126
127 const bool is3D = QgsWkbTypes::hasZ( type );
128 const bool isMeasure = QgsWkbTypes::hasM( type );
129 switch ( geometryType )
130 {
131 case Qgis::GeometryType::Point:
132 {
133 const QgsPointXY pointXYWgs84 = lastPosition();
134
135 QgsGeometry g;
136 if ( is3D )
137 g = QgsGeometry( new QgsPoint( pointXYWgs84.x(), pointXYWgs84.y(), lastElevation() ) );
138 else
139 g = QgsGeometry::fromPointXY( pointXYWgs84 );
140
141 if ( isMeasure )
142 g.get()->addMValue( lastMValue() );
143
144 if ( QgsWkbTypes::isMultiType( type ) )
146
147 return g;
148 }
149
150 case Qgis::GeometryType::Line:
151 case Qgis::GeometryType::Polygon:
152 {
153 QgsGeometry g;
154
155 std::unique_ptr<QgsLineString> ringWgs84( new QgsLineString( captureListWgs84 ) );
156 if ( !is3D )
157 ringWgs84->dropZValue();
158 if ( !isMeasure )
159 ringWgs84->dropMValue();
160
161 if ( geometryType == Qgis::GeometryType::Line )
162 {
163 g = QgsGeometry( ringWgs84.release() );
164 if ( QgsWkbTypes::isMultiType( type ) )
166 }
167 else if ( geometryType == Qgis::GeometryType::Polygon )
168 {
169 ringWgs84->close();
170 std::unique_ptr<QgsPolygon> polygon( new QgsPolygon() );
171 polygon->setExteriorRing( ringWgs84.release() );
172
173 g = QgsGeometry( polygon.release() );
174
175 if ( QgsWkbTypes::isMultiType( type ) )
177 }
178 return g;
179 }
180
181 case Qgis::GeometryType::Null:
182 case Qgis::GeometryType::Unknown:
183 break;
184 }
185 return QgsGeometry();
186}
187
189{
190 return mLastGpsPositionWgs84;
191}
192
194{
195 return mLastElevation;
196}
197
199{
200 return mLastMValue;
201}
202
204{
206
207 const bool trackWasEmpty = mCaptureListWgs84.isEmpty();
208 mCaptureListWgs84.clear();
210 mTrackStartTime = QDateTime();
211
212 if ( !trackWasEmpty )
213 emit trackIsEmptyChanged( true );
214
215 emit trackReset();
216}
217
219{
220 int acquisitionInterval = 0;
222 {
223 acquisitionInterval = static_cast< int >( QgsGpsConnection::settingGpsAcquisitionInterval->value() );
226 mLeapSeconds = static_cast< int >( QgsGpsConnection::settingGpsLeapSeconds->value() );
229 mOffsetFromUtc = static_cast< int >( QgsGpsConnection::settingsGpsTimeStampOffsetFromUtc->value() );
230
231 mStoreAttributeInMValues = settingsGpsStoreAttributeInMValues->value();
232 mMValueComponent = settingsGpsMValueComponent->value();
233 }
234 else
235 {
236 acquisitionInterval = QgsGpsLogger::settingsAcquisitionInterval->value();
237 mDistanceThreshold = QgsGpsLogger::settingsDistanceThreshold->value();
238 mApplyLeapSettings = QgsGpsLogger::settingsApplyLeapSeconds->value();
240
241 switch ( QgsGpsLogger::settingsTimeStampFormat->value() )
242 {
243 case 0:
244 mTimeStampSpec = Qt::TimeSpec::LocalTime;
245 break;
246
247 case 1:
248 mTimeStampSpec = Qt::TimeSpec::UTC;
249 break;
250
251 case 2:
252 mTimeStampSpec = Qt::TimeSpec::TimeZone;
253 break;
254 }
256 }
257
258 mAcquisitionInterval = acquisitionInterval * 1000;
259 if ( mAcquisitionTimer->isActive() )
260 mAcquisitionTimer->stop();
261 mAcquisitionEnabled = true;
262
263 switchAcquisition();
264}
265
267{
268 QVector<QgsPointXY> points;
269 QgsGeometry::convertPointList( mCaptureListWgs84, points );
270 return mDistanceCalculator.measureLine( points );
271}
272
274{
275 if ( mCaptureListWgs84.empty() )
276 return 0;
277
278 return mDistanceCalculator.measureLine( { QgsPointXY( mCaptureListWgs84.constFirst() ), QgsPointXY( mCaptureListWgs84.constLast() )} );
279}
280
282{
283 if ( !mConnection )
284 return QVariant();
285
286 switch ( component )
287 {
301 return mConnection->currentGPSInformation().componentValue( component );
302
304 return lastTimestamp();
305
307 return totalTrackLength();
309 return trackDistanceFromStart();
311 return trackStartTime();
313 return lastTimestamp();
314
316 return mPreviousTrackPoint.isEmpty() ? QVariant() : distanceArea().measureLine( mPreviousTrackPoint, lastPosition() );
318 return mPreviousTrackPointTime.isValid() ? static_cast< double >( mPreviousTrackPointTime.msecsTo( lastTimestamp() ) ) / 1000 : QVariant();
319 }
321}
322
323void QgsGpsLogger::switchAcquisition()
324{
325 if ( mAcquisitionInterval > 0 )
326 {
327 if ( mAcquisitionEnabled )
328 mAcquisitionTimer->start( mAcquisitionInterval );
329 else
330 //wait only acquisitionInterval/10 for new valid data
331 mAcquisitionTimer->start( mAcquisitionInterval / 10 );
332 // anyway switch to enabled / disabled acquisition
333 mAcquisitionEnabled = !mAcquisitionEnabled;
334 }
335}
336
337void QgsGpsLogger::gpsStateChanged( const QgsGpsInformation &info )
338{
340 return;
341
342 const bool validFlag = info.isValid();
343 QgsPointXY newLocationWgs84;
344 nmeaPOS newNmeaPosition;
345 double newAlt = 0.0;
346 if ( validFlag )
347 {
348 newLocationWgs84 = QgsPointXY( info.longitude, info.latitude );
349 newNmeaPosition.lat = nmea_degree2radian( info.latitude );
350 newNmeaPosition.lon = nmea_degree2radian( info.longitude );
351 newAlt = info.elevation;
352
353 if ( info.utcDateTime.isValid() )
354 {
355 mLastTime = info.utcDateTime;
356 }
357
358 switch ( mMValueComponent )
359 {
372 {
373 const QVariant value = info.componentValue( mMValueComponent );
374 mLastMValue = value.isValid() ? info.componentValue( mMValueComponent ).toDouble() : std::numeric_limits< double >::quiet_NaN();
375 break;
376 }
377
379 mLastMValue = static_cast< double >( info.utcDateTime.toMSecsSinceEpoch() );
380 break;
381
389 // not possible
390 break;
391 }
392 }
393 else
394 {
395 newLocationWgs84 = mLastGpsPositionWgs84;
396 newNmeaPosition = mLastNmeaPosition;
397 newAlt = mLastElevation;
398 }
399 if ( !mAcquisitionEnabled || ( nmea_distance( &newNmeaPosition, &mLastNmeaPosition ) < mDistanceThreshold ) )
400 {
401 // do not update position if update is disabled by timer or distance is under threshold
402 newLocationWgs84 = mLastGpsPositionWgs84;
403
404 }
405 if ( validFlag && mAcquisitionEnabled )
406 {
407 // position updated by valid data, reset timer
408 switchAcquisition();
409 }
410
411 // Avoid adding track vertices when we haven't moved
412 if ( mLastGpsPositionWgs84 != newLocationWgs84 )
413 {
414 mLastGpsPositionWgs84 = newLocationWgs84;
415 mLastNmeaPosition = newNmeaPosition;
416 mLastElevation = newAlt;
417
418 if ( mAutomaticallyAddTrackVertices )
419 {
421 }
422 }
423
424 emit stateChanged( info );
425
426 mPreviousTrackPointTime = lastTimestamp();
427 mPreviousTrackPoint = mLastGpsPositionWgs84;
428}
429
431{
432 QgsPoint pointWgs84 = QgsPoint( mLastGpsPositionWgs84.x(), mLastGpsPositionWgs84.y(), mLastElevation );
433
434 if ( mStoreAttributeInMValues )
435 {
436 pointWgs84.addMValue( mLastMValue );
437 }
438
439 const bool trackWasEmpty = mCaptureListWgs84.empty();
440 mCaptureListWgs84.push_back( pointWgs84 );
441
442 emit trackVertexAdded( pointWgs84 );
443
444 if ( trackWasEmpty )
445 {
446 mTrackStartTime = lastTimestamp();
447 emit trackIsEmptyChanged( false );
448 }
449}
450
452{
453 return mAutomaticallyAddTrackVertices;
454}
455
457{
458 mAutomaticallyAddTrackVertices = enabled;
459}
460
462{
463 if ( !mLastTime.isValid() )
464 return QDateTime();
465
466 QDateTime time = mLastTime;
467
468 // Time from GPS is UTC time
469 time.setTimeSpec( Qt::UTC );
470 // Apply leap seconds correction
471 if ( mApplyLeapSettings && mLeapSeconds != 0 )
472 {
473 time = time.addSecs( mLeapSeconds );
474 }
475 // Desired format
476 if ( mTimeStampSpec != Qt::TimeSpec::OffsetFromUTC )
477 time = time.toTimeSpec( mTimeStampSpec );
478
479 if ( mTimeStampSpec == Qt::TimeSpec::TimeZone )
480 {
481 // Get timezone from the combo
482 const QTimeZone destTz( mTimeZone.toUtf8() );
483 if ( destTz.isValid() )
484 {
485 time = time.toTimeZone( destTz );
486 }
487 }
488 else if ( mTimeStampSpec == Qt::TimeSpec::LocalTime )
489 {
490 time = time.toLocalTime();
491 }
492 else if ( mTimeStampSpec == Qt::TimeSpec::OffsetFromUTC )
493 {
494 time = time.toOffsetFromUtc( mOffsetFromUtc );
495 }
496 else if ( mTimeStampSpec == Qt::TimeSpec::UTC )
497 {
498 // Do nothing: we are already in UTC
499 }
500
501 return time;
502}
503
505{
506 return mTrackStartTime;
507}
GpsInformationComponent
GPS information component.
Definition: qgis.h:1180
@ TrackStartTime
Timestamp at start of current track (available from QgsGpsLogger class only)
@ TrackTimeSinceLastPoint
Time since last recorded location (available from QgsGpsLogger class only)
@ Pdop
Dilution of precision.
@ TrackEndTime
Timestamp at end (current point) of current track (available from QgsGpsLogger class only)
@ Altitude
Altitude/elevation above or below the mean sea level.
@ TrackDistanceFromStart
Direct distance from first vertex in current GPS track to last vertex (available from QgsGpsLogger cl...
@ TotalTrackLength
Total distance of current GPS track (available from QgsGpsLogger class only)
@ Hdop
Horizontal dilution of precision.
@ EllipsoidAltitude
Altitude/elevation above or below the WGS-84 Earth ellipsoid.
@ Bearing
Bearing measured in degrees clockwise from true north to the direction of travel.
@ Vdop
Vertical dilution of precision.
@ GeoidalSeparation
Geoidal separation, the difference between the WGS-84 Earth ellipsoid and mean-sea-level (geoid),...
@ VerticalAccuracy
Vertical accuracy in meters.
@ Location
2D location (latitude/longitude), as a QgsPointXY value
@ TrackDistanceSinceLastPoint
Distance since last recorded location (available from QgsGpsLogger class only)
@ HorizontalAccuracy
Horizontal accuracy in meters.
@ SatellitesUsed
Count of satellites used in obtaining the fix.
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition: qgis.h:228
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition: qgis.h:155
virtual bool addMValue(double mValue=0)=0
Adds a measure to the geometry, initialized to a preset value.
This class represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
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.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
static QgsGeometry fromPointXY(const QgsPointXY &point) SIP_HOLDGIL
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 connection 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.
Base class for objects which log incoming GPS data.
Definition: qgsgpslogger.h:56
static const QgsSettingsEntryBool * settingsGpsStoreAttributeInMValues
Settings entry for whether storing GPS attributes as geometry M values should be enabled.
Definition: qgsgpslogger.h:75
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.
Definition: qgsgpslogger.h:78
double lastElevation() const
Returns the last recorded elevation the device.
static const QgsSettingsEntryInteger * settingsTimeStampFormat
Definition: qgsgpslogger.h:65
QgsCoordinateReferenceSystem mWgs84CRS
WGS84 coordinate reference system.
Definition: qgsgpslogger.h:260
static const QgsSettingsEntryInteger * settingsLeapSecondsCorrection
Definition: qgsgpslogger.h:67
int mBlockGpsStateChanged
Used to pause logging of incoming GPS messages.
Definition: qgsgpslogger.h:263
static const QgsSettingsEntryBool * settingsApplyLeapSeconds
Definition: qgsgpslogger.h:61
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
Definition: qgsgpslogger.h:71
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
Definition: qgsgpslogger.h:63
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
Definition: qgsgpslogger.h:69
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.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:45
A class to represent a 2D point.
Definition: qgspointxy.h:59
bool isEmpty() const SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgspointxy.h:249
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
Definition: qgspoint.cpp:562
Polygon geometry type.
Definition: qgspolygon.h:34
A boolean settings entry.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
A double settings entry.
An integer settings entry.
A string settings entry.
QgsSettingsTree holds the tree structure for the settings in QGIS core.
static QgsSettingsTreeNode * sTreeGps
static Qgis::GeometryType geometryType(Qgis::WkbType type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:865
static bool isMultiType(Qgis::WkbType type) SIP_HOLDGIL
Returns true if the WKB type is a multi type.
Definition: qgswkbtypes.h:759
static bool hasZ(Qgis::WkbType type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:977
static bool hasM(Qgis::WkbType type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1027
#define BUILTIN_UNREACHABLE
Definition: qgis.h:4180
#define SIP_SKIP
Definition: qgis_sip.h:126