From 965028888d8ddddcaef8dc53e65102449fbce138 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Tue, 11 Nov 2025 14:33:08 +0200 Subject: [PATCH 01/16] Enhance position altitude processing Add EGM96_15 geoid model, which recalculates ellipsoid altitudes returned by position providers. Expose this information in GPS information panel. --- app/appsettings.cpp | 12 ++++++++++-- app/position/positionkit.cpp | 7 +++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/appsettings.cpp b/app/appsettings.cpp index 58e340ec1..2c1ae630e 100644 --- a/app/appsettings.cpp +++ b/app/appsettings.cpp @@ -10,6 +10,7 @@ #include "appsettings.h" #include "coreutils.h" +#include #include #include @@ -197,8 +198,15 @@ void AppSettings::setActivePositionProviderId( const QString &id ) if ( mActivePositionProviderId == id ) return; - mActivePositionProviderId = id; - setValue( QStringLiteral( "activePositionProviderId" ), id ); + // this is a workaround for android 15+, check PositionKit::constructProvider for more info + if (id == QStringLiteral( "android_gps" ) && QNativeInterface::QAndroidApplication::sdkVersion() >= 35) + { + mActivePositionProviderId = QStringLiteral( "devicegps" ); + } else + { + mActivePositionProviderId = id; + } + setValue( QStringLiteral( "activePositionProviderId" ), mActivePositionProviderId ); emit activePositionProviderIdChanged( mActivePositionProviderId ); } diff --git a/app/position/positionkit.cpp b/app/position/positionkit.cpp index 191377aa3..28851af73 100644 --- a/app/position/positionkit.cpp +++ b/app/position/positionkit.cpp @@ -40,7 +40,7 @@ QgsCoordinateReferenceSystem PositionKit::positionCrs3D() QgsCoordinateReferenceSystem PositionKit::positionCrs2D() { - return QgsCoordinateReferenceSystem::fromEpsgId( 4326 ); + return QgsCoordinateReferenceSystem::fromEpsgId( 9707 ); } QgsCoordinateReferenceSystem PositionKit::positionCrs3DEllipsoidHeight() @@ -120,7 +120,10 @@ AbstractPositionProvider *PositionKit::constructProvider( const QString &type, c return provider; } #ifdef ANDROID - else if ( id == QStringLiteral( "android_fused" ) || id == QStringLiteral( "android_gps" ) ) + else if ( id == QStringLiteral( "android_fused" ) || id == QStringLiteral( "android_gps" ) + // this is a workaround for https://github.com/MerginMaps/mobile/issues/4165 + // we use the android implementation on android 15+ to report correct altitudes + || (id == QStringLiteral( "devicegps" ) && QNativeInterface::QAndroidApplication::sdkVersion() >= 35)) { bool fused = ( id == QStringLiteral( "android_fused" ) ); if ( fused && !AndroidPositionProvider::isFusedAvailable() ) From 792bca028f858fabf8b469c7269b409f02e49f3a Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Tue, 11 Nov 2025 14:51:57 +0200 Subject: [PATCH 02/16] Fix broken builds --- app/appsettings.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/appsettings.cpp b/app/appsettings.cpp index 2c1ae630e..b50235bb8 100644 --- a/app/appsettings.cpp +++ b/app/appsettings.cpp @@ -198,6 +198,7 @@ void AppSettings::setActivePositionProviderId( const QString &id ) if ( mActivePositionProviderId == id ) return; +#ifdef ANDROID // this is a workaround for android 15+, check PositionKit::constructProvider for more info if (id == QStringLiteral( "android_gps" ) && QNativeInterface::QAndroidApplication::sdkVersion() >= 35) { @@ -206,6 +207,9 @@ void AppSettings::setActivePositionProviderId( const QString &id ) { mActivePositionProviderId = id; } +#else + mActivePositionProviderId = id; +#endif setValue( QStringLiteral( "activePositionProviderId" ), mActivePositionProviderId ); emit activePositionProviderIdChanged( mActivePositionProviderId ); } From 5274b26a5c7cf3bca12866521926c14f1fe9e836 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Tue, 11 Nov 2025 14:53:19 +0200 Subject: [PATCH 03/16] Fix formatting --- app/appsettings.cpp | 5 +++-- app/position/positionkit.cpp | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/appsettings.cpp b/app/appsettings.cpp index b50235bb8..4f7a90d25 100644 --- a/app/appsettings.cpp +++ b/app/appsettings.cpp @@ -200,10 +200,11 @@ void AppSettings::setActivePositionProviderId( const QString &id ) #ifdef ANDROID // this is a workaround for android 15+, check PositionKit::constructProvider for more info - if (id == QStringLiteral( "android_gps" ) && QNativeInterface::QAndroidApplication::sdkVersion() >= 35) + if ( id == QStringLiteral( "android_gps" ) && QNativeInterface::QAndroidApplication::sdkVersion() >= 35 ) { mActivePositionProviderId = QStringLiteral( "devicegps" ); - } else + } + else { mActivePositionProviderId = id; } diff --git a/app/position/positionkit.cpp b/app/position/positionkit.cpp index 28851af73..7bfbf9b8f 100644 --- a/app/position/positionkit.cpp +++ b/app/position/positionkit.cpp @@ -121,9 +121,9 @@ AbstractPositionProvider *PositionKit::constructProvider( const QString &type, c } #ifdef ANDROID else if ( id == QStringLiteral( "android_fused" ) || id == QStringLiteral( "android_gps" ) - // this is a workaround for https://github.com/MerginMaps/mobile/issues/4165 - // we use the android implementation on android 15+ to report correct altitudes - || (id == QStringLiteral( "devicegps" ) && QNativeInterface::QAndroidApplication::sdkVersion() >= 35)) + // this is a workaround for https://github.com/MerginMaps/mobile/issues/4165 + // we use the android implementation on android 15+ to report correct altitudes + || ( id == QStringLiteral( "devicegps" ) && QNativeInterface::QAndroidApplication::sdkVersion() >= 35 ) ) { bool fused = ( id == QStringLiteral( "android_fused" ) ); if ( fused && !AndroidPositionProvider::isFusedAvailable() ) From 991bf545d0cfeb3f21e465a38f3a9e440a3163cd Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Tue, 11 Nov 2025 22:12:30 +0200 Subject: [PATCH 04/16] Add geoid info for iOS --- app/qml/gps/MMGpsDataDrawer.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/qml/gps/MMGpsDataDrawer.qml b/app/qml/gps/MMGpsDataDrawer.qml index 915c0d2f9..839ba91fa 100644 --- a/app/qml/gps/MMGpsDataDrawer.qml +++ b/app/qml/gps/MMGpsDataDrawer.qml @@ -198,7 +198,7 @@ MMComponents.MMDrawer { } alignmentRight: Positioner.index % 2 === 1 - desc: qsTr("Orthometric height, using EGM96 geoid") + desc: __iosUtils.isIos? qsTr("Final value calculated with EGM2008 geoid model.") : qsTr("Orthometric height, using EGM96 geoid") } MMGpsComponents.MMGpsDataText { From 37b25c3a90f7b8e954a5c77dca52fd70474ae0cb Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Wed, 12 Nov 2025 12:05:14 +0200 Subject: [PATCH 05/16] Refactor PositionKit to singleton from context property --- app/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/main.cpp b/app/main.cpp index c2fd5523f..8e86fbbdb 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -545,7 +545,7 @@ int main( int argc, char *argv[] ) // build position kit, save active provider to QSettings and load previously active provider PositionKit *pk = engine.singletonInstance( "MMInput", "PositionKit" ); - QObject::connect( pk, &PositionKit::positionProviderChanged, as, [as]( AbstractPositionProvider * provider ) + QObject::connect( pk, &PositionKit::positionProviderChanged, as, [as]( AbstractPositionProvider *provider ) { as->setActivePositionProviderId( provider ? provider->id() : QLatin1String() ); } ); From c9375c2c2f47f611f96ec25743faab92deeda323 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Thu, 13 Nov 2025 15:00:37 +0200 Subject: [PATCH 06/16] Patch ios internal positioning provider Provider returns now WGS84 ellipsoidal height on iOS --- app/qml/gps/MMGpsDataDrawer.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/qml/gps/MMGpsDataDrawer.qml b/app/qml/gps/MMGpsDataDrawer.qml index 839ba91fa..915c0d2f9 100644 --- a/app/qml/gps/MMGpsDataDrawer.qml +++ b/app/qml/gps/MMGpsDataDrawer.qml @@ -198,7 +198,7 @@ MMComponents.MMDrawer { } alignmentRight: Positioner.index % 2 === 1 - desc: __iosUtils.isIos? qsTr("Final value calculated with EGM2008 geoid model.") : qsTr("Orthometric height, using EGM96 geoid") + desc: qsTr("Orthometric height, using EGM96 geoid") } MMGpsComponents.MMGpsDataText { From ab8e57aa9d940179d613902a0d0fd72ff242a972 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Fri, 14 Nov 2025 15:45:18 +0200 Subject: [PATCH 07/16] Fix elevation transform & android workaround Create new 3D transform utils function. Fix coordinate order passing. Rework android 15+ Qt positioning workaround to VCPKG patch. --- app/appsettings.cpp | 12 ------- app/inpututils.cpp | 35 +++++++++++++++++++ app/inpututils.h | 10 ++++++ app/main.cpp | 2 +- app/position/positionkit.cpp | 5 +-- .../providers/androidpositionprovider.cpp | 2 +- 6 files changed, 48 insertions(+), 18 deletions(-) diff --git a/app/appsettings.cpp b/app/appsettings.cpp index 4f7a90d25..96891d6a0 100644 --- a/app/appsettings.cpp +++ b/app/appsettings.cpp @@ -198,19 +198,7 @@ void AppSettings::setActivePositionProviderId( const QString &id ) if ( mActivePositionProviderId == id ) return; -#ifdef ANDROID - // this is a workaround for android 15+, check PositionKit::constructProvider for more info - if ( id == QStringLiteral( "android_gps" ) && QNativeInterface::QAndroidApplication::sdkVersion() >= 35 ) - { - mActivePositionProviderId = QStringLiteral( "devicegps" ); - } - else - { - mActivePositionProviderId = id; - } -#else mActivePositionProviderId = id; -#endif setValue( QStringLiteral( "activePositionProviderId" ), mActivePositionProviderId ); emit activePositionProviderIdChanged( mActivePositionProviderId ); } diff --git a/app/inpututils.cpp b/app/inpututils.cpp index e0c8bf99b..84a154ebe 100644 --- a/app/inpututils.cpp +++ b/app/inpututils.cpp @@ -905,6 +905,41 @@ QgsPoint InputUtils::transformPoint( const QgsCoordinateReferenceSystem &srcCrs, return {}; } +QgsPoint InputUtils::transformPoint3D( const QgsCoordinateReferenceSystem &srcCrs, + const QgsCoordinateReferenceSystem &destCrs, + const QgsCoordinateTransformContext &context, + const QgsPoint &srcPoint ) +{ + // we do not want to transform empty points, + // QGIS would convert them to a valid (0, 0) points + if ( srcPoint.isEmpty() ) + { + return {}; + } + + try + { + const QgsCoordinateTransform ct( srcCrs, destCrs, context ); + if ( ct.isValid() ) + { + if ( !ct.isShortCircuited() ) + { + const QgsVector3D transformed = ct.transform( QgsVector3D(srcPoint.x(), srcPoint.y(), srcPoint.z()) ); + const QgsPoint pt( transformed.x(), transformed.y(), transformed.z(), srcPoint.m() ); + return pt; + } + + return srcPoint; + } + } + catch ( QgsCsException &cse ) + { + Q_UNUSED( cse ) + } + + return {}; +} + QPointF InputUtils::transformPointToScreenCoordinates( const QgsCoordinateReferenceSystem &srcCrs, InputMapSettings *mapSettings, const QgsPoint &srcPoint ) { if ( !mapSettings || srcPoint.isEmpty() ) diff --git a/app/inpututils.h b/app/inpututils.h index 545b358d0..b6de55b56 100644 --- a/app/inpututils.h +++ b/app/inpututils.h @@ -299,12 +299,22 @@ class InputUtils: public QObject /** * Transforms point between different crs * Return empty QgsPoint if the transformation could not be applied or srcPoint is empty + * \note This function only does 2D crs transformation, use \a transformPoint3D for 3D transformation! */ Q_INVOKABLE static QgsPoint transformPoint( const QgsCoordinateReferenceSystem &srcCrs, const QgsCoordinateReferenceSystem &destCrs, const QgsCoordinateTransformContext &context, const QgsPoint &srcPoint ); + /** + * Transforms point between different crs + * Return empty QgsPoint if the transformation could not be applied or srcPoint is empty + */ + Q_INVOKABLE static QgsPoint transformPoint3D( const QgsCoordinateReferenceSystem &srcCrs, + const QgsCoordinateReferenceSystem &destCrs, + const QgsCoordinateTransformContext &context, + const QgsPoint &srcPoint ); + /** * Transforms point between CRS and screen pixels * Return empty QgsPoint if the transformation could not be applied or srcPoint is empty diff --git a/app/main.cpp b/app/main.cpp index 8e86fbbdb..c2fd5523f 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -545,7 +545,7 @@ int main( int argc, char *argv[] ) // build position kit, save active provider to QSettings and load previously active provider PositionKit *pk = engine.singletonInstance( "MMInput", "PositionKit" ); - QObject::connect( pk, &PositionKit::positionProviderChanged, as, [as]( AbstractPositionProvider *provider ) + QObject::connect( pk, &PositionKit::positionProviderChanged, as, [as]( AbstractPositionProvider * provider ) { as->setActivePositionProviderId( provider ? provider->id() : QLatin1String() ); } ); diff --git a/app/position/positionkit.cpp b/app/position/positionkit.cpp index 7bfbf9b8f..229c2a4f4 100644 --- a/app/position/positionkit.cpp +++ b/app/position/positionkit.cpp @@ -120,10 +120,7 @@ AbstractPositionProvider *PositionKit::constructProvider( const QString &type, c return provider; } #ifdef ANDROID - else if ( id == QStringLiteral( "android_fused" ) || id == QStringLiteral( "android_gps" ) - // this is a workaround for https://github.com/MerginMaps/mobile/issues/4165 - // we use the android implementation on android 15+ to report correct altitudes - || ( id == QStringLiteral( "devicegps" ) && QNativeInterface::QAndroidApplication::sdkVersion() >= 35 ) ) + else if ( id == QStringLiteral( "android_fused" ) || id == QStringLiteral( "android_gps" ) ) { bool fused = ( id == QStringLiteral( "android_fused" ) ); if ( fused && !AndroidPositionProvider::isFusedAvailable() ) diff --git a/app/position/providers/androidpositionprovider.cpp b/app/position/providers/androidpositionprovider.cpp index 77193bcb4..8145d9ba7 100644 --- a/app/position/providers/androidpositionprovider.cpp +++ b/app/position/providers/androidpositionprovider.cpp @@ -57,7 +57,7 @@ void jniOnPositionUpdated( JNIEnv *env, jclass clazz, jint instanceId, jobject l if ( !qFuzzyIsNull( value ) ) { // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to specified geoid model - const QgsPoint geoidPosition = InputUtils::transformPoint( + const QgsPoint geoidPosition = InputUtils::transformPoint3D( PositionKit::positionCrs3DEllipsoidHeight(), PositionKit::positionCrs3D(), QgsProject::instance()->transformContext(), From d6910f5ed049547096c3ae2d65679c50751fdf1a Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Fri, 14 Nov 2025 16:33:32 +0200 Subject: [PATCH 08/16] Clean up & format code --- app/inpututils.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/inpututils.cpp b/app/inpututils.cpp index 84a154ebe..bc165886c 100644 --- a/app/inpututils.cpp +++ b/app/inpututils.cpp @@ -906,9 +906,9 @@ QgsPoint InputUtils::transformPoint( const QgsCoordinateReferenceSystem &srcCrs, } QgsPoint InputUtils::transformPoint3D( const QgsCoordinateReferenceSystem &srcCrs, - const QgsCoordinateReferenceSystem &destCrs, - const QgsCoordinateTransformContext &context, - const QgsPoint &srcPoint ) + const QgsCoordinateReferenceSystem &destCrs, + const QgsCoordinateTransformContext &context, + const QgsPoint &srcPoint ) { // we do not want to transform empty points, // QGIS would convert them to a valid (0, 0) points @@ -924,7 +924,7 @@ QgsPoint InputUtils::transformPoint3D( const QgsCoordinateReferenceSystem &srcCr { if ( !ct.isShortCircuited() ) { - const QgsVector3D transformed = ct.transform( QgsVector3D(srcPoint.x(), srcPoint.y(), srcPoint.z()) ); + const QgsVector3D transformed = ct.transform( QgsVector3D( srcPoint.x(), srcPoint.y(), srcPoint.z() ) ); const QgsPoint pt( transformed.x(), transformed.y(), transformed.z(), srcPoint.m() ); return pt; } From 1a3be9cb70dc391e4cf53b6d39d51ce6c7bccfc1 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Sat, 15 Nov 2025 10:43:04 +0200 Subject: [PATCH 09/16] Fix geoid separation to use new PositionKit ref --- app/qml/gps/MMGpsDataDrawer.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/qml/gps/MMGpsDataDrawer.qml b/app/qml/gps/MMGpsDataDrawer.qml index 915c0d2f9..a6277ddb6 100644 --- a/app/qml/gps/MMGpsDataDrawer.qml +++ b/app/qml/gps/MMGpsDataDrawer.qml @@ -323,10 +323,10 @@ MMComponents.MMDrawer { title: qsTr( "Geoid separation" ) value: { - if ( !__positionKit.hasPosition || Number.isNaN( __positionKit.geoidSeparation ) ) { + if ( !PositionKit.hasPosition || Number.isNaN( PositionKit.geoidSeparation ) ) { return qsTr( "N/A" ) } - __inputUtils.formatNumber( __positionKit.geoidSeparation, 2 ) + " m" + __inputUtils.formatNumber( PositionKit.geoidSeparation, 2 ) + " m" } alignmentRight: Positioner.index % 2 === 1 From 517fd1575cbabf4d56ea53836fc09f82492b44c3 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Sat, 15 Nov 2025 13:15:30 +0200 Subject: [PATCH 10/16] Add geoid undulation for internal, fused, simulated providers --- .../providers/androidpositionprovider.cpp | 3 +++ .../providers/internalpositionprovider.cpp | 7 ++++++ .../providers/simulatedpositionprovider.cpp | 23 +++++++++++++++---- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/app/position/providers/androidpositionprovider.cpp b/app/position/providers/androidpositionprovider.cpp index 8145d9ba7..b2f5ce51e 100644 --- a/app/position/providers/androidpositionprovider.cpp +++ b/app/position/providers/androidpositionprovider.cpp @@ -63,6 +63,9 @@ void jniOnPositionUpdated( JNIEnv *env, jclass clazz, jint instanceId, jobject l QgsProject::instance()->transformContext(), {longitude, latitude, value} ); pos.elevation = geoidPosition.z(); + + const double geoidSeparation = value - geoidPosition.z(); + pos.elevation_diff = geoidSeparation; } } diff --git a/app/position/providers/internalpositionprovider.cpp b/app/position/providers/internalpositionprovider.cpp index b5e041a62..71f3979bf 100644 --- a/app/position/providers/internalpositionprovider.cpp +++ b/app/position/providers/internalpositionprovider.cpp @@ -153,6 +153,13 @@ void InternalPositionProvider::parsePositionUpdate( const QGeoPositionInfo &posi positionDataHasChanged = true; } + const double geoidSeparation = position.coordinate().altitude() - geoidPosition.z(); + if ( !qgsDoubleNear( geoidSeparation, mLastPosition.elevation_diff ) ) + { + mLastPosition.elevation_diff = geoidSeparation; + positionDataHasChanged = true; + } + bool hasSpeedInfo = position.hasAttribute( QGeoPositionInfo::GroundSpeed ); if ( hasSpeedInfo && !qgsDoubleNear( position.attribute( QGeoPositionInfo::GroundSpeed ), mLastPosition.speed ) ) { diff --git a/app/position/providers/simulatedpositionprovider.cpp b/app/position/providers/simulatedpositionprovider.cpp index 75287127d..c10409f47 100644 --- a/app/position/providers/simulatedpositionprovider.cpp +++ b/app/position/providers/simulatedpositionprovider.cpp @@ -8,6 +8,8 @@ ***************************************************************************/ #include "simulatedpositionprovider.h" + +#include "inpututils.h" #include "qgspoint.h" SimulatedPositionProvider::SimulatedPositionProvider( double longitude, double latitude, double flightRadius, double timerTimeout, QObject *parent ) @@ -84,10 +86,16 @@ void SimulatedPositionProvider::generateRadiusPosition() position.latitude = latitude; position.longitude = longitude; - double altitude = ( *mGenerator )() % 40 + 20; // rand altitude <20,55>m and lost (0) - if ( altitude <= 55 ) + double altitude = ( *mGenerator )() % 40 + 80; // rand altitude <80,120>m and lost (0) + if ( altitude <= 120 ) { - position.elevation = altitude; + const QgsPoint geoidPosition = InputUtils::transformPoint3D( + QgsCoordinateReferenceSystem::fromEpsgId( 4979 ), + PositionKit::positionCRS(), + QgsCoordinateTransformContext(), + {longitude, latitude, altitude} ); + position.elevation = geoidPosition.z(); + position.elevation_diff = altitude - position.elevation; } QDateTime timestamp = QDateTime::currentDateTime(); @@ -115,7 +123,14 @@ void SimulatedPositionProvider::generateConstantPosition() GeoPosition position; position.latitude = mLatitude; position.longitude = mLongitude; - position.elevation = 20; + // we take 100 as elevation returned by WGS84 ellipsoid and recalculate it to geoid + const QgsPoint geoidPosition = InputUtils::transformPoint3D( + QgsCoordinateReferenceSystem::fromEpsgId( 4979 ), + PositionKit::positionCRS(), + QgsCoordinateTransformContext(), + {mLongitude, mLatitude, 100} ); + position.elevation = geoidPosition.z(); + position.elevation_diff = 100 - position.elevation; position.utcDateTime = QDateTime::currentDateTime(); position.direction = 360 - int( mAngle ) % 360; position.hacc = ( *mGenerator )() % 20; From f08783cb6f37453ac91298d7aacb091ebbf50e83 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Sat, 15 Nov 2025 13:26:56 +0200 Subject: [PATCH 11/16] Refactor simulated provider --- .../providers/simulatedpositionprovider.cpp | 26 ++++++++++--------- .../providers/simulatedpositionprovider.h | 12 ++++----- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/app/position/providers/simulatedpositionprovider.cpp b/app/position/providers/simulatedpositionprovider.cpp index c10409f47..2b5863cb0 100644 --- a/app/position/providers/simulatedpositionprovider.cpp +++ b/app/position/providers/simulatedpositionprovider.cpp @@ -9,26 +9,28 @@ #include "simulatedpositionprovider.h" +#include + #include "inpututils.h" #include "qgspoint.h" -SimulatedPositionProvider::SimulatedPositionProvider( double longitude, double latitude, double flightRadius, double timerTimeout, QObject *parent ) +SimulatedPositionProvider::SimulatedPositionProvider( const double longitude, const double latitude, const double flightRadius, const double updateTimeout, QObject *parent ) : AbstractPositionProvider( QStringLiteral( "simulated" ), QStringLiteral( "internal" ), QStringLiteral( "Simulated provider" ), parent ) , mTimer( new QTimer() ) , mLongitude( longitude ) , mLatitude( latitude ) , mFlightRadius( flightRadius ) - , mTimerTimeout( timerTimeout ) + , mTimerTimeout( updateTimeout ) { std::random_device seed; - mGenerator = std::unique_ptr( new std::mt19937( seed() ) ); + mGenerator = std::make_unique( seed() ); connect( mTimer.get(), &QTimer::timeout, this, &SimulatedPositionProvider::generateNextPosition ); SimulatedPositionProvider::startUpdates(); } -void SimulatedPositionProvider::setUpdateInterval( double msecs ) +void SimulatedPositionProvider::setUpdateInterval( const double msecs ) { stopUpdates(); mTimerTimeout = msecs; @@ -39,7 +41,7 @@ SimulatedPositionProvider::~SimulatedPositionProvider() = default; void SimulatedPositionProvider::startUpdates() { - mTimer->start( mTimerTimeout ); + mTimer->start( static_cast( mTimerTimeout ) ); generateNextPosition(); } @@ -53,7 +55,7 @@ void SimulatedPositionProvider::closeProvider() mTimer->stop(); } -void SimulatedPositionProvider::setPosition( QgsPoint position ) +void SimulatedPositionProvider::setPosition( const QgsPoint position ) { if ( position.isEmpty() ) return; @@ -90,18 +92,18 @@ void SimulatedPositionProvider::generateRadiusPosition() if ( altitude <= 120 ) { const QgsPoint geoidPosition = InputUtils::transformPoint3D( - QgsCoordinateReferenceSystem::fromEpsgId( 4979 ), - PositionKit::positionCRS(), - QgsCoordinateTransformContext(), + QgsCoordinateReferenceSystem::fromEpsgId( 4979 ), + PositionKit::positionCRS(), + QgsCoordinateTransformContext(), {longitude, latitude, altitude} ); position.elevation = geoidPosition.z(); position.elevation_diff = altitude - position.elevation; } - QDateTime timestamp = QDateTime::currentDateTime(); + const QDateTime timestamp = QDateTime::currentDateTime(); position.utcDateTime = timestamp; - position.direction = 360 - int( mAngle ) % 360; + position.direction = 360 - static_cast( mAngle ) % 360; int accuracy = ( *mGenerator )() % 40; // rand accuracy <0,35>m and lost (-1) if ( accuracy > 35 ) @@ -132,7 +134,7 @@ void SimulatedPositionProvider::generateConstantPosition() position.elevation = geoidPosition.z(); position.elevation_diff = 100 - position.elevation; position.utcDateTime = QDateTime::currentDateTime(); - position.direction = 360 - int( mAngle ) % 360; + position.direction = 360 - static_cast( mAngle ) % 360; position.hacc = ( *mGenerator )() % 20; position.satellitesUsed = ( *mGenerator )() % 30; position.satellitesVisible = ( *mGenerator )() % 30; diff --git a/app/position/providers/simulatedpositionprovider.h b/app/position/providers/simulatedpositionprovider.h index 61d947d82..33f9488a0 100644 --- a/app/position/providers/simulatedpositionprovider.h +++ b/app/position/providers/simulatedpositionprovider.h @@ -39,16 +39,16 @@ class SimulatedPositionProvider : public AbstractPositionProvider double updateTimeout = 1000, QObject *parent = nullptr ); - virtual ~SimulatedPositionProvider() override; + ~SimulatedPositionProvider() override; - virtual void setUpdateInterval( double msecs ) override; + void setUpdateInterval( double msecs ) override; public slots: - virtual void startUpdates() override; - virtual void stopUpdates() override; - virtual void closeProvider() override; + void startUpdates() override; + void stopUpdates() override; + void closeProvider() override; - virtual void setPosition( QgsPoint position ) override; + void setPosition( QgsPoint position ) override; void generateNextPosition(); From f9a38c8ef522c908f8083eb8d9235080905c9e6a Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Sat, 15 Nov 2025 13:51:57 +0200 Subject: [PATCH 12/16] Fix position test --- app/test/testposition.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/test/testposition.cpp b/app/test/testposition.cpp index 2ca3ee608..d51d2b933 100644 --- a/app/test/testposition.cpp +++ b/app/test/testposition.cpp @@ -396,8 +396,8 @@ void TestPosition::testPositionTracking() QSignalSpy trackingSpy( &manager, &PositionTrackingManager::trackedGeometryChanged ); trackingSpy.wait( 4000 ); // new position should be emited in 2k ms - - QVERIFY( manager.trackedGeometry().asWkt( 3 ).startsWith( QStringLiteral( "LineString ZM (-92.36 38.93 20" ) ) ); + qDebug() << manager.trackedGeometry().asWkt( 3 ); + QVERIFY( manager.trackedGeometry().asWkt( 3 ).startsWith( QStringLiteral( "LineString ZM (-92.36 38.93 133.331" ) ) ); // store the geometry QgsVectorLayer *trackingLayer = QgsProject::instance()->mapLayer( "tracking_layer_aad89df7_21db_466e_b5c1_a80160f74c01" ); @@ -411,7 +411,7 @@ void TestPosition::testPositionTracking() int addedFid = addedSpy.at( 1 ).at( 0 ).toInt(); QgsFeature f = trackingLayer->getFeature( addedFid ); - QVERIFY( f.geometry().asWkt( 3 ).startsWith( QStringLiteral( "LineString ZM (-92.36 38.93 20" ) ) ); + QVERIFY( f.geometry().asWkt( 3 ).startsWith( QStringLiteral( "LineString ZM (-92.36 38.93 133.331" ) ) ); QString datetimeFormat = QStringLiteral( "dd.MM.yyyy hh:mm:ss" ); QString dateTrackingStartedFromManager = manager.startTime().toString( datetimeFormat ); From 572bd423a5aee849ab142c40d2acdfdf7d4337d9 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Wed, 19 Nov 2025 16:38:31 +0200 Subject: [PATCH 13/16] Transform GNSS received elevation --- .../providers/bluetoothpositionprovider.cpp | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/app/position/providers/bluetoothpositionprovider.cpp b/app/position/providers/bluetoothpositionprovider.cpp index 678455c11..3af994b7a 100644 --- a/app/position/providers/bluetoothpositionprovider.cpp +++ b/app/position/providers/bluetoothpositionprovider.cpp @@ -204,11 +204,23 @@ void BluetoothPositionProvider::positionUpdateReceived() // we know the connection is working because we just received data from the device setState( tr( "Connected" ), State::Connected ); - QByteArray rawNmea = mSocket->readAll(); - QString nmea( rawNmea ); - - QgsGpsInformation data = mNmeaParser.parseNmeaString( nmea ); - - emit positionChanged( GeoPosition::fromQgsGpsInformation( data ) ); + const QByteArray rawNmea = mSocket->readAll(); + const QString nmea( rawNmea ); + + const QgsGpsInformation data = mNmeaParser.parseNmeaString( nmea ); + GeoPosition positionData = GeoPosition::fromQgsGpsInformation( data ); + + // The geoid models used in GNSS devices can be often times unreliable, thus we apply the transformations ourselves + // GNSS supplied orthometric elevation -> ellipsoid elevation -> orthometric elevation based on our model + const double ellipsoidElevation = positionData.elevation + positionData.elevation_diff; + const QgsPoint geoidPosition = InputUtils::transformPoint( + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D(), + QgsProject::instance()->transformContext(), + {positionData.longitude, positionData.latitude, ellipsoidElevation} ); + positionData.elevation = geoidPosition.z(); + positionData.elevation_diff = ellipsoidElevation - geoidPosition.z(); + + emit positionChanged( positionData ); } } From 70c7f05a71c201f5a8780a9c5e3b4f7cb606b166 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Thu, 20 Nov 2025 11:07:09 +0200 Subject: [PATCH 14/16] Cleanup after rebase --- app/appsettings.cpp | 3 +- app/inpututils.cpp | 35 ------------------- app/inpututils.h | 10 ------ app/position/positionkit.cpp | 2 +- .../providers/androidpositionprovider.cpp | 2 +- .../providers/simulatedpositionprovider.cpp | 12 +++---- app/test/testposition.cpp | 1 - 7 files changed, 9 insertions(+), 56 deletions(-) diff --git a/app/appsettings.cpp b/app/appsettings.cpp index 96891d6a0..58e340ec1 100644 --- a/app/appsettings.cpp +++ b/app/appsettings.cpp @@ -10,7 +10,6 @@ #include "appsettings.h" #include "coreutils.h" -#include #include #include @@ -199,7 +198,7 @@ void AppSettings::setActivePositionProviderId( const QString &id ) return; mActivePositionProviderId = id; - setValue( QStringLiteral( "activePositionProviderId" ), mActivePositionProviderId ); + setValue( QStringLiteral( "activePositionProviderId" ), id ); emit activePositionProviderIdChanged( mActivePositionProviderId ); } diff --git a/app/inpututils.cpp b/app/inpututils.cpp index bc165886c..e0c8bf99b 100644 --- a/app/inpututils.cpp +++ b/app/inpututils.cpp @@ -905,41 +905,6 @@ QgsPoint InputUtils::transformPoint( const QgsCoordinateReferenceSystem &srcCrs, return {}; } -QgsPoint InputUtils::transformPoint3D( const QgsCoordinateReferenceSystem &srcCrs, - const QgsCoordinateReferenceSystem &destCrs, - const QgsCoordinateTransformContext &context, - const QgsPoint &srcPoint ) -{ - // we do not want to transform empty points, - // QGIS would convert them to a valid (0, 0) points - if ( srcPoint.isEmpty() ) - { - return {}; - } - - try - { - const QgsCoordinateTransform ct( srcCrs, destCrs, context ); - if ( ct.isValid() ) - { - if ( !ct.isShortCircuited() ) - { - const QgsVector3D transformed = ct.transform( QgsVector3D( srcPoint.x(), srcPoint.y(), srcPoint.z() ) ); - const QgsPoint pt( transformed.x(), transformed.y(), transformed.z(), srcPoint.m() ); - return pt; - } - - return srcPoint; - } - } - catch ( QgsCsException &cse ) - { - Q_UNUSED( cse ) - } - - return {}; -} - QPointF InputUtils::transformPointToScreenCoordinates( const QgsCoordinateReferenceSystem &srcCrs, InputMapSettings *mapSettings, const QgsPoint &srcPoint ) { if ( !mapSettings || srcPoint.isEmpty() ) diff --git a/app/inpututils.h b/app/inpututils.h index b6de55b56..545b358d0 100644 --- a/app/inpututils.h +++ b/app/inpututils.h @@ -299,22 +299,12 @@ class InputUtils: public QObject /** * Transforms point between different crs * Return empty QgsPoint if the transformation could not be applied or srcPoint is empty - * \note This function only does 2D crs transformation, use \a transformPoint3D for 3D transformation! */ Q_INVOKABLE static QgsPoint transformPoint( const QgsCoordinateReferenceSystem &srcCrs, const QgsCoordinateReferenceSystem &destCrs, const QgsCoordinateTransformContext &context, const QgsPoint &srcPoint ); - /** - * Transforms point between different crs - * Return empty QgsPoint if the transformation could not be applied or srcPoint is empty - */ - Q_INVOKABLE static QgsPoint transformPoint3D( const QgsCoordinateReferenceSystem &srcCrs, - const QgsCoordinateReferenceSystem &destCrs, - const QgsCoordinateTransformContext &context, - const QgsPoint &srcPoint ); - /** * Transforms point between CRS and screen pixels * Return empty QgsPoint if the transformation could not be applied or srcPoint is empty diff --git a/app/position/positionkit.cpp b/app/position/positionkit.cpp index 229c2a4f4..191377aa3 100644 --- a/app/position/positionkit.cpp +++ b/app/position/positionkit.cpp @@ -40,7 +40,7 @@ QgsCoordinateReferenceSystem PositionKit::positionCrs3D() QgsCoordinateReferenceSystem PositionKit::positionCrs2D() { - return QgsCoordinateReferenceSystem::fromEpsgId( 9707 ); + return QgsCoordinateReferenceSystem::fromEpsgId( 4326 ); } QgsCoordinateReferenceSystem PositionKit::positionCrs3DEllipsoidHeight() diff --git a/app/position/providers/androidpositionprovider.cpp b/app/position/providers/androidpositionprovider.cpp index b2f5ce51e..fec18362f 100644 --- a/app/position/providers/androidpositionprovider.cpp +++ b/app/position/providers/androidpositionprovider.cpp @@ -57,7 +57,7 @@ void jniOnPositionUpdated( JNIEnv *env, jclass clazz, jint instanceId, jobject l if ( !qFuzzyIsNull( value ) ) { // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to specified geoid model - const QgsPoint geoidPosition = InputUtils::transformPoint3D( + const QgsPoint geoidPosition = InputUtils::transformPoint( PositionKit::positionCrs3DEllipsoidHeight(), PositionKit::positionCrs3D(), QgsProject::instance()->transformContext(), diff --git a/app/position/providers/simulatedpositionprovider.cpp b/app/position/providers/simulatedpositionprovider.cpp index 2b5863cb0..793572613 100644 --- a/app/position/providers/simulatedpositionprovider.cpp +++ b/app/position/providers/simulatedpositionprovider.cpp @@ -91,9 +91,9 @@ void SimulatedPositionProvider::generateRadiusPosition() double altitude = ( *mGenerator )() % 40 + 80; // rand altitude <80,120>m and lost (0) if ( altitude <= 120 ) { - const QgsPoint geoidPosition = InputUtils::transformPoint3D( - QgsCoordinateReferenceSystem::fromEpsgId( 4979 ), - PositionKit::positionCRS(), + const QgsPoint geoidPosition = InputUtils::transformPoint( + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D(), QgsCoordinateTransformContext(), {longitude, latitude, altitude} ); position.elevation = geoidPosition.z(); @@ -126,9 +126,9 @@ void SimulatedPositionProvider::generateConstantPosition() position.latitude = mLatitude; position.longitude = mLongitude; // we take 100 as elevation returned by WGS84 ellipsoid and recalculate it to geoid - const QgsPoint geoidPosition = InputUtils::transformPoint3D( - QgsCoordinateReferenceSystem::fromEpsgId( 4979 ), - PositionKit::positionCRS(), + const QgsPoint geoidPosition = InputUtils::transformPoint( + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D(), QgsCoordinateTransformContext(), {mLongitude, mLatitude, 100} ); position.elevation = geoidPosition.z(); diff --git a/app/test/testposition.cpp b/app/test/testposition.cpp index d51d2b933..c8ea628fb 100644 --- a/app/test/testposition.cpp +++ b/app/test/testposition.cpp @@ -396,7 +396,6 @@ void TestPosition::testPositionTracking() QSignalSpy trackingSpy( &manager, &PositionTrackingManager::trackedGeometryChanged ); trackingSpy.wait( 4000 ); // new position should be emited in 2k ms - qDebug() << manager.trackedGeometry().asWkt( 3 ); QVERIFY( manager.trackedGeometry().asWkt( 3 ).startsWith( QStringLiteral( "LineString ZM (-92.36 38.93 133.331" ) ) ); // store the geometry From 3e685488c279089c2f9752114b2e0573376701ca Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Thu, 20 Nov 2025 11:09:47 +0200 Subject: [PATCH 15/16] Format code --- app/position/providers/bluetoothpositionprovider.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/position/providers/bluetoothpositionprovider.cpp b/app/position/providers/bluetoothpositionprovider.cpp index 3af994b7a..7b5374b69 100644 --- a/app/position/providers/bluetoothpositionprovider.cpp +++ b/app/position/providers/bluetoothpositionprovider.cpp @@ -214,10 +214,10 @@ void BluetoothPositionProvider::positionUpdateReceived() // GNSS supplied orthometric elevation -> ellipsoid elevation -> orthometric elevation based on our model const double ellipsoidElevation = positionData.elevation + positionData.elevation_diff; const QgsPoint geoidPosition = InputUtils::transformPoint( - PositionKit::positionCrs3DEllipsoidHeight(), - PositionKit::positionCrs3D(), - QgsProject::instance()->transformContext(), - {positionData.longitude, positionData.latitude, ellipsoidElevation} ); + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D(), + QgsProject::instance()->transformContext(), + {positionData.longitude, positionData.latitude, ellipsoidElevation} ); positionData.elevation = geoidPosition.z(); positionData.elevation_diff = ellipsoidElevation - geoidPosition.z(); From 0984bbfd9aceb19e2c385638f81046d5d46f63f6 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Thu, 20 Nov 2025 16:04:56 +0200 Subject: [PATCH 16/16] Fix review issues --- app/position/providers/androidpositionprovider.cpp | 8 ++++---- .../providers/internalpositionprovider.cpp | 9 ++++++++- .../providers/simulatedpositionprovider.cpp | 14 ++++++++++---- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/app/position/providers/androidpositionprovider.cpp b/app/position/providers/androidpositionprovider.cpp index fec18362f..c6e25793c 100644 --- a/app/position/providers/androidpositionprovider.cpp +++ b/app/position/providers/androidpositionprovider.cpp @@ -53,18 +53,18 @@ void jniOnPositionUpdated( JNIEnv *env, jclass clazz, jint instanceId, jobject l if ( location.callMethod( "hasAltitude" ) ) { - const jdouble value = location.callMethod( "getAltitude" ); - if ( !qFuzzyIsNull( value ) ) + const jdouble ellipsoidHeight = location.callMethod( "getAltitude" ); + if ( !qFuzzyIsNull( ellipsoidHeight ) ) { // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to specified geoid model const QgsPoint geoidPosition = InputUtils::transformPoint( PositionKit::positionCrs3DEllipsoidHeight(), PositionKit::positionCrs3D(), QgsProject::instance()->transformContext(), - {longitude, latitude, value} ); + {longitude, latitude, ellipsoidHeight} ); pos.elevation = geoidPosition.z(); - const double geoidSeparation = value - geoidPosition.z(); + const double geoidSeparation = ellipsoidHeight - geoidPosition.z(); pos.elevation_diff = geoidSeparation; } } diff --git a/app/position/providers/internalpositionprovider.cpp b/app/position/providers/internalpositionprovider.cpp index 71f3979bf..04a058297 100644 --- a/app/position/providers/internalpositionprovider.cpp +++ b/app/position/providers/internalpositionprovider.cpp @@ -153,7 +153,14 @@ void InternalPositionProvider::parsePositionUpdate( const QGeoPositionInfo &posi positionDataHasChanged = true; } - const double geoidSeparation = position.coordinate().altitude() - geoidPosition.z(); + // QGeoCoordinate::altitude() docs claim that it is above the sea level (i.e. geoid) altitude, + // but that's not really true in our case: + // - on Android - it is MSL altitude only if "useMslAltitude" parameter is passed to the Android + // Qt positioning plugin, which we don't do - see https://doc.qt.io/qt-6/position-plugin-android.html + // - on iOS - it would return MSL altitude, but we have a custom patch in vcpkg to return + // ellipsoid altitude (so we do not rely on geoid model of unknown quality/resolution) + const double ellipsoidAltitude = position.coordinate().altitude(); + const double geoidSeparation = ellipsoidAltitude - geoidPosition.z(); if ( !qgsDoubleNear( geoidSeparation, mLastPosition.elevation_diff ) ) { mLastPosition.elevation_diff = geoidSeparation; diff --git a/app/position/providers/simulatedpositionprovider.cpp b/app/position/providers/simulatedpositionprovider.cpp index 793572613..762bbc416 100644 --- a/app/position/providers/simulatedpositionprovider.cpp +++ b/app/position/providers/simulatedpositionprovider.cpp @@ -10,6 +10,7 @@ #include "simulatedpositionprovider.h" #include +#include #include "inpututils.h" #include "qgspoint.h" @@ -88,16 +89,21 @@ void SimulatedPositionProvider::generateRadiusPosition() position.latitude = latitude; position.longitude = longitude; - double altitude = ( *mGenerator )() % 40 + 80; // rand altitude <80,120>m and lost (0) - if ( altitude <= 120 ) + double ellipsoidAltitude = ( *mGenerator )() % 40 + 80; // rand altitude <80,115>m and lost (NaN) + if ( ellipsoidAltitude <= 115 ) { const QgsPoint geoidPosition = InputUtils::transformPoint( PositionKit::positionCrs3DEllipsoidHeight(), PositionKit::positionCrs3D(), QgsCoordinateTransformContext(), - {longitude, latitude, altitude} ); + {longitude, latitude, ellipsoidAltitude} ); position.elevation = geoidPosition.z(); - position.elevation_diff = altitude - position.elevation; + position.elevation_diff = ellipsoidAltitude - position.elevation; + } + else + { + position.elevation = qQNaN(); + position.elevation_diff = qQNaN(); } const QDateTime timestamp = QDateTime::currentDateTime();