QGIS API Documentation 3.43.0-Master (e01d6d7c4c0)
qgsalgorithmgpsbabeltools.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmgpsbabeltools.cpp
3 ------------------
4 begin : July 2021
5 copyright : (C) 2021 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include <QtGlobal>
19#if QT_CONFIG( process )
20
21
23#include "qgsvectorlayer.h"
24#include "qgsrunprocess.h"
25#include "qgsproviderutils.h"
26#include "qgssettings.h"
29#include "qgsbabelformat.h"
30#include "qgsgpsdetector.h"
31#include "qgsbabelgpsdevice.h"
32
34
35QString QgsConvertGpxFeatureTypeAlgorithm::name() const
36{
37 return QStringLiteral( "convertgpxfeaturetype" );
38}
39
40QString QgsConvertGpxFeatureTypeAlgorithm::displayName() const
41{
42 return QObject::tr( "Convert GPX feature type" );
43}
44
45QStringList QgsConvertGpxFeatureTypeAlgorithm::tags() const
46{
47 return QObject::tr( "gps,tools,babel,tracks,waypoints,routes" ).split( ',' );
48}
49
50QString QgsConvertGpxFeatureTypeAlgorithm::group() const
51{
52 return QObject::tr( "GPS" );
53}
54
55QString QgsConvertGpxFeatureTypeAlgorithm::groupId() const
56{
57 return QStringLiteral( "gps" );
58}
59
60void QgsConvertGpxFeatureTypeAlgorithm::initAlgorithm( const QVariantMap & )
61{
62 addParameter( new QgsProcessingParameterFile( QStringLiteral( "INPUT" ), QObject::tr( "Input file" ), Qgis::ProcessingFileParameterBehavior::File, QString(), QVariant(), false, QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );
63
64 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 ) );
65
66 addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output" ), QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );
67
68 addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT_LAYER" ), QObject::tr( "Output layer" ) ) );
69}
70
71QIcon QgsConvertGpxFeatureTypeAlgorithm::icon() const
72{
73 return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
74}
75
76QString QgsConvertGpxFeatureTypeAlgorithm::svgIconPath() const
77{
78 return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
79}
80
81QString QgsConvertGpxFeatureTypeAlgorithm::shortHelpString() const
82{
83 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)." );
84}
85
86QString QgsConvertGpxFeatureTypeAlgorithm::shortDescription() const
87{
88 return QObject::tr( "Converts GPX features from one type to another." );
89}
90
91QgsConvertGpxFeatureTypeAlgorithm *QgsConvertGpxFeatureTypeAlgorithm::createInstance() const
92{
93 return new QgsConvertGpxFeatureTypeAlgorithm();
94}
95
96
97QVariantMap QgsConvertGpxFeatureTypeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
98{
99 const QString inputPath = parameterAsString( parameters, QStringLiteral( "INPUT" ), context );
100 const QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
101
102 const ConversionType convertType = static_cast<ConversionType>( parameterAsEnum( parameters, QStringLiteral( "CONVERSION" ), context ) );
103
105 if ( babelPath.isEmpty() )
106 babelPath = QStringLiteral( "gpsbabel" );
107
108 QStringList processArgs;
109 QStringList logArgs;
110 createArgumentLists( inputPath, outputPath, convertType, processArgs, logArgs );
111 feedback->pushCommandInfo( QObject::tr( "Conversion command: " ) + babelPath + ' ' + logArgs.join( ' ' ) );
112
113 QgsBlockingProcess babelProcess( babelPath, processArgs );
114 babelProcess.setStdErrHandler( [=]( const QByteArray &ba ) {
115 feedback->reportError( ba );
116 } );
117 babelProcess.setStdOutHandler( [=]( const QByteArray &ba ) {
118 feedback->pushDebugInfo( ba );
119 } );
120
121 const int res = babelProcess.run( feedback );
122 if ( feedback->isCanceled() && res != 0 )
123 {
124 feedback->pushInfo( QObject::tr( "Process was canceled and did not complete" ) );
125 }
126 else if ( !feedback->isCanceled() && babelProcess.exitStatus() == QProcess::CrashExit )
127 {
128 throw QgsProcessingException( QObject::tr( "Process was unexpectedly terminated" ) );
129 }
130 else if ( res == 0 )
131 {
132 feedback->pushInfo( QObject::tr( "Process completed successfully" ) );
133 }
134 else if ( babelProcess.processError() == QProcess::FailedToStart )
135 {
136 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 ) );
137 }
138 else
139 {
140 throw QgsProcessingException( QObject::tr( "Process returned error code %1" ).arg( res ) );
141 }
142
143 std::unique_ptr<QgsVectorLayer> layer;
144 const QString layerName = QgsProviderUtils::suggestLayerNameFromFilePath( outputPath );
145 // add the layer
146 switch ( convertType )
147 {
148 case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromRoute:
149 case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromTrack:
150 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=waypoint", layerName, QStringLiteral( "gpx" ) );
151 break;
152 case QgsConvertGpxFeatureTypeAlgorithm::RouteFromWaypoints:
153 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=route", layerName, QStringLiteral( "gpx" ) );
154 break;
155 case QgsConvertGpxFeatureTypeAlgorithm::TrackFromWaypoints:
156 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=track", layerName, QStringLiteral( "gpx" ) );
157 break;
158 }
159
160 QVariantMap outputs;
161 if ( !layer->isValid() )
162 {
163 feedback->reportError( QObject::tr( "Resulting file is not a valid GPX layer" ) );
164 }
165 else
166 {
167 const QString layerId = layer->id();
168 outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), layerId );
169 const QgsProcessingContext::LayerDetails details( layer->name(), context.project(), QStringLiteral( "OUTPUT_LAYER" ), QgsProcessingUtils::LayerHint::Vector );
170 context.addLayerToLoadOnCompletion( layerId, details );
171 context.temporaryLayerStore()->addMapLayer( layer.release() );
172 }
173
174 outputs.insert( QStringLiteral( "OUTPUT" ), outputPath );
175 return outputs;
176}
177
178void QgsConvertGpxFeatureTypeAlgorithm::createArgumentLists( const QString &inputPath, const QString &outputPath, ConversionType conversion, QStringList &processArgs, QStringList &logArgs )
179{
180 logArgs.reserve( 10 );
181 processArgs.reserve( 10 );
182 for ( const QString &arg : { QStringLiteral( "-i" ), QStringLiteral( "gpx" ), QStringLiteral( "-f" ) } )
183 {
184 logArgs << arg;
185 processArgs << arg;
186 }
187
188 // when showing the babel command, wrap filenames in "", which is what QProcess does internally.
189 logArgs << QStringLiteral( "\"%1\"" ).arg( inputPath );
190 processArgs << inputPath;
191
192 QStringList convertStrings;
193 switch ( conversion )
194 {
195 case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromRoute:
196 convertStrings << QStringLiteral( "-x" ) << QStringLiteral( "transform,wpt=rte,del" );
197 break;
198 case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromTrack:
199 convertStrings << QStringLiteral( "-x" ) << QStringLiteral( "transform,wpt=trk,del" );
200 break;
201 case QgsConvertGpxFeatureTypeAlgorithm::RouteFromWaypoints:
202 convertStrings << QStringLiteral( "-x" ) << QStringLiteral( "transform,rte=wpt,del" );
203 break;
204 case QgsConvertGpxFeatureTypeAlgorithm::TrackFromWaypoints:
205 convertStrings << QStringLiteral( "-x" ) << QStringLiteral( "transform,trk=wpt,del" );
206 break;
207 }
208 logArgs << convertStrings;
209 processArgs << convertStrings;
210
211 for ( const QString &arg : { QStringLiteral( "-o" ), QStringLiteral( "gpx" ), QStringLiteral( "-F" ) } )
212 {
213 logArgs << arg;
214 processArgs << arg;
215 }
216
217 logArgs << QStringLiteral( "\"%1\"" ).arg( outputPath );
218 processArgs << outputPath;
219}
220
221
222//
223// QgsConvertGpsDataAlgorithm
224//
225
226QString QgsConvertGpsDataAlgorithm::name() const
227{
228 return QStringLiteral( "convertgpsdata" );
229}
230
231QString QgsConvertGpsDataAlgorithm::displayName() const
232{
233 return QObject::tr( "Convert GPS data" );
234}
235
236QStringList QgsConvertGpsDataAlgorithm::tags() const
237{
238 return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export" ).split( ',' );
239}
240
241QString QgsConvertGpsDataAlgorithm::group() const
242{
243 return QObject::tr( "GPS" );
244}
245
246QString QgsConvertGpsDataAlgorithm::groupId() const
247{
248 return QStringLiteral( "gps" );
249}
250
251void QgsConvertGpsDataAlgorithm::initAlgorithm( const QVariantMap & )
252{
253 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 (*.*)" ) ) ) );
254
255 auto formatParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "FORMAT" ), QObject::tr( "Format" ) );
256
257 QStringList formats;
258 const QStringList formatNames = QgsApplication::gpsBabelFormatRegistry()->importFormatNames();
259 for ( const QString &format : formatNames )
260 formats << QgsApplication::gpsBabelFormatRegistry()->importFormat( format )->description();
261
262 std::sort( formats.begin(), formats.end(), []( const QString &a, const QString &b ) {
263 return a.compare( b, Qt::CaseInsensitive ) < 0;
264 } );
265
266 formatParam->setMetadata( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "value_hints" ), formats } } ) }
267 } );
268 addParameter( formatParam.release() );
269
270 addParameter( new QgsProcessingParameterEnum( QStringLiteral( "FEATURE_TYPE" ), QObject::tr( "Feature type" ), { QObject::tr( "Waypoints" ), QObject::tr( "Routes" ), QObject::tr( "Tracks" ) }, false, 0 ) );
271
272 addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output" ), QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );
273
274 addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT_LAYER" ), QObject::tr( "Output layer" ) ) );
275}
276
277QIcon QgsConvertGpsDataAlgorithm::icon() const
278{
279 return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
280}
281
282QString QgsConvertGpsDataAlgorithm::svgIconPath() const
283{
284 return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
285}
286
287QString QgsConvertGpsDataAlgorithm::shortHelpString() const
288{
289 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." );
290}
291
292QString QgsConvertGpsDataAlgorithm::shortDescription() const
293{
294 return QObject::tr( "Converts a GPS data file from a range of formats to the GPX standard format." );
295}
296
297QgsConvertGpsDataAlgorithm *QgsConvertGpsDataAlgorithm::createInstance() const
298{
299 return new QgsConvertGpsDataAlgorithm();
300}
301
302QVariantMap QgsConvertGpsDataAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
303{
304 const QString inputPath = parameterAsString( parameters, QStringLiteral( "INPUT" ), context );
305 const QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
306
307 const Qgis::GpsFeatureType featureType = static_cast<Qgis::GpsFeatureType>( parameterAsEnum( parameters, QStringLiteral( "FEATURE_TYPE" ), context ) );
308
310 if ( babelPath.isEmpty() )
311 babelPath = QStringLiteral( "gpsbabel" );
312
313 const QString formatName = parameterAsString( parameters, QStringLiteral( "FORMAT" ), context );
315 if ( !format ) // second try, match using descriptions instead of names
317
318 if ( !format )
319 {
320 throw QgsProcessingException( QObject::tr( "Unknown GPSBabel format “%1”. Valid formats are: %2" )
321 .arg( formatName, QgsApplication::gpsBabelFormatRegistry()->importFormatNames().join( QLatin1String( ", " ) ) ) );
322 }
323
324 switch ( featureType )
325 {
328 {
329 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting waypoints." )
330 .arg( formatName ) );
331 }
332 break;
333
336 {
337 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting routes." )
338 .arg( formatName ) );
339 }
340 break;
341
344 {
345 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting tracks." )
346 .arg( formatName ) );
347 }
348 break;
349 }
350
351 // note that for the log we should quote file paths, but for the actual command we don't. That's
352 // because QProcess does this internally for us, and double quoting causes issues
353 const QStringList logCommand = format->importCommand( babelPath, featureType, inputPath, outputPath, Qgis::BabelCommandFlag::QuoteFilePaths );
354 const QStringList processCommand = format->importCommand( babelPath, featureType, inputPath, outputPath );
355 feedback->pushCommandInfo( QObject::tr( "Conversion command: " ) + logCommand.join( ' ' ) );
356
357 QgsBlockingProcess babelProcess( processCommand.value( 0 ), processCommand.mid( 1 ) );
358 babelProcess.setStdErrHandler( [=]( const QByteArray &ba ) {
359 feedback->reportError( ba );
360 } );
361 babelProcess.setStdOutHandler( [=]( const QByteArray &ba ) {
362 feedback->pushDebugInfo( ba );
363 } );
364
365 const int res = babelProcess.run( feedback );
366 if ( feedback->isCanceled() && res != 0 )
367 {
368 feedback->pushInfo( QObject::tr( "Process was canceled and did not complete" ) );
369 }
370 else if ( !feedback->isCanceled() && babelProcess.exitStatus() == QProcess::CrashExit )
371 {
372 throw QgsProcessingException( QObject::tr( "Process was unexpectedly terminated" ) );
373 }
374 else if ( res == 0 )
375 {
376 feedback->pushInfo( QObject::tr( "Process completed successfully" ) );
377 }
378 else if ( babelProcess.processError() == QProcess::FailedToStart )
379 {
380 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 ) );
381 }
382 else
383 {
384 throw QgsProcessingException( QObject::tr( "Process returned error code %1" ).arg( res ) );
385 }
386
387 std::unique_ptr<QgsVectorLayer> layer;
388 const QString layerName = QgsProviderUtils::suggestLayerNameFromFilePath( outputPath );
389 // add the layer
390 switch ( featureType )
391 {
393 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=waypoint", layerName, QStringLiteral( "gpx" ) );
394 break;
396 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=route", layerName, QStringLiteral( "gpx" ) );
397 break;
399 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=track", layerName, QStringLiteral( "gpx" ) );
400 break;
401 }
402
403 QVariantMap outputs;
404 if ( !layer->isValid() )
405 {
406 feedback->reportError( QObject::tr( "Resulting file is not a valid GPX layer" ) );
407 }
408 else
409 {
410 const QString layerId = layer->id();
411 outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), layerId );
412 const QgsProcessingContext::LayerDetails details( layer->name(), context.project(), QStringLiteral( "OUTPUT_LAYER" ), QgsProcessingUtils::LayerHint::Vector );
413 context.addLayerToLoadOnCompletion( layerId, details );
414 context.temporaryLayerStore()->addMapLayer( layer.release() );
415 }
416
417 outputs.insert( QStringLiteral( "OUTPUT" ), outputPath );
418 return outputs;
419}
420
421//
422// QgsDownloadGpsDataAlgorithm
423//
424
425QString QgsDownloadGpsDataAlgorithm::name() const
426{
427 return QStringLiteral( "downloadgpsdata" );
428}
429
430QString QgsDownloadGpsDataAlgorithm::displayName() const
431{
432 return QObject::tr( "Download GPS data from device" );
433}
434
435QStringList QgsDownloadGpsDataAlgorithm::tags() const
436{
437 return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export,export,device,serial" ).split( ',' );
438}
439
440QString QgsDownloadGpsDataAlgorithm::group() const
441{
442 return QObject::tr( "GPS" );
443}
444
445QString QgsDownloadGpsDataAlgorithm::groupId() const
446{
447 return QStringLiteral( "gps" );
448}
449
450void QgsDownloadGpsDataAlgorithm::initAlgorithm( const QVariantMap & )
451{
452 auto deviceParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "DEVICE" ), QObject::tr( "Device" ) );
453
454 QStringList deviceNames = QgsApplication::gpsBabelFormatRegistry()->deviceNames();
455 std::sort( deviceNames.begin(), deviceNames.end(), []( const QString &a, const QString &b ) {
456 return a.compare( b, Qt::CaseInsensitive ) < 0;
457 } );
458
459 deviceParam->setMetadata( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "value_hints" ), deviceNames } } ) }
460 } );
461 addParameter( deviceParam.release() );
462
463
464 const QList<QPair<QString, QString>> devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( QStringLiteral( "usb:" ), QStringLiteral( "usb:" ) );
465 auto portParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "PORT" ), QObject::tr( "Port" ) );
466
467 QStringList ports;
468 for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
469 ports << it->second;
470 std::sort( ports.begin(), ports.end(), []( const QString &a, const QString &b ) {
471 return a.compare( b, Qt::CaseInsensitive ) < 0;
472 } );
473
474 portParam->setMetadata( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "value_hints" ), ports } } ) }
475 } );
476 addParameter( portParam.release() );
477
478 addParameter( new QgsProcessingParameterEnum( QStringLiteral( "FEATURE_TYPE" ), QObject::tr( "Feature type" ), { QObject::tr( "Waypoints" ), QObject::tr( "Routes" ), QObject::tr( "Tracks" ) }, false, 0 ) );
479
480 addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output" ), QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );
481
482 addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT_LAYER" ), QObject::tr( "Output layer" ) ) );
483}
484
485QIcon QgsDownloadGpsDataAlgorithm::icon() const
486{
487 return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
488}
489
490QString QgsDownloadGpsDataAlgorithm::svgIconPath() const
491{
492 return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
493}
494
495QString QgsDownloadGpsDataAlgorithm::shortHelpString() const
496{
497 return QObject::tr( "This algorithm uses the GPSBabel tool to download data from a GPS device into the GPX standard format." );
498}
499
500QString QgsDownloadGpsDataAlgorithm::shortDescription() const
501{
502 return QObject::tr( "Downloads data from a GPS device into the GPX standard format." );
503}
504
505QgsDownloadGpsDataAlgorithm *QgsDownloadGpsDataAlgorithm::createInstance() const
506{
507 return new QgsDownloadGpsDataAlgorithm();
508}
509
510QVariantMap QgsDownloadGpsDataAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
511{
512 const QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
513 const Qgis::GpsFeatureType featureType = static_cast<Qgis::GpsFeatureType>( parameterAsEnum( parameters, QStringLiteral( "FEATURE_TYPE" ), context ) );
514
516 if ( babelPath.isEmpty() )
517 babelPath = QStringLiteral( "gpsbabel" );
518
519 const QString deviceName = parameterAsString( parameters, QStringLiteral( "DEVICE" ), context );
521 if ( !format )
522 {
523 throw QgsProcessingException( QObject::tr( "Unknown GPSBabel device “%1”. Valid devices are: %2" )
524 .arg( deviceName, QgsApplication::gpsBabelFormatRegistry()->deviceNames().join( QLatin1String( ", " ) ) ) );
525 }
526
527 const QString portName = parameterAsString( parameters, QStringLiteral( "PORT" ), context );
528 QString inputPort;
529 const QList<QPair<QString, QString>> devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( QStringLiteral( "usb:" ), QStringLiteral( "usb:" ) );
530 QStringList validPorts;
531 for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
532 {
533 if ( it->first.compare( portName, Qt::CaseInsensitive ) == 0 || it->second.compare( portName, Qt::CaseInsensitive ) == 0 )
534 {
535 inputPort = it->first;
536 }
537 validPorts << it->first;
538 }
539 if ( inputPort.isEmpty() )
540 {
541 throw QgsProcessingException( QObject::tr( "Unknown port “%1”. Valid ports are: %2" )
542 .arg( portName, validPorts.join( QLatin1String( ", " ) ) ) );
543 }
544
545 switch ( featureType )
546 {
549 {
550 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting waypoints." )
551 .arg( deviceName ) );
552 }
553 break;
554
557 {
558 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting routes." )
559 .arg( deviceName ) );
560 }
561 break;
562
565 {
566 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting tracks." )
567 .arg( deviceName ) );
568 }
569 break;
570 }
571
572 // note that for the log we should quote file paths, but for the actual command we don't. That's
573 // because QProcess does this internally for us, and double quoting causes issues
574 const QStringList logCommand = format->importCommand( babelPath, featureType, inputPort, outputPath, Qgis::BabelCommandFlag::QuoteFilePaths );
575 const QStringList processCommand = format->importCommand( babelPath, featureType, inputPort, outputPath );
576 feedback->pushCommandInfo( QObject::tr( "Download command: " ) + logCommand.join( ' ' ) );
577
578 QgsBlockingProcess babelProcess( processCommand.value( 0 ), processCommand.mid( 1 ) );
579 babelProcess.setStdErrHandler( [=]( const QByteArray &ba ) {
580 feedback->reportError( ba );
581 } );
582 babelProcess.setStdOutHandler( [=]( const QByteArray &ba ) {
583 feedback->pushDebugInfo( ba );
584 } );
585
586 const int res = babelProcess.run( feedback );
587 if ( feedback->isCanceled() && res != 0 )
588 {
589 feedback->pushInfo( QObject::tr( "Process was canceled and did not complete" ) );
590 }
591 else if ( !feedback->isCanceled() && babelProcess.exitStatus() == QProcess::CrashExit )
592 {
593 throw QgsProcessingException( QObject::tr( "Process was unexpectedly terminated" ) );
594 }
595 else if ( res == 0 )
596 {
597 feedback->pushInfo( QObject::tr( "Process completed successfully" ) );
598 }
599 else if ( babelProcess.processError() == QProcess::FailedToStart )
600 {
601 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 ) );
602 }
603 else
604 {
605 throw QgsProcessingException( QObject::tr( "Process returned error code %1" ).arg( res ) );
606 }
607
608 std::unique_ptr<QgsVectorLayer> layer;
609 const QString layerName = QgsProviderUtils::suggestLayerNameFromFilePath( outputPath );
610 // add the layer
611 switch ( featureType )
612 {
614 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=waypoint", layerName, QStringLiteral( "gpx" ) );
615 break;
617 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=route", layerName, QStringLiteral( "gpx" ) );
618 break;
620 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=track", layerName, QStringLiteral( "gpx" ) );
621 break;
622 }
623
624 QVariantMap outputs;
625 if ( !layer->isValid() )
626 {
627 feedback->reportError( QObject::tr( "Resulting file is not a valid GPX layer" ) );
628 }
629 else
630 {
631 const QString layerId = layer->id();
632 outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), layerId );
633 const QgsProcessingContext::LayerDetails details( layer->name(), context.project(), QStringLiteral( "OUTPUT_LAYER" ), QgsProcessingUtils::LayerHint::Vector );
634 context.addLayerToLoadOnCompletion( layerId, details );
635 context.temporaryLayerStore()->addMapLayer( layer.release() );
636 }
637
638 outputs.insert( QStringLiteral( "OUTPUT" ), outputPath );
639 return outputs;
640}
641
642
643//
644// QgsUploadGpsDataAlgorithm
645//
646
647QString QgsUploadGpsDataAlgorithm::name() const
648{
649 return QStringLiteral( "uploadgpsdata" );
650}
651
652QString QgsUploadGpsDataAlgorithm::displayName() const
653{
654 return QObject::tr( "Upload GPS data to device" );
655}
656
657QStringList QgsUploadGpsDataAlgorithm::tags() const
658{
659 return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export,export,device,serial" ).split( ',' );
660}
661
662QString QgsUploadGpsDataAlgorithm::group() const
663{
664 return QObject::tr( "GPS" );
665}
666
667QString QgsUploadGpsDataAlgorithm::groupId() const
668{
669 return QStringLiteral( "gps" );
670}
671
672void QgsUploadGpsDataAlgorithm::initAlgorithm( const QVariantMap & )
673{
674 addParameter( new QgsProcessingParameterFile( QStringLiteral( "INPUT" ), QObject::tr( "Input file" ), Qgis::ProcessingFileParameterBehavior::File, QString(), QVariant(), false, QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );
675
676 auto deviceParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "DEVICE" ), QObject::tr( "Device" ) );
677
678 QStringList deviceNames = QgsApplication::gpsBabelFormatRegistry()->deviceNames();
679 std::sort( deviceNames.begin(), deviceNames.end(), []( const QString &a, const QString &b ) {
680 return a.compare( b, Qt::CaseInsensitive ) < 0;
681 } );
682
683 deviceParam->setMetadata( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "value_hints" ), deviceNames } } ) }
684 } );
685 addParameter( deviceParam.release() );
686
687 const QList<QPair<QString, QString>> devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( QStringLiteral( "usb:" ), QStringLiteral( "usb:" ) );
688 auto portParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "PORT" ), QObject::tr( "Port" ) );
689
690 QStringList ports;
691 for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
692 ports << it->second;
693 std::sort( ports.begin(), ports.end(), []( const QString &a, const QString &b ) {
694 return a.compare( b, Qt::CaseInsensitive ) < 0;
695 } );
696
697 portParam->setMetadata( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "value_hints" ), ports } } ) }
698 } );
699 addParameter( portParam.release() );
700
701 addParameter( new QgsProcessingParameterEnum( QStringLiteral( "FEATURE_TYPE" ), QObject::tr( "Feature type" ), { QObject::tr( "Waypoints" ), QObject::tr( "Routes" ), QObject::tr( "Tracks" ) }, false, 0 ) );
702}
703
704QIcon QgsUploadGpsDataAlgorithm::icon() const
705{
706 return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
707}
708
709QString QgsUploadGpsDataAlgorithm::svgIconPath() const
710{
711 return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
712}
713
714QString QgsUploadGpsDataAlgorithm::shortHelpString() const
715{
716 return QObject::tr( "This algorithm uses the GPSBabel tool to upload data to a GPS device from the GPX standard format." );
717}
718
719QString QgsUploadGpsDataAlgorithm::shortDescription() const
720{
721 return QObject::tr( "Uploads data to a GPS device from the GPX standard format." );
722}
723
724QgsUploadGpsDataAlgorithm *QgsUploadGpsDataAlgorithm::createInstance() const
725{
726 return new QgsUploadGpsDataAlgorithm();
727}
728
729QVariantMap QgsUploadGpsDataAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
730{
731 const QString inputPath = parameterAsString( parameters, QStringLiteral( "INPUT" ), context );
732 const Qgis::GpsFeatureType featureType = static_cast<Qgis::GpsFeatureType>( parameterAsEnum( parameters, QStringLiteral( "FEATURE_TYPE" ), context ) );
733
735 if ( babelPath.isEmpty() )
736 babelPath = QStringLiteral( "gpsbabel" );
737
738 const QString deviceName = parameterAsString( parameters, QStringLiteral( "DEVICE" ), context );
740 if ( !format )
741 {
742 throw QgsProcessingException( QObject::tr( "Unknown GPSBabel device “%1”. Valid devices are: %2" )
743 .arg( deviceName, QgsApplication::gpsBabelFormatRegistry()->deviceNames().join( QLatin1String( ", " ) ) ) );
744 }
745
746 const QString portName = parameterAsString( parameters, QStringLiteral( "PORT" ), context );
747 QString outputPort;
748 const QList<QPair<QString, QString>> devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( QStringLiteral( "usb:" ), QStringLiteral( "usb:" ) );
749 QStringList validPorts;
750 for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
751 {
752 if ( it->first.compare( portName, Qt::CaseInsensitive ) == 0 || it->second.compare( portName, Qt::CaseInsensitive ) == 0 )
753 {
754 outputPort = it->first;
755 }
756 validPorts << it->first;
757 }
758 if ( outputPort.isEmpty() )
759 {
760 throw QgsProcessingException( QObject::tr( "Unknown port “%1”. Valid ports are: %2" )
761 .arg( portName, validPorts.join( QLatin1String( ", " ) ) ) );
762 }
763
764
765 switch ( featureType )
766 {
769 {
770 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support waypoints." )
771 .arg( deviceName ) );
772 }
773 break;
774
777 {
778 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support routes." )
779 .arg( deviceName ) );
780 }
781 break;
782
785 {
786 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support tracks." )
787 .arg( deviceName ) );
788 }
789 break;
790 }
791
792 // note that for the log we should quote file paths, but for the actual command we don't. That's
793 // because QProcess does this internally for us, and double quoting causes issues
794 const QStringList logCommand = format->exportCommand( babelPath, featureType, inputPath, outputPort, Qgis::BabelCommandFlag::QuoteFilePaths );
795 const QStringList processCommand = format->exportCommand( babelPath, featureType, inputPath, outputPort );
796 feedback->pushCommandInfo( QObject::tr( "Upload command: " ) + logCommand.join( ' ' ) );
797
798 QgsBlockingProcess babelProcess( processCommand.value( 0 ), processCommand.mid( 1 ) );
799 babelProcess.setStdErrHandler( [=]( const QByteArray &ba ) {
800 feedback->reportError( ba );
801 } );
802 babelProcess.setStdOutHandler( [=]( const QByteArray &ba ) {
803 feedback->pushDebugInfo( ba );
804 } );
805
806 const int res = babelProcess.run( feedback );
807 if ( feedback->isCanceled() && res != 0 )
808 {
809 feedback->pushInfo( QObject::tr( "Process was canceled and did not complete" ) );
810 }
811 else if ( !feedback->isCanceled() && babelProcess.exitStatus() == QProcess::CrashExit )
812 {
813 throw QgsProcessingException( QObject::tr( "Process was unexpectedly terminated" ) );
814 }
815 else if ( res == 0 )
816 {
817 feedback->pushInfo( QObject::tr( "Process completed successfully" ) );
818 }
819 else if ( babelProcess.processError() == QProcess::FailedToStart )
820 {
821 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 ) );
822 }
823 else
824 {
825 throw QgsProcessingException( QObject::tr( "Process returned error code %1" ).arg( res ) );
826 }
827
828 return {};
829}
830
832#endif // process
@ File
Parameter is a single file.
@ QuoteFilePaths
File paths should be enclosed in quotations and escaped.
GpsFeatureType
GPS feature types.
Definition qgis.h:1990
@ Tracks
Format supports tracks.
@ Waypoints
Format supports waypoints.
@ Routes
Format supports routes.
Qgis::BabelFormatCapabilities capabilities() const
Returns the format's capabilities.
Extends QApplication to provide access to QGIS specific resources such as theme paths,...
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsBabelFormatRegistry * gpsBabelFormatRegistry()
Returns the application's GPSBabel format registry, used for managing GPSBabel formats.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
QgsBabelSimpleImportFormat * importFormatByDescription(const QString &description)
Returns a registered import format by description.
QStringList importFormatNames() const
Returns a list of the names of all registered import formats.
QStringList deviceNames() const
Returns a list of the names of all registered devices.
QgsBabelSimpleImportFormat * importFormat(const QString &name)
Returns a registered import format by name.
QgsBabelGpsDeviceFormat * deviceFormat(const QString &name)
Returns a registered device format by name.
A babel format capable of interacting directly with a GPS device.
QStringList exportCommand(const QString &babel, Qgis::GpsFeatureType type, const QString &in, const QString &out, Qgis::BabelCommandFlags flags=Qgis::BabelCommandFlags()) const override
Generates a command for exporting GPS data into a different format using babel.
QStringList importCommand(const QString &babel, Qgis::GpsFeatureType type, const QString &in, const QString &out, Qgis::BabelCommandFlags flags=Qgis::BabelCommandFlags()) const override
Generates a command for importing data into a GPS format using babel.
A babel format capable of converting input files to GPX files.
QStringList importCommand(const QString &babel, Qgis::GpsFeatureType featureType, const QString &input, const QString &output, Qgis::BabelCommandFlags flags=Qgis::BabelCommandFlags()) const override
Generates a command for importing data into a GPS format using babel.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
static QList< QPair< QString, QString > > availablePorts()
QgsMapLayer * addMapLayer(QgsMapLayer *layer, bool takeOwnership=true)
Add a layer to the store.
Details for layers to load into projects.
Contains information about the context in which a processing algorithm is executed.
void addLayerToLoadOnCompletion(const QString &layer, const QgsProcessingContext::LayerDetails &details)
Adds a layer to load (by ID or datasource) into the canvas upon completion of the algorithm or model.
QgsProject * project() const
Returns the project in which the algorithm is being executed.
QgsMapLayerStore * temporaryLayerStore()
Returns a reference to the layer store used for storing temporary layers during algorithm execution.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void pushCommandInfo(const QString &info)
Pushes an informational message containing a command from the algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
A vector layer output for processing algorithms.
An enum based parameter for processing algorithms, allowing for selection from predefined values.
A generic file based destination parameter, for specifying the destination path for a file (non-map l...
An input file or folder parameter for processing algorithms.
@ Vector
Vector layer type.
static QString suggestLayerNameFromFilePath(const QString &path)
Suggests a suitable layer name given only a file path.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
static const QgsSettingsEntryString * settingsGpsBabelPath
Settings entry path to GPSBabel executable.