mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-08 00:06:51 -05:00
833 lines
33 KiB
C++
833 lines
33 KiB
C++
/***************************************************************************
|
|
qgsalgorithmgpsbabeltools.cpp
|
|
------------------
|
|
begin : July 2021
|
|
copyright : (C) 2021 by Nyall Dawson
|
|
email : nyall dot dawson at gmail dot com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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 <QtGlobal>
|
|
#if QT_CONFIG( process )
|
|
|
|
|
|
#include "qgsalgorithmgpsbabeltools.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgsrunprocess.h"
|
|
#include "qgsproviderutils.h"
|
|
#include "qgssettings.h"
|
|
#include "qgssettingsregistrycore.h"
|
|
#include "qgsbabelformatregistry.h"
|
|
#include "qgsbabelformat.h"
|
|
#include "qgsgpsdetector.h"
|
|
#include "qgsbabelgpsdevice.h"
|
|
|
|
///@cond PRIVATE
|
|
|
|
QString QgsConvertGpxFeatureTypeAlgorithm::name() const
|
|
{
|
|
return QStringLiteral( "convertgpxfeaturetype" );
|
|
}
|
|
|
|
QString QgsConvertGpxFeatureTypeAlgorithm::displayName() const
|
|
{
|
|
return QObject::tr( "Convert GPX feature type" );
|
|
}
|
|
|
|
QStringList QgsConvertGpxFeatureTypeAlgorithm::tags() const
|
|
{
|
|
return QObject::tr( "gps,tools,babel,tracks,waypoints,routes" ).split( ',' );
|
|
}
|
|
|
|
QString QgsConvertGpxFeatureTypeAlgorithm::group() const
|
|
{
|
|
return QObject::tr( "GPS" );
|
|
}
|
|
|
|
QString QgsConvertGpxFeatureTypeAlgorithm::groupId() const
|
|
{
|
|
return QStringLiteral( "gps" );
|
|
}
|
|
|
|
void QgsConvertGpxFeatureTypeAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFile( QStringLiteral( "INPUT" ), QObject::tr( "Input file" ), Qgis::ProcessingFileParameterBehavior::File, QString(), QVariant(), false, QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );
|
|
|
|
addParameter( new QgsProcessingParameterEnum( QStringLiteral( "CONVERSION" ), QObject::tr( "Conversion" ), { QObject::tr( "Waypoints from a Route" ), QObject::tr( "Waypoints from a Track" ), QObject::tr( "Route from Waypoints" ), QObject::tr( "Track from Waypoints" ) }, 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 QgsConvertGpxFeatureTypeAlgorithm::icon() const
|
|
{
|
|
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
|
|
}
|
|
|
|
QString QgsConvertGpxFeatureTypeAlgorithm::svgIconPath() const
|
|
{
|
|
return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
|
|
}
|
|
|
|
QString QgsConvertGpxFeatureTypeAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm uses the GPSBabel tool to convert GPX features from one type to another (e.g. converting all waypoint features to a route feature)." );
|
|
}
|
|
|
|
QString QgsConvertGpxFeatureTypeAlgorithm::shortDescription() const
|
|
{
|
|
return QObject::tr( "Converts GPX features from one type to another." );
|
|
}
|
|
|
|
QgsConvertGpxFeatureTypeAlgorithm *QgsConvertGpxFeatureTypeAlgorithm::createInstance() const
|
|
{
|
|
return new QgsConvertGpxFeatureTypeAlgorithm();
|
|
}
|
|
|
|
|
|
QVariantMap QgsConvertGpxFeatureTypeAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
const QString inputPath = parameterAsString( parameters, QStringLiteral( "INPUT" ), context );
|
|
const QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
|
|
|
|
const ConversionType convertType = static_cast<ConversionType>( parameterAsEnum( parameters, QStringLiteral( "CONVERSION" ), context ) );
|
|
|
|
QString babelPath = QgsSettingsRegistryCore::settingsGpsBabelPath->value();
|
|
if ( babelPath.isEmpty() )
|
|
babelPath = QStringLiteral( "gpsbabel" );
|
|
|
|
QStringList processArgs;
|
|
QStringList logArgs;
|
|
createArgumentLists( inputPath, outputPath, convertType, processArgs, logArgs );
|
|
feedback->pushCommandInfo( QObject::tr( "Conversion command: " ) + babelPath + ' ' + logArgs.join( ' ' ) );
|
|
|
|
QgsBlockingProcess babelProcess( babelPath, processArgs );
|
|
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 ( convertType )
|
|
{
|
|
case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromRoute:
|
|
case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromTrack:
|
|
layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=waypoint", layerName, QStringLiteral( "gpx" ) );
|
|
break;
|
|
case QgsConvertGpxFeatureTypeAlgorithm::RouteFromWaypoints:
|
|
layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=route", layerName, QStringLiteral( "gpx" ) );
|
|
break;
|
|
case QgsConvertGpxFeatureTypeAlgorithm::TrackFromWaypoints:
|
|
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 );
|
|
const 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;
|
|
}
|
|
|
|
void QgsConvertGpxFeatureTypeAlgorithm::createArgumentLists( const QString &inputPath, const QString &outputPath, ConversionType conversion, QStringList &processArgs, QStringList &logArgs )
|
|
{
|
|
logArgs.reserve( 10 );
|
|
processArgs.reserve( 10 );
|
|
for ( const QString &arg : { QStringLiteral( "-i" ), QStringLiteral( "gpx" ), QStringLiteral( "-f" ) } )
|
|
{
|
|
logArgs << arg;
|
|
processArgs << arg;
|
|
}
|
|
|
|
// when showing the babel command, wrap filenames in "", which is what QProcess does internally.
|
|
logArgs << QStringLiteral( "\"%1\"" ).arg( inputPath );
|
|
processArgs << inputPath;
|
|
|
|
QStringList convertStrings;
|
|
switch ( conversion )
|
|
{
|
|
case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromRoute:
|
|
convertStrings << QStringLiteral( "-x" ) << QStringLiteral( "transform,wpt=rte,del" );
|
|
break;
|
|
case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromTrack:
|
|
convertStrings << QStringLiteral( "-x" ) << QStringLiteral( "transform,wpt=trk,del" );
|
|
break;
|
|
case QgsConvertGpxFeatureTypeAlgorithm::RouteFromWaypoints:
|
|
convertStrings << QStringLiteral( "-x" ) << QStringLiteral( "transform,rte=wpt,del" );
|
|
break;
|
|
case QgsConvertGpxFeatureTypeAlgorithm::TrackFromWaypoints:
|
|
convertStrings << QStringLiteral( "-x" ) << QStringLiteral( "transform,trk=wpt,del" );
|
|
break;
|
|
}
|
|
logArgs << convertStrings;
|
|
processArgs << convertStrings;
|
|
|
|
for ( const QString &arg : { QStringLiteral( "-o" ), QStringLiteral( "gpx" ), QStringLiteral( "-F" ) } )
|
|
{
|
|
logArgs << arg;
|
|
processArgs << arg;
|
|
}
|
|
|
|
logArgs << QStringLiteral( "\"%1\"" ).arg( outputPath );
|
|
processArgs << outputPath;
|
|
}
|
|
|
|
|
|
//
|
|
// 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" ), Qgis::ProcessingFileParameterBehavior::File, QString(), QVariant(), false, QgsApplication::gpsBabelFormatRegistry()->importFileFilter() + QStringLiteral( ";;%1" ).arg( QObject::tr( "All files (*.*)" ) ) ) );
|
|
|
|
auto 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." );
|
|
}
|
|
|
|
QString QgsConvertGpsDataAlgorithm::shortDescription() const
|
|
{
|
|
return QObject::tr( "Converts 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 ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
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( QLatin1String( ", " ) ) ) );
|
|
}
|
|
|
|
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 );
|
|
const 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;
|
|
}
|
|
|
|
//
|
|
// QgsDownloadGpsDataAlgorithm
|
|
//
|
|
|
|
QString QgsDownloadGpsDataAlgorithm::name() const
|
|
{
|
|
return QStringLiteral( "downloadgpsdata" );
|
|
}
|
|
|
|
QString QgsDownloadGpsDataAlgorithm::displayName() const
|
|
{
|
|
return QObject::tr( "Download GPS data from device" );
|
|
}
|
|
|
|
QStringList QgsDownloadGpsDataAlgorithm::tags() const
|
|
{
|
|
return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export,export,device,serial" ).split( ',' );
|
|
}
|
|
|
|
QString QgsDownloadGpsDataAlgorithm::group() const
|
|
{
|
|
return QObject::tr( "GPS" );
|
|
}
|
|
|
|
QString QgsDownloadGpsDataAlgorithm::groupId() const
|
|
{
|
|
return QStringLiteral( "gps" );
|
|
}
|
|
|
|
void QgsDownloadGpsDataAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
auto deviceParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "DEVICE" ), QObject::tr( "Device" ) );
|
|
|
|
QStringList deviceNames = QgsApplication::gpsBabelFormatRegistry()->deviceNames();
|
|
std::sort( deviceNames.begin(), deviceNames.end(), []( const QString &a, const QString &b ) {
|
|
return a.compare( b, Qt::CaseInsensitive ) < 0;
|
|
} );
|
|
|
|
deviceParam->setMetadata( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "value_hints" ), deviceNames } } ) }
|
|
} );
|
|
addParameter( deviceParam.release() );
|
|
|
|
|
|
const QList<QPair<QString, QString>> devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( QStringLiteral( "usb:" ), QStringLiteral( "usb:" ) );
|
|
auto portParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "PORT" ), QObject::tr( "Port" ) );
|
|
|
|
QStringList ports;
|
|
for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
|
|
ports << it->second;
|
|
std::sort( ports.begin(), ports.end(), []( const QString &a, const QString &b ) {
|
|
return a.compare( b, Qt::CaseInsensitive ) < 0;
|
|
} );
|
|
|
|
portParam->setMetadata( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "value_hints" ), ports } } ) }
|
|
} );
|
|
addParameter( portParam.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 QgsDownloadGpsDataAlgorithm::icon() const
|
|
{
|
|
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
|
|
}
|
|
|
|
QString QgsDownloadGpsDataAlgorithm::svgIconPath() const
|
|
{
|
|
return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
|
|
}
|
|
|
|
QString QgsDownloadGpsDataAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm uses the GPSBabel tool to download data from a GPS device into the GPX standard format." );
|
|
}
|
|
|
|
QString QgsDownloadGpsDataAlgorithm::shortDescription() const
|
|
{
|
|
return QObject::tr( "Downloads data from a GPS device into the GPX standard format." );
|
|
}
|
|
|
|
QgsDownloadGpsDataAlgorithm *QgsDownloadGpsDataAlgorithm::createInstance() const
|
|
{
|
|
return new QgsDownloadGpsDataAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsDownloadGpsDataAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
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 deviceName = parameterAsString( parameters, QStringLiteral( "DEVICE" ), context );
|
|
const QgsBabelGpsDeviceFormat *format = QgsApplication::gpsBabelFormatRegistry()->deviceFormat( deviceName );
|
|
if ( !format )
|
|
{
|
|
throw QgsProcessingException( QObject::tr( "Unknown GPSBabel device “%1”. Valid devices are: %2" )
|
|
.arg( deviceName, QgsApplication::gpsBabelFormatRegistry()->deviceNames().join( QLatin1String( ", " ) ) ) );
|
|
}
|
|
|
|
const QString portName = parameterAsString( parameters, QStringLiteral( "PORT" ), context );
|
|
QString inputPort;
|
|
const QList<QPair<QString, QString>> devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( QStringLiteral( "usb:" ), QStringLiteral( "usb:" ) );
|
|
QStringList validPorts;
|
|
for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
|
|
{
|
|
if ( it->first.compare( portName, Qt::CaseInsensitive ) == 0 || it->second.compare( portName, Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
inputPort = it->first;
|
|
}
|
|
validPorts << it->first;
|
|
}
|
|
if ( inputPort.isEmpty() )
|
|
{
|
|
throw QgsProcessingException( QObject::tr( "Unknown port “%1”. Valid ports are: %2" )
|
|
.arg( portName, validPorts.join( QLatin1String( ", " ) ) ) );
|
|
}
|
|
|
|
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( deviceName ) );
|
|
}
|
|
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( deviceName ) );
|
|
}
|
|
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( deviceName ) );
|
|
}
|
|
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, inputPort, outputPath, Qgis::BabelCommandFlag::QuoteFilePaths );
|
|
const QStringList processCommand = format->importCommand( babelPath, featureType, inputPort, outputPath );
|
|
feedback->pushCommandInfo( QObject::tr( "Download 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 );
|
|
const 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;
|
|
}
|
|
|
|
|
|
//
|
|
// QgsUploadGpsDataAlgorithm
|
|
//
|
|
|
|
QString QgsUploadGpsDataAlgorithm::name() const
|
|
{
|
|
return QStringLiteral( "uploadgpsdata" );
|
|
}
|
|
|
|
QString QgsUploadGpsDataAlgorithm::displayName() const
|
|
{
|
|
return QObject::tr( "Upload GPS data to device" );
|
|
}
|
|
|
|
QStringList QgsUploadGpsDataAlgorithm::tags() const
|
|
{
|
|
return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export,export,device,serial" ).split( ',' );
|
|
}
|
|
|
|
QString QgsUploadGpsDataAlgorithm::group() const
|
|
{
|
|
return QObject::tr( "GPS" );
|
|
}
|
|
|
|
QString QgsUploadGpsDataAlgorithm::groupId() const
|
|
{
|
|
return QStringLiteral( "gps" );
|
|
}
|
|
|
|
void QgsUploadGpsDataAlgorithm::initAlgorithm( const QVariantMap & )
|
|
{
|
|
addParameter( new QgsProcessingParameterFile( QStringLiteral( "INPUT" ), QObject::tr( "Input file" ), Qgis::ProcessingFileParameterBehavior::File, QString(), QVariant(), false, QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );
|
|
|
|
auto deviceParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "DEVICE" ), QObject::tr( "Device" ) );
|
|
|
|
QStringList deviceNames = QgsApplication::gpsBabelFormatRegistry()->deviceNames();
|
|
std::sort( deviceNames.begin(), deviceNames.end(), []( const QString &a, const QString &b ) {
|
|
return a.compare( b, Qt::CaseInsensitive ) < 0;
|
|
} );
|
|
|
|
deviceParam->setMetadata( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "value_hints" ), deviceNames } } ) }
|
|
} );
|
|
addParameter( deviceParam.release() );
|
|
|
|
const QList<QPair<QString, QString>> devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( QStringLiteral( "usb:" ), QStringLiteral( "usb:" ) );
|
|
auto portParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "PORT" ), QObject::tr( "Port" ) );
|
|
|
|
QStringList ports;
|
|
for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
|
|
ports << it->second;
|
|
std::sort( ports.begin(), ports.end(), []( const QString &a, const QString &b ) {
|
|
return a.compare( b, Qt::CaseInsensitive ) < 0;
|
|
} );
|
|
|
|
portParam->setMetadata( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "value_hints" ), ports } } ) }
|
|
} );
|
|
addParameter( portParam.release() );
|
|
|
|
addParameter( new QgsProcessingParameterEnum( QStringLiteral( "FEATURE_TYPE" ), QObject::tr( "Feature type" ), { QObject::tr( "Waypoints" ), QObject::tr( "Routes" ), QObject::tr( "Tracks" ) }, false, 0 ) );
|
|
}
|
|
|
|
QIcon QgsUploadGpsDataAlgorithm::icon() const
|
|
{
|
|
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
|
|
}
|
|
|
|
QString QgsUploadGpsDataAlgorithm::svgIconPath() const
|
|
{
|
|
return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
|
|
}
|
|
|
|
QString QgsUploadGpsDataAlgorithm::shortHelpString() const
|
|
{
|
|
return QObject::tr( "This algorithm uses the GPSBabel tool to upload data to a GPS device from the GPX standard format." );
|
|
}
|
|
|
|
QString QgsUploadGpsDataAlgorithm::shortDescription() const
|
|
{
|
|
return QObject::tr( "Uploads data to a GPS device from the GPX standard format." );
|
|
}
|
|
|
|
QgsUploadGpsDataAlgorithm *QgsUploadGpsDataAlgorithm::createInstance() const
|
|
{
|
|
return new QgsUploadGpsDataAlgorithm();
|
|
}
|
|
|
|
QVariantMap QgsUploadGpsDataAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
|
{
|
|
const QString inputPath = parameterAsString( parameters, QStringLiteral( "INPUT" ), 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 deviceName = parameterAsString( parameters, QStringLiteral( "DEVICE" ), context );
|
|
const QgsBabelGpsDeviceFormat *format = QgsApplication::gpsBabelFormatRegistry()->deviceFormat( deviceName );
|
|
if ( !format )
|
|
{
|
|
throw QgsProcessingException( QObject::tr( "Unknown GPSBabel device “%1”. Valid devices are: %2" )
|
|
.arg( deviceName, QgsApplication::gpsBabelFormatRegistry()->deviceNames().join( QLatin1String( ", " ) ) ) );
|
|
}
|
|
|
|
const QString portName = parameterAsString( parameters, QStringLiteral( "PORT" ), context );
|
|
QString outputPort;
|
|
const QList<QPair<QString, QString>> devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( QStringLiteral( "usb:" ), QStringLiteral( "usb:" ) );
|
|
QStringList validPorts;
|
|
for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
|
|
{
|
|
if ( it->first.compare( portName, Qt::CaseInsensitive ) == 0 || it->second.compare( portName, Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
outputPort = it->first;
|
|
}
|
|
validPorts << it->first;
|
|
}
|
|
if ( outputPort.isEmpty() )
|
|
{
|
|
throw QgsProcessingException( QObject::tr( "Unknown port “%1”. Valid ports are: %2" )
|
|
.arg( portName, validPorts.join( QLatin1String( ", " ) ) ) );
|
|
}
|
|
|
|
|
|
switch ( featureType )
|
|
{
|
|
case Qgis::GpsFeatureType::Waypoint:
|
|
if ( !( format->capabilities() & Qgis::BabelFormatCapability::Waypoints ) )
|
|
{
|
|
throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support waypoints." )
|
|
.arg( deviceName ) );
|
|
}
|
|
break;
|
|
|
|
case Qgis::GpsFeatureType::Route:
|
|
if ( !( format->capabilities() & Qgis::BabelFormatCapability::Routes ) )
|
|
{
|
|
throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support routes." )
|
|
.arg( deviceName ) );
|
|
}
|
|
break;
|
|
|
|
case Qgis::GpsFeatureType::Track:
|
|
if ( !( format->capabilities() & Qgis::BabelFormatCapability::Tracks ) )
|
|
{
|
|
throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support tracks." )
|
|
.arg( deviceName ) );
|
|
}
|
|
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->exportCommand( babelPath, featureType, inputPath, outputPort, Qgis::BabelCommandFlag::QuoteFilePaths );
|
|
const QStringList processCommand = format->exportCommand( babelPath, featureType, inputPath, outputPort );
|
|
feedback->pushCommandInfo( QObject::tr( "Upload 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 ) );
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
///@endcond
|
|
#endif // process
|