QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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 Qgis::GpsInformationComponent>( u"m-value-attribute"_s, QgsSettingsTree::sTreeGps, Qgis::GpsInformationComponent::Timestamp, u"Which GPS attribute should be stored in geometry m values"_s ) SIP_SKIP;
46
48 = 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;
49
51 : QObject( parent )
52 , mWgs84CRS( QgsCoordinateReferenceSystem( u"EPSG:4326"_s ) )
53{
55
56 mLastNmeaPosition.lat = nmea_degree2radian( 0.0 );
57 mLastNmeaPosition.lon = nmea_degree2radian( 0.0 );
58
59 mAcquisitionTimer = std::make_unique<QTimer>( this );
60 mAcquisitionTimer->setSingleShot( true );
61
63
64 connect( mAcquisitionTimer.get(), &QTimer::timeout, this, &QgsGpsLogger::switchAcquisition );
65}
66
69
71{
72 return mConnection;
73}
74
76{
77 if ( mConnection )
78 {
79 disconnect( mConnection, &QgsGpsConnection::stateChanged, this, &QgsGpsLogger::gpsStateChanged );
80 }
81
82 mConnection = connection;
83
84 if ( mConnection )
85 {
86 connect( mConnection, &QgsGpsConnection::stateChanged, this, &QgsGpsLogger::gpsStateChanged );
87 }
88}
89
90void QgsGpsLogger::setEllipsoid( const QString &ellipsoid )
91{
92 mDistanceCalculator.setEllipsoid( ellipsoid );
94}
95
97{
98 mTransformContext = context;
99 mDistanceCalculator.setSourceCrs( mWgs84CRS, mTransformContext );
100 emit distanceAreaChanged();
101}
102
104{
105 return mTransformContext;
106}
107
109{
110 return mDistanceCalculator;
111}
112
113QVector<QgsPoint> QgsGpsLogger::currentTrack() const
114{
115 return mCaptureListWgs84;
116}
117
119{
120 const Qgis::GeometryType geometryType = QgsWkbTypes::geometryType( type );
121 const QVector< QgsPoint > captureListWgs84 = currentTrack();
122 if ( geometryType == Qgis::GeometryType::Line && captureListWgs84.size() < 2 )
123 {
124 error = tr( "Creating a line feature requires a track with at least two vertices." );
125 return QgsGeometry();
126 }
127 else if ( geometryType == Qgis::GeometryType::Polygon && captureListWgs84.size() < 3 )
128 {
129 error = tr( "Creating a polygon feature requires a track with at least three vertices." );
130 return QgsGeometry();
131 }
132
133 const bool is3D = QgsWkbTypes::hasZ( type );
134 const bool isMeasure = QgsWkbTypes::hasM( type );
135 switch ( geometryType )
136 {
138 {
139 const QgsPointXY pointXYWgs84 = lastPosition();
140
141 QgsGeometry g;
142 if ( is3D )
143 g = QgsGeometry( new QgsPoint( pointXYWgs84.x(), pointXYWgs84.y(), lastElevation() ) );
144 else
145 g = QgsGeometry::fromPointXY( pointXYWgs84 );
146
147 if ( isMeasure )
148 g.get()->addMValue( lastMValue() );
149
150 if ( QgsWkbTypes::isMultiType( type ) )
152
153 return g;
154 }
155
158 {
159 QgsGeometry g;
160
161 auto ringWgs84 = std::make_unique<QgsLineString>( captureListWgs84 );
162 if ( !is3D )
163 ringWgs84->dropZValue();
164 if ( !isMeasure )
165 ringWgs84->dropMValue();
166
167 if ( geometryType == Qgis::GeometryType::Line )
168 {
169 g = QgsGeometry( ringWgs84.release() );
170 if ( QgsWkbTypes::isMultiType( type ) )
172 }
173 else if ( geometryType == Qgis::GeometryType::Polygon )
174 {
175 ringWgs84->close();
176 auto polygon = std::make_unique<QgsPolygon>();
177 polygon->setExteriorRing( ringWgs84.release() );
178
179 g = QgsGeometry( polygon.release() );
180
181 if ( QgsWkbTypes::isMultiType( type ) )
183 }
184 return g;
185 }
186
189 break;
190 }
191 return QgsGeometry();
192}
193
195{
196 return mLastGpsPositionWgs84;
197}
198
200{
201 return mLastElevation;
202}
203
205{
206 return mLastMValue;
207}
208
210{
212
213 const bool trackWasEmpty = mCaptureListWgs84.isEmpty();
214 mCaptureListWgs84.clear();
216 mTrackStartTime = QDateTime();
217
218 if ( !trackWasEmpty )
219 emit trackIsEmptyChanged( true );
220
221 emit trackReset();
222}
223
225{
226 int acquisitionInterval = 0;
228 {
229 acquisitionInterval = static_cast< int >( QgsGpsConnection::settingGpsAcquisitionInterval->value() );
230 mDistanceThreshold = QgsGpsConnection::settingGpsDistanceThreshold->value();
231 mApplyLeapSettings = QgsGpsConnection::settingGpsApplyLeapSecondsCorrection->value();
232 mLeapSeconds = static_cast< int >( QgsGpsConnection::settingGpsLeapSeconds->value() );
235 mOffsetFromUtc = static_cast< int >( QgsGpsConnection::settingsGpsTimeStampOffsetFromUtc->value() );
236
237 mStoreAttributeInMValues = settingsGpsStoreAttributeInMValues->value();
238 mMValueComponent = settingsGpsMValueComponent->value();
239 }
240 else
241 {
242 acquisitionInterval = QgsGpsLogger::settingsAcquisitionInterval->value();
243 mDistanceThreshold = QgsGpsLogger::settingsDistanceThreshold->value();
244 mApplyLeapSettings = QgsGpsLogger::settingsApplyLeapSeconds->value();
245 mLeapSeconds = QgsGpsLogger::settingsLeapSecondsCorrection->value();
246
247 switch ( QgsGpsLogger::settingsTimeStampFormat->value() )
248 {
249 case 0:
250 mTimeStampSpec = Qt::TimeSpec::LocalTime;
251 break;
252
253 case 1:
254 mTimeStampSpec = Qt::TimeSpec::UTC;
255 break;
256
257 case 2:
258 mTimeStampSpec = Qt::TimeSpec::TimeZone;
259 break;
260 }
261 mTimeZone = QgsGpsLogger::settingsTimestampTimeZone->value();
262 }
263
264 mAcquisitionInterval = acquisitionInterval * 1000;
265 if ( mAcquisitionTimer->isActive() )
266 mAcquisitionTimer->stop();
267 mAcquisitionEnabled = true;
268
269 switchAcquisition();
270}
271
273{
274 QVector<QgsPointXY> points;
275 QgsGeometry::convertPointList( mCaptureListWgs84, points );
276 try
277 {
278 return mDistanceCalculator.measureLine( points );
279 }
280 catch ( QgsCsException & )
281 {
282 // TODO report errors to user
283 QgsDebugError( u"An error occurred while calculating length"_s );
284 return 0;
285 }
286}
287
289{
290 if ( mCaptureListWgs84.empty() )
291 return 0;
292
293 try
294 {
295 return mDistanceCalculator.measureLine( { QgsPointXY( mCaptureListWgs84.constFirst() ), QgsPointXY( mCaptureListWgs84.constLast() ) } );
296 }
297 catch ( QgsCsException & )
298 {
299 // TODO report errors to user
300 QgsDebugError( u"An error occurred while calculating length"_s );
301 return 0;
302 }
303}
304
306{
307 if ( !mConnection )
308 return QVariant();
309
310 switch ( component )
311 {
325 return mConnection->currentGPSInformation().componentValue( component );
326
328 return lastTimestamp();
329
331 return totalTrackLength();
333 return trackDistanceFromStart();
335 return trackStartTime();
337 return lastTimestamp();
338
340 try
341 {
342 return mPreviousTrackPoint.isEmpty() ? QVariant() : distanceArea().measureLine( mPreviousTrackPoint, lastPosition() );
343 }
344 catch ( QgsCsException & )
345 {
346 // TODO report errors to user
347 QgsDebugError( u"An error occurred while calculating length"_s );
348 return 0;
349 }
350
352 return mPreviousTrackPointTime.isValid() ? static_cast< double >( mPreviousTrackPointTime.msecsTo( lastTimestamp() ) ) / 1000 : QVariant();
353 }
355}
356
357void QgsGpsLogger::switchAcquisition()
358{
359 if ( mAcquisitionInterval > 0 )
360 {
361 if ( mAcquisitionEnabled )
362 mAcquisitionTimer->start( mAcquisitionInterval );
363 else
364 //wait only acquisitionInterval/10 for new valid data
365 mAcquisitionTimer->start( mAcquisitionInterval / 10 );
366 // anyway switch to enabled / disabled acquisition
367 mAcquisitionEnabled = !mAcquisitionEnabled;
368 }
369}
370
371void QgsGpsLogger::gpsStateChanged( const QgsGpsInformation &info )
372{
374 return;
375
376 const bool validFlag = info.isValid();
377 QgsPointXY newLocationWgs84;
378 nmeaPOS newNmeaPosition;
379 double newAlt = 0.0;
380 if ( validFlag )
381 {
382 newLocationWgs84 = QgsPointXY( info.longitude, info.latitude );
383 newNmeaPosition.lat = nmea_degree2radian( info.latitude );
384 newNmeaPosition.lon = nmea_degree2radian( info.longitude );
385 newAlt = info.elevation;
386
387 if ( info.utcDateTime.isValid() )
388 {
389 mLastTime = info.utcDateTime;
390 }
391
392 switch ( mMValueComponent )
393 {
406 {
407 const QVariant value = info.componentValue( mMValueComponent );
408 mLastMValue = value.isValid() ? info.componentValue( mMValueComponent ).toDouble() : std::numeric_limits< double >::quiet_NaN();
409 break;
410 }
411
413 mLastMValue = static_cast< double >( info.utcDateTime.toMSecsSinceEpoch() );
414 break;
415
423 // not possible
424 break;
425 }
426 }
427 else
428 {
429 newLocationWgs84 = mLastGpsPositionWgs84;
430 newNmeaPosition = mLastNmeaPosition;
431 newAlt = mLastElevation;
432 }
433 if ( !mAcquisitionEnabled || ( nmea_distance( &newNmeaPosition, &mLastNmeaPosition ) < mDistanceThreshold ) )
434 {
435 // do not update position if update is disabled by timer or distance is under threshold
436 newLocationWgs84 = mLastGpsPositionWgs84;
437 }
438 if ( validFlag && mAcquisitionEnabled )
439 {
440 // position updated by valid data, reset timer
441 switchAcquisition();
442 }
443
444 // Avoid adding track vertices when we haven't moved
445 if ( mLastGpsPositionWgs84 != newLocationWgs84 )
446 {
447 mLastGpsPositionWgs84 = newLocationWgs84;
448 mLastNmeaPosition = newNmeaPosition;
449 mLastElevation = newAlt;
450
451 if ( mAutomaticallyAddTrackVertices )
452 {
454 }
455 }
456
457 emit stateChanged( info );
458
459 mPreviousTrackPointTime = lastTimestamp();
460 mPreviousTrackPoint = mLastGpsPositionWgs84;
461}
462
464{
465 QgsPoint pointWgs84 = QgsPoint( mLastGpsPositionWgs84.x(), mLastGpsPositionWgs84.y(), mLastElevation );
466
467 if ( mStoreAttributeInMValues )
468 {
469 pointWgs84.addMValue( mLastMValue );
470 }
471
472 const bool trackWasEmpty = mCaptureListWgs84.empty();
473 mCaptureListWgs84.push_back( pointWgs84 );
474
475 emit trackVertexAdded( pointWgs84 );
476
477 if ( trackWasEmpty )
478 {
479 mTrackStartTime = lastTimestamp();
480 emit trackIsEmptyChanged( false );
481 }
482}
483
485{
486 return mAutomaticallyAddTrackVertices;
487}
488
490{
491 mAutomaticallyAddTrackVertices = enabled;
492}
493
495{
496 if ( !mLastTime.isValid() )
497 return QDateTime();
498
499 QDateTime time = mLastTime;
500
501 // Time from GPS is UTC time
502 time.setTimeSpec( Qt::UTC );
503 // Apply leap seconds correction
504 if ( mApplyLeapSettings && mLeapSeconds != 0 )
505 {
506 time = time.addSecs( mLeapSeconds );
507 }
508 // Desired format
509 if ( mTimeStampSpec != Qt::TimeSpec::OffsetFromUTC )
510 time = time.toTimeSpec( mTimeStampSpec );
511
512 if ( mTimeStampSpec == Qt::TimeSpec::TimeZone )
513 {
514#if QT_FEATURE_timezone > 0
515 // Get timezone from the combo
516 const QTimeZone destTz( mTimeZone.toUtf8() );
517 if ( destTz.isValid() )
518 {
519 time = time.toTimeZone( destTz );
520 }
521#else
522 QgsDebugError( u"Qt is built without timezone support, cannot convert GPS timestamps to specified timezone."_s );
523#endif
524 }
525 else if ( mTimeStampSpec == Qt::TimeSpec::LocalTime )
526 {
527 time = time.toLocalTime();
528 }
529 else if ( mTimeStampSpec == Qt::TimeSpec::OffsetFromUTC )
530 {
531 time = time.toOffsetFromUtc( mOffsetFromUtc );
532 }
533 else if ( mTimeStampSpec == Qt::TimeSpec::UTC )
534 {
535 // Do nothing: we are already in UTC
536 }
537
538 return time;
539}
540
542{
543 return mTrackStartTime;
544}
GpsInformationComponent
GPS information component.
Definition qgis.h:2035
@ TrackStartTime
Timestamp at start of current track (available from QgsGpsLogger class only).
Definition qgis.h:2050
@ GroundSpeed
Ground speed.
Definition qgis.h:2038
@ TrackTimeSinceLastPoint
Time since last recorded location (available from QgsGpsLogger class only).
Definition qgis.h:2053
@ Pdop
Dilution of precision.
Definition qgis.h:2042
@ TrackEndTime
Timestamp at end (current point) of current track (available from QgsGpsLogger class only).
Definition qgis.h:2051
@ Altitude
Altitude/elevation above or below the mean sea level.
Definition qgis.h:2037
@ TrackDistanceFromStart
Direct distance from first vertex in current GPS track to last vertex (available from QgsGpsLogger cl...
Definition qgis.h:2041
@ TotalTrackLength
Total distance of current GPS track (available from QgsGpsLogger class only).
Definition qgis.h:2040
@ Hdop
Horizontal dilution of precision.
Definition qgis.h:2043
@ EllipsoidAltitude
Altitude/elevation above or below the WGS-84 Earth ellipsoid.
Definition qgis.h:2055
@ Bearing
Bearing measured in degrees clockwise from true north to the direction of travel.
Definition qgis.h:2039
@ Vdop
Vertical dilution of precision.
Definition qgis.h:2044
@ GeoidalSeparation
Geoidal separation, the difference between the WGS-84 Earth ellipsoid and mean-sea-level (geoid),...
Definition qgis.h:2054
@ VerticalAccuracy
Vertical accuracy in meters.
Definition qgis.h:2046
@ Location
2D location (latitude/longitude), as a QgsPointXY value
Definition qgis.h:2036
@ TrackDistanceSinceLastPoint
Distance since last recorded location (available from QgsGpsLogger class only).
Definition qgis.h:2052
@ HorizontalAccuracy
Horizontal accuracy in meters.
Definition qgis.h:2045
@ SatellitesUsed
Count of satellites used in obtaining the fix.
Definition qgis.h:2048
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:379
@ Point
Points.
Definition qgis.h:380
@ Line
Lines.
Definition qgis.h:381
@ Polygon
Polygons.
Definition qgis.h:382
@ Unknown
Unknown types.
Definition qgis.h:383
@ Null
No geometry.
Definition qgis.h:384
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
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:599
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:7540
#define SIP_SKIP
Definition qgis_sip.h:133
#define QgsDebugError(str)
Definition qgslogger.h:59