diff --git a/app/position/providers/androidpositionprovider.cpp b/app/position/providers/androidpositionprovider.cpp index 77193bcb4..c6e25793c 100644 --- a/app/position/providers/androidpositionprovider.cpp +++ b/app/position/providers/androidpositionprovider.cpp @@ -53,16 +53,19 @@ 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 = ellipsoidHeight - geoidPosition.z(); + pos.elevation_diff = geoidSeparation; } } diff --git a/app/position/providers/bluetoothpositionprovider.cpp b/app/position/providers/bluetoothpositionprovider.cpp index 678455c11..7b5374b69 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 ); } } diff --git a/app/position/providers/internalpositionprovider.cpp b/app/position/providers/internalpositionprovider.cpp index b5e041a62..04a058297 100644 --- a/app/position/providers/internalpositionprovider.cpp +++ b/app/position/providers/internalpositionprovider.cpp @@ -153,6 +153,20 @@ void InternalPositionProvider::parsePositionUpdate( const QGeoPositionInfo &posi positionDataHasChanged = true; } + // 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; + 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..762bbc416 100644 --- a/app/position/providers/simulatedpositionprovider.cpp +++ b/app/position/providers/simulatedpositionprovider.cpp @@ -8,25 +8,30 @@ ***************************************************************************/ #include "simulatedpositionprovider.h" + +#include +#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; @@ -37,7 +42,7 @@ SimulatedPositionProvider::~SimulatedPositionProvider() = default; void SimulatedPositionProvider::startUpdates() { - mTimer->start( mTimerTimeout ); + mTimer->start( static_cast( mTimerTimeout ) ); generateNextPosition(); } @@ -51,7 +56,7 @@ void SimulatedPositionProvider::closeProvider() mTimer->stop(); } -void SimulatedPositionProvider::setPosition( QgsPoint position ) +void SimulatedPositionProvider::setPosition( const QgsPoint position ) { if ( position.isEmpty() ) return; @@ -84,16 +89,27 @@ 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 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, ellipsoidAltitude} ); + position.elevation = geoidPosition.z(); + position.elevation_diff = ellipsoidAltitude - position.elevation; + } + else { - position.elevation = altitude; + position.elevation = qQNaN(); + position.elevation_diff = qQNaN(); } - 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 ) @@ -115,9 +131,16 @@ 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::transformPoint( + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D(), + 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.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(); 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 diff --git a/app/test/testposition.cpp b/app/test/testposition.cpp index 2ca3ee608..c8ea628fb 100644 --- a/app/test/testposition.cpp +++ b/app/test/testposition.cpp @@ -396,8 +396,7 @@ 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" ) ) ); + 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 +410,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 );