QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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,
63 QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );
64
65 addParameter( new QgsProcessingParameterEnum( QStringLiteral( "CONVERSION" ), QObject::tr( "Conversion" ),
66 {
67 QObject::tr( "Waypoints from a Route" ),
68 QObject::tr( "Waypoints from a Track" ),
69 QObject::tr( "Route from Waypoints" ),
70 QObject::tr( "Track from Waypoints" )
71 }, false, 0 ) );
72
73 addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output" ), QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );
74
75 addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT_LAYER" ), QObject::tr( "Output layer" ) ) );
76}
77
78QIcon QgsConvertGpxFeatureTypeAlgorithm::icon() const
79{
80 return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
81}
82
83QString QgsConvertGpxFeatureTypeAlgorithm::svgIconPath() const
84{
85 return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
86}
87
88QString QgsConvertGpxFeatureTypeAlgorithm::shortHelpString() const
89{
90 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)." );
91}
92
93QgsConvertGpxFeatureTypeAlgorithm *QgsConvertGpxFeatureTypeAlgorithm::createInstance() const
94{
95 return new QgsConvertGpxFeatureTypeAlgorithm();
96}
97
98
99QVariantMap QgsConvertGpxFeatureTypeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
100{
101 const QString inputPath = parameterAsString( parameters, QStringLiteral( "INPUT" ), context );
102 const QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
103
104 const ConversionType convertType = static_cast< ConversionType >( parameterAsEnum( parameters, QStringLiteral( "CONVERSION" ), context ) );
105
107 if ( babelPath.isEmpty() )
108 babelPath = QStringLiteral( "gpsbabel" );
109
110 QStringList processArgs;
111 QStringList logArgs;
112 createArgumentLists( inputPath, outputPath, convertType, processArgs, logArgs );
113 feedback->pushCommandInfo( QObject::tr( "Conversion command: " ) + babelPath + ' ' + logArgs.join( ' ' ) );
114
115 QgsBlockingProcess babelProcess( babelPath, processArgs );
116 babelProcess.setStdErrHandler( [ = ]( const QByteArray & ba )
117 {
118 feedback->reportError( ba );
119 } );
120 babelProcess.setStdOutHandler( [ = ]( const QByteArray & ba )
121 {
122 feedback->pushDebugInfo( ba );
123 } );
124
125 const int res = babelProcess.run( feedback );
126 if ( feedback->isCanceled() && res != 0 )
127 {
128 feedback->pushInfo( QObject::tr( "Process was canceled and did not complete" ) ) ;
129 }
130 else if ( !feedback->isCanceled() && babelProcess.exitStatus() == QProcess::CrashExit )
131 {
132 throw QgsProcessingException( QObject::tr( "Process was unexpectedly terminated" ) );
133 }
134 else if ( res == 0 )
135 {
136 feedback->pushInfo( QObject::tr( "Process completed successfully" ) );
137 }
138 else if ( babelProcess.processError() == QProcess::FailedToStart )
139 {
140 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 ) );
141 }
142 else
143 {
144 throw QgsProcessingException( QObject::tr( "Process returned error code %1" ).arg( res ) );
145 }
146
147 std::unique_ptr< QgsVectorLayer > layer;
148 const QString layerName = QgsProviderUtils::suggestLayerNameFromFilePath( outputPath );
149 // add the layer
150 switch ( convertType )
151 {
152 case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromRoute:
153 case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromTrack:
154 layer = std::make_unique< QgsVectorLayer >( outputPath + "?type=waypoint", layerName, QStringLiteral( "gpx" ) );
155 break;
156 case QgsConvertGpxFeatureTypeAlgorithm::RouteFromWaypoints:
157 layer = std::make_unique< QgsVectorLayer >( outputPath + "?type=route", layerName, QStringLiteral( "gpx" ) );
158 break;
159 case QgsConvertGpxFeatureTypeAlgorithm::TrackFromWaypoints:
160 layer = std::make_unique< QgsVectorLayer >( outputPath + "?type=track", layerName, QStringLiteral( "gpx" ) );
161 break;
162 }
163
164 QVariantMap outputs;
165 if ( !layer->isValid() )
166 {
167 feedback->reportError( QObject::tr( "Resulting file is not a valid GPX layer" ) );
168 }
169 else
170 {
171 const QString layerId = layer->id();
172 outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), layerId );
173 const QgsProcessingContext::LayerDetails details( layer->name(), context.project(), QStringLiteral( "OUTPUT_LAYER" ), QgsProcessingUtils::LayerHint::Vector );
174 context.addLayerToLoadOnCompletion( layerId, details );
175 context.temporaryLayerStore()->addMapLayer( layer.release() );
176 }
177
178 outputs.insert( QStringLiteral( "OUTPUT" ), outputPath );
179 return outputs;
180}
181
182void QgsConvertGpxFeatureTypeAlgorithm::createArgumentLists( const QString &inputPath, const QString &outputPath, ConversionType conversion, QStringList &processArgs, QStringList &logArgs )
183{
184 logArgs.reserve( 10 );
185 processArgs.reserve( 10 );
186 for ( const QString &arg : { QStringLiteral( "-i" ), QStringLiteral( "gpx" ), QStringLiteral( "-f" ) } )
187 {
188 logArgs << arg;
189 processArgs << arg;
190 }
191
192 // when showing the babel command, wrap filenames in "", which is what QProcess does internally.
193 logArgs << QStringLiteral( "\"%1\"" ).arg( inputPath );
194 processArgs << inputPath;
195
196 QStringList convertStrings;
197 switch ( conversion )
198 {
199 case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromRoute:
200 convertStrings << QStringLiteral( "-x" ) << QStringLiteral( "transform,wpt=rte,del" );
201 break;
202 case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromTrack:
203 convertStrings << QStringLiteral( "-x" ) << QStringLiteral( "transform,wpt=trk,del" );
204 break;
205 case QgsConvertGpxFeatureTypeAlgorithm::RouteFromWaypoints:
206 convertStrings << QStringLiteral( "-x" ) << QStringLiteral( "transform,rte=wpt,del" );
207 break;
208 case QgsConvertGpxFeatureTypeAlgorithm::TrackFromWaypoints:
209 convertStrings << QStringLiteral( "-x" ) << QStringLiteral( "transform,trk=wpt,del" );
210 break;
211 }
212 logArgs << convertStrings;
213 processArgs << convertStrings;
214
215 for ( const QString &arg : { QStringLiteral( "-o" ), QStringLiteral( "gpx" ), QStringLiteral( "-F" ) } )
216 {
217 logArgs << arg;
218 processArgs << arg;
219 }
220
221 logArgs << QStringLiteral( "\"%1\"" ).arg( outputPath );
222 processArgs << outputPath;
223
224}
225
226
227//
228// QgsConvertGpsDataAlgorithm
229//
230
231QString QgsConvertGpsDataAlgorithm::name() const
232{
233 return QStringLiteral( "convertgpsdata" );
234}
235
236QString QgsConvertGpsDataAlgorithm::displayName() const
237{
238 return QObject::tr( "Convert GPS data" );
239}
240
241QStringList QgsConvertGpsDataAlgorithm::tags() const
242{
243 return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export" ).split( ',' );
244}
245
246QString QgsConvertGpsDataAlgorithm::group() const
247{
248 return QObject::tr( "GPS" );
249}
250
251QString QgsConvertGpsDataAlgorithm::groupId() const
252{
253 return QStringLiteral( "gps" );
254}
255
256void QgsConvertGpsDataAlgorithm::initAlgorithm( const QVariantMap & )
257{
258 addParameter( new QgsProcessingParameterFile( QStringLiteral( "INPUT" ), QObject::tr( "Input file" ), Qgis::ProcessingFileParameterBehavior::File, QString(), QVariant(), false,
259 QgsApplication::gpsBabelFormatRegistry()->importFileFilter() + QStringLiteral( ";;%1" ).arg( QObject::tr( "All files (*.*)" ) ) ) );
260
261 std::unique_ptr< QgsProcessingParameterString > formatParam = std::make_unique< QgsProcessingParameterString >( QStringLiteral( "FORMAT" ), QObject::tr( "Format" ) );
262
263 QStringList formats;
264 const QStringList formatNames = QgsApplication::gpsBabelFormatRegistry()->importFormatNames();
265 for ( const QString &format : formatNames )
267
268 std::sort( formats.begin(), formats.end(), []( const QString & a, const QString & b )
269 {
270 return a.compare( b, Qt::CaseInsensitive ) < 0;
271 } );
272
273 formatParam->setMetadata( {{
274 QStringLiteral( "widget_wrapper" ), QVariantMap(
275 {{QStringLiteral( "value_hints" ), formats }}
276 )
277 }
278 } );
279 addParameter( formatParam.release() );
280
281 addParameter( new QgsProcessingParameterEnum( QStringLiteral( "FEATURE_TYPE" ), QObject::tr( "Feature type" ),
282 {
283 QObject::tr( "Waypoints" ),
284 QObject::tr( "Routes" ),
285 QObject::tr( "Tracks" )
286 }, false, 0 ) );
287
288 addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output" ), QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );
289
290 addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT_LAYER" ), QObject::tr( "Output layer" ) ) );
291}
292
293QIcon QgsConvertGpsDataAlgorithm::icon() const
294{
295 return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
296}
297
298QString QgsConvertGpsDataAlgorithm::svgIconPath() const
299{
300 return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
301}
302
303QString QgsConvertGpsDataAlgorithm::shortHelpString() const
304{
305 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." );
306}
307
308QgsConvertGpsDataAlgorithm *QgsConvertGpsDataAlgorithm::createInstance() const
309{
310 return new QgsConvertGpsDataAlgorithm();
311}
312
313QVariantMap QgsConvertGpsDataAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
314{
315 const QString inputPath = parameterAsString( parameters, QStringLiteral( "INPUT" ), context );
316 const QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
317
318 const Qgis::GpsFeatureType featureType = static_cast< Qgis::GpsFeatureType >( parameterAsEnum( parameters, QStringLiteral( "FEATURE_TYPE" ), context ) );
319
321 if ( babelPath.isEmpty() )
322 babelPath = QStringLiteral( "gpsbabel" );
323
324 const QString formatName = parameterAsString( parameters, QStringLiteral( "FORMAT" ), context );
326 if ( !format ) // second try, match using descriptions instead of names
328
329 if ( !format )
330 {
331 throw QgsProcessingException( QObject::tr( "Unknown GPSBabel format “%1”. Valid formats are: %2" )
332 .arg( formatName,
333 QgsApplication::gpsBabelFormatRegistry()->importFormatNames().join( QLatin1String( ", " ) ) ) );
334 }
335
336 switch ( featureType )
337 {
340 {
341 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting waypoints." )
342 .arg( formatName ) );
343 }
344 break;
345
348 {
349 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting routes." )
350 .arg( formatName ) );
351 }
352 break;
353
356 {
357 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting tracks." )
358 .arg( formatName ) );
359 }
360 break;
361 }
362
363 // note that for the log we should quote file paths, but for the actual command we don't. That's
364 // because QProcess does this internally for us, and double quoting causes issues
365 const QStringList logCommand = format->importCommand( babelPath, featureType, inputPath, outputPath, Qgis::BabelCommandFlag::QuoteFilePaths );
366 const QStringList processCommand = format->importCommand( babelPath, featureType, inputPath, outputPath );
367 feedback->pushCommandInfo( QObject::tr( "Conversion command: " ) + logCommand.join( ' ' ) );
368
369 QgsBlockingProcess babelProcess( processCommand.value( 0 ), processCommand.mid( 1 ) );
370 babelProcess.setStdErrHandler( [ = ]( const QByteArray & ba )
371 {
372 feedback->reportError( ba );
373 } );
374 babelProcess.setStdOutHandler( [ = ]( const QByteArray & ba )
375 {
376 feedback->pushDebugInfo( ba );
377 } );
378
379 const int res = babelProcess.run( feedback );
380 if ( feedback->isCanceled() && res != 0 )
381 {
382 feedback->pushInfo( QObject::tr( "Process was canceled and did not complete" ) ) ;
383 }
384 else if ( !feedback->isCanceled() && babelProcess.exitStatus() == QProcess::CrashExit )
385 {
386 throw QgsProcessingException( QObject::tr( "Process was unexpectedly terminated" ) );
387 }
388 else if ( res == 0 )
389 {
390 feedback->pushInfo( QObject::tr( "Process completed successfully" ) );
391 }
392 else if ( babelProcess.processError() == QProcess::FailedToStart )
393 {
394 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 ) );
395 }
396 else
397 {
398 throw QgsProcessingException( QObject::tr( "Process returned error code %1" ).arg( res ) );
399 }
400
401 std::unique_ptr< QgsVectorLayer > layer;
402 const QString layerName = QgsProviderUtils::suggestLayerNameFromFilePath( outputPath );
403 // add the layer
404 switch ( featureType )
405 {
407 layer = std::make_unique< QgsVectorLayer >( outputPath + "?type=waypoint", layerName, QStringLiteral( "gpx" ) );
408 break;
410 layer = std::make_unique< QgsVectorLayer >( outputPath + "?type=route", layerName, QStringLiteral( "gpx" ) );
411 break;
413 layer = std::make_unique< QgsVectorLayer >( outputPath + "?type=track", layerName, QStringLiteral( "gpx" ) );
414 break;
415 }
416
417 QVariantMap outputs;
418 if ( !layer->isValid() )
419 {
420 feedback->reportError( QObject::tr( "Resulting file is not a valid GPX layer" ) );
421 }
422 else
423 {
424 const QString layerId = layer->id();
425 outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), layerId );
426 const QgsProcessingContext::LayerDetails details( layer->name(), context.project(), QStringLiteral( "OUTPUT_LAYER" ), QgsProcessingUtils::LayerHint::Vector );
427 context.addLayerToLoadOnCompletion( layerId, details );
428 context.temporaryLayerStore()->addMapLayer( layer.release() );
429 }
430
431 outputs.insert( QStringLiteral( "OUTPUT" ), outputPath );
432 return outputs;
433}
434
435//
436// QgsDownloadGpsDataAlgorithm
437//
438
439QString QgsDownloadGpsDataAlgorithm::name() const
440{
441 return QStringLiteral( "downloadgpsdata" );
442}
443
444QString QgsDownloadGpsDataAlgorithm::displayName() const
445{
446 return QObject::tr( "Download GPS data from device" );
447}
448
449QStringList QgsDownloadGpsDataAlgorithm::tags() const
450{
451 return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export,export,device,serial" ).split( ',' );
452}
453
454QString QgsDownloadGpsDataAlgorithm::group() const
455{
456 return QObject::tr( "GPS" );
457}
458
459QString QgsDownloadGpsDataAlgorithm::groupId() const
460{
461 return QStringLiteral( "gps" );
462}
463
464void QgsDownloadGpsDataAlgorithm::initAlgorithm( const QVariantMap & )
465{
466 std::unique_ptr< QgsProcessingParameterString > deviceParam = std::make_unique< QgsProcessingParameterString >( QStringLiteral( "DEVICE" ), QObject::tr( "Device" ) );
467
468 QStringList deviceNames = QgsApplication::gpsBabelFormatRegistry()->deviceNames();
469 std::sort( deviceNames.begin(), deviceNames.end(), []( const QString & a, const QString & b )
470 {
471 return a.compare( b, Qt::CaseInsensitive ) < 0;
472 } );
473
474 deviceParam->setMetadata( {{
475 QStringLiteral( "widget_wrapper" ), QVariantMap(
476 {{QStringLiteral( "value_hints" ), deviceNames }}
477 )
478 }
479 } );
480 addParameter( deviceParam.release() );
481
482
483 const QList< QPair<QString, QString> > devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( QStringLiteral( "usb:" ), QStringLiteral( "usb:" ) );
484 std::unique_ptr< QgsProcessingParameterString > portParam = std::make_unique< QgsProcessingParameterString >( QStringLiteral( "PORT" ), QObject::tr( "Port" ) );
485
486 QStringList ports;
487 for ( auto it = devices.constBegin(); it != devices.constEnd(); ++ it )
488 ports << it->second;
489 std::sort( ports.begin(), ports.end(), []( const QString & a, const QString & b )
490 {
491 return a.compare( b, Qt::CaseInsensitive ) < 0;
492 } );
493
494 portParam->setMetadata( {{
495 QStringLiteral( "widget_wrapper" ), QVariantMap(
496 {{QStringLiteral( "value_hints" ), ports }}
497 )
498 }
499 } );
500 addParameter( portParam.release() );
501
502 addParameter( new QgsProcessingParameterEnum( QStringLiteral( "FEATURE_TYPE" ), QObject::tr( "Feature type" ),
503 {
504 QObject::tr( "Waypoints" ),
505 QObject::tr( "Routes" ),
506 QObject::tr( "Tracks" )
507 }, false, 0 ) );
508
509 addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output" ), QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );
510
511 addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT_LAYER" ), QObject::tr( "Output layer" ) ) );
512}
513
514QIcon QgsDownloadGpsDataAlgorithm::icon() const
515{
516 return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
517}
518
519QString QgsDownloadGpsDataAlgorithm::svgIconPath() const
520{
521 return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
522}
523
524QString QgsDownloadGpsDataAlgorithm::shortHelpString() const
525{
526 return QObject::tr( "This algorithm uses the GPSBabel tool to download data from a GPS device into the GPX standard format." );
527}
528
529QgsDownloadGpsDataAlgorithm *QgsDownloadGpsDataAlgorithm::createInstance() const
530{
531 return new QgsDownloadGpsDataAlgorithm();
532}
533
534QVariantMap QgsDownloadGpsDataAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
535{
536 const QString outputPath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
537 const Qgis::GpsFeatureType featureType = static_cast< Qgis::GpsFeatureType >( parameterAsEnum( parameters, QStringLiteral( "FEATURE_TYPE" ), context ) );
538
540 if ( babelPath.isEmpty() )
541 babelPath = QStringLiteral( "gpsbabel" );
542
543 const QString deviceName = parameterAsString( parameters, QStringLiteral( "DEVICE" ), context );
545 if ( !format )
546 {
547 throw QgsProcessingException( QObject::tr( "Unknown GPSBabel device “%1”. Valid devices are: %2" )
548 .arg( deviceName,
549 QgsApplication::gpsBabelFormatRegistry()->deviceNames().join( QLatin1String( ", " ) ) ) );
550 }
551
552 const QString portName = parameterAsString( parameters, QStringLiteral( "PORT" ), context );
553 QString inputPort;
554 const QList< QPair<QString, QString> > devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( QStringLiteral( "usb:" ), QStringLiteral( "usb:" ) );
555 QStringList validPorts;
556 for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
557 {
558 if ( it->first.compare( portName, Qt::CaseInsensitive ) == 0 || it->second.compare( portName, Qt::CaseInsensitive ) == 0 )
559 {
560 inputPort = it->first;
561 }
562 validPorts << it->first;
563 }
564 if ( inputPort.isEmpty() )
565 {
566 throw QgsProcessingException( QObject::tr( "Unknown port “%1”. Valid ports are: %2" )
567 .arg( portName,
568 validPorts.join( QLatin1String( ", " ) ) ) );
569 }
570
571 switch ( featureType )
572 {
575 {
576 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting waypoints." )
577 .arg( deviceName ) );
578 }
579 break;
580
583 {
584 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting routes." )
585 .arg( deviceName ) );
586 }
587 break;
588
591 {
592 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting tracks." )
593 .arg( deviceName ) );
594 }
595 break;
596 }
597
598 // note that for the log we should quote file paths, but for the actual command we don't. That's
599 // because QProcess does this internally for us, and double quoting causes issues
600 const QStringList logCommand = format->importCommand( babelPath, featureType, inputPort, outputPath, Qgis::BabelCommandFlag::QuoteFilePaths );
601 const QStringList processCommand = format->importCommand( babelPath, featureType, inputPort, outputPath );
602 feedback->pushCommandInfo( QObject::tr( "Download command: " ) + logCommand.join( ' ' ) );
603
604 QgsBlockingProcess babelProcess( processCommand.value( 0 ), processCommand.mid( 1 ) );
605 babelProcess.setStdErrHandler( [ = ]( const QByteArray & ba )
606 {
607 feedback->reportError( ba );
608 } );
609 babelProcess.setStdOutHandler( [ = ]( const QByteArray & ba )
610 {
611 feedback->pushDebugInfo( ba );
612 } );
613
614 const int res = babelProcess.run( feedback );
615 if ( feedback->isCanceled() && res != 0 )
616 {
617 feedback->pushInfo( QObject::tr( "Process was canceled and did not complete" ) ) ;
618 }
619 else if ( !feedback->isCanceled() && babelProcess.exitStatus() == QProcess::CrashExit )
620 {
621 throw QgsProcessingException( QObject::tr( "Process was unexpectedly terminated" ) );
622 }
623 else if ( res == 0 )
624 {
625 feedback->pushInfo( QObject::tr( "Process completed successfully" ) );
626 }
627 else if ( babelProcess.processError() == QProcess::FailedToStart )
628 {
629 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 ) );
630 }
631 else
632 {
633 throw QgsProcessingException( QObject::tr( "Process returned error code %1" ).arg( res ) );
634 }
635
636 std::unique_ptr< QgsVectorLayer > layer;
637 const QString layerName = QgsProviderUtils::suggestLayerNameFromFilePath( outputPath );
638 // add the layer
639 switch ( featureType )
640 {
642 layer = std::make_unique< QgsVectorLayer >( outputPath + "?type=waypoint", layerName, QStringLiteral( "gpx" ) );
643 break;
645 layer = std::make_unique< QgsVectorLayer >( outputPath + "?type=route", layerName, QStringLiteral( "gpx" ) );
646 break;
648 layer = std::make_unique< QgsVectorLayer >( outputPath + "?type=track", layerName, QStringLiteral( "gpx" ) );
649 break;
650 }
651
652 QVariantMap outputs;
653 if ( !layer->isValid() )
654 {
655 feedback->reportError( QObject::tr( "Resulting file is not a valid GPX layer" ) );
656 }
657 else
658 {
659 const QString layerId = layer->id();
660 outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), layerId );
661 const QgsProcessingContext::LayerDetails details( layer->name(), context.project(), QStringLiteral( "OUTPUT_LAYER" ), QgsProcessingUtils::LayerHint::Vector );
662 context.addLayerToLoadOnCompletion( layerId, details );
663 context.temporaryLayerStore()->addMapLayer( layer.release() );
664 }
665
666 outputs.insert( QStringLiteral( "OUTPUT" ), outputPath );
667 return outputs;
668}
669
670
671//
672// QgsUploadGpsDataAlgorithm
673//
674
675QString QgsUploadGpsDataAlgorithm::name() const
676{
677 return QStringLiteral( "uploadgpsdata" );
678}
679
680QString QgsUploadGpsDataAlgorithm::displayName() const
681{
682 return QObject::tr( "Upload GPS data to device" );
683}
684
685QStringList QgsUploadGpsDataAlgorithm::tags() const
686{
687 return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export,export,device,serial" ).split( ',' );
688}
689
690QString QgsUploadGpsDataAlgorithm::group() const
691{
692 return QObject::tr( "GPS" );
693}
694
695QString QgsUploadGpsDataAlgorithm::groupId() const
696{
697 return QStringLiteral( "gps" );
698}
699
700void QgsUploadGpsDataAlgorithm::initAlgorithm( const QVariantMap & )
701{
702 addParameter( new QgsProcessingParameterFile( QStringLiteral( "INPUT" ), QObject::tr( "Input file" ), Qgis::ProcessingFileParameterBehavior::File, QString(), QVariant(), false,
703 QObject::tr( "GPX files" ) + QStringLiteral( " (*.gpx *.GPX)" ) ) );
704
705 std::unique_ptr< QgsProcessingParameterString > deviceParam = std::make_unique< QgsProcessingParameterString >( QStringLiteral( "DEVICE" ), QObject::tr( "Device" ) );
706
707 QStringList deviceNames = QgsApplication::gpsBabelFormatRegistry()->deviceNames();
708 std::sort( deviceNames.begin(), deviceNames.end(), []( const QString & a, const QString & b )
709 {
710 return a.compare( b, Qt::CaseInsensitive ) < 0;
711 } );
712
713 deviceParam->setMetadata( {{
714 QStringLiteral( "widget_wrapper" ), QVariantMap(
715 {{QStringLiteral( "value_hints" ), deviceNames }}
716 )
717 }
718 } );
719 addParameter( deviceParam.release() );
720
721 const QList< QPair<QString, QString> > devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( QStringLiteral( "usb:" ), QStringLiteral( "usb:" ) );
722 std::unique_ptr< QgsProcessingParameterString > portParam = std::make_unique< QgsProcessingParameterString >( QStringLiteral( "PORT" ), QObject::tr( "Port" ) );
723
724 QStringList ports;
725 for ( auto it = devices.constBegin(); it != devices.constEnd(); ++ it )
726 ports << it->second;
727 std::sort( ports.begin(), ports.end(), []( const QString & a, const QString & b )
728 {
729 return a.compare( b, Qt::CaseInsensitive ) < 0;
730 } );
731
732 portParam->setMetadata( {{
733 QStringLiteral( "widget_wrapper" ), QVariantMap(
734 {{QStringLiteral( "value_hints" ), ports }}
735 )
736 }
737 } );
738 addParameter( portParam.release() );
739
740 addParameter( new QgsProcessingParameterEnum( QStringLiteral( "FEATURE_TYPE" ), QObject::tr( "Feature type" ),
741 {
742 QObject::tr( "Waypoints" ),
743 QObject::tr( "Routes" ),
744 QObject::tr( "Tracks" )
745 }, false, 0 ) );
746
747}
748
749QIcon QgsUploadGpsDataAlgorithm::icon() const
750{
751 return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
752}
753
754QString QgsUploadGpsDataAlgorithm::svgIconPath() const
755{
756 return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
757}
758
759QString QgsUploadGpsDataAlgorithm::shortHelpString() const
760{
761 return QObject::tr( "This algorithm uses the GPSBabel tool to upload data to a GPS device from the GPX standard format." );
762}
763
764QgsUploadGpsDataAlgorithm *QgsUploadGpsDataAlgorithm::createInstance() const
765{
766 return new QgsUploadGpsDataAlgorithm();
767}
768
769QVariantMap QgsUploadGpsDataAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
770{
771 const QString inputPath = parameterAsString( parameters, QStringLiteral( "INPUT" ), context );
772 const Qgis::GpsFeatureType featureType = static_cast< Qgis::GpsFeatureType >( parameterAsEnum( parameters, QStringLiteral( "FEATURE_TYPE" ), context ) );
773
775 if ( babelPath.isEmpty() )
776 babelPath = QStringLiteral( "gpsbabel" );
777
778 const QString deviceName = parameterAsString( parameters, QStringLiteral( "DEVICE" ), context );
780 if ( !format )
781 {
782 throw QgsProcessingException( QObject::tr( "Unknown GPSBabel device “%1”. Valid devices are: %2" )
783 .arg( deviceName,
784 QgsApplication::gpsBabelFormatRegistry()->deviceNames().join( QLatin1String( ", " ) ) ) );
785 }
786
787 const QString portName = parameterAsString( parameters, QStringLiteral( "PORT" ), context );
788 QString outputPort;
789 const QList< QPair<QString, QString> > devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( QStringLiteral( "usb:" ), QStringLiteral( "usb:" ) );
790 QStringList validPorts;
791 for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
792 {
793 if ( it->first.compare( portName, Qt::CaseInsensitive ) == 0 || it->second.compare( portName, Qt::CaseInsensitive ) == 0 )
794 {
795 outputPort = it->first;
796 }
797 validPorts << it->first;
798 }
799 if ( outputPort.isEmpty() )
800 {
801 throw QgsProcessingException( QObject::tr( "Unknown port “%1”. Valid ports are: %2" )
802 .arg( portName,
803 validPorts.join( QLatin1String( ", " ) ) ) );
804 }
805
806
807 switch ( featureType )
808 {
811 {
812 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support waypoints." )
813 .arg( deviceName ) );
814 }
815 break;
816
819 {
820 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support routes." )
821 .arg( deviceName ) );
822 }
823 break;
824
827 {
828 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support tracks." )
829 .arg( deviceName ) );
830 }
831 break;
832 }
833
834 // note that for the log we should quote file paths, but for the actual command we don't. That's
835 // because QProcess does this internally for us, and double quoting causes issues
836 const QStringList logCommand = format->exportCommand( babelPath, featureType, inputPath, outputPort, Qgis::BabelCommandFlag::QuoteFilePaths );
837 const QStringList processCommand = format->exportCommand( babelPath, featureType, inputPath, outputPort );
838 feedback->pushCommandInfo( QObject::tr( "Upload command: " ) + logCommand.join( ' ' ) );
839
840 QgsBlockingProcess babelProcess( processCommand.value( 0 ), processCommand.mid( 1 ) );
841 babelProcess.setStdErrHandler( [ = ]( const QByteArray & ba )
842 {
843 feedback->reportError( ba );
844 } );
845 babelProcess.setStdOutHandler( [ = ]( const QByteArray & ba )
846 {
847 feedback->pushDebugInfo( ba );
848 } );
849
850 const int res = babelProcess.run( feedback );
851 if ( feedback->isCanceled() && res != 0 )
852 {
853 feedback->pushInfo( QObject::tr( "Process was canceled and did not complete" ) ) ;
854 }
855 else if ( !feedback->isCanceled() && babelProcess.exitStatus() == QProcess::CrashExit )
856 {
857 throw QgsProcessingException( QObject::tr( "Process was unexpectedly terminated" ) );
858 }
859 else if ( res == 0 )
860 {
861 feedback->pushInfo( QObject::tr( "Process completed successfully" ) );
862 }
863 else if ( babelProcess.processError() == QProcess::FailedToStart )
864 {
865 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 ) );
866 }
867 else
868 {
869 throw QgsProcessingException( QObject::tr( "Process returned error code %1" ).arg( res ) );
870 }
871
872 return {};
873}
874
876#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:1600
@ Tracks
Format supports tracks.
@ Waypoints
Format supports waypoints.
@ Routes
Format supports routes.
Qgis::BabelFormatCapabilities capabilities() const
Returns the format's capabilities.
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.
QString description() const
Returns the friendly description for the format.
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.
Definition: qgsexception.h:83
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.