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/CMakeLists.txt b/app/CMakeLists.txt index e49bfd223..1b57a5bb2 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -43,6 +43,7 @@ set(MM_SRCS position/mapposition.cpp position/positiondirection.cpp position/positionkit.cpp + position/positiontransformer.cpp activelayer.cpp activeproject.cpp androidutils.cpp @@ -133,6 +134,7 @@ set(MM_HDRS position/mapposition.h position/positiondirection.h position/positionkit.h + position/positiontransformer.h activelayer.h activeproject.h androidutils.h 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..3bf90eed4 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 ); @@ -875,29 +875,37 @@ QgsPoint InputUtils::transformPoint( const QgsCoordinateReferenceSystem &srcCrs, const QgsCoordinateReferenceSystem &destCrs, const QgsCoordinateTransformContext &context, const QgsPoint &srcPoint ) +{ + bool dummyFallbackOperationHappened; //ignored + return transformPoint( srcCrs, destCrs, context, srcPoint, dummyFallbackOperationHappened ); +} + +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 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() ) ); + fallbackOperationOccurred = ct.fallbackOperationOccurred(); + const QgsPoint pt( transformed.x(), transformed.y(), transformed.z(), srcPoint.m() ); return pt; } - else - { - return srcPoint; - } + + return srcPoint; } } catch ( QgsCsException &cse ) @@ -905,7 +913,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 +962,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 +1723,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 +1771,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..48c302fb5 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -544,13 +544,14 @@ 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 ); + QObject::connect( &activeProject, &ActiveProject::projectReloaded, pk, &PositionKit::refreshPositionTransformer ); + pk->setPositionProvider( pk->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 +659,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 +691,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 +734,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..9a9b927c0 100644 --- a/app/position/positionkit.cpp +++ b/app/position/positionkit.cpp @@ -31,13 +31,51 @@ PositionKit::PositionKit( QObject *parent ) : QObject( parent ) { + refreshPositionTransformer( QgsProject::instance() ); } -QgsCoordinateReferenceSystem PositionKit::positionCRS() +QgsCoordinateReferenceSystem PositionKit::positionCrs3D() +{ + 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 +{ + bool valueRead = false; + const bool isVerticalCRSPassedThrough = QgsProject::instance()->readBoolEntry( QStringLiteral( "Mergin" ), QStringLiteral( "VerticalCRSPassThrough" ), true, &valueRead ); + 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 ) @@ -83,6 +121,15 @@ void PositionKit::setPositionProvider( AbstractPositionProvider *provider ) parsePositionUpdate( GeoPosition() ); } +QString PositionKit::positionProviderName() const +{ + if ( isMockPosition() ) + { + return tr( "Mocked position provider" ); + } + return mPositionProvider->name(); +} + AbstractPositionProvider *PositionKit::constructProvider( const QString &type, const QString &id, const QString &name ) { QString providerType( type ); @@ -96,7 +143,7 @@ AbstractPositionProvider *PositionKit::constructProvider( const QString &type, c if ( providerType == QStringLiteral( "external" ) ) { #ifdef HAVE_BLUETOOTH - AbstractPositionProvider *provider = new BluetoothPositionProvider( id, name ); + AbstractPositionProvider *provider = new BluetoothPositionProvider( id, name, *mPositionTransformer ); QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership ); return provider; #endif @@ -105,32 +152,32 @@ AbstractPositionProvider *PositionKit::constructProvider( const QString &type, c { if ( id == QStringLiteral( "simulated" ) ) { - AbstractPositionProvider *provider = new SimulatedPositionProvider(); + AbstractPositionProvider *provider = new SimulatedPositionProvider( *mPositionTransformer ); QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership ); return provider; } #ifdef ANDROID - else if ( id == QStringLiteral( "android_fused" ) || id == QStringLiteral( "android_gps" ) ) + if ( id == QStringLiteral( "android_fused" ) || id == QStringLiteral( "android_gps" ) ) { - bool fused = ( id == QStringLiteral( "android_fused" ) ); + const bool fused = ( id == QStringLiteral( "android_fused" ) ); if ( fused && !AndroidPositionProvider::isFusedAvailable() ) { // TODO: inform user + use AndroidPositionProvider::fusedErrorString() output? // fallback to the default - at this point the Qt Positioning implementation - AbstractPositionProvider *provider = new InternalPositionProvider(); + AbstractPositionProvider *provider = new InternalPositionProvider( *mPositionTransformer ); QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership ); return provider; } __android_log_print( ANDROID_LOG_INFO, "CPP", "MAKE PROVIDER %d", fused ); - AbstractPositionProvider *provider = new AndroidPositionProvider( fused ); + AbstractPositionProvider *provider = new AndroidPositionProvider( fused, *mPositionTransformer ); QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership ); return provider; } #endif else // id == devicegps { - AbstractPositionProvider *provider = new InternalPositionProvider(); + AbstractPositionProvider *provider = new InternalPositionProvider( *mPositionTransformer ); QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership ); return provider; } @@ -148,7 +195,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 +256,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 +374,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 ); @@ -341,6 +399,13 @@ void PositionKit::appStateChanged( Qt::ApplicationState state ) } } +void PositionKit::refreshPositionTransformer( const QgsProject *project ) +{ + free( mPositionTransformer ); + const bool isVerticalCRSPassThroughEnabled = project->readBoolEntry( QStringLiteral( "Mergin" ), QStringLiteral( "VerticalCRSPassThrough" ), true ); + mPositionTransformer = new PositionTransformer( positionCrs3DEllipsoidHeight(), positionCrs3D(), isVerticalCRSPassThroughEnabled ); +} + double PositionKit::latitude() const { return mPosition.latitude; @@ -449,6 +514,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..59820ffaf 100644 --- a/app/position/positionkit.h +++ b/app/position/positionkit.h @@ -10,11 +10,14 @@ #ifndef POSITIONKIT_H #define POSITIONKIT_H -#include "position/providers/abstractpositionprovider.h" +#include + +#include +#include +#include -#include "qgspoint.h" -#include "qgscoordinatereferencesystem.h" -#include +#include "positiontransformer.h" +#include "position/providers/abstractpositionprovider.h" class AppSettings; @@ -27,6 +30,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 +77,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,19 +113,30 @@ class PositionKit : public QObject QString fix() const; const GeoPosition &position() const; + bool isMockPosition() const; AbstractPositionProvider *positionProvider() const; void setPositionProvider( AbstractPositionProvider *newPositionProvider ); + Q_INVOKABLE QString positionProviderName() const; double hdop() const; double vdop() const; double pdop() const; - // Coordinate reference system of position - WGS84 (constant) - Q_INVOKABLE static QgsCoordinateReferenceSystem positionCRS(); - - Q_INVOKABLE static AbstractPositionProvider *constructProvider( const QString &type, const QString &id, const QString &name = QString() ); - Q_INVOKABLE static AbstractPositionProvider *constructActiveProvider( AppSettings *appsettings ); + // 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(); + // Returns the model name used for elevation transformations + Q_INVOKABLE QString positionCrs3DGeoidModelName() const; + + Q_INVOKABLE AbstractPositionProvider *constructProvider( const QString &type, const QString &id, const QString &name = QString() ); + Q_INVOKABLE AbstractPositionProvider *constructActiveProvider( AppSettings *appsettings ); AppSettings *appSettings() const; void setAppSettings( AppSettings *appSettings ); @@ -156,6 +173,7 @@ class PositionKit : public QObject void positionProviderChanged( AbstractPositionProvider *provider ); void positionChanged( const GeoPosition & ); + void isMockPositionChanged( bool ); void appSettingsChanged(); @@ -167,11 +185,17 @@ class PositionKit : public QObject // stop updates when application is minimized void appStateChanged( Qt::ApplicationState state ); + // gets triggered when active project changes + void refreshPositionTransformer( const QgsProject *project ); + private: GeoPosition mPosition; bool mHasPosition = false; std::unique_ptr mPositionProvider; AppSettings *mAppSettings = nullptr; // not owned + PositionTransformer *mPositionTransformer = nullptr; // owned + + friend class TestPosition; }; #endif // POSITIONKIT_H diff --git a/app/position/positiontransformer.cpp b/app/position/positiontransformer.cpp new file mode 100644 index 000000000..be2d13f0e --- /dev/null +++ b/app/position/positiontransformer.cpp @@ -0,0 +1,214 @@ +/*************************************************************************** +* * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "positiontransformer.h" + +#include "inpututils.h" + +PositionTransformer::PositionTransformer( const QgsCoordinateReferenceSystem &sourceCrs, + const QgsCoordinateReferenceSystem &destinationCrs, const bool verticalPassThroughEnabled, QObject *parent ) + : QObject( parent ), + mSourceCrs( sourceCrs ), + mDestinationCrs( destinationCrs ), + mVerticalPassThroughEnabled( verticalPassThroughEnabled ) +{ +} + +GeoPosition PositionTransformer::processBluetoothPosition( GeoPosition geoPosition ) +{ + // if the user sets custom vertical crs we apply our transformation if not we propagate the value from GNSS device + // also check if we have data for elevation and elevation undulation + if ( !mVerticalPassThroughEnabled && geoPosition.elevation && geoPosition.elevation_diff ) + { + // 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 = geoPosition.elevation + geoPosition.elevation_diff; + bool positionOutsideGeoidModelArea = false; + const QgsPoint geoidPosition = InputUtils::transformPoint( + mSourceCrs, + mDestinationCrs, + mTransformContext, + {geoPosition.longitude, geoPosition.latitude, ellipsoidElevation}, + positionOutsideGeoidModelArea ); + if ( !positionOutsideGeoidModelArea ) + { + geoPosition.elevation = geoidPosition.z(); + geoPosition.elevation_diff = ellipsoidElevation - geoidPosition.z(); + } + } + + return geoPosition; +} + +GeoPosition PositionTransformer::processAndroidPosition( GeoPosition geoPosition ) +{ + if ( !qFuzzyIsNull( geoPosition.elevation ) ) + { + bool positionOutsideGeoidModelArea = false; + // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to specified geoid model + // (by default EPSG:9707 (WGS84 + EGM96)) + // we do the transformation only in case the position is not mocked, and it's ellipsoidal altitude + // the second variant is when the position is mocked, the altitude is ellipsoidal plus pass through is enabled + if ( !geoPosition.isMock || !mVerticalPassThroughEnabled ) + { + const QgsPoint geoidPosition = InputUtils::transformPoint( + mSourceCrs, + mDestinationCrs, + mTransformContext, + {geoPosition.longitude, geoPosition.latitude, geoPosition.elevation}, + positionOutsideGeoidModelArea ); + if ( !positionOutsideGeoidModelArea ) + { + const double geoidSeparation = geoPosition.elevation - geoidPosition.z(); + + geoPosition.elevation = geoidPosition.z(); + geoPosition.elevation_diff = geoidSeparation; + } + } + } + + return geoPosition; +} + +GeoPosition PositionTransformer::processInternalAndroidPosition( const QGeoPositionInfo &geoPosition ) +{ + GeoPosition newPosition; + bool positionOutsideGeoidModelArea = false; + // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to specified geoid model + // (by default EPSG:9707 (WGS84 + EGM96)) + const QgsPoint geoidPosition = InputUtils::transformPoint( + mSourceCrs, + mDestinationCrs, + mTransformContext, + {geoPosition.coordinate().longitude(), geoPosition.coordinate().latitude(), geoPosition.coordinate().altitude()}, + positionOutsideGeoidModelArea ); + + if ( !positionOutsideGeoidModelArea ) + { + newPosition.elevation = 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, 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 + const double ellipsoidAltitude = geoPosition.coordinate().altitude(); + const double geoidSeparation = ellipsoidAltitude - geoidPosition.z(); + newPosition.elevation_diff = geoidSeparation; + } + return newPosition; +} + +GeoPosition PositionTransformer::processInternalIosPosition( QGeoPositionInfo &geoPosition ) +{ + GeoPosition newPosition; + bool positionOutsideGeoidModelArea = false; + // 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 + const bool isEllipsoidalAltitude = geoPosition.hasAttribute( QGeoPositionInfo::VerticalSpeed ); + geoPosition.removeAttribute( QGeoPositionInfo::VerticalSpeed ); + const bool isMockedLocation = geoPosition.hasAttribute( QGeoPositionInfo::MagneticVariation ); + newPosition.isMock = isMockedLocation; + geoPosition.removeAttribute( QGeoPositionInfo::MagneticVariation ); + + QgsPoint geoidPosition; + + // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to specified geoid model + // (by default EPSG:9707 (WGS84 + EGM96)) + // we do the transformation only in case the position is not mocked, and it's ellipsoidal altitude + // the second variant is when the position is mocked, the altitude is ellipsoidal plus pass through is not enabled + const bool isInternalProviderEllipsoidAltitude = !isMockedLocation && isEllipsoidalAltitude; + const bool isMockedProviderEllipsoidAltitude = isMockedLocation && isEllipsoidalAltitude; + + if ( isInternalProviderEllipsoidAltitude || ( isMockedProviderEllipsoidAltitude && !mVerticalPassThroughEnabled ) ) + { + geoidPosition = InputUtils::transformPoint( + mSourceCrs, + mDestinationCrs, + mTransformContext, + {geoPosition.coordinate().longitude(), geoPosition.coordinate().latitude(), geoPosition.coordinate().altitude()}, + positionOutsideGeoidModelArea ); + } + // everything else gets propagated as received + else + { + geoidPosition = + { + geoPosition.coordinate().longitude(), + geoPosition.coordinate().latitude(), + geoPosition.coordinate().altitude() + }; + } + + if ( !positionOutsideGeoidModelArea ) + { + newPosition.elevation = 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, 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 + if ( isEllipsoidalAltitude && !mVerticalPassThroughEnabled ) + { + const double ellipsoidAltitude = geoPosition.coordinate().altitude(); + const double geoidSeparation = ellipsoidAltitude - geoidPosition.z(); + newPosition.elevation_diff = geoidSeparation; + } + } + + return newPosition; +} + +GeoPosition PositionTransformer::processInternalDesktopPosition( const QGeoPositionInfo &geoPosition ) +{ + GeoPosition newPosition; + newPosition.elevation = geoPosition.coordinate().altitude(); + + // 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 + + return newPosition; +} + +GeoPosition PositionTransformer::processSimulatedPosition( const GeoPosition &geoPosition ) +{ + GeoPosition newPosition; + bool positionOutsideGeoidModelArea = false; + const QgsPoint geoidPosition = InputUtils::transformPoint( + mSourceCrs, + mDestinationCrs, + mTransformContext, + {geoPosition.longitude, geoPosition.latitude, geoPosition.elevation}, + positionOutsideGeoidModelArea ); + if ( !positionOutsideGeoidModelArea ) + { + newPosition.elevation = geoidPosition.z(); + newPosition.elevation_diff = geoPosition.elevation - newPosition.elevation; + } + else + { + newPosition.elevation = std::numeric_limits::quiet_NaN(); + newPosition.elevation_diff = std::numeric_limits::quiet_NaN(); + } + + return newPosition; +} diff --git a/app/position/positiontransformer.h b/app/position/positiontransformer.h new file mode 100644 index 000000000..09d58df1b --- /dev/null +++ b/app/position/positiontransformer.h @@ -0,0 +1,48 @@ +/*************************************************************************** +* * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef POSITIONTRANSFORMER_H +#define POSITIONTRANSFORMER_H + +#include + +#include +#include + +#include "geoposition.h" + +/** + * PositionTransformer is a utility class, which should be used with providers to transform received position into set + * CRS. Currently only elevation is transformed! The PositionTransformer is project specific as different projects can + * have different set-ups. + * + * \todo This class could have a nicer API. With just one public method accepting GeoPosition and provider specifier. + */ +class PositionTransformer : QObject +{ + Q_OBJECT + public: + PositionTransformer( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, bool verticalPassThroughEnabled, QObject *parent = nullptr ); + + GeoPosition processBluetoothPosition( GeoPosition geoPosition ); + GeoPosition processAndroidPosition( GeoPosition geoPosition ); + GeoPosition processInternalAndroidPosition( const QGeoPositionInfo &geoPosition ); + GeoPosition processInternalIosPosition( QGeoPositionInfo &geoPosition ); + GeoPosition processInternalDesktopPosition( const QGeoPositionInfo &geoPosition ); + GeoPosition processSimulatedPosition( const GeoPosition &geoPosition ); + + private: + QgsCoordinateReferenceSystem mSourceCrs; + QgsCoordinateReferenceSystem mDestinationCrs; + QgsCoordinateTransformContext mTransformContext; + bool mVerticalPassThroughEnabled; +}; + + +#endif //POSITIONTRANSFORMER_H \ No newline at end of file diff --git a/app/position/providers/abstractpositionprovider.cpp b/app/position/providers/abstractpositionprovider.cpp index ea1c95554..30ae52983 100644 --- a/app/position/providers/abstractpositionprovider.cpp +++ b/app/position/providers/abstractpositionprovider.cpp @@ -9,16 +9,15 @@ #include "abstractpositionprovider.h" -AbstractPositionProvider::AbstractPositionProvider( const QString &id, const QString &type, const QString &name, QObject *object ) +AbstractPositionProvider::AbstractPositionProvider( const QString &id, const QString &type, const QString &name, PositionTransformer &positionTransformer, QObject *object ) : QObject( object ) , mProviderId( id ) , mProviderType( type ) , mProviderName( name ) + , mPositionTransformer( &positionTransformer ) { } -AbstractPositionProvider::~AbstractPositionProvider() = default; - void AbstractPositionProvider::setPosition( QgsPoint ) { } diff --git a/app/position/providers/abstractpositionprovider.h b/app/position/providers/abstractpositionprovider.h index 6705057b1..44fe57838 100644 --- a/app/position/providers/abstractpositionprovider.h +++ b/app/position/providers/abstractpositionprovider.h @@ -10,13 +10,11 @@ #ifndef ABSTRACTPOSITIONPROVIDER_H #define ABSTRACTPOSITIONPROVIDER_H -#include "qobject.h" +#include +#include "positiontransformer.h" #include "position/geoposition.h" -#include "qgspoint.h" - - class AbstractPositionProvider : public QObject { Q_OBJECT @@ -35,8 +33,8 @@ class AbstractPositionProvider : public QObject }; Q_ENUM( State ) - AbstractPositionProvider( const QString &id, const QString &type, const QString &name, QObject *object = nullptr ); - virtual ~AbstractPositionProvider(); + AbstractPositionProvider( const QString &id, const QString &type, const QString &name, PositionTransformer &positionTransformer, QObject *object = nullptr ); + ~AbstractPositionProvider() override = default; virtual void startUpdates() = 0; virtual void stopUpdates() = 0; @@ -75,9 +73,12 @@ class AbstractPositionProvider : public QObject // Internal providers has constant values of "Internal" and "Simulated provider" QString mProviderName; - // State of this provider, see State enum. Message bears human readable explanation of the state + // State of this provider, see State enum. Message bears human-readable explanation of the state QString mStateMessage; State mState = State::NoConnection; + + // Transformer used for vertical CRS transformations before providers emit new position + PositionTransformer *mPositionTransformer = nullptr; }; Q_DECLARE_METATYPE( AbstractPositionProvider::State ); diff --git a/app/position/providers/androidpositionprovider.cpp b/app/position/providers/androidpositionprovider.cpp index 41d3ef4bf..3d3b7b441 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,58 @@ 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; + + if ( location.callMethod( "hasAltitude" ) ) + { + // const jdouble altitude = location.callMethod( "getAltitude" ); + // if ( !qFuzzyIsNull( altitude ) ) + // { + // bool positionOutsideGeoidModelArea = false; + // bool valueRead = false; + // const bool isVerticalCRSPassedThrough = QgsProject::instance()->readBoolEntry( QStringLiteral( "Mergin" ), QStringLiteral( "VerticalCRSPassThrough" ), true, &valueRead ); + // // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to specified geoid model + // // (by default EPSG:9707 (WGS84 + EGM96)) + // // we do the transformation only in case the position is not mocked, and it's ellipsoidal altitude + // // the second variant is when the position is mocked, the altitude is ellipsoidal plus pass through is enabled + // if ( !isMock || ( valueRead && !isVerticalCRSPassedThrough ) ) + // { + // 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; + // } + // + // } + + pos.elevation = location.callMethod( "getAltitude" ); + pos = inst->processElevation( pos ); - // 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 + } // horizontal accuracy if ( location.callMethod( "hasAccuracy" ) ) @@ -100,9 +143,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) @@ -133,10 +173,10 @@ void jniOnPositionUpdated( JNIEnv *env, jclass clazz, jint instanceId, jobject l } -AndroidPositionProvider::AndroidPositionProvider( bool fused, QObject *parent ) +AndroidPositionProvider::AndroidPositionProvider( const bool fused, PositionTransformer &positionTransformer, QObject *parent ) : AbstractPositionProvider( fused ? QStringLiteral( "android_fused" ) : QStringLiteral( "android_gps" ), QStringLiteral( "internal" ), - fused ? tr( "Internal (fused)" ) : tr( "Internal (gps)" ), parent ) + fused ? tr( "Internal (fused)" ) : tr( "Internal (gps)" ), positionTransformer, parent ) , mFused( fused ) , mInstanceId( ++sLastInstanceId ) { @@ -258,3 +298,8 @@ void AndroidPositionProvider::closeProvider() mAndroidPos = QJniObject(); } + +GeoPosition AndroidPositionProvider::processElevation( const GeoPosition &position ) +{ + return mPositionTransformer->processAndroidPosition( position ); +} diff --git a/app/position/providers/androidpositionprovider.h b/app/position/providers/androidpositionprovider.h index f20faf8b7..c6603a8c0 100644 --- a/app/position/providers/androidpositionprovider.h +++ b/app/position/providers/androidpositionprovider.h @@ -19,7 +19,7 @@ * or Fused Location Provider from Google Play Services (when fused=true). * * Compared to Qt Positioning, it can use Fused Location Provider and it is - * potentially more flexible becuase we are not going through a generic + * potentially more flexible because we are not going through a generic * positioning API. */ class AndroidPositionProvider : public AbstractPositionProvider @@ -27,12 +27,15 @@ class AndroidPositionProvider : public AbstractPositionProvider Q_OBJECT public: - explicit AndroidPositionProvider( bool fused, QObject *parent = nullptr ); - virtual ~AndroidPositionProvider() override; + explicit AndroidPositionProvider( bool fused, PositionTransformer &positionTransformer, QObject *parent = nullptr ); + ~AndroidPositionProvider() override; - virtual void startUpdates() override; - virtual void stopUpdates() override; - virtual void closeProvider() override; + void startUpdates() override; + void stopUpdates() override; + void closeProvider() override; + + // Exposes PositionTransformer process function, as we need to access it outside the scope of this class + GeoPosition processElevation( const GeoPosition &position ); //! Checks whether the fused location provider can be used (i.e. Google Play services are present) static bool isFusedAvailable(); @@ -40,8 +43,6 @@ class AndroidPositionProvider : public AbstractPositionProvider //! It is not very human friendly, but at least something (e.g. "SERVICE_DISABLED") static QString fusedErrorString(); - public slots: - private: bool mFused; int mInstanceId; diff --git a/app/position/providers/bluetoothpositionprovider.cpp b/app/position/providers/bluetoothpositionprovider.cpp index 678455c11..35a288cf8 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" @@ -27,8 +30,8 @@ QgsGpsInformation NmeaParser::parseNmeaString( const QString &nmeastring ) return mLastGPSInformation; } -BluetoothPositionProvider::BluetoothPositionProvider( const QString &addr, const QString &name, QObject *parent ) - : AbstractPositionProvider( addr, QStringLiteral( "external" ), name, parent ) +BluetoothPositionProvider::BluetoothPositionProvider( const QString &addr, const QString &name, PositionTransformer &positionTransformer, QObject *parent ) + : AbstractPositionProvider( addr, QStringLiteral( "external" ), name, positionTransformer, parent ) , mTargetAddress( addr ) { mSocket = std::unique_ptr( new QBluetoothSocket( QBluetoothServiceInfo::RfcommProtocol ) ); @@ -204,11 +207,36 @@ 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 ); + + // bool valueRead = false; + // const bool isVerticalCRSPassedThrough = QgsProject::instance()->readBoolEntry( QStringLiteral( "Mergin" ), QStringLiteral( "VerticalCRSPassThrough" ), true, &valueRead ); + // // if the user sets custom vertical crs we apply our transformation if not we propagate the value from GNSS device + // // also check if we have data for elevation and elevation undulation + // if ( valueRead && !isVerticalCRSPassedThrough && positionData.elevation && positionData.elevation_diff ) + // { + // // 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(); + // } + // } + + GeoPosition transformedPosition = mPositionTransformer->processBluetoothPosition( positionData ); + emit positionChanged( positionData ); } } diff --git a/app/position/providers/bluetoothpositionprovider.h b/app/position/providers/bluetoothpositionprovider.h index b872b1c01..f872f1a19 100644 --- a/app/position/providers/bluetoothpositionprovider.h +++ b/app/position/providers/bluetoothpositionprovider.h @@ -51,12 +51,12 @@ class BluetoothPositionProvider : public AbstractPositionProvider }; public: - BluetoothPositionProvider( const QString &addr, const QString &name, QObject *parent = nullptr ); - virtual ~BluetoothPositionProvider() override; + BluetoothPositionProvider( const QString &addr, const QString &name, PositionTransformer &positionTransformer, QObject *parent = nullptr ); + ~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..68fa7c7d0 100644 --- a/app/position/providers/internalpositionprovider.cpp +++ b/app/position/providers/internalpositionprovider.cpp @@ -8,12 +8,14 @@ ***************************************************************************/ #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 ) +InternalPositionProvider::InternalPositionProvider( PositionTransformer &positionTransformer, QObject *parent ) + : AbstractPositionProvider( QStringLiteral( "devicegps" ), QStringLiteral( "internal" ), tr( "Internal" ), positionTransformer, parent ) { mGpsPositionSource = std::unique_ptr( QGeoPositionInfoSource::createDefaultSource( nullptr ) ); @@ -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,170 @@ 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 = QgsProject::instance()->readBoolEntry( QStringLiteral( "Mergin" ), QStringLiteral( "VerticalCRSPassThrough" ), true, &valueRead ); +// const bool isEllipsoidalAltitude = localPosition.attribute( QGeoPositionInfo::VerticalSpeed ); +// localPosition.removeAttribute( QGeoPositionInfo::VerticalSpeed ); +// const bool isMockedLocation = localPosition.attribute( QGeoPositionInfo::MagneticVariation ); +// mLastPosition.isMock = isMockedLocation; +// localPosition.removeAttribute( QGeoPositionInfo::MagneticVariation ); +// +// QgsPoint geoidPosition; +// +// // transform the altitude from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to specified geoid model +// // (by default EPSG:9707 (WGS84 + EGM96)) +// // we do the transformation only in case the position is not mocked, and it's ellipsoidal altitude +// // the second variant is when the position is mocked, the altitude is ellipsoidal plus pass through is not enabled +// const bool isInternalProviderEllipsoidAltitude = !isMockedLocation && isEllipsoidalAltitude; +// const bool isMockedProviderEllipsoidAltitude = isMockedLocation && isEllipsoidalAltitude && valueRead; +// +// if ( isInternalProviderEllipsoidAltitude || ( isMockedProviderEllipsoidAltitude && !isVerticalCRSPassedThrough ) ) +// { +// geoidPosition = InputUtils::transformPoint( +// PositionKit::positionCrs3DEllipsoidHeight(), +// PositionKit::positionCrs3D(), +// QgsProject::instance()->transformContext(), +// {localPosition.coordinate().longitude(), localPosition.coordinate().latitude(), localPosition.coordinate().altitude()}, +// positionOutsideGeoidModelArea ); +// } +// // everything else gets propagated as received +// 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 specified geoid model +// // (by default EPSG:9707 (WGS84 + EGM96)) +// const QgsPoint geoidPosition = InputUtils::transformPoint( +// PositionKit::positionCrs3DEllipsoidHeight(), +// PositionKit::positionCrs3D(), +// 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 +// } + + GeoPosition transformedPosition; +#ifdef Q_OS_IOS + transformedPosition = mPositionTransformer->processInternalIosPosition( localPosition ); +#elif defined (ANDROID) + transformedPosition = mPositionTransformer->processInternalAndroidPosition( localPosition ); +#else + transformedPosition = mPositionTransformer->processInternalDesktopPosition( localPosition ); +#endif + + if ( mLastPosition.isMock != transformedPosition.isMock ) { - mLastPosition.elevation = position.coordinate().altitude(); + mLastPosition.isMock = transformedPosition.isMock; positionDataHasChanged = true; } - bool hasSpeedInfo = position.hasAttribute( QGeoPositionInfo::GroundSpeed ); - if ( hasSpeedInfo && !qgsDoubleNear( position.attribute( QGeoPositionInfo::GroundSpeed ), mLastPosition.speed ) ) + if ( !qgsDoubleNear( transformedPosition.elevation, mLastPosition.elevation ) ) + { + mLastPosition.elevation = transformedPosition.elevation; + positionDataHasChanged = true; + } + + if ( !qgsDoubleNear( transformedPosition.elevation_diff, mLastPosition.elevation_diff ) ) + { + mLastPosition.elevation_diff = transformedPosition.elevation_diff; + positionDataHasChanged = true; + } + + + 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..79ee79ee3 100644 --- a/app/position/providers/internalpositionprovider.h +++ b/app/position/providers/internalpositionprovider.h @@ -27,12 +27,12 @@ class InternalPositionProvider : public AbstractPositionProvider Q_OBJECT public: - explicit InternalPositionProvider( QObject *parent = nullptr ); - virtual ~InternalPositionProvider() override; + explicit InternalPositionProvider( PositionTransformer &positionTransformer, QObject *parent = nullptr ); + ~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..338a1645c 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 ) - : AbstractPositionProvider( QStringLiteral( "simulated" ), QStringLiteral( "internal" ), QStringLiteral( "Simulated provider" ), parent ) +SimulatedPositionProvider::SimulatedPositionProvider( PositionTransformer &positionTransformer, const double longitude, const double latitude, const double flightRadius, const double updateTimeout, QObject *parent ) + : AbstractPositionProvider( QStringLiteral( "simulated" ), QStringLiteral( "internal" ), QStringLiteral( "Simulated provider" ), positionTransformer, 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,24 @@ 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 ) + const double ellipsoidAltitude = ( *mGenerator )() % 40 + 80; // rand altitude <80,115>m and lost (NaN) + if ( ellipsoidAltitude <= 115 ) + { + position.elevation = ellipsoidAltitude; + GeoPosition transformedPosition = mPositionTransformer->processSimulatedPosition( position ); + position.elevation = transformedPosition.elevation; + position.elevation_diff = transformedPosition.elevation_diff; + } + 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 +128,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(), + 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..3a3167057 100644 --- a/app/position/providers/simulatedpositionprovider.h +++ b/app/position/providers/simulatedpositionprovider.h @@ -33,22 +33,23 @@ class SimulatedPositionProvider : public AbstractPositionProvider * Set flightRadius to 0 in order to get constant position (no movement) */ explicit SimulatedPositionProvider( + PositionTransformer &positionTransformer, double longitude = 17.107137342092614, double latitude = 48.10301740375036, double flightRadius = 0, 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..52e791a69 100644 --- a/app/qml/gps/MMGpsDataDrawer.qml +++ b/app/qml/gps/MMGpsDataDrawer.qml @@ -62,7 +62,13 @@ MMComponents.MMDrawer { width: parent.width / 2 title: qsTr( "Source" ) - value: __positionKit.positionProvider ? __positionKit.positionProvider.name() : qsTr( "No receiver" ) + value: { + if ( PositionKit.positionProvider ) { + PositionKit.positionProviderName() + } else { + qsTr( "No receiver" ) + } + } alignmentRight: Positioner.index % 2 === 1 } @@ -72,10 +78,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 +99,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 +118,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 +137,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 +152,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 +167,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 +182,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 +197,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 +212,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 +229,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 +245,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 +262,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 +279,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 +296,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 +310,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 +329,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 +354,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..9396c250a 100644 --- a/app/test/testposition.cpp +++ b/app/test/testposition.cpp @@ -54,7 +54,7 @@ void TestPosition::simulatedPosition() { QVERIFY( !positionKit->positionProvider() ); - SimulatedPositionProvider *simulatedProvider = new SimulatedPositionProvider( -92.36, 38.93, 0 ); + SimulatedPositionProvider *simulatedProvider = new SimulatedPositionProvider( *positionKit->mPositionTransformer, -92.36, 38.93, 0 ); positionKit->setPositionProvider( simulatedProvider ); // ownership of provider is passed to positionkit @@ -72,7 +72,7 @@ void TestPosition::simulatedPosition() QVERIFY( positionKit->satellitesVisible() >= 0 ); QVERIFY( positionKit->satellitesUsed() >= 0 ); - SimulatedPositionProvider *simulatedProvider2 = new SimulatedPositionProvider( 90.36, 33.93, 0 ); + SimulatedPositionProvider *simulatedProvider2 = new SimulatedPositionProvider( *positionKit->mPositionTransformer, 90.36, 33.93, 0 ); // position kit ignores new provider if it is the same type and id, so delete the previous one first positionKit->setPositionProvider( nullptr ); // deletes the first provider @@ -95,7 +95,7 @@ void TestPosition::simulatedPosition() void TestPosition::testBluetoothProviderConnection() { - BluetoothPositionProvider *btProvider = new BluetoothPositionProvider( "AA:AA:AA:AA:00:00", "testBluetoothProvider" ); + BluetoothPositionProvider *btProvider = new BluetoothPositionProvider( "AA:AA:AA:AA:00:00", "testBluetoothProvider", *positionKit->mPositionTransformer ); positionKit->setPositionProvider( btProvider ); // positionKit takes ownership of this provider @@ -194,7 +194,7 @@ void TestPosition::testBluetoothProviderPosition() // NOTE: If you want to read NMEA sentences from file, make sure that files has CRLF line endings! // - BluetoothPositionProvider *btProvider = new BluetoothPositionProvider( "AA:AA:FF:AA:00:10", "testBluetoothProvider" ); + BluetoothPositionProvider *btProvider = new BluetoothPositionProvider( "AA:AA:FF:AA:00:10", "testBluetoothProvider", *positionKit->mPositionTransformer ); positionKit->setPositionProvider( btProvider ); // positionKit takes ownership of this provider @@ -322,7 +322,7 @@ void TestPosition::testMapPosition() ms->setLayers( QList() << tempLayer ); // Create position kit provider - SimulatedPositionProvider *provider = new SimulatedPositionProvider( 17.1, 48.1, 0 ); + SimulatedPositionProvider *provider = new SimulatedPositionProvider( *positionKit->mPositionTransformer, 17.1, 48.1, 0 ); positionKit->setPositionProvider( provider ); // Create MapPosition @@ -351,7 +351,7 @@ void TestPosition::testMapPosition() // Now let's assign a not stationary provider positionKit->setPositionProvider( nullptr ); - SimulatedPositionProvider *provider2 = new SimulatedPositionProvider( 15.1, 48.1, 1, 500 ); + SimulatedPositionProvider *provider2 = new SimulatedPositionProvider( *positionKit->mPositionTransformer, 15.1, 48.1, 1, 500 ); positionKit->setPositionProvider( provider2 ); QSignalSpy positionUpdateSpy2( provider2, &AbstractPositionProvider::positionChanged ); @@ -372,7 +372,7 @@ void TestPosition::testPositionTracking() QVERIFY( !PositionTrackingManager::constructTrackingBackend( QgsProject::instance(), nullptr ) ); // should return null without pk - SimulatedPositionProvider *simulatedProvider = new SimulatedPositionProvider( -92.36, 38.93, 0 ); + SimulatedPositionProvider *simulatedProvider = new SimulatedPositionProvider( *positionKit->mPositionTransformer, -92.36, 38.93, 0 ); positionKit->setPositionProvider( simulatedProvider ); // ownership of the provider is passed to pk simulatedProvider = nullptr; @@ -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/test/testutilsfunctions.cpp b/app/test/testutilsfunctions.cpp index 1c949f4e7..baccea051 100644 --- a/app/test/testutilsfunctions.cpp +++ b/app/test/testutilsfunctions.cpp @@ -389,7 +389,7 @@ void TestUtilsFunctions::testStakeoutPathExtent() ms.setOutputSize( QSize( 400, 620 ) ); PositionKit positionKit; - positionKit.setPositionProvider( PositionKit::constructProvider( "internal", "simulated", "simulated" ) ); + positionKit.setPositionProvider( positionKit.constructProvider( "internal", "simulated", "simulated" ) ); AbstractPositionProvider *provider = positionKit.positionProvider(); MapPosition mapPositioner; diff --git a/app/variablesmanager.cpp b/app/variablesmanager.cpp index 16fdf184a..8ecd50b6d 100644 --- a/app/variablesmanager.cpp +++ b/app/variablesmanager.cpp @@ -71,7 +71,8 @@ QgsExpressionContextScope *VariablesManager::positionScope() addPositionVariable( scope, QStringLiteral( "coordinate" ), QVariant::fromValue( point ) ); addPositionVariable( scope, QStringLiteral( "longitude" ), position.longitude ); addPositionVariable( scope, QStringLiteral( "latitude" ), position.latitude ); - addPositionVariable( scope, QStringLiteral( "altitude" ), position.elevation ); + addPositionVariable( scope, QStringLiteral( "elevation" ), position.elevation ); + addPositionVariable( scope, QStringLiteral( "elevation_ellipsoid" ), 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..9eabca42d --- /dev/null +++ b/vcpkg/ports/qtpositioning/ios_orthometric_altitude.patch @@ -0,0 +1,38 @@ +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,31 @@ + 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); ++ } ++ ++ if (newLocation.sourceInformation.isSimulatedBySoftware) { ++ location.setAttribute(QGeoPositionInfo::MagneticVariation, 1); ++ } ++ + 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