Navigation Menu

Skip to content

Commit

Permalink
Migrate GPS tools "import data" functionality to new Processing
Browse files Browse the repository at this point in the history
algorithm "Convert GPS data"

Exposes the same functionality which allows users to convert
from a range of raw GPS data formats to the standard GPX file
type (using a GPSBabel backend).
  • Loading branch information
nyalldawson committed Aug 2, 2021
1 parent 6818a55 commit 3c7c600
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 276 deletions.
213 changes: 213 additions & 0 deletions src/analysis/processing/qgsalgorithmgpsbabeltools.cpp
Expand Up @@ -21,6 +21,8 @@
#include "qgsproviderutils.h"
#include "qgssettings.h"
#include "qgssettingsregistrycore.h"
#include "qgsbabelformatregistry.h"
#include "qgsbabelformat.h"

///@cond PRIVATE

Expand Down Expand Up @@ -217,4 +219,215 @@ void QgsConvertGpxFeatureTypeAlgorithm::createArgumentLists( const QString &inpu

}


//
// QgsConvertGpsDataAlgorithm
//

QString QgsConvertGpsDataAlgorithm::name() const
{
return QStringLiteral( "convertgpsdata" );
}

QString QgsConvertGpsDataAlgorithm::displayName() const
{
return QObject::tr( "Convert GPS data" );
}

QStringList QgsConvertGpsDataAlgorithm::tags() const
{
return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export" ).split( ',' );
}

QString QgsConvertGpsDataAlgorithm::group() const
{
return QObject::tr( "GPS" );
}

QString QgsConvertGpsDataAlgorithm::groupId() const
{
return QStringLiteral( "gps" );
}

void QgsConvertGpsDataAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterFile( QStringLiteral( "INPUT" ), QObject::tr( "Input file" ), QgsProcessingParameterFile::File, QString(), QVariant(), false,
QgsApplication::gpsBabelFormatRegistry()->importFileFilter() + QStringLiteral( ";;%1" ).arg( QObject::tr( "All files (*.*)" ) ) ) );

std::unique_ptr< QgsProcessingParameterString > formatParam = std::make_unique< QgsProcessingParameterString >( QStringLiteral( "FORMAT" ), QObject::tr( "Format" ) );

QStringList formats;
const QStringList formatNames = QgsApplication::gpsBabelFormatRegistry()->importFormatNames();
for ( const QString &format : formatNames )
formats << QgsApplication::gpsBabelFormatRegistry()->importFormat( format )->description();

std::sort( formats.begin(), formats.end(), []( const QString & a, const QString & b )
{
return a.compare( b, Qt::CaseInsensitive ) < 0;
} );

formatParam->setMetadata( {{
QStringLiteral( "widget_wrapper" ), QVariantMap(
{{QStringLiteral( "value_hints" ), formats }}
)
}
} );
addParameter( formatParam.release() );

addParameter( new QgsProcessingParameterEnum( QStringLiteral( "FEATURE_TYPE" ), QObject::tr( "Feature type" ),
{
QObject::tr( "Waypoints" ),
QObject::tr( "Routes" ),
QObject::tr( "Tracks" )
}, false, 0 ) );

addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output" ), QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );

addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT_LAYER" ), QObject::tr( "Output layer" ) ) );
}

QIcon QgsConvertGpsDataAlgorithm::icon() const
{
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
}

QString QgsConvertGpsDataAlgorithm::svgIconPath() const
{
return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
}

QString QgsConvertGpsDataAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm uses the GPSBabel tool to convert a GPS data file from a range of formats to the GPX standard format." );
}

QgsConvertGpsDataAlgorithm *QgsConvertGpsDataAlgorithm::createInstance() const
{
return new QgsConvertGpsDataAlgorithm();
}

QVariantMap QgsConvertGpsDataAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
QStringList convertStrings;

const QString inputPath = parameterAsString( parameters, QStringLiteral( "INPUT" ), context );
const QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );

const Qgis::GpsFeatureType featureType = static_cast< Qgis::GpsFeatureType >( parameterAsEnum( parameters, QStringLiteral( "FEATURE_TYPE" ), context ) );

QString babelPath = QgsSettingsRegistryCore::settingsGpsBabelPath.value();
if ( babelPath.isEmpty() )
babelPath = QStringLiteral( "gpsbabel" );

const QString formatName = parameterAsString( parameters, QStringLiteral( "FORMAT" ), context );
const QgsBabelSimpleImportFormat *format = QgsApplication::gpsBabelFormatRegistry()->importFormat( formatName );
if ( !format ) // second try, match using descriptions instead of names
format = QgsApplication::gpsBabelFormatRegistry()->importFormatByDescription( formatName );

if ( !format )
{
throw QgsProcessingException( QObject::tr( "Unknown GPSBabel format “%1”. Valid formats are: %2" )
.arg( formatName,
QgsApplication::gpsBabelFormatRegistry()->importFormatNames().join( QStringLiteral( ", " ) ) ) );
}

switch ( featureType )
{
case Qgis::GpsFeatureType::Waypoint:
if ( !( format->capabilities() & Qgis::BabelFormatCapability::Waypoints ) )
{
throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting waypoints." )
.arg( formatName ) );
}
break;

case Qgis::GpsFeatureType::Route:
if ( !( format->capabilities() & Qgis::BabelFormatCapability::Routes ) )
{
throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting routes." )
.arg( formatName ) );
}
break;

case Qgis::GpsFeatureType::Track:
if ( !( format->capabilities() & Qgis::BabelFormatCapability::Tracks ) )
{
throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting tracks." )
.arg( formatName ) );
}
break;
}

// note that for the log we should quote file paths, but for the actual command we don't. That's
// because QProcess does this internally for us, and double quoting causes issues
const QStringList logCommand = format->importCommand( babelPath, featureType, inputPath, outputPath, Qgis::BabelCommandFlag::QuoteFilePaths );
const QStringList processCommand = format->importCommand( babelPath, featureType, inputPath, outputPath );
feedback->pushCommandInfo( QObject::tr( "Conversion command: " ) + logCommand.join( ' ' ) );

QgsBlockingProcess babelProcess( processCommand.value( 0 ), processCommand.mid( 1 ) );
babelProcess.setStdErrHandler( [ = ]( const QByteArray & ba )
{
feedback->reportError( ba );
} );
babelProcess.setStdOutHandler( [ = ]( const QByteArray & ba )
{
feedback->pushDebugInfo( ba );
} );

const int res = babelProcess.run( feedback );
if ( feedback->isCanceled() && res != 0 )
{
feedback->pushInfo( QObject::tr( "Process was canceled and did not complete" ) ) ;
}
else if ( !feedback->isCanceled() && babelProcess.exitStatus() == QProcess::CrashExit )
{
throw QgsProcessingException( QObject::tr( "Process was unexpectedly terminated" ) );
}
else if ( res == 0 )
{
feedback->pushInfo( QObject::tr( "Process completed successfully" ) );
}
else if ( babelProcess.processError() == QProcess::FailedToStart )
{
throw QgsProcessingException( QObject::tr( "Process %1 failed to start. Either %1 is missing, or you may have insufficient permissions to run the program." ).arg( babelPath ) );
}
else
{
throw QgsProcessingException( QObject::tr( "Process returned error code %1" ).arg( res ) );
}

std::unique_ptr< QgsVectorLayer > layer;
const QString layerName = QgsProviderUtils::suggestLayerNameFromFilePath( outputPath );
// add the layer
switch ( featureType )
{
case Qgis::GpsFeatureType::Waypoint:
layer = std::make_unique< QgsVectorLayer >( outputPath + "?type=waypoint", layerName, QStringLiteral( "gpx" ) );
break;
case Qgis::GpsFeatureType::Route:
layer = std::make_unique< QgsVectorLayer >( outputPath + "?type=route", layerName, QStringLiteral( "gpx" ) );
break;
case Qgis::GpsFeatureType::Track:
layer = std::make_unique< QgsVectorLayer >( outputPath + "?type=track", layerName, QStringLiteral( "gpx" ) );
break;
}

QVariantMap outputs;
if ( !layer->isValid() )
{
feedback->reportError( QObject::tr( "Resulting file is not a valid GPX layer" ) );
}
else
{
const QString layerId = layer->id();
outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), layerId );
QgsProcessingContext::LayerDetails details( layer->name(), context.project(), QStringLiteral( "OUTPUT_LAYER" ), QgsProcessingUtils::LayerHint::Vector );
context.addLayerToLoadOnCompletion( layerId, details );
context.temporaryLayerStore()->addMapLayer( layer.release() );
}

outputs.insert( QStringLiteral( "OUTPUT" ), outputPath );
return outputs;
}

///@endcond
33 changes: 33 additions & 0 deletions src/analysis/processing/qgsalgorithmgpsbabeltools.h
Expand Up @@ -78,6 +78,39 @@ class ANALYSIS_EXPORT QgsConvertGpxFeatureTypeAlgorithm : public QgsProcessingAl

};


/**
* Convert GPS data to GPX algorithm
*/
class ANALYSIS_EXPORT QgsConvertGpsDataAlgorithm : public QgsProcessingAlgorithm
{

public:

QgsConvertGpsDataAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QIcon icon() const override;
QString svgIconPath() const override;
QString name() const override;
QString displayName() const override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QString shortHelpString() const override;
QgsConvertGpsDataAlgorithm *createInstance() const override SIP_FACTORY;

protected:

QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;

private:

friend class TestQgsProcessingAlgs;

};


///@endcond PRIVATE

#endif // QGSALGORITHMGPSBABELTOOLS_H
Expand Down
1 change: 1 addition & 0 deletions src/analysis/processing/qgsnativealgorithms.cpp
Expand Up @@ -336,6 +336,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsFuzzifyRasterNearMembershipAlgorithm() );
addAlgorithm( new QgsGeometryByExpressionAlgorithm() );
addAlgorithm( new QgsConvertGpxFeatureTypeAlgorithm() );
addAlgorithm( new QgsConvertGpsDataAlgorithm() );
addAlgorithm( new QgsGridAlgorithm() );
addAlgorithm( new QgsHillshadeAlgorithm() );
addAlgorithm( new QgsImportPhotosAlgorithm() );
Expand Down
63 changes: 0 additions & 63 deletions src/plugins/gps_importer/qgsgpsplugin.cpp
Expand Up @@ -117,8 +117,6 @@ void QgsGpsPlugin::run()
QgsGuiUtils::ModalDialogFlags );
myPluginGui->setAttribute( Qt::WA_DeleteOnClose );
//listen for when the layer has been made so we can draw it
connect( myPluginGui, &QgsGpsPluginGui::importGPSFile,
this, &QgsGpsPlugin::importGPSFile );
connect( myPluginGui, &QgsGpsPluginGui::downloadFromGPS,
this, &QgsGpsPlugin::downloadFromGPS );
connect( myPluginGui, &QgsGpsPluginGui::uploadToGPS,
Expand Down Expand Up @@ -146,67 +144,6 @@ void QgsGpsPlugin::unload()
mQActionPointer = nullptr;
}

void QgsGpsPlugin::importGPSFile( const QString &inputFileName, QgsAbstractBabelFormat *importer,
Qgis::GpsFeatureType type, const QString &outputFileName,
const QString &layerName )
{
// try to start the gpsbabel process
QStringList babelArgs = importer->importCommand( mBabelPath, type, inputFileName, outputFileName );

QgsDebugMsg( QStringLiteral( "Import command: " ) + babelArgs.join( "|" ) );

QProcess babelProcess;
babelProcess.start( babelArgs.value( 0 ), babelArgs.mid( 1 ) );
if ( !babelProcess.waitForStarted() )
{
QMessageBox::warning( nullptr, tr( "Import GPS File" ),
tr( "Could not start GPSBabel." ) );
return;
}

// wait for gpsbabel to finish (or the user to cancel)
QProgressDialog progressDialog( tr( "Importing data…" ), tr( "Cancel" ), 0, 0 );
progressDialog.setWindowModality( Qt::WindowModal );
for ( int i = 0; babelProcess.state() == QProcess::Running; ++i )
{
progressDialog.setValue( i / 64 );
if ( progressDialog.wasCanceled() )
return;
}

babelProcess.waitForFinished();

// did we get any data?
if ( babelProcess.exitCode() != 0 )
{
QString babelError( babelProcess.readAllStandardError() );
QString errorMsg( tr( "Could not import data from %1!\n\n" )
.arg( inputFileName ) );
errorMsg += babelError;
QMessageBox::warning( nullptr, tr( "Import GPS File" ), errorMsg );
return;
}

// add the layer
switch ( type )
{
case Qgis::GpsFeatureType::Waypoint:
drawVectorLayer( outputFileName + "?type=waypoint",
layerName, QStringLiteral( "gpx" ) );
break;
case Qgis::GpsFeatureType::Route:
drawVectorLayer( outputFileName + "?type=route",
layerName, QStringLiteral( "gpx" ) );
break;
case Qgis::GpsFeatureType::Track:
drawVectorLayer( outputFileName + "?type=track",
layerName, QStringLiteral( "gpx" ) );
break;
}

emit closeGui();
}

void QgsGpsPlugin::downloadFromGPS( const QString &device, const QString &port,
Qgis::GpsFeatureType type, const QString &outputFileName,
const QString &layerName )
Expand Down
3 changes: 0 additions & 3 deletions src/plugins/gps_importer/qgsgpsplugin.h
Expand Up @@ -60,9 +60,6 @@ class QgsGpsPlugin: public QObject, public QgisPlugin
//! update the plugins theme when the app tells us its theme is changed
void setCurrentTheme( const QString &themeName );

void importGPSFile( const QString &inputFileName, QgsAbstractBabelFormat *importer,
Qgis::GpsFeatureType type, const QString &outputFileName,
const QString &layerName );
void downloadFromGPS( const QString &device, const QString &port,
Qgis::GpsFeatureType type, const QString &outputFileName,
const QString &layerName );
Expand Down

0 comments on commit 3c7c600

Please sign in to comment.