diff --git a/CMakeLists.txt b/CMakeLists.txt index 652a7a031..9e5b24a3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -389,6 +389,9 @@ file( PATTERN "*.cmake" EXCLUDE PATTERN "vcpkg*" EXCLUDE ) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/app/assets/us_nga_egm96_15.tif + DESTINATION ${ASSETS_DIR_PATH}/app/android/assets/qgis-data/proj +) message(STATUS "QGIS_QUICK_DATA_PATH set to ${QGIS_QUICK_DATA_PATH}") # ######################################################################################## diff --git a/app/assets/us_nga_egm96_15.tif b/app/assets/us_nga_egm96_15.tif new file mode 100644 index 000000000..94a9f967e Binary files /dev/null and b/app/assets/us_nga_egm96_15.tif differ diff --git a/app/inpututils.cpp b/app/inpututils.cpp index 47b37b021..72299d198 100644 --- a/app/inpututils.cpp +++ b/app/inpututils.cpp @@ -477,7 +477,7 @@ QString InputUtils::geometryLengthAsString( const QgsGeometry &geometry ) { QgsDistanceArea distanceArea; distanceArea.setEllipsoid( QStringLiteral( "WGS84" ) ); - distanceArea.setSourceCrs( QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), QgsCoordinateTransformContext() ); + distanceArea.setSourceCrs( PositionKit::positionCrs2D(), QgsProject::instance()->transformContext() ); qreal length = distanceArea.measureLength( geometry ); @@ -880,24 +880,58 @@ QgsPoint InputUtils::transformPoint( const QgsCoordinateReferenceSystem &srcCrs, // QGIS would convert them to a valid (0, 0) points if ( srcPoint.isEmpty() ) { - return QgsPoint(); + return {}; } try { - QgsCoordinateTransform ct( srcCrs, destCrs, context ); + const QgsCoordinateTransform ct( srcCrs, destCrs, context ); if ( ct.isValid() ) { if ( !ct.isShortCircuited() ) { - const QgsPointXY transformed = ct.transform( srcPoint.x(), srcPoint.y() ); - const QgsPoint pt( transformed.x(), transformed.y(), srcPoint.z(), srcPoint.m() ); + 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; } - else + + return srcPoint; + } + } + catch ( QgsCsException &cse ) + { + Q_UNUSED( cse ) + } + + return {}; +} + +QgsPoint InputUtils::transformPoint( const QgsCoordinateReferenceSystem &srcCrs, + const QgsCoordinateReferenceSystem &destCrs, + const QgsCoordinateTransformContext &context, + const QgsPoint &srcPoint, bool &fallbackOperationOccurred ) +{ + // 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() ) { - return srcPoint; + const QgsVector3D transformed = ct.transform( QgsVector3D( srcPoint.x(), srcPoint.y(), srcPoint.z() ) ); + fallbackOperationOccurred = ct.fallbackOperationOccurred(); + const QgsPoint pt( transformed.x(), transformed.y(), transformed.z(), srcPoint.m() ); + return pt; } + + return srcPoint; } } catch ( QgsCsException &cse ) @@ -905,7 +939,7 @@ QgsPoint InputUtils::transformPoint( const QgsCoordinateReferenceSystem &srcCrs, Q_UNUSED( cse ) } - return QgsPoint(); + return {}; } QPointF InputUtils::transformPointToScreenCoordinates( const QgsCoordinateReferenceSystem &srcCrs, InputMapSettings *mapSettings, const QgsPoint &srcPoint ) @@ -954,12 +988,11 @@ QgsPoint InputUtils::mapPointToGps( QPointF mapPosition, InputMapSettings *mapSe return QgsPoint(); QgsPoint positionMapCrs = mapSettings->screenToCoordinate( mapPosition ); - QgsCoordinateReferenceSystem crsGPS = coordinateReferenceSystemFromEpsgId( 4326 ); const QgsPointXY transformedXY = transformPoint( mapSettings->destinationCrs(), - crsGPS, - QgsCoordinateTransformContext(), + PositionKit::positionCrs2D(), + mapSettings->transformContext(), positionMapCrs ); @@ -1716,7 +1749,7 @@ qreal InputUtils::distanceBetweenGpsAndFeature( QgsPoint gpsPosition, const Feat // Transform gps position to map CRS QgsPointXY transformedPosition = transformPoint( - coordinateReferenceSystemFromEpsgId( 4326 ), + PositionKit::positionCrs3D(), mapSettings->destinationCrs(), mapSettings->transformContext(), gpsPosition @@ -1764,7 +1797,7 @@ qreal InputUtils::angleBetweenGpsAndFeature( QgsPoint gpsPoint, const FeatureLay // Transform gps position to map CRS QgsPointXY transformedPosition = transformPoint( - coordinateReferenceSystemFromEpsgId( 4326 ), + PositionKit::positionCrs3D(), mapSettings->destinationCrs(), mapSettings->transformContext(), gpsPoint diff --git a/app/inpututils.h b/app/inpututils.h index 00791f0f1..f9406ca2c 100644 --- a/app/inpututils.h +++ b/app/inpututils.h @@ -305,6 +305,14 @@ class InputUtils: public QObject const QgsCoordinateTransformContext &context, const QgsPoint &srcPoint ); + /** + * Overload of transformPoint function, which also notifies the caller if PROJ fallback operation occurred + */ + static QgsPoint transformPoint( const QgsCoordinateReferenceSystem &srcCrs, + const QgsCoordinateReferenceSystem &destCrs, + const QgsCoordinateTransformContext &context, + const QgsPoint &srcPoint, bool &fallbackOperationOccurred ); + /** * 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 e5bda4ca6..d2ca14913 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -544,13 +544,13 @@ int main( int argc, char *argv[] ) LayerDetailLegendImageProvider *layerDetailLegendImageProvider( new LayerDetailLegendImageProvider ); // build position kit, save active provider to QSettings and load previously active provider - PositionKit pk; - QObject::connect( &pk, &PositionKit::positionProviderChanged, as, [as]( AbstractPositionProvider * provider ) + PositionKit *pk = engine.singletonInstance( "MMInput", "PositionKit" ); + QObject::connect( pk, &PositionKit::positionProviderChanged, as, [as]( AbstractPositionProvider * provider ) { as->setActivePositionProviderId( provider ? provider->id() : QLatin1String() ); } ); - pk.setPositionProvider( pk.constructActiveProvider( as ) ); - pk.setAppSettings( as ); + pk->setPositionProvider( PositionKit::constructActiveProvider( as ) ); + pk->setAppSettings( as ); // Lambda context object can be used in all lambda functions defined here, // it secures lambdas, so that they are destroyed when this object is destroyed to avoid crashes. @@ -658,7 +658,7 @@ int main( int argc, char *argv[] ) notificationModel.addError( message ); } ); // Direct connections - QObject::connect( &app, &QGuiApplication::applicationStateChanged, &pk, &PositionKit::appStateChanged ); + QObject::connect( &app, &QGuiApplication::applicationStateChanged, pk, &PositionKit::appStateChanged ); QObject::connect( &app, &QGuiApplication::applicationStateChanged, &activeProject, &ActiveProject::appStateChanged ); QObject::connect( &pw, &ProjectWizard::projectCreated, &localProjectsManager, &LocalProjectsManager::addLocalProject ); QObject::connect( &activeProject, &ActiveProject::projectReloaded, vm.get(), &VariablesManager::merginProjectChanged ); @@ -690,7 +690,7 @@ int main( int argc, char *argv[] ) if ( tests.testingRequested() ) { tests.initTestDeclarative(); - tests.init( ma.get(), &iu, vm.get(), &pk, as ); + tests.init( ma.get(), &iu, vm.get(), pk, as ); return tests.runTest(); } #endif @@ -733,7 +733,6 @@ int main( int argc, char *argv[] ) engine.rootContext()->setContextProperty( "__projectWizard", &pw ); engine.rootContext()->setContextProperty( "__localProjectsManager", &localProjectsManager ); engine.rootContext()->setContextProperty( "__variablesManager", vm.get() ); - engine.rootContext()->setContextProperty( "__positionKit", &pk ); // add image provider to pass QIcons/QImages from C++ to QML engine.rootContext()->setContextProperty( "__layerTreeModelPixmapProvider", layerTreeModelPixmapProvider ); diff --git a/app/map/inputcoordinatetransformer.cpp b/app/map/inputcoordinatetransformer.cpp index 3caf41e10..1d45a356e 100644 --- a/app/map/inputcoordinatetransformer.cpp +++ b/app/map/inputcoordinatetransformer.cpp @@ -13,12 +13,14 @@ */ #include "inputcoordinatetransformer.h" + +#include "positionkit.h" #include "qgslogger.h" InputCoordinateTransformer::InputCoordinateTransformer( QObject *parent ) : QObject( parent ) { - mCoordinateTransform.setSourceCrs( QgsCoordinateReferenceSystem::fromEpsgId( 4326 ) ); + mCoordinateTransform.setSourceCrs( PositionKit::positionCrs3D() ); } QgsPoint InputCoordinateTransformer::projectedPosition() const diff --git a/app/maptools/recordingmaptool.cpp b/app/maptools/recordingmaptool.cpp index 91402114b..ab2ee44eb 100644 --- a/app/maptools/recordingmaptool.cpp +++ b/app/maptools/recordingmaptool.cpp @@ -59,7 +59,7 @@ void RecordingMapTool::addPoint( const QgsPoint &point ) pointToAdd = mPositionKit->positionCoordinate(); QgsPoint transformed = InputUtils::transformPoint( - PositionKit::positionCRS(), + PositionKit::positionCrs3D(), mActiveLayer->crs(), mActiveLayer->transformContext(), pointToAdd @@ -594,7 +594,7 @@ void RecordingMapTool::onPositionChanged() QgsPoint position = mPositionKit->positionCoordinate(); QgsPointXY transformed = InputUtils::transformPoint( - PositionKit::positionCRS(), + PositionKit::positionCrs3D(), mActiveLayer->sourceCrs(), mActiveLayer->transformContext(), position diff --git a/app/position/geoposition.cpp b/app/position/geoposition.cpp index 92ae15728..c955cb669 100644 --- a/app/position/geoposition.cpp +++ b/app/position/geoposition.cpp @@ -15,6 +15,7 @@ GeoPosition::GeoPosition() : QgsGpsInformation() latitude = std::numeric_limits::quiet_NaN(); longitude = std::numeric_limits::quiet_NaN(); elevation = std::numeric_limits::quiet_NaN(); + elevation_diff = std::numeric_limits::quiet_NaN(); direction = -1; speed = -1; pdop = -1; @@ -48,6 +49,11 @@ GeoPosition GeoPosition::fromQgsGpsInformation( const QgsGpsInformation &other ) out.elevation = other.elevation; } + if ( !qgsDoubleNear( other.elevation_diff, 0 ) ) + { + out.elevation_diff = other.elevation_diff; + } + if ( !std::isnan( other.direction ) ) { out.direction = other.direction; @@ -88,7 +94,6 @@ GeoPosition GeoPosition::fromQgsGpsInformation( const QgsGpsInformation &other ) out.vdop = other.vdop; } - out.elevation_diff = other.elevation_diff; out.satellitesVisible = other.satellitesInView.count(); out.satellitesInView = other.satellitesInView; out.satellitesUsed = other.satellitesUsed; diff --git a/app/position/geoposition.h b/app/position/geoposition.h index 1d673b2b6..5dd413c8b 100644 --- a/app/position/geoposition.h +++ b/app/position/geoposition.h @@ -29,6 +29,8 @@ class GeoPosition : public QgsGpsInformation QString fixStatusString; + bool isMock = false; + // copies all data from QgsGpsInformation other and updates satellitesVisible static GeoPosition fromQgsGpsInformation( const QgsGpsInformation &other ); diff --git a/app/position/mapposition.cpp b/app/position/mapposition.cpp index 6659981c4..be6e18177 100644 --- a/app/position/mapposition.cpp +++ b/app/position/mapposition.cpp @@ -105,7 +105,7 @@ void MapPosition::recalculateMapPosition() { QgsPointXY srcPoint = QgsPointXY( geoposition.x(), geoposition.y() ); QgsPointXY mapPositionXY = InputUtils::transformPointXY( - mPositionKit->positionCRS(), + mPositionKit->positionCrs3D(), mMapSettings->destinationCrs(), mMapSettings->transformContext(), srcPoint diff --git a/app/position/positionkit.cpp b/app/position/positionkit.cpp index 92e3ddae0..57967a80a 100644 --- a/app/position/positionkit.cpp +++ b/app/position/positionkit.cpp @@ -33,11 +33,57 @@ PositionKit::PositionKit( QObject *parent ) { } -QgsCoordinateReferenceSystem PositionKit::positionCRS() +QgsCoordinateReferenceSystem PositionKit::positionCrs3D( const bool forceDefault ) +{ + if ( !forceDefault ) + { + bool crsExists = false; + const QString crsWktDef = QgsProject::instance()->readEntry( QStringLiteral( "Mergin" ), QStringLiteral( "TargetVerticalCRS" ), QString(), &crsExists ); + if ( crsExists ) + { + const QgsCoordinateReferenceSystem verticalCrs = QgsCoordinateReferenceSystem::fromWkt( crsWktDef ); + QString compoundCrsError{}; + const QgsCoordinateReferenceSystem compoundCrs = QgsCoordinateReferenceSystem::createCompoundCrs( positionCrs2D(), verticalCrs, compoundCrsError ); + if ( compoundCrs.isValid() && compoundCrsError.isEmpty() ) + { + return compoundCrs; + } + CoreUtils::log( QStringLiteral( "PositionKit" ), QStringLiteral( "Failed to create custom compound crs: %1" ).arg( compoundCrsError ) ); + } + } + + return QgsCoordinateReferenceSystem::fromEpsgId( 9707 ); +} + +QString PositionKit::positionCrs3DGeoidModelName() const +{ + if ( !mPosition.isMock ) + { + const QgsCoordinateReferenceSystem crs = positionCrs3D( true ).verticalCrs(); + return crs.description(); + } + + bool valueRead = false; + const bool isVerticalCRSPassedThrough = QVariant( QgsProject::instance()->readEntry( QStringLiteral( "Mergin" ), QStringLiteral( "VerticalCRSPassThrough" ), QVariant( true ).toString(), &valueRead ) ).toBool(); + if ( valueRead && !isVerticalCRSPassedThrough ) + { + const QgsCoordinateReferenceSystem crs = positionCrs3D().verticalCrs(); + return crs.description(); + } + + return {}; +} + +QgsCoordinateReferenceSystem PositionKit::positionCrs2D() { return QgsCoordinateReferenceSystem::fromEpsgId( 4326 ); } +QgsCoordinateReferenceSystem PositionKit::positionCrs3DEllipsoidHeight() +{ + return QgsCoordinateReferenceSystem::fromEpsgId( 4979 ); +} + void PositionKit::startUpdates() { if ( mPositionProvider ) @@ -148,7 +194,11 @@ AbstractPositionProvider *PositionKit::constructActiveProvider( AppSettings *app { if ( InputUtils::isMobilePlatform() ) { +#ifdef ANDROID + return constructProvider( QStringLiteral( "internal" ), QStringLiteral( "android_fused" ) ); +#else return constructProvider( QStringLiteral( "internal" ), QStringLiteral( "devicegps" ) ); +#endif } else // desktop { @@ -205,9 +255,9 @@ void PositionKit::parsePositionUpdate( const GeoPosition &newPosition ) hasAnythingChanged = true; } - if ( !qgsDoubleNear( newPosition.elevation, mPosition.elevation ) ) + if ( !qgsDoubleNear( newPosition.elevation - antennaHeight(), mPosition.elevation ) ) { - mPosition.elevation = newPosition.elevation; + mPosition.elevation = newPosition.elevation - antennaHeight(); emit altitudeChanged( mPosition.elevation ); hasAnythingChanged = true; } @@ -323,6 +373,13 @@ void PositionKit::parsePositionUpdate( const GeoPosition &newPosition ) hasAnythingChanged = true; } + if ( newPosition.isMock != mPosition.isMock ) + { + mPosition.isMock = newPosition.isMock; + emit isMockPositionChanged( mPosition.isMock ); + hasAnythingChanged = true; + } + if ( hasAnythingChanged ) { emit positionChanged( mPosition ); @@ -449,6 +506,11 @@ const GeoPosition &PositionKit::position() const return mPosition; } +bool PositionKit::isMockPosition() const +{ + return mPosition.isMock; +} + AppSettings *PositionKit::appSettings() const { return mAppSettings; diff --git a/app/position/positionkit.h b/app/position/positionkit.h index b8a6859de..017ba5fa2 100644 --- a/app/position/positionkit.h +++ b/app/position/positionkit.h @@ -15,6 +15,7 @@ #include "qgspoint.h" #include "qgscoordinatereferencesystem.h" #include +#include class AppSettings; @@ -27,6 +28,8 @@ class AppSettings; class PositionKit : public QObject { Q_OBJECT + QML_ELEMENT + QML_SINGLETON Q_PROPERTY( double latitude READ latitude NOTIFY latitudeChanged ) Q_PROPERTY( double longitude READ longitude NOTIFY longitudeChanged ) @@ -72,6 +75,7 @@ class PositionKit : public QObject // Provider of position data Q_PROPERTY( AbstractPositionProvider *positionProvider READ positionProvider WRITE setPositionProvider NOTIFY positionProviderChanged ) + Q_PROPERTY( bool isMockPosition READ isMockPosition NOTIFY isMockPositionChanged ) Q_PROPERTY( AppSettings *appSettings READ appSettings WRITE setAppSettings NOTIFY appSettingsChanged ) Q_PROPERTY( double antennaHeight READ antennaHeight NOTIFY antennaHeightChanged ) @@ -107,6 +111,7 @@ class PositionKit : public QObject QString fix() const; const GeoPosition &position() const; + bool isMockPosition() const; AbstractPositionProvider *positionProvider() const; void setPositionProvider( AbstractPositionProvider *newPositionProvider ); @@ -115,8 +120,17 @@ class PositionKit : public QObject double vdop() const; double pdop() const; - // Coordinate reference system of position - WGS84 (constant) - Q_INVOKABLE static QgsCoordinateReferenceSystem positionCRS(); + // Coordinate reference system - WGS84 (EPSG:4326) + static QgsCoordinateReferenceSystem positionCrs2D(); + // Coordinate reference system - WGS84 + ellipsoid height (EPSG:4979) + static QgsCoordinateReferenceSystem positionCrs3DEllipsoidHeight(); + /** + * Coordinate reference system of position (WGS84 + geoid height) - can use custom geoid model + * \note by default we use egm96_15 model (EPSG:9707) + */ + static QgsCoordinateReferenceSystem positionCrs3D( bool forceDefault = false ); + // Returns the model name used for elevation transformations + Q_INVOKABLE QString positionCrs3DGeoidModelName() const; Q_INVOKABLE static AbstractPositionProvider *constructProvider( const QString &type, const QString &id, const QString &name = QString() ); Q_INVOKABLE static AbstractPositionProvider *constructActiveProvider( AppSettings *appsettings ); @@ -156,6 +170,7 @@ class PositionKit : public QObject void positionProviderChanged( AbstractPositionProvider *provider ); void positionChanged( const GeoPosition & ); + void isMockPositionChanged( bool ); void appSettingsChanged(); diff --git a/app/position/providers/androidpositionprovider.cpp b/app/position/providers/androidpositionprovider.cpp index 41d3ef4bf..24d324176 100644 --- a/app/position/providers/androidpositionprovider.cpp +++ b/app/position/providers/androidpositionprovider.cpp @@ -19,6 +19,8 @@ #include +#include "inpututils.h" + int AndroidPositionProvider::sLastInstanceId = 0; QMap AndroidPositionProvider::sInstances; @@ -49,17 +51,71 @@ void jniOnPositionUpdated( JNIEnv *env, jclass clazz, jint instanceId, jobject l pos.longitude = longitude; pos.utcDateTime = QDateTime::fromMSecsSinceEpoch( timestamp, QTimeZone::UTC ); - if ( location.callMethod( "hasAltitude" ) ) + // detect if location is mocked (useful to check if 3rd party app is setting it for external GNSS receiver) + // we only use this to show users that the mock location is active + jboolean isMock = false; + if ( QtAndroidPrivate::androidSdkVersion() >= 31 ) + { + isMock = location.callMethod( "isMock" ); + } + else { - const jdouble value = location.callMethod( "getAltitude" ); - if ( !qFuzzyIsNull( value ) ) - pos.elevation = value; + isMock = location.callMethod( "isFromMockProvider" ); } + pos.isMock = isMock; - // TODO: we are getting ellipsoid elevation here. From API level 34 (Android 14), - // there is AltitudeConverter() class in Java that can be used to add MSL altitude - // to Location object. How to deal with this correctly? (we could also convert - // to MSL (orthometric) altitude ourselves if we add geoid model to our APK + if ( location.callMethod( "hasAltitude" ) ) + { + const jdouble altitude = location.callMethod( "getAltitude" ); + if ( !qFuzzyIsNull( altitude ) ) + { + bool positionOutsideGeoidModelArea = false; + if ( !isMock ) + { + // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to EPSG:9707 (WGS84 + EGM96) + const QgsPoint geoidPosition = InputUtils::transformPoint( + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D( true ), + QgsProject::instance()->transformContext(), + {longitude, latitude, altitude}, + positionOutsideGeoidModelArea ); + if ( !positionOutsideGeoidModelArea ) + { + pos.elevation = geoidPosition.z(); + + const double geoidSeparation = altitude - geoidPosition.z(); + pos.elevation_diff = geoidSeparation; + } + } + else + { + bool valueRead = false; + const bool isVerticalCRSPassedThrough = QVariant( QgsProject::instance()->readEntry( QStringLiteral( "Mergin" ), QStringLiteral( "VerticalCRSPassThrough" ), QVariant( true ).toString(), &valueRead ) ).toBool(); + if ( valueRead && !isVerticalCRSPassedThrough ) + { + // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to specified model + const QgsPoint geoidPosition = InputUtils::transformPoint( + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D(), + QgsProject::instance()->transformContext(), + {longitude, latitude, altitude}, + positionOutsideGeoidModelArea ); + if ( !positionOutsideGeoidModelArea ) + { + pos.elevation = geoidPosition.z(); + + const double geoidSeparation = altitude - geoidPosition.z(); + pos.elevation_diff = geoidSeparation; + } + } + else + { + pos.elevation = altitude; + } + } + + } + } // horizontal accuracy if ( location.callMethod( "hasAccuracy" ) ) @@ -100,9 +156,6 @@ void jniOnPositionUpdated( JNIEnv *env, jclass clazz, jint instanceId, jobject l // could also use getBearingAccuracyDegrees() since API level 26 (Android 8.0) } - // could also use isMock() to detect if location is mocked - // (may useful to check if 3rd party app is setting it for external GNSS receiver) - // could also use getExtras() to get further details from mocked location // (the key/value pairs are vendor-specific, and could include things like DOP, // info about corrections, geoid undulation, receiver model) diff --git a/app/position/providers/bluetoothpositionprovider.cpp b/app/position/providers/bluetoothpositionprovider.cpp index 678455c11..73d7b14eb 100644 --- a/app/position/providers/bluetoothpositionprovider.cpp +++ b/app/position/providers/bluetoothpositionprovider.cpp @@ -8,6 +8,9 @@ ***************************************************************************/ #include "bluetoothpositionprovider.h" + +#include + #include "coreutils.h" #include "androidutils.h" #include "inpututils.h" @@ -204,11 +207,33 @@ 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 ); + const QByteArray rawNmea = mSocket->readAll(); + const QString nmea( rawNmea ); - QgsGpsInformation data = mNmeaParser.parseNmeaString( nmea ); + const QgsGpsInformation data = mNmeaParser.parseNmeaString( nmea ); + GeoPosition positionData = GeoPosition::fromQgsGpsInformation( data ); - emit positionChanged( GeoPosition::fromQgsGpsInformation( data ) ); + bool valueRead = false; + const bool isVerticalCRSPassedThrough = QVariant( QgsProject::instance()->readEntry( QStringLiteral( "Mergin" ), QStringLiteral( "VerticalCRSPassThrough" ), QVariant( true ).toString(), &valueRead ) ).toBool(); + // if the user sets custom vertical crs we apply our transformation if not we propagate the value from GNSS device + if ( valueRead && !isVerticalCRSPassedThrough ) + { + // 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; + bool positionOutsideGeoidModelArea = false; + const QgsPoint geoidPosition = InputUtils::transformPoint( + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D(), + QgsProject::instance()->transformContext(), + {positionData.longitude, positionData.latitude, ellipsoidElevation}, + positionOutsideGeoidModelArea ); + if ( !positionOutsideGeoidModelArea ) + { + positionData.elevation = geoidPosition.z(); + positionData.elevation_diff = ellipsoidElevation - geoidPosition.z(); + } + } + emit positionChanged( positionData ); } } diff --git a/app/position/providers/bluetoothpositionprovider.h b/app/position/providers/bluetoothpositionprovider.h index b872b1c01..a630010d3 100644 --- a/app/position/providers/bluetoothpositionprovider.h +++ b/app/position/providers/bluetoothpositionprovider.h @@ -52,11 +52,11 @@ class BluetoothPositionProvider : public AbstractPositionProvider public: BluetoothPositionProvider( const QString &addr, const QString &name, QObject *parent = nullptr ); - virtual ~BluetoothPositionProvider() override; + ~BluetoothPositionProvider() override; - virtual void startUpdates() override; - virtual void stopUpdates() override; - virtual void closeProvider() override; + void startUpdates() override; + void stopUpdates() override; + void closeProvider() override; void handleLostConnection(); void startReconnectionTime(); diff --git a/app/position/providers/internalpositionprovider.cpp b/app/position/providers/internalpositionprovider.cpp index fc8a02670..96f4d74c3 100644 --- a/app/position/providers/internalpositionprovider.cpp +++ b/app/position/providers/internalpositionprovider.cpp @@ -8,9 +8,11 @@ ***************************************************************************/ #include "internalpositionprovider.h" -#include "coreutils.h" -#include "qgis.h" +#include + +#include "coreutils.h" +#include "inpututils.h" InternalPositionProvider::InternalPositionProvider( QObject *parent ) : AbstractPositionProvider( QStringLiteral( "devicegps" ), QStringLiteral( "internal" ), tr( "Internal" ), parent ) @@ -119,7 +121,11 @@ void InternalPositionProvider::parsePositionUpdate( const QGeoPositionInfo &posi // emit connected signal here to know that the connection is OK setState( tr( "Connected" ), State::Connected ); - bool hasPosition = position.coordinate().isValid(); + // we create a local copy of position because on iOS we use QGeoPositionInfo::VerticalSpeed attribute as helper value + // for transformation, we need to remove it afterwards + QGeoPositionInfo localPosition( position ); + + const bool hasPosition = localPosition.coordinate().isValid(); if ( !hasPosition ) { return; @@ -128,69 +134,141 @@ void InternalPositionProvider::parsePositionUpdate( const QGeoPositionInfo &posi // go over attributes and find if there are any changes from previous position, emit position update if so bool positionDataHasChanged = false; - if ( !qgsDoubleNear( position.coordinate().latitude(), mLastPosition.latitude ) ) + if ( !qgsDoubleNear( localPosition.coordinate().latitude(), mLastPosition.latitude ) ) { - mLastPosition.latitude = position.coordinate().latitude(); + mLastPosition.latitude = localPosition.coordinate().latitude(); positionDataHasChanged = true; } - if ( !qgsDoubleNear( position.coordinate().longitude(), mLastPosition.longitude ) ) + if ( !qgsDoubleNear( localPosition.coordinate().longitude(), mLastPosition.longitude ) ) { - mLastPosition.longitude = position.coordinate().longitude(); + mLastPosition.longitude = localPosition.coordinate().longitude(); positionDataHasChanged = true; } - if ( !qgsDoubleNear( position.coordinate().altitude(), mLastPosition.elevation ) ) + bool positionOutsideGeoidModelArea = false; +#ifdef Q_OS_IOS + // on ios we can get both ellipsoid and geoid altitude, depending on what is available we transform the altitude or not + // we also check if the user set vertical CRS pass through in plugin, which prohibits any transformation + bool valueRead = false; + const bool isVerticalCRSPassedThrough = QVariant( QgsProject::instance()->readEntry( QStringLiteral( "Mergin" ), QStringLiteral( "VerticalCRSPassThrough" ), QVariant( true ).toString(), &valueRead ) ).toBool(); + const bool isEllipsoidalAltitude = localPosition.attribute( QGeoPositionInfo::VerticalSpeed ); + localPosition.removeAttribute( QGeoPositionInfo::VerticalSpeed ); + const bool isMockedLocation = localPosition.attribute( QGeoPositionInfo::MagneticVariation ); + mLastPosition.isMock = isMockedLocation; + QgsPoint geoidPosition; + if ( !isMockedLocation && isEllipsoidalAltitude ) { - mLastPosition.elevation = position.coordinate().altitude(); - positionDataHasChanged = true; + // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to EPSG:9707 (WGS84 + EGM96) + geoidPosition = InputUtils::transformPoint( + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D( true ), + QgsProject::instance()->transformContext(), + {localPosition.coordinate().longitude(), localPosition.coordinate().latitude(), localPosition.coordinate().altitude()}, + positionOutsideGeoidModelArea ); + } + else if ( isMockedLocation && isEllipsoidalAltitude && valueRead && !isVerticalCRSPassedThrough ) + { + // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to specified geoid model + geoidPosition = InputUtils::transformPoint( + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D(), + QgsProject::instance()->transformContext(), + {localPosition.coordinate().longitude(), localPosition.coordinate().latitude(), localPosition.coordinate().altitude()}, + positionOutsideGeoidModelArea ); + } + else + { + geoidPosition = {localPosition.coordinate().longitude(), localPosition.coordinate().latitude(), localPosition.coordinate().altitude()}; + } +#elif defined (ANDROID) + // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to EPSG:9707 (WGS84 + EGM96) + const QgsPoint geoidPosition = InputUtils::transformPoint( + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D( true ), + QgsProject::instance()->transformContext(), + {localPosition.coordinate().longitude(), localPosition.coordinate().latitude(), localPosition.coordinate().altitude()}, + positionOutsideGeoidModelArea ); +#else + const QgsPoint geoidPosition = {localPosition.coordinate().longitude(), localPosition.coordinate().latitude(), localPosition.coordinate().altitude()}; +#endif + if ( !positionOutsideGeoidModelArea ) + { + if ( !qgsDoubleNear( geoidPosition.z(), mLastPosition.elevation ) ) + { + mLastPosition.elevation = geoidPosition.z(); + 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, if it's available (so we do not rely on geoid model of unknown quality/resolution), + // or we get orthometric altitude from mocked location, but the altitude separation is unknown + // - on Windows - it returns MSL altitude, which we pass along, but the altitude separation is unknown +#ifdef Q_OS_IOS + if ( isEllipsoidalAltitude && !isVerticalCRSPassedThrough ) + { +#endif + const double ellipsoidAltitude = localPosition.coordinate().altitude(); + const double geoidSeparation = ellipsoidAltitude - geoidPosition.z(); + if ( !qgsDoubleNear( geoidSeparation, mLastPosition.elevation_diff ) ) + { + mLastPosition.elevation_diff = geoidSeparation; + positionDataHasChanged = true; + } +#ifdef Q_OS_IOS + } +#endif } - bool hasSpeedInfo = position.hasAttribute( QGeoPositionInfo::GroundSpeed ); - if ( hasSpeedInfo && !qgsDoubleNear( position.attribute( QGeoPositionInfo::GroundSpeed ), mLastPosition.speed ) ) + const bool hasSpeedInfo = localPosition.hasAttribute( QGeoPositionInfo::GroundSpeed ); + if ( hasSpeedInfo && !qgsDoubleNear( localPosition.attribute( QGeoPositionInfo::GroundSpeed ), mLastPosition.speed ) ) { - mLastPosition.speed = position.attribute( QGeoPositionInfo::GroundSpeed ) * 3.6; // convert from m/s to km/h + mLastPosition.speed = localPosition.attribute( QGeoPositionInfo::GroundSpeed ) * 3.6; // convert from m/s to km/h positionDataHasChanged = true; } - bool hasVerticalSpeedInfo = position.hasAttribute( QGeoPositionInfo::VerticalSpeed ); - if ( hasVerticalSpeedInfo && !qgsDoubleNear( position.attribute( QGeoPositionInfo::VerticalSpeed ), mLastPosition.verticalSpeed ) ) + const bool hasVerticalSpeedInfo = localPosition.hasAttribute( QGeoPositionInfo::VerticalSpeed ); + if ( hasVerticalSpeedInfo && !qgsDoubleNear( localPosition.attribute( QGeoPositionInfo::VerticalSpeed ), mLastPosition.verticalSpeed ) ) { - mLastPosition.verticalSpeed = position.attribute( QGeoPositionInfo::VerticalSpeed ) * 3.6; // convert from m/s to km/h + mLastPosition.verticalSpeed = localPosition.attribute( QGeoPositionInfo::VerticalSpeed ) * 3.6; // convert from m/s to km/h positionDataHasChanged = true; } - bool hasDirectionInfo = position.hasAttribute( QGeoPositionInfo::Direction ); - if ( hasDirectionInfo && !qgsDoubleNear( position.attribute( QGeoPositionInfo::Direction ), mLastPosition.direction ) ) + const bool hasDirectionInfo = localPosition.hasAttribute( QGeoPositionInfo::Direction ); + if ( hasDirectionInfo && !qgsDoubleNear( localPosition.attribute( QGeoPositionInfo::Direction ), mLastPosition.direction ) ) { - mLastPosition.direction = position.attribute( QGeoPositionInfo::Direction ); + mLastPosition.direction = localPosition.attribute( QGeoPositionInfo::Direction ); positionDataHasChanged = true; } - bool hasMagneticVariation = position.hasAttribute( QGeoPositionInfo::MagneticVariation ); - if ( hasMagneticVariation && !qgsDoubleNear( position.attribute( QGeoPositionInfo::MagneticVariation ), mLastPosition.magneticVariation ) ) + const bool hasMagneticVariation = localPosition.hasAttribute( QGeoPositionInfo::MagneticVariation ); + if ( hasMagneticVariation && !qgsDoubleNear( localPosition.attribute( QGeoPositionInfo::MagneticVariation ), mLastPosition.magneticVariation ) ) { - mLastPosition.magneticVariation = position.attribute( QGeoPositionInfo::MagneticVariation ); + mLastPosition.magneticVariation = localPosition.attribute( QGeoPositionInfo::MagneticVariation ); positionDataHasChanged = true; } - bool hasHacc = position.hasAttribute( QGeoPositionInfo::HorizontalAccuracy ); - if ( hasHacc && !qgsDoubleNear( position.attribute( QGeoPositionInfo::HorizontalAccuracy ), mLastPosition.hacc ) ) + const bool hasHacc = localPosition.hasAttribute( QGeoPositionInfo::HorizontalAccuracy ); + if ( hasHacc && !qgsDoubleNear( localPosition.attribute( QGeoPositionInfo::HorizontalAccuracy ), mLastPosition.hacc ) ) { - mLastPosition.hacc = position.attribute( QGeoPositionInfo::HorizontalAccuracy ); + mLastPosition.hacc = localPosition.attribute( QGeoPositionInfo::HorizontalAccuracy ); positionDataHasChanged = true; } - bool hasVacc = position.hasAttribute( QGeoPositionInfo::VerticalAccuracy ); - if ( hasVacc && !qgsDoubleNear( position.attribute( QGeoPositionInfo::VerticalAccuracy ), mLastPosition.vacc ) ) + const bool hasVacc = localPosition.hasAttribute( QGeoPositionInfo::VerticalAccuracy ); + if ( hasVacc && !qgsDoubleNear( localPosition.attribute( QGeoPositionInfo::VerticalAccuracy ), mLastPosition.vacc ) ) { - mLastPosition.vacc = position.attribute( QGeoPositionInfo::VerticalAccuracy ); + mLastPosition.vacc = localPosition.attribute( QGeoPositionInfo::VerticalAccuracy ); positionDataHasChanged = true; } - if ( position.timestamp() != mLastPosition.utcDateTime ) + if ( localPosition.timestamp() != mLastPosition.utcDateTime ) { - mLastPosition.utcDateTime = position.timestamp(); + mLastPosition.utcDateTime = localPosition.timestamp(); positionDataHasChanged = true; } diff --git a/app/position/providers/internalpositionprovider.h b/app/position/providers/internalpositionprovider.h index b3d1d4fdc..3a0e0aa33 100644 --- a/app/position/providers/internalpositionprovider.h +++ b/app/position/providers/internalpositionprovider.h @@ -28,11 +28,11 @@ class InternalPositionProvider : public AbstractPositionProvider public: explicit InternalPositionProvider( QObject *parent = nullptr ); - virtual ~InternalPositionProvider() override; + ~InternalPositionProvider() override; - virtual void startUpdates() override; - virtual void stopUpdates() override; - virtual void closeProvider() override; + void startUpdates() override; + void stopUpdates() override; + void closeProvider() override; public slots: void parsePositionUpdate( const QGeoPositionInfo &position ); diff --git a/app/position/providers/simulatedpositionprovider.cpp b/app/position/providers/simulatedpositionprovider.cpp index 75287127d..b5e3b8284 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,37 @@ 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 ) + { + bool positionOutsideGeoidModelArea = false; + const QgsPoint geoidPosition = InputUtils::transformPoint( + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D( true ), + QgsCoordinateTransformContext(), + {longitude, latitude, ellipsoidAltitude}, + positionOutsideGeoidModelArea ); + if ( !positionOutsideGeoidModelArea ) + { + position.elevation = geoidPosition.z(); + position.elevation_diff = ellipsoidAltitude - position.elevation; + } + else + { + position.elevation = std::numeric_limits::quiet_NaN(); + position.elevation_diff = std::numeric_limits::quiet_NaN(); + } + } + else { - position.elevation = altitude; + position.elevation = std::numeric_limits::quiet_NaN(); + position.elevation_diff = std::numeric_limits::quiet_NaN(); } - 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 +141,26 @@ 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 + bool positionOutsideGeoidModelArea = false; + const QgsPoint geoidPosition = InputUtils::transformPoint( + PositionKit::positionCrs3DEllipsoidHeight(), + PositionKit::positionCrs3D( true ), + QgsCoordinateTransformContext(), + {mLongitude, mLatitude, 100}, + positionOutsideGeoidModelArea ); + if ( !positionOutsideGeoidModelArea ) + { + position.elevation = geoidPosition.z(); + position.elevation_diff = 100 - position.elevation; + } + else + { + position.elevation = std::numeric_limits::quiet_NaN(); + position.elevation_diff = std::numeric_limits::quiet_NaN(); + } 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/position/tracking/positiontrackingmanager.cpp b/app/position/tracking/positiontrackingmanager.cpp index 6bd7332a7..0f1ff6109 100644 --- a/app/position/tracking/positiontrackingmanager.cpp +++ b/app/position/tracking/positiontrackingmanager.cpp @@ -95,7 +95,7 @@ void PositionTrackingManager::commitTrackedPath() } // convert captured geometry to the destination layer's CRS - QgsGeometry geometryInLayerCRS = InputUtils::transformGeometry( mTrackedGeometry, QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), trackingLayer ); + QgsGeometry geometryInLayerCRS = InputUtils::transformGeometry( mTrackedGeometry, PositionKit::positionCrs3D(), trackingLayer ); // create feature - add tracking variables to scope QgsExpressionContextScope *scope = new QgsExpressionContextScope( QStringLiteral( "MM_Tracking" ) ); @@ -332,7 +332,7 @@ void PositionTrackingManager::setVariablesManager( VariablesManager *newVariable QgsCoordinateReferenceSystem PositionTrackingManager::crs() const { - return QgsCoordinateReferenceSystem::fromEpsgId( 4326 ); + return PositionKit::positionCrs3D(); } void PositionTrackingManager::tryAgain() diff --git a/app/projectwizard.cpp b/app/projectwizard.cpp index c149ca85a..f7156fd83 100644 --- a/app/projectwizard.cpp +++ b/app/projectwizard.cpp @@ -88,7 +88,7 @@ QgsVectorLayer *ProjectWizard::createGpkgLayer( QString const &projectDir, QList return l; } -static QgsVectorLayer *createTrackingLayer( const QString &trackingGpkgPath ) +QgsVectorLayer *ProjectWizard::createTrackingLayer( const QString &trackingGpkgPath ) { // based on the code in https://github.com/MerginMaps/qgis-plugin/blob/master/Mergin/utils.py // (create_tracking_layer(), setup_tracking_layer(), set_tracking_layer_flags()) @@ -108,7 +108,7 @@ static QgsVectorLayer *createTrackingLayer( const QString &trackingGpkgPath ) fields, Qgis::WkbType::LineStringZM, QgsCoordinateReferenceSystem( "EPSG:4326" ), - QgsCoordinateTransformContext(), + mSettings->transformContext(), options ); delete writer; diff --git a/app/projectwizard.h b/app/projectwizard.h index 192f622f2..f55c734e4 100644 --- a/app/projectwizard.h +++ b/app/projectwizard.h @@ -48,6 +48,7 @@ class ProjectWizard : public QObject void notifySuccess( const QString &message ); private: QgsVectorLayer *createGpkgLayer( QString const &projectDir, QList const &fieldsConfig ); + QgsVectorLayer *createTrackingLayer( const QString &trackingGpkgPath ); QgsFields createFields( const QList fieldsConfig ) const; QgsSingleSymbolRenderer *surveyLayerRenderer(); QVariant::Type parseType( const QString &type ) const; diff --git a/app/qml/gps/MMBluetoothConnectionDrawer.qml b/app/qml/gps/MMBluetoothConnectionDrawer.qml index 5e018337b..64d5c9af4 100644 --- a/app/qml/gps/MMBluetoothConnectionDrawer.qml +++ b/app/qml/gps/MMBluetoothConnectionDrawer.qml @@ -12,13 +12,14 @@ import QtQuick.Controls import QtQuick.Layouts import mm 1.0 as MM +import MMInput import "../components" as MMComponents MMComponents.MMDrawer { id: root - property var positionProvider: __positionKit.positionProvider + property var positionProvider: PositionKit.positionProvider property string howToConnectGPSLink: __inputHelp.howToConnectGPSLink property string titleText: { @@ -52,7 +53,7 @@ MMComponents.MMDrawer { } else if ( rootstate.state === "waitingToReconnect" ) { - return __positionKit.positionProvider.stateMessage + "

" + + return PositionKit.positionProvider.stateMessage + "

" + qsTr( "You can close this message, we will try to repeatedly connect to your device." ) } diff --git a/app/qml/gps/MMGpsDataDrawer.qml b/app/qml/gps/MMGpsDataDrawer.qml index b9b600c39..53de02ba7 100644 --- a/app/qml/gps/MMGpsDataDrawer.qml +++ b/app/qml/gps/MMGpsDataDrawer.qml @@ -62,7 +62,17 @@ MMComponents.MMDrawer { width: parent.width / 2 title: qsTr( "Source" ) - value: __positionKit.positionProvider ? __positionKit.positionProvider.name() : qsTr( "No receiver" ) + value: { + if ( PositionKit.positionProvider ) { + if ( PositionKit.isMockPosition ) { + qsTr( "Mocked position provider" ) + } else { + PositionKit.positionProvider.name() + } + } else { + qsTr( "No receiver" ) + } + } alignmentRight: Positioner.index % 2 === 1 } @@ -72,10 +82,10 @@ MMComponents.MMDrawer { width: parent.width / 2 - visible: __positionKit.positionProvider && __positionKit.positionProvider.type() === "external" + visible: PositionKit.positionProvider && PositionKit.positionProvider.type() === "external" title: qsTr( "Status" ) - value: __positionKit.positionProvider ? __positionKit.positionProvider.stateMessage : "" + value: PositionKit.positionProvider ? PositionKit.positionProvider.stateMessage : "" alignmentRight: Positioner.index % 2 === 1 } @@ -93,7 +103,7 @@ MMComponents.MMDrawer { title: qsTr( "Longitude") value: { - if ( !__positionKit.hasPosition || Number.isNaN( __positionKit.longitude ) ) { + if ( !PositionKit.hasPosition || Number.isNaN( PositionKit.longitude ) ) { return qsTr( "N/A" ) } @@ -112,7 +122,7 @@ MMComponents.MMDrawer { title: qsTr( "Latitude" ) value: { - if ( !__positionKit.hasPosition || Number.isNaN( __positionKit.latitude ) ) { + if ( !PositionKit.hasPosition || Number.isNaN( PositionKit.latitude ) ) { return qsTr( "N/A" ) } @@ -131,7 +141,7 @@ MMComponents.MMDrawer { title: qsTr( "X" ) value: { - if ( !__positionKit.hasPosition || Number.isNaN( mapPositioning.mapPosition.x ) ) { + if ( !PositionKit.hasPosition || Number.isNaN( mapPositioning.mapPosition.x ) ) { return qsTr( "N/A" ) } @@ -146,7 +156,7 @@ MMComponents.MMDrawer { title: qsTr( "Y" ) value: { - if ( !__positionKit.hasPosition || Number.isNaN( mapPositioning.mapPosition.y ) ) { + if ( !PositionKit.hasPosition || Number.isNaN( mapPositioning.mapPosition.y ) ) { return qsTr( "N/A" ) } @@ -161,11 +171,11 @@ MMComponents.MMDrawer { title: qsTr( "Horizontal accuracy" ) value: { - if ( !__positionKit.hasPosition || __positionKit.horizontalAccuracy < 0 ) { + if ( !PositionKit.hasPosition || PositionKit.horizontalAccuracy < 0 ) { return qsTr( "N/A" ) } - __inputUtils.formatNumber( __positionKit.horizontalAccuracy, 2 ) + " m" + __inputUtils.formatNumber( PositionKit.horizontalAccuracy, 2 ) + " m" } alignmentRight: Positioner.index % 2 === 1 @@ -176,11 +186,11 @@ MMComponents.MMDrawer { title: qsTr( "Vertical accuracy" ) value: { - if ( !__positionKit.hasPosition || __positionKit.verticalAccuracy < 0 ) { + if ( !PositionKit.hasPosition || PositionKit.verticalAccuracy < 0 ) { return qsTr( "N/A" ) } - __inputUtils.formatNumber( __positionKit.verticalAccuracy, 2 ) + " m" + __inputUtils.formatNumber( PositionKit.verticalAccuracy, 2 ) + " m" } alignmentRight: Positioner.index % 2 === 1 @@ -191,13 +201,14 @@ MMComponents.MMDrawer { title: qsTr( "Altitude" ) value: { - if ( !__positionKit.hasPosition || Number.isNaN( __positionKit.altitude ) ) { + if ( !PositionKit.hasPosition || Number.isNaN( PositionKit.altitude ) ) { return qsTr( "N/A" ) } - __inputUtils.formatNumber( __positionKit.altitude, 2 ) + " m" + __inputUtils.formatNumber( PositionKit.altitude, 2 ) + " m" } alignmentRight: Positioner.index % 2 === 1 + desc: PositionKit.positionCrs3DGeoidModelName().length > 0 ? qsTr(("Orthometric height, using %1 geoid").arg(PositionKit.positionCrs3DGeoidModelName())) : "" } MMGpsComponents.MMGpsDataText { @@ -205,14 +216,14 @@ MMComponents.MMDrawer { title: qsTr( "Fix quality" ) value: { - if ( !__positionKit.hasPosition ) { + if ( !PositionKit.hasPosition ) { return qsTr( "N/A" ) } - __positionKit.fix + PositionKit.fix } - visible: __positionKit.positionProvider && __positionKit.positionProvider.type() === "external" + visible: PositionKit.positionProvider && PositionKit.positionProvider.type() === "external" alignmentRight: Positioner.index % 2 === 1 } @@ -222,12 +233,12 @@ MMComponents.MMDrawer { title: qsTr( "Satellites (in use/view)" ) value: { - if ( __positionKit.satellitesUsed < 0 || __positionKit.satellitesVisible < 0 ) + if ( PositionKit.satellitesUsed < 0 || PositionKit.satellitesVisible < 0 ) { return qsTr( "N/A" ) } - __positionKit.satellitesUsed + "/" + __positionKit.satellitesVisible + PositionKit.satellitesUsed + "/" + PositionKit.satellitesVisible } alignmentRight: Positioner.index % 2 === 1 @@ -238,14 +249,14 @@ MMComponents.MMDrawer { title: qsTr( "HDOP" ) value: { - if ( !__positionKit.hasPosition || __positionKit.hdop < 0 ) { + if ( !PositionKit.hasPosition || PositionKit.hdop < 0 ) { return qsTr( "N/A" ) } - __inputUtils.formatNumber( __positionKit.hdop, 2 ) + __inputUtils.formatNumber( PositionKit.hdop, 2 ) } - visible: __positionKit.positionProvider && __positionKit.positionProvider.type() === "external" + visible: PositionKit.positionProvider && PositionKit.positionProvider.type() === "external" alignmentRight: Positioner.index % 2 === 1 } @@ -255,14 +266,14 @@ MMComponents.MMDrawer { title: qsTr( "VDOP" ) value: { - if ( !__positionKit.hasPosition || __positionKit.vdop < 0 ) { + if ( !PositionKit.hasPosition || PositionKit.vdop < 0 ) { return qsTr( "N/A" ) } - __inputUtils.formatNumber( __positionKit.vdop, 2 ) + __inputUtils.formatNumber( PositionKit.vdop, 2 ) } - visible: __positionKit.positionProvider && __positionKit.positionProvider.type() === "external" + visible: PositionKit.positionProvider && PositionKit.positionProvider.type() === "external" alignmentRight: Positioner.index % 2 === 1 } @@ -272,14 +283,14 @@ MMComponents.MMDrawer { title: qsTr( "PDOP" ) value: { - if ( !__positionKit.hasPosition || __positionKit.pdop < 0 ) { + if ( !PositionKit.hasPosition || PositionKit.pdop < 0 ) { return qsTr( "N/A" ) } - __inputUtils.formatNumber( __positionKit.pdop, 2 ) + __inputUtils.formatNumber( PositionKit.pdop, 2 ) } - visible: __positionKit.positionProvider && __positionKit.positionProvider.type() === "external" + visible: PositionKit.positionProvider && PositionKit.positionProvider.type() === "external" alignmentRight: Positioner.index % 2 === 1 } @@ -289,11 +300,11 @@ MMComponents.MMDrawer { title: qsTr( "Speed" ) value: { - if ( !__positionKit.hasPosition || __positionKit.speed < 0 ) { + if ( !PositionKit.hasPosition || PositionKit.speed < 0 ) { return qsTr( "N/A" ) } - __inputUtils.formatNumber( __positionKit.speed, 2 ) + " km/h" + __inputUtils.formatNumber( PositionKit.speed, 2 ) + " km/h" } alignmentRight: Positioner.index % 2 === 1 @@ -303,7 +314,7 @@ MMComponents.MMDrawer { width: parent.width / 2 title: qsTr( "Last Fix" ) - value: __positionKit.lastRead.toLocaleTimeString( Qt.locale() ) || qsTr( "N/A" ) + value: PositionKit.lastRead.toLocaleTimeString( Qt.locale() ) || qsTr( "N/A" ) alignmentRight: Positioner.index % 2 === 1 } @@ -322,12 +333,11 @@ 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" } - visible: __positionKit.positionProvider && __positionKit.positionProvider.type() === "external" alignmentRight: Positioner.index % 2 === 1 } @@ -348,13 +358,13 @@ MMComponents.MMDrawer { MM.MapPosition { id: mapPositioning - positionKit: __positionKit + positionKit: PositionKit mapSettings: root.mapSettings } QtObject { id: internal - property string coordinatesInDegrees: __inputUtils.degreesString( __positionKit.positionCoordinate ) + property string coordinatesInDegrees: __inputUtils.degreesString( PositionKit.positionCoordinate ) } } diff --git a/app/qml/gps/MMPositionProviderPage.qml b/app/qml/gps/MMPositionProviderPage.qml index 9f3b501f4..c98d9a0c8 100644 --- a/app/qml/gps/MMPositionProviderPage.qml +++ b/app/qml/gps/MMPositionProviderPage.qml @@ -81,7 +81,7 @@ MMComponents.MMPage { } text: model.ProviderName ? model.ProviderName : qsTr( "Unknown device" ) - secondaryText: listdelegate.isActive ? __positionKit.positionProvider.stateMessage : model.ProviderDescription + secondaryText: listdelegate.isActive ? PositionKit.positionProvider.stateMessage : model.ProviderDescription rightContent: MMComponents.MMRoundButton { visible: model.ProviderType !== "internal" @@ -205,7 +205,7 @@ MMComponents.MMPage { MMAddPositionProviderDrawer { onInitiatedConnectionTo: function ( deviceAddress, deviceName ) { - __positionKit.positionProvider = __positionKit.constructProvider( "external", deviceAddress, deviceName ) + PositionKit.positionProvider = PositionKit.constructProvider( "external", deviceAddress, deviceName ) providersModel.addProvider( deviceName, deviceAddress ) list.model.discovering = false @@ -226,7 +226,7 @@ MMComponents.MMPage { onClosed: connectingDialogLoader.active = false // revert position provider back to internal provider - onFailure: __positionKit.positionProvider = __positionKit.constructProvider( "internal", "devicegps", "" ) + onFailure: PositionKit.positionProvider = PositionKit.constructProvider( "internal", "devicegps", "" ) Component.onCompleted: open() } @@ -251,7 +251,7 @@ MMComponents.MMPage { return // do not construct the same provider again } - __positionKit.positionProvider = __positionKit.constructProvider( type, id, name ) + PositionKit.positionProvider = PositionKit.constructProvider( type, id, name ) if ( type === "external" ) { connectingDialogLoader.open() diff --git a/app/qml/gps/MMStakeoutDrawer.qml b/app/qml/gps/MMStakeoutDrawer.qml index 61f6ca354..1958e3b6a 100644 --- a/app/qml/gps/MMStakeoutDrawer.qml +++ b/app/qml/gps/MMStakeoutDrawer.qml @@ -14,6 +14,7 @@ import Qt5Compat.GraphicalEffects import QtQuick.Shapes import mm 1.0 as MM +import MMInput import "../components" import "../map/components" @@ -26,7 +27,7 @@ MMDrawer { property var targetPair: null property real remainingDistance: targetPair ? __inputUtils.distanceBetweenGpsAndFeature( - __positionKit.positionCoordinate, + PositionKit.positionCoordinate, targetPair, mapCanvas.mapSettings ) : -1 property var extent @@ -220,7 +221,7 @@ MMDrawer { MM.PositionDirection { id: positionDirection - positionKit: __positionKit + positionKit: PositionKit compass: MM.Compass { id: ccompass } } @@ -228,7 +229,7 @@ MMDrawer { id: positionMarker property real bearing: root.targetPair ? __inputUtils.angleBetweenGpsAndFeature( - __positionKit.positionCoordinate, + PositionKit.positionCoordinate, root.targetPair, root.mapCanvas.mapSettings ) : 0 @@ -238,9 +239,9 @@ MMDrawer { hasDirection: positionDirection.hasDirection direction: positionDirection.direction - hasPosition: __positionKit.hasPosition + hasPosition: PositionKit.hasPosition - horizontalAccuracy: __positionKit.horizontalAccuracy + horizontalAccuracy: PositionKit.horizontalAccuracy accuracyRingSize: 0 // do not show any accuracy ring in stakeout mode trackingMode: closeRangeModeComponent.state === "notAtTarget" diff --git a/app/qml/gps/components/MMGpsDataText.qml b/app/qml/gps/components/MMGpsDataText.qml index ed3ce98fa..b5b91a4fd 100644 --- a/app/qml/gps/components/MMGpsDataText.qml +++ b/app/qml/gps/components/MMGpsDataText.qml @@ -8,6 +8,7 @@ ***************************************************************************/ import QtQuick +import QtQuick.Layouts import "../../components" as MMComponents @@ -17,6 +18,7 @@ Item { property alias title: titletxt.text property alias value: valuetxt.text + property string desc: "" property bool alignmentRight: false implicitHeight: contentColumn.implicitHeight @@ -36,18 +38,42 @@ Item { spacing: 0 - MMComponents.MMText { - id: titletxt - - leftPadding: alignmentRight ? __style.margin4 : 0 - rightPadding: alignmentRight ? 0 : __style.margin4 - - width: parent.width - leftPadding - rightPadding - x: leftPadding - - font: __style.p6 - - horizontalAlignment: alignmentRight ? Text.AlignRight : Text.AlignLeft + RowLayout { + width: parent.width + spacing: __style.margin10 + + MMComponents.MMText { + id: titletxt + + leftPadding: alignmentRight ? __style.margin4 : 0 + rightPadding: alignmentRight ? 0 : __style.margin4 + + font: __style.p6 + + horizontalAlignment: alignmentRight ? Text.AlignRight : Text.AlignLeft + Layout.alignment: root.alignmentRight? Qt.AlignRight: Qt.AlignLeft + Layout.fillWidth: root.alignmentRight + } + + MMComponents.MMIcon { + id: infoIcon + source: __style.infoIcon + visible: root.desc + Layout.alignment: root.alignmentRight? Qt.AlignRight | Qt.AlignBaseline : Qt.AlignLeft | Qt.AlignBaseline + Layout.preferredWidth: __style.icon16 + Layout.preferredHeight: __style.icon16 + + TapHandler{ + gesturePolicy: TapHandler.ReleaseWithinBounds + margin: __style.margin10 + onTapped: () => infoPopup.open() + } + } + + MMComponents.MMListSpacer{ + visible: !root.alignmentRight + Layout.fillWidth: !root.alignmentRight + } } MMComponents.MMText { @@ -65,4 +91,13 @@ Item { horizontalAlignment: alignmentRight ? Text.AlignRight : Text.AlignLeft } } + + MMComponents.MMPopup { + id: infoPopup + y: ( -root.height / 2 ) - __style.margin8 + MMComponents.MMText { + font: __style.p6 + text: root.desc + } + } } diff --git a/app/qml/main.qml b/app/qml/main.qml index 0c9f8332f..05f664b61 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -258,10 +258,10 @@ ApplicationWindow { Component.onCompleted: { __activeProject.mapSettings = map.mapSettings - __iosUtils.positionKit = __positionKit + __iosUtils.positionKit = PositionKit __iosUtils.compass = map.compass __variablesManager.compass = map.compass - __variablesManager.positionKit = __positionKit + __variablesManager.positionKit = PositionKit } } @@ -796,7 +796,7 @@ ApplicationWindow { onStakeoutFeature: function( feature ) { if ( !__inputUtils.isPointLayerFeature( feature ) ) return; - if ( !__positionKit.hasPosition ) + if ( !PositionKit.hasPosition ) { __notificationModel.addWarning( qsTr( "Stake out is disabled because location is unavailable!" ) ); return; diff --git a/app/qml/map/MMMapController.qml b/app/qml/map/MMMapController.qml index 01efc9958..ae892281d 100644 --- a/app/qml/map/MMMapController.qml +++ b/app/qml/map/MMMapController.qml @@ -203,7 +203,7 @@ Item { states: [ State { name: "good" // GPS provides position AND horizontal accuracy is below set tolerance (threshold) - when: __positionKit.hasPosition && __positionKit.horizontalAccuracy > 0 && __positionKit.horizontalAccuracy <= AppSettings.gpsAccuracyTolerance + when: PositionKit.hasPosition && PositionKit.horizontalAccuracy > 0 && PositionKit.horizontalAccuracy <= AppSettings.gpsAccuracyTolerance PropertyChanges { target: gpsStateGroup indicatorColor: __style.positiveColor @@ -211,7 +211,7 @@ Item { }, State { name: "low" // below accuracy tolerance OR GPS does not provide horizontal accuracy - when: __positionKit.hasPosition && (__positionKit.horizontalAccuracy < 0 || __positionKit.horizontalAccuracy > AppSettings.gpsAccuracyTolerance ) + when: PositionKit.hasPosition && (PositionKit.horizontalAccuracy < 0 || PositionKit.horizontalAccuracy > AppSettings.gpsAccuracyTolerance ) PropertyChanges { target: gpsStateGroup indicatorColor: __style.warningColor @@ -219,7 +219,7 @@ Item { }, State { name: "unavailable" // GPS does not provide position - when: !__positionKit.hasPosition + when: !PositionKit.hasPosition PropertyChanges { target: gpsStateGroup indicatorColor: __style.negativeColor @@ -275,9 +275,9 @@ Item { onLongPressed: function( point ) { // Alter position of simulated provider - if ( __positionKit.positionProvider && __positionKit.positionProvider.id() === "simulated" ) + if ( PositionKit.positionProvider && PositionKit.positionProvider.id() === "simulated" ) { - __positionKit.positionProvider.setPosition( __inputUtils.mapPointToGps( Qt.point( point.x, point.y ), mapCanvas.mapSettings ) ) + PositionKit.positionProvider.setPosition( __inputUtils.mapPointToGps( Qt.point( point.x, point.y ), mapCanvas.mapSettings ) ) } if ( root.state === "view" ) @@ -410,7 +410,7 @@ Item { } Component.onCompleted: { - trackingManager.trackingBackend = trackingManager.constructTrackingBackend( __activeProject.qgsProject, __positionKit ) + trackingManager.trackingBackend = trackingManager.constructTrackingBackend( __activeProject.qgsProject, PositionKit ) } Connections { @@ -433,9 +433,9 @@ Item { hasDirection: positionDirectionSource.hasDirection direction: positionDirectionSource.direction - hasPosition: __positionKit.hasPosition + hasPosition: PositionKit.hasPosition - horizontalAccuracy: __positionKit.horizontalAccuracy + horizontalAccuracy: PositionKit.horizontalAccuracy accuracyRingSize: mapPositionSource.screenAccuracy trackingMode: root.state !== "inactive" && tracking.active @@ -679,7 +679,7 @@ Item { visible: { if ( root.mapExtentOffset > 0 && root.state !== "stakeout" ) return false - if ( __positionKit.positionProvider && __positionKit.positionProvider.type() === "external" ) { + if ( PositionKit.positionProvider && PositionKit.positionProvider.type() === "external" ) { // for external receivers we want to show gps panel and accuracy button // even when the GPS receiver is not sending position data return true @@ -695,36 +695,36 @@ Item { } text: { - if ( !__positionKit.positionProvider ) + if ( !PositionKit.positionProvider ) { return "" } - else if ( __positionKit.positionProvider.type() === "external" ) + else if ( PositionKit.positionProvider.type() === "external" ) { - if ( __positionKit.positionProvider.state === MM.PositionProvider.Connecting ) + if ( PositionKit.positionProvider.state === MM.PositionProvider.Connecting ) { - return qsTr( "Connecting to %1" ).arg( __positionKit.positionProvider.name() ) + return qsTr( "Connecting to %1" ).arg( PositionKit.positionProvider.name() ) } - else if ( __positionKit.positionProvider.state === MM.PositionProvider.WaitingToReconnect ) + else if ( PositionKit.positionProvider.state === MM.PositionProvider.WaitingToReconnect ) { - return __positionKit.positionProvider.stateMessage + return PositionKit.positionProvider.stateMessage } - else if ( __positionKit.positionProvider.state === MM.PositionProvider.NoConnection ) + else if ( PositionKit.positionProvider.state === MM.PositionProvider.NoConnection ) { - return __positionKit.positionProvider.stateMessage + return PositionKit.positionProvider.stateMessage } } - if ( !__positionKit.hasPosition ) + if ( !PositionKit.hasPosition ) { return qsTr( "Connected, no position" ) } - else if ( Number.isNaN( __positionKit.horizontalAccuracy ) || __positionKit.horizontalAccuracy < 0 ) + else if ( Number.isNaN( PositionKit.horizontalAccuracy ) || PositionKit.horizontalAccuracy < 0 ) { return qsTr( "Unknown accuracy" ) } - let accuracyText = __inputUtils.formatNumber( __positionKit.horizontalAccuracy, __positionKit.horizontalAccuracy > 1 ? 1 : 2 ) + " m" + let accuracyText = __inputUtils.formatNumber( PositionKit.horizontalAccuracy, PositionKit.horizontalAccuracy > 1 ? 1 : 2 ) + " m" if ( AppSettings.gpsAntennaHeight > 0 ) { let gpsText = Number( AppSettings.gpsAntennaHeight.toFixed( 3 ) ) + " m" @@ -1092,14 +1092,14 @@ Item { id: mapPositionSource mapSettings: mapCanvas.mapSettings - positionKit: __positionKit + positionKit: PositionKit onScreenPositionChanged: root.updatePosition() } MM.PositionDirection { id: positionDirectionSource - positionKit: __positionKit + positionKit: PositionKit compass: deviceCompass } @@ -1386,7 +1386,7 @@ Item { } function centerToPosition( animate = false ) { - if ( __positionKit.hasPosition ) { + if ( PositionKit.hasPosition ) { if ( animate ) { let screenPt = mapCanvas.mapSettings.coordinateToScreen( mapPositionSource.mapPosition ) diff --git a/app/qml/map/MMRecordingTools.qml b/app/qml/map/MMRecordingTools.qml index e9ebf8a92..da976654c 100644 --- a/app/qml/map/MMRecordingTools.qml +++ b/app/qml/map/MMRecordingTools.qml @@ -77,7 +77,7 @@ Item { recordingInterval: AppSettings.lineRecordingInterval recordingIntervalType: AppSettings.intervalType - positionKit: __positionKit + positionKit: PositionKit activeLayer: __activeLayer.vectorLayer activeFeature: root.activeFeature diff --git a/app/qml/map/MMStakeoutTools.qml b/app/qml/map/MMStakeoutTools.qml index 0464e4b4f..530987503 100644 --- a/app/qml/map/MMStakeoutTools.qml +++ b/app/qml/map/MMStakeoutTools.qml @@ -10,6 +10,7 @@ import QtQuick import mm 1.0 as MM +import MMInput Item { id: root @@ -34,7 +35,7 @@ Item { id: mapPositioning mapSettings: map.mapSettings - positionKit: __positionKit + positionKit: PositionKit onMapPositionChanged: updateStakeout() } diff --git a/app/qml/settings/MMSettingsPage.qml b/app/qml/settings/MMSettingsPage.qml index ff635f456..26435fddc 100644 --- a/app/qml/settings/MMSettingsPage.qml +++ b/app/qml/settings/MMSettingsPage.qml @@ -78,7 +78,7 @@ MMPage { MMSettingsComponents.MMSettingsItem { width: parent.width title: qsTr("Manage GPS receivers") - value: __positionKit.positionProvider.name() + value: PositionKit.positionProvider.name() onClicked: root.manageGpsClicked() } 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 ); diff --git a/app/variablesmanager.cpp b/app/variablesmanager.cpp index 16fdf184a..ebe8990a0 100644 --- a/app/variablesmanager.cpp +++ b/app/variablesmanager.cpp @@ -72,6 +72,7 @@ QgsExpressionContextScope *VariablesManager::positionScope() addPositionVariable( scope, QStringLiteral( "longitude" ), position.longitude ); addPositionVariable( scope, QStringLiteral( "latitude" ), position.latitude ); addPositionVariable( scope, QStringLiteral( "altitude" ), position.elevation ); + addPositionVariable( scope, QStringLiteral( "altitude_ellipsoidal" ), position.elevation - position.elevation_diff ); addPositionVariable( scope, QStringLiteral( "geoid_separation" ), position.elevation_diff ); addPositionVariable( scope, QStringLiteral( "horizontal_accuracy" ), getGeoPositionAttribute( position.hacc ) ); addPositionVariable( scope, QStringLiteral( "vertical_accuracy" ), getGeoPositionAttribute( position.vacc ) ); diff --git a/gallery/main.cpp b/gallery/main.cpp index 5a82fd026..3dfbbaba8 100644 --- a/gallery/main.cpp +++ b/gallery/main.cpp @@ -22,7 +22,6 @@ #include "qrcodedecoder.h" #include "inpututils.h" #include "scalebarkit.h" -#include "positionkit.h" #include "formfeaturesmodel.h" #include "enums.h" @@ -80,9 +79,6 @@ int main( int argc, char *argv[] ) NotificationModel notificationModel; - PositionKit pk; - engine.rootContext()->setContextProperty( "__positionKit", &pk ); - engine.rootContext()->setContextProperty( "__notificationModel", ¬ificationModel ); // path to local wrapper pages engine.rootContext()->setContextProperty( "_qmlWrapperPath", QGuiApplication::applicationDirPath() + "/HotReload/qml/pages/" ); diff --git a/gallery/positionkit.h b/gallery/positionkit.h index e889f1a17..059a3e737 100644 --- a/gallery/positionkit.h +++ b/gallery/positionkit.h @@ -12,10 +12,13 @@ #include #include +#include class PositionKit : public QObject { Q_OBJECT + QML_ELEMENT + QML_SINGLETON Q_PROPERTY( double latitude READ latitude CONSTANT ) Q_PROPERTY( double longitude READ longitude CONSTANT ) diff --git a/vcpkg/ports/qtpositioning/ios_orthometric_altitude.patch b/vcpkg/ports/qtpositioning/ios_orthometric_altitude.patch new file mode 100644 index 000000000..ca77db9ec --- /dev/null +++ b/vcpkg/ports/qtpositioning/ios_orthometric_altitude.patch @@ -0,0 +1,43 @@ +diff --git a/src/plugins/position/corelocation/qgeopositioninfosource_cl.mm b/src/plugins/position/corelocation/qgeopositioninfosource_cl.mm +index 95d51a86..bdb8641b 100644 +--- a/src/plugins/position/corelocation/qgeopositioninfosource_cl.mm ++++ b/src/plugins/position/corelocation/qgeopositioninfosource_cl.mm +@@ -47,12 +47,36 @@ + NSTimeInterval locationTimeStamp = [newLocation.timestamp timeIntervalSince1970]; + const QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(qRound64(locationTimeStamp * 1000), + QTimeZone::UTC); +- ++ // The ellipsoidalAltitude property can be populated depending on the manufacturer, we either use this and apply ++ // transformation with our geoid model or just propagate the geoid elevation from the mocked location ++ double availableAltitude; ++ if (newLocation.ellipsoidalAltitude) { ++ availableAltitude = newLocation.ellipsoidalAltitude; ++ } ++ else { ++ availableAltitude = newLocation.altitude; ++ } + // Construct position info from location data + QGeoPositionInfo location(QGeoCoordinate(newLocation.coordinate.latitude, + newLocation.coordinate.longitude, +- newLocation.altitude), ++ availableAltitude), + timeStamp); ++ // we abuse the QGeoPositionInfo API here a bit, but we want to give our app some more info ++ // VerticalSpeed - true if it's ellipsoidal altitude, false if geoid ++ // MagneticVariation - true if the location is mocked, false if not ++ if (newLocation.ellipsoidalAltitude) { ++ location.setAttribute(QGeoPositionInfo::VerticalSpeed, 1); ++ } ++ else { ++ location.setAttribute(QGeoPositionInfo::VerticalSpeed, 0); ++ } ++ if (newLocation.sourceInformation.isSimulatedBySoftware) { ++ location.setAttribute(QGeoPositionInfo::MagneticVariation, 1); ++ } ++ else { ++ location.setAttribute(QGeoPositionInfo::MagneticVariation, 0); ++ } ++ + if (newLocation.horizontalAccuracy >= 0) + location.setAttribute(QGeoPositionInfo::HorizontalAccuracy, newLocation.horizontalAccuracy); + if (newLocation.verticalAccuracy >= 0) diff --git a/vcpkg/ports/qtpositioning/portfile.cmake b/vcpkg/ports/qtpositioning/portfile.cmake index f4f3231b6..8a0a6b634 100644 --- a/vcpkg/ports/qtpositioning/portfile.cmake +++ b/vcpkg/ports/qtpositioning/portfile.cmake @@ -4,6 +4,8 @@ include("${SCRIPT_PATH}/qt_install_submodule.cmake") set(${PORT}_PATCHES devendor-poly2tri.patch foregroundservice.patch + ios_orthometric_altitude.patch + # TODO: The android patch should be removed after migration to Qt 6.9+ as it is a backport of their bugfix android15_altitude_fix.patch) vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS