14#include <QtCore/QtNumeric>
15#include <QtCore/QDateTime>
16#include <QtCore/QTimeZone>
22#define USE_POSITION_NMEA_PIMPL 0
25class QGeoPositionInfoPrivateNmea :
public QGeoPositionInfoPrivate
28 virtual ~QGeoPositionInfoPrivateNmea();
30 QList<QByteArray> nmeaSentences;
34QGeoPositionInfoPrivateNmea::~QGeoPositionInfoPrivateNmea()
39typedef QGeoPositionInfoPrivate QGeoPositionInfoPrivateNmea;
45 QGeoCoordinate c = dst.coordinate();
46 const QGeoCoordinate & srcCoordinate = src.coordinate();
47 if (qIsFinite(src.coordinate().latitude())
48 && (!qIsFinite(dst.coordinate().latitude()) || force)) {
49 updated |= (c.latitude() != srcCoordinate.latitude());
50 c.setLatitude(src.coordinate().latitude());
52 if (qIsFinite(src.coordinate().longitude())
53 && (!qIsFinite(dst.coordinate().longitude()) || force)) {
54 updated |= (c.longitude() != srcCoordinate.longitude());
55 c.setLongitude(src.coordinate().longitude());
57 if (qIsFinite(src.coordinate().altitude())
58 && (!qIsFinite(dst.coordinate().altitude()) || force)) {
59 updated |= (c.altitude() != srcCoordinate.altitude());
60 c.setAltitude(src.coordinate().altitude());
66static bool propagateDate(QGeoPositionInfo &dst,
const QGeoPositionInfo &src)
68 if (!dst.timestamp().date().isValid() && src.timestamp().isValid()) {
69 dst.setTimestamp(src.timestamp());
78 static constexpr std::array<QGeoPositionInfo::Attribute, 6> attrs {
79 { QGeoPositionInfo::GroundSpeed
80 ,QGeoPositionInfo::HorizontalAccuracy
81 ,QGeoPositionInfo::VerticalAccuracy
82 ,QGeoPositionInfo::Direction
83 ,QGeoPositionInfo::VerticalSpeed
84 ,QGeoPositionInfo::MagneticVariation} };
85 for (
const auto a: attrs) {
86 if (src.hasAttribute(a) && (!dst.hasAttribute(a) || force)) {
87 updated |= (dst.attribute(a) != src.attribute(a));
88 dst.setAttribute(a, src.attribute(a));
106 QGeoPositionInfoPrivateNmea *dstPimpl =
static_cast<QGeoPositionInfoPrivateNmea *>(QGeoPositionInfoPrivate::get(dst));
107 dstPimpl->nmeaSentences.append(nmeaSentence);
109 Q_UNUSED(nmeaSentence);
116 if (!from.time().isValid() || !to.time().isValid())
119 if (!from.date().isValid() || !to.date().isValid())
120 return from.time().msecsTo(to.time());
122 return from.msecsTo(to);
129 : QNmeaReader(sourcePrivate), m_update(*
new QGeoPositionInfoPrivateNmea)
137 int pushDelay = qEnvironmentVariableIntValue(
"QT_NMEA_PUSH_DELAY", &ok);
139 pushDelay = std::clamp(pushDelay, -1, 1000);
143 if (pushDelay >= 0) {
144 m_timer.setSingleShot(
true);
145 m_timer.setInterval(pushDelay);
146 m_timer.connect(&m_timer, &QTimer::timeout, &m_timer, [
this]() {
147 this->notifyNewUpdate();
150 m_pushDelay = pushDelay;
153QNmeaRealTimeReader::~QNmeaRealTimeReader()
158 while (
m_proxy->m_device->canReadLine()) {
159 const QTime infoTime = m_update.timestamp().time();
160 const QDate infoDate = m_update.timestamp().date();
162 QGeoPositionInfoPrivateNmea *pimpl =
new QGeoPositionInfoPrivateNmea;
163 QGeoPositionInfo pos(*pimpl);
166 qint64 size =
m_proxy->m_device->readLine(buf,
sizeof(buf));
172 const bool parsed = m_proxy->parsePosInfoFromNmeaData(
173 QByteArrayView{buf,
static_cast<qsizetype>(size)}, &pos, &hasFix);
187 if (infoTime.isValid()) {
188 if (pos.timestamp().time().isValid()) {
189 const bool newerTime = infoTime < pos.timestamp().time();
190 const bool newerDate = (infoDate.isValid()
191 && pos.timestamp().date().isValid()
192 && infoDate < pos.timestamp().date());
193 if (newerTime || newerDate) {
196 const QDate updateDate = m_update.timestamp().date();
197 const QDate lastPushedDate = m_lastPushedTS.date();
198 const bool newerTimestampSinceLastPushed = m_update.timestamp() > m_lastPushedTS;
199 const bool invalidDate = !(updateDate.isValid() && lastPushedDate.isValid());
200 const bool newerTimeSinceLastPushed = m_update.timestamp().time() > m_lastPushedTS.time();
201 if ( newerTimestampSinceLastPushed || (invalidDate && newerTimeSinceLastPushed)) {
202 m_proxy->notifyNewUpdate(&m_update, oldFix);
203 m_lastPushedTS = m_update.timestamp();
207 propagateAttributes(pos, m_update,
false);
211 if (infoTime == pos.timestamp().time())
213 if (mergePositions(m_update, pos, QByteArray(buf, size))) {
223 if (mergePositions(m_update, pos, QByteArray(buf, size)))
229 pimpl->nmeaSentences.append(QByteArray(buf, size));
231 propagateAttributes(pos, m_update);
247 const bool newerTime = m_update.timestamp().time() > m_lastPushedTS.time();
248 const bool newerDate = (m_update.timestamp().date().isValid()
249 && m_lastPushedTS.date().isValid()
250 && m_update.timestamp().date() > m_lastPushedTS.date());
251 if (newerTime || newerDate) {
252 m_proxy->notifyNewUpdate(&m_update, m_hasFix);
253 m_lastPushedTS = m_update.timestamp();
264 m_hasValidDateTime(
false)
270 if (m_currTimerId > 0)
271 killTimer(m_currTimerId);
276 if (m_currTimerId > 0)
279 if (!m_hasValidDateTime) {
280 Q_ASSERT(m_proxy->m_device && (m_proxy->m_device->openMode() & QIODevice::ReadOnly));
282 if (!setFirstDateTime()) {
284 qWarning(
"QNmeaPositionInfoSource: cannot find NMEA sentence with valid date & time");
288 m_hasValidDateTime =
true;
289 simulatePendingUpdate();
293 processNextSentence();
300 QQueue<QPendingGeoPositionInfo> &m_pendingUpdates,
303 int timeToNextUpdate = -1;
305 if (!m_pendingUpdates.isEmpty())
306 prevTs = m_pendingUpdates.head().info.timestamp();
310 while (!m_nextLine.isEmpty() || (m_proxy->m_device && m_proxy->m_device->bytesAvailable() > 0)) {
311 char static_buf[1024];
312 char *buf = static_buf;
315 if (!m_nextLine.isEmpty()) {
317 size = m_nextLine.size();
318 nextLine = m_nextLine;
320 buf = nextLine.data();
322 size = m_proxy->m_device->readLine(buf,
sizeof(static_buf));
328 const QTime infoTime = info.timestamp().time();
329 const QDate infoDate = info.timestamp().date();
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
348 QGeoPositionInfoPrivateNmea *pimpl =
new QGeoPositionInfoPrivateNmea;
349 QGeoPositionInfo pos(*pimpl);
350 if (m_proxy->parsePosInfoFromNmeaData(
351 QByteArrayView{buf,
static_cast<qsizetype>(size)}, &pos, &hasFix)) {
356 if (infoTime.isValid()) {
357 if (pos.timestamp().time().isValid()) {
358 const bool newerTime = infoTime < pos.timestamp().time();
359 const bool newerDate = (infoDate.isValid()
360 && pos.timestamp().date().isValid()
361 && infoDate < pos.timestamp().date());
362 if (newerTime || newerDate) {
367 if (infoTime == pos.timestamp().time())
369 mergePositions(info, pos,
QByteArray(buf, size));
374 mergePositions(info, pos,
QByteArray(buf, size));
379 pimpl->nmeaSentences.append(QByteArray(buf, size));
384 if (prevTs.time().isValid()) {
385 timeToNextUpdate = msecsTo(prevTs, info.timestamp());
386 if (timeToNextUpdate < 0)
387 info = QGeoPositionInfo(*
new QGeoPositionInfoPrivateNmea);
392 return timeToNextUpdate;
398 QGeoPositionInfo info(*
new QGeoPositionInfoPrivateNmea);
400 processSentence(info, m_nextLine, m_proxy, m_pendingUpdates, hasFix);
402 if (info.timestamp().time().isValid()) {
406 m_pendingUpdates.enqueue(pending);
414 if (!m_pendingUpdates.isEmpty()) {
420 processNextSentence();
425 killTimer(event->timerId());
427 simulatePendingUpdate();
432 QGeoPositionInfo info(*
new QGeoPositionInfoPrivateNmea);
435 int timeToNextUpdate = processSentence(info, m_nextLine, m_proxy, m_pendingUpdates, hasFix);
436 if (timeToNextUpdate < 0)
439 m_pendingUpdates.dequeue();
444 m_pendingUpdates.enqueue(pending);
445 m_currTimerId = startTimer(timeToNextUpdate);
454 m_updateMode(updateMode),
457 m_positionError(QGeoPositionInfoSource::UnknownSourceError),
463 m_horizontalAccuracy(qQNaN()),
464 m_verticalAccuracy(qQNaN()),
465 m_noUpdateLastInterval(
false),
466 m_updateTimeoutSent(
false),
467 m_connectedReadyRead(
false)
474 delete m_updateTimer;
480 qWarning(
"QNmeaPositionInfoSource: no QIODevice data source, call setDevice() first");
484 if (!m_device->isOpen() && !m_device->open(QIODevice::ReadOnly)) {
485 qWarning(
"QNmeaPositionInfoSource: cannot open QIODevice data source");
489 connect(m_device, SIGNAL(aboutToClose()), SLOT(sourceDataClosed()));
490 connect(m_device, SIGNAL(readChannelFinished()), SLOT(sourceDataClosed()));
491 connect(m_device, SIGNAL(destroyed()), SLOT(sourceDataClosed()));
498 if (m_nmeaReader && m_device && m_device->bytesAvailable())
513 if (!openSourceDevice())
516 if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode)
519 m_nmeaReader =
new QNmeaSimulatedReader(
this);
527 if (m_updateMode == QNmeaPositionInfoSource::SimulationMode) {
528 if (m_nmeaReader && m_device->bytesAvailable())
532 if (!m_connectedReadyRead) {
533 connect(m_device, SIGNAL(readyRead()), SLOT(readyRead()));
534 m_connectedReadyRead =
true;
539 QGeoPositionInfo *posInfo,
bool *hasFix)
541 return m_source->parsePosInfoFromNmeaData(data, posInfo, hasFix);
549 m_positionError = QGeoPositionInfoSource::NoError;
552 m_pendingUpdate = QGeoPositionInfo();
553 m_noUpdateLastInterval =
false;
555 bool initialized = initialize();
557 m_source->setError(QGeoPositionInfoSource::AccessError);
561 if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode) {
564 if (m_device->bytesAvailable()) {
565 if (m_device->isSequential())
568 m_device->seek(m_device->bytesAvailable());
573 m_updateTimer->stop();
575 if (m_source->updateInterval() > 0) {
577 m_updateTimer =
new QBasicTimer;
578 m_updateTimer->start(m_source->updateInterval(),
this);
582 prepareSourceDevice();
589 m_updateTimer->stop();
590 m_pendingUpdate = QGeoPositionInfo();
591 m_noUpdateLastInterval =
false;
596 if (m_requestTimer && m_requestTimer->isActive())
599 m_positionError = QGeoPositionInfoSource::NoError;
601 if (msec <= 0 || msec < m_source->minimumUpdateInterval()) {
602 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
606 if (!m_requestTimer) {
607 m_requestTimer =
new QTimer(
this);
608 connect(m_requestTimer, SIGNAL(timeout()), SLOT(updateRequestTimeout()));
611 bool initialized = initialize();
613 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
617 m_requestTimer->start(msec);
618 prepareSourceDevice();
623 m_requestTimer->stop();
624 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
632 QDate date = update->timestamp().date();
633 if (date.isValid()) {
634 m_currentDate = date;
637 QTime time = update->timestamp().time();
638 if (time.isValid() && m_currentDate.isValid())
639 update->setTimestamp(QDateTime(m_currentDate, time, QTimeZone::UTC));
644 if (update->hasAttribute(QGeoPositionInfo::HorizontalAccuracy))
645 m_horizontalAccuracy = update->attribute(QGeoPositionInfo::HorizontalAccuracy);
646 else if (!qIsNaN(m_horizontalAccuracy))
647 update->setAttribute(QGeoPositionInfo::HorizontalAccuracy, m_horizontalAccuracy);
649 if (update->hasAttribute(QGeoPositionInfo::VerticalAccuracy))
650 m_verticalAccuracy = update->attribute(QGeoPositionInfo::VerticalAccuracy);
651 else if (!qIsNaN(m_verticalAccuracy))
652 update->setAttribute(QGeoPositionInfo::VerticalAccuracy, m_verticalAccuracy);
654 if (hasFix && update->isValid()) {
655 if (m_requestTimer && m_requestTimer->isActive()) {
656 m_requestTimer->stop();
657 emitUpdated(*update);
659 if (m_updateTimer && m_updateTimer->isActive()) {
661 m_pendingUpdate = *update;
662 if (m_noUpdateLastInterval) {
666 m_noUpdateLastInterval =
false;
669 emitUpdated(*update);
672 m_lastUpdate = *update;
683 if (m_pendingUpdate.isValid()) {
684 m_updateTimeoutSent =
false;
685 m_noUpdateLastInterval =
false;
686 emitUpdated(m_pendingUpdate);
687 m_pendingUpdate = QGeoPositionInfo();
689 if (m_noUpdateLastInterval && !m_updateTimeoutSent) {
690 m_updateTimeoutSent =
true;
691 m_pendingUpdate = QGeoPositionInfo();
692 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
694 m_noUpdateLastInterval =
true;
702 m_lastUpdate = update;
703 emit m_source->positionUpdated(update);
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
742
743
744
745
746
747
751
752
753
754QNmeaPositionInfoSource::QNmeaPositionInfoSource(UpdateMode updateMode, QObject *parent)
755 : QGeoPositionInfoSource(parent),
756 d(
new QNmeaPositionInfoSourcePrivate(
this, updateMode))
761
762
763QNmeaPositionInfoSource::~QNmeaPositionInfoSource()
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784void QNmeaPositionInfoSource::setUserEquivalentRangeError(
double uere)
786 d->m_userEquivalentRangeError = uere;
790
791
792
793
794
795
796
797
798double QNmeaPositionInfoSource::userEquivalentRangeError()
const
800 return d->m_userEquivalentRangeError;
804
805
806
807
808
809
810
811
812
813
814
815
816
818#if QT_DEPRECATED_SINCE(7
, 0
)
819bool QNmeaPositionInfoSource::parsePosInfoFromNmeaData(
const char *data,
int size,
820 QGeoPositionInfo *posInfo,
bool *hasFix)
822#if QT_VERSION < QT_VERSION_CHECK(7
, 0
, 0
)
823 return QLocationUtils::getPosInfoFromNmea(QByteArrayView{data, size}, posInfo,
824 d->m_userEquivalentRangeError, hasFix);
826 return parsePosInfoFromNmeaData(QByteArrayView{data, size}, posInfo, hasFix);
831bool QNmeaPositionInfoSource::parsePosInfoFromNmeaData(QByteArrayView data,
832 QGeoPositionInfo *posInfo,
bool *hasFix)
834#if QT_VERSION < QT_VERSION_CHECK(7
, 0
, 0
)
835 return parsePosInfoFromNmeaData(data.data(),
static_cast<
int>(data.size()),
838 return QLocationUtils::getPosInfoFromNmea(data, posInfo,
839 d->m_userEquivalentRangeError, hasFix);
845
846
847QNmeaPositionInfoSource::UpdateMode QNmeaPositionInfoSource::updateMode()
const
849 return d->m_updateMode;
853
854
855
856
857
858
859
860
861
862
863
864void QNmeaPositionInfoSource::setDevice(QIODevice *device)
866 if (device != d->m_device) {
868 d->m_device = device;
870 qWarning(
"QNmeaPositionInfoSource: source device has already been set");
875
876
877QIODevice *QNmeaPositionInfoSource::device()
const
883
884
885void QNmeaPositionInfoSource::setUpdateInterval(
int msec)
889 interval = qMax(msec, minimumUpdateInterval());
890 QGeoPositionInfoSource::setUpdateInterval(interval);
891 if (d->m_invokedStart) {
898
899
900void QNmeaPositionInfoSource::startUpdates()
906
907
908void QNmeaPositionInfoSource::stopUpdates()
914
915
916void QNmeaPositionInfoSource::requestUpdate(
int msec)
918 d->requestUpdate(msec == 0 ? 60000 * 5 : msec);
922
923
924QGeoPositionInfo QNmeaPositionInfoSource::lastKnownPosition(
bool)
const
927 return d->m_lastUpdate;
931
932
933QGeoPositionInfoSource::PositioningMethods QNmeaPositionInfoSource::supportedPositioningMethods()
const
935 return SatellitePositioningMethods;
939
940
941int QNmeaPositionInfoSource::minimumUpdateInterval()
const
947
948
949QGeoPositionInfoSource::Error QNmeaPositionInfoSource::error()
const
951 return d->m_positionError;
954void QNmeaPositionInfoSource::setError(QGeoPositionInfoSource::Error positionError)
956 d->m_positionError = positionError;
957 if (d->m_positionError != QGeoPositionInfoSource::NoError)
958 emit QGeoPositionInfoSource::errorOccurred(positionError);
963#include "moc_qnmeapositioninfosource_p.cpp"
964#include "moc_qnmeapositioninfosource.cpp"
void notifyNewUpdate(QGeoPositionInfo *update, bool fixStatus)
~QNmeaPositionInfoSourcePrivate()
bool parsePosInfoFromNmeaData(QByteArrayView data, QGeoPositionInfo *posInfo, bool *hasFix)
void requestUpdate(int msec)
double m_userEquivalentRangeError
void timerEvent(QTimerEvent *event) override
This event handler can be reimplemented in a subclass to receive timer events for the object.
QNmeaReader(QNmeaPositionInfoSourcePrivate *sourcePrivate)
QNmeaPositionInfoSourcePrivate * m_proxy
virtual void readAvailableData()=0
void readAvailableData() override
QNmeaRealTimeReader(QNmeaPositionInfoSourcePrivate *sourcePrivate)
void timerEvent(QTimerEvent *event) override
This event handler can be reimplemented in a subclass to receive timer events for the object.
void readAvailableData() override
#define USE_POSITION_NMEA_PIMPL
static bool propagateCoordinate(QGeoPositionInfo &dst, const QGeoPositionInfo &src, bool force=true)
static int processSentence(QGeoPositionInfo &info, QByteArray &m_nextLine, QNmeaPositionInfoSourcePrivate *m_proxy, QQueue< QPendingGeoPositionInfo > &m_pendingUpdates, bool &hasFix)
static bool propagateAttributes(QGeoPositionInfo &dst, const QGeoPositionInfo &src, bool force=true)
static qint64 msecsTo(const QDateTime &from, const QDateTime &to)
static bool propagateDate(QGeoPositionInfo &dst, const QGeoPositionInfo &src)
static bool mergePositions(QGeoPositionInfo &dst, const QGeoPositionInfo &src, QByteArray nmeaSentence)