QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
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#include "moc_qgsgpslogger.cpp"
18#include "qgsgpsconnection.h"
19#include "gmath.h"
20#include "qgsgeometry.h"
21#include "qgslinestring.h"
22#include "qgspolygon.h"
25#include "qgssettingstree.h"
26
27#include <QTimer>
28#include <QTimeZone>
29
30
33const QgsSettingsEntryString *QgsGpsLogger::settingsTimestampTimeZone = new QgsSettingsEntryString( QStringLiteral( "timestampTimeZone" ), QgsSettingsTree::sTreeGps, QString() );
34const QgsSettingsEntryInteger *QgsGpsLogger::settingsTimeStampFormat = new QgsSettingsEntryInteger( QStringLiteral( "timeStampFormat" ), QgsSettingsTree::sTreeGps, Qt::LocalTime );
37
39
40const 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;
41
42QgsGpsLogger::QgsGpsLogger( QgsGpsConnection *connection, QObject *parent )
43 : QObject( parent )
44 , mWgs84CRS( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) )
45{
46 setConnection( connection );
47
48 mLastNmeaPosition.lat = nmea_degree2radian( 0.0 );
49 mLastNmeaPosition.lon = nmea_degree2radian( 0.0 );
50
51 mAcquisitionTimer = std::unique_ptr<QTimer>( new QTimer( this ) );
52 mAcquisitionTimer->setSingleShot( true );
53
54 updateGpsSettings();
55
56 connect( mAcquisitionTimer.get(), &QTimer::timeout,
57 this, &QgsGpsLogger::switchAcquisition );
58}
59
64
66{
67 return mConnection;
68}
69
71{
72 if ( mConnection )
73 {
74 disconnect( mConnection, &QgsGpsConnection::stateChanged, this, &QgsGpsLogger::gpsStateChanged );
75 }
76
77 mConnection = connection;
78
79 if ( mConnection )
80 {
81 connect( mConnection, &QgsGpsConnection::stateChanged, this, &QgsGpsLogger::gpsStateChanged );
82 }
83}
84
85void QgsGpsLogger::setEllipsoid( const QString &ellipsoid )
86{
87 mDistanceCalculator.setEllipsoid( ellipsoid );
89}
90
92{
93 mTransformContext = context;
94 mDistanceCalculator.setSourceCrs( mWgs84CRS, mTransformContext );
96}
97
99{
100 return mTransformContext;
101}
102
104{
105 return mDistanceCalculator;
106}
107
108QVector<QgsPoint> QgsGpsLogger::currentTrack() const
109{
110 return mCaptureListWgs84;
111}
112
114{
115 const Qgis::GeometryType geometryType = QgsWkbTypes::geometryType( type );
116 const QVector< QgsPoint > captureListWgs84 = currentTrack();
117 if ( geometryType == Qgis::GeometryType::Line && captureListWgs84.size() < 2 )
118 {
119 error = tr( "Creating a line feature requires a track with at least two vertices." );
120 return QgsGeometry();
121 }
122 else if ( geometryType == Qgis::GeometryType::Polygon && captureListWgs84.size() < 3 )
123 {
124 error = tr( "Creating a polygon feature requires a track with at least three vertices." );
125 return QgsGeometry();
126 }
127
128 const bool is3D = QgsWkbTypes::hasZ( type );
129 const bool isMeasure = QgsWkbTypes::hasM( type );
130 switch ( geometryType )
131 {
133 {
134 const QgsPointXY pointXYWgs84 = lastPosition();
135
136 QgsGeometry g;
137 if ( is3D )
138 g = QgsGeometry( new QgsPoint( pointXYWgs84.x(), pointXYWgs84.y(), lastElevation() ) );
139 else
140 g = QgsGeometry::fromPointXY( pointXYWgs84 );
141
142 if ( isMeasure )
143 g.get()->addMValue( lastMValue() );
144
145 if ( QgsWkbTypes::isMultiType( type ) )
147
148 return g;
149 }
150
153 {
154 QgsGeometry g;
155
156 std::unique_ptr<QgsLineString> ringWgs84( new QgsLineString( captureListWgs84 ) );
157 if ( !is3D )
158 ringWgs84->dropZValue();
159 if ( !isMeasure )
160 ringWgs84->dropMValue();
161
162 if ( geometryType == Qgis::GeometryType::Line )
163 {
164 g = QgsGeometry( ringWgs84.release() );
165 if ( QgsWkbTypes::isMultiType( type ) )
167 }
168 else if ( geometryType == Qgis::GeometryType::Polygon )
169 {
170 ringWgs84->close();
171 std::unique_ptr<QgsPolygon> polygon( new QgsPolygon() );
172 polygon->setExteriorRing( ringWgs84.release() );
173
174 g = QgsGeometry( polygon.release() );
175
176 if ( QgsWkbTypes::isMultiType( type ) )
178 }
179 return g;
180 }
181
184 break;
185 }
186 return QgsGeometry();
187}
188
190{
191 return mLastGpsPositionWgs84;
192}
193
195{
196 return mLastElevation;
197}
198
200{
201 return mLastMValue;
202}
203
205{
207
208 const bool trackWasEmpty = mCaptureListWgs84.isEmpty();
209 mCaptureListWgs84.clear();
211 mTrackStartTime = QDateTime();
212
213 if ( !trackWasEmpty )
214 emit trackIsEmptyChanged( true );
215
216 emit trackReset();
217}
218
220{
221 int acquisitionInterval = 0;
223 {
224 acquisitionInterval = static_cast< int >( QgsGpsConnection::settingGpsAcquisitionInterval->value() );
227 mLeapSeconds = static_cast< int >( QgsGpsConnection::settingGpsLeapSeconds->value() );
230 mOffsetFromUtc = static_cast< int >( QgsGpsConnection::settingsGpsTimeStampOffsetFromUtc->value() );
231
232 mStoreAttributeInMValues = settingsGpsStoreAttributeInMValues->value();
233 mMValueComponent = settingsGpsMValueComponent->value();
234 }
235 else
236 {
237 acquisitionInterval = QgsGpsLogger::settingsAcquisitionInterval->value();
238 mDistanceThreshold = QgsGpsLogger::settingsDistanceThreshold->value();
239 mApplyLeapSettings = QgsGpsLogger::settingsApplyLeapSeconds->value();
241
242 switch ( QgsGpsLogger::settingsTimeStampFormat->value() )
243 {
244 case 0:
245 mTimeStampSpec = Qt::TimeSpec::LocalTime;
246 break;
247
248 case 1:
249 mTimeStampSpec = Qt::TimeSpec::UTC;
250 break;
251
252 case 2:
253 mTimeStampSpec = Qt::TimeSpec::TimeZone;
254 break;
255 }
257 }
258
259 mAcquisitionInterval = acquisitionInterval * 1000;
260 if ( mAcquisitionTimer->isActive() )
261 mAcquisitionTimer->stop();
262 mAcquisitionEnabled = true;
263
264 switchAcquisition();
265}
266
268{
269 QVector<QgsPointXY> points;
270 QgsGeometry::convertPointList( mCaptureListWgs84, points );
271 try
272 {
273 return mDistanceCalculator.measureLine( points );
274 }
275 catch ( QgsCsException & )
276 {
277 // TODO report errors to user
278 QgsDebugError( QStringLiteral( "An error occurred while calculating length" ) );
279 return 0;
280 }
281}
282
284{
285 if ( mCaptureListWgs84.empty() )
286 return 0;
287
288 try
289 {
290 return mDistanceCalculator.measureLine( { QgsPointXY( mCaptureListWgs84.constFirst() ), QgsPointXY( mCaptureListWgs84.constLast() )} );
291 }
292 catch ( QgsCsException & )
293 {
294 // TODO report errors to user
295 QgsDebugError( QStringLiteral( "An error occurred while calculating length" ) );
296 return 0;
297 }
298}
299
301{
302 if ( !mConnection )
303 return QVariant();
304
305 switch ( component )
306 {
320 return mConnection->currentGPSInformation().componentValue( component );
321
323 return lastTimestamp();
324
326 return totalTrackLength();
328 return trackDistanceFromStart();
330 return trackStartTime();
332 return lastTimestamp();
333
335 try
336 {
337 return mPreviousTrackPoint.isEmpty() ? QVariant() : distanceArea().measureLine( mPreviousTrackPoint, lastPosition() );
338 }
339 catch ( QgsCsException & )
340 {
341 // TODO report errors to user
342 QgsDebugError( QStringLiteral( "An error occurred while calculating length" ) );
343 return 0;
344 }
345
347 return mPreviousTrackPointTime.isValid() ? static_cast< double >( mPreviousTrackPointTime.msecsTo( lastTimestamp() ) ) / 1000 : QVariant();
348 }
350}
351
352void QgsGpsLogger::switchAcquisition()
353{
354 if ( mAcquisitionInterval > 0 )
355 {
356 if ( mAcquisitionEnabled )
357 mAcquisitionTimer->start( mAcquisitionInterval );
358 else
359 //wait only acquisitionInterval/10 for new valid data
360 mAcquisitionTimer->start( mAcquisitionInterval / 10 );
361 // anyway switch to enabled / disabled acquisition
362 mAcquisitionEnabled = !mAcquisitionEnabled;
363 }
364}
365
366void QgsGpsLogger::gpsStateChanged( const QgsGpsInformation &info )
367{
369 return;
370
371 const bool validFlag = info.isValid();
372 QgsPointXY newLocationWgs84;
373 nmeaPOS newNmeaPosition;
374 double newAlt = 0.0;
375 if ( validFlag )
376 {
377 newLocationWgs84 = QgsPointXY( info.longitude, info.latitude );
378 newNmeaPosition.lat = nmea_degree2radian( info.latitude );
379 newNmeaPosition.lon = nmea_degree2radian( info.longitude );
380 newAlt = info.elevation;
381
382 if ( info.utcDateTime.isValid() )
383 {
384 mLastTime = info.utcDateTime;
385 }
386
387 switch ( mMValueComponent )
388 {
401 {
402 const QVariant value = info.componentValue( mMValueComponent );
403 mLastMValue = value.isValid() ? info.componentValue( mMValueComponent ).toDouble() : std::numeric_limits< double >::quiet_NaN();
404 break;
405 }
406
408 mLastMValue = static_cast< double >( info.utcDateTime.toMSecsSinceEpoch() );
409 break;
410
418 // not possible
419 break;
420 }
421 }
422 else
423 {
424 newLocationWgs84 = mLastGpsPositionWgs84;
425 newNmeaPosition = mLastNmeaPosition;
426 newAlt = mLastElevation;
427 }
428 if ( !mAcquisitionEnabled || ( nmea_distance( &newNmeaPosition, &mLastNmeaPosition ) < mDistanceThreshold ) )
429 {
430 // do not update position if update is disabled by timer or distance is under threshold
431 newLocationWgs84 = mLastGpsPositionWgs84;
432
433 }
434 if ( validFlag && mAcquisitionEnabled )
435 {
436 // position updated by valid data, reset timer
437 switchAcquisition();
438 }
439
440 // Avoid adding track vertices when we haven't moved
441 if ( mLastGpsPositionWgs84 != newLocationWgs84 )
442 {
443 mLastGpsPositionWgs84 = newLocationWgs84;
444 mLastNmeaPosition = newNmeaPosition;
445 mLastElevation = newAlt;
446
447 if ( mAutomaticallyAddTrackVertices )
448 {
450 }
451 }
452
453 emit stateChanged( info );
454
455 mPreviousTrackPointTime = lastTimestamp();
456 mPreviousTrackPoint = mLastGpsPositionWgs84;
457}
458
460{
461 QgsPoint pointWgs84 = QgsPoint( mLastGpsPositionWgs84.x(), mLastGpsPositionWgs84.y(), mLastElevation );
462
463 if ( mStoreAttributeInMValues )
464 {
465 pointWgs84.addMValue( mLastMValue );
466 }
467
468 const bool trackWasEmpty = mCaptureListWgs84.empty();
469 mCaptureListWgs84.push_back( pointWgs84 );
470
471 emit trackVertexAdded( pointWgs84 );
472
473 if ( trackWasEmpty )
474 {
475 mTrackStartTime = lastTimestamp();
476 emit trackIsEmptyChanged( false );
477 }
478}
479
481{
482 return mAutomaticallyAddTrackVertices;
483}
484
486{
487 mAutomaticallyAddTrackVertices = enabled;
488}
489
491{
492 if ( !mLastTime.isValid() )
493 return QDateTime();
494
495 QDateTime time = mLastTime;
496
497 // Time from GPS is UTC time
498 time.setTimeSpec( Qt::UTC );
499 // Apply leap seconds correction
500 if ( mApplyLeapSettings && mLeapSeconds != 0 )
501 {
502 time = time.addSecs( mLeapSeconds );
503 }
504 // Desired format
505 if ( mTimeStampSpec != Qt::TimeSpec::OffsetFromUTC )
506 time = time.toTimeSpec( mTimeStampSpec );
507
508 if ( mTimeStampSpec == Qt::TimeSpec::TimeZone )
509 {
510 // Get timezone from the combo
511 const QTimeZone destTz( mTimeZone.toUtf8() );
512 if ( destTz.isValid() )
513 {
514 time = time.toTimeZone( destTz );
515 }
516 }
517 else if ( mTimeStampSpec == Qt::TimeSpec::LocalTime )
518 {
519 time = time.toLocalTime();
520 }
521 else if ( mTimeStampSpec == Qt::TimeSpec::OffsetFromUTC )
522 {
523 time = time.toOffsetFromUtc( mOffsetFromUtc );
524 }
525 else if ( mTimeStampSpec == Qt::TimeSpec::UTC )
526 {
527 // Do nothing: we are already in UTC
528 }
529
530 return time;
531}
532
534{
535 return mTrackStartTime;
536}
GpsInformationComponent
GPS information component.
Definition qgis.h:1803
@ 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:337
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:256
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.
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.
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.
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 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.
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
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.
Line string geometry type, with support for z-dimension and m-values.
A class to represent a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
bool isEmpty() const
Returns true if the geometry is empty.
Definition qgspointxy.h:242
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:569
Polygon geometry type.
Definition qgspolygon.h:33
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
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.
QgsSettingsTree holds the tree structure for the settings in QGIS core.
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 bool isMultiType(Qgis::WkbType type)
Returns true if the WKB type is a multi type.
static bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
#define BUILTIN_UNREACHABLE
Definition qgis.h:6571
#define SIP_SKIP
Definition qgis_sip.h:126
#define QgsDebugError(str)
Definition qgslogger.h:38