QGIS API Documentation 3.99.0-Master (09f76ad7019)
Loading...
Searching...
No Matches
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 <QString>
19#include <QtGlobal>
20
21using namespace Qt::StringLiterals;
22
23#if QT_CONFIG( process )
24
25
27#include "qgsvectorlayer.h"
28#include "qgsrunprocess.h"
29#include "qgsproviderutils.h"
30#include "qgssettings.h"
33#include "qgsbabelformat.h"
34#include "qgsgpsdetector.h"
35#include "qgsbabelgpsdevice.h"
36
38
39QString QgsConvertGpxFeatureTypeAlgorithm::name() const
40{
41 return u"convertgpxfeaturetype"_s;
42}
43
44QString QgsConvertGpxFeatureTypeAlgorithm::displayName() const
45{
46 return QObject::tr( "Convert GPX feature type" );
47}
48
49QStringList QgsConvertGpxFeatureTypeAlgorithm::tags() const
50{
51 return QObject::tr( "gps,tools,babel,tracks,waypoints,routes" ).split( ',' );
52}
53
54QString QgsConvertGpxFeatureTypeAlgorithm::group() const
55{
56 return QObject::tr( "GPS" );
57}
58
59QString QgsConvertGpxFeatureTypeAlgorithm::groupId() const
60{
61 return u"gps"_s;
62}
63
64void QgsConvertGpxFeatureTypeAlgorithm::initAlgorithm( const QVariantMap & )
65{
66 addParameter( new QgsProcessingParameterFile( u"INPUT"_s, QObject::tr( "Input file" ), Qgis::ProcessingFileParameterBehavior::File, QString(), QVariant(), false, QObject::tr( "GPX files" ) + u" (*.gpx *.GPX)"_s ) );
67
68 addParameter( new QgsProcessingParameterEnum( u"CONVERSION"_s, 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 ) );
69
70 addParameter( new QgsProcessingParameterFileDestination( u"OUTPUT"_s, QObject::tr( "Output" ), QObject::tr( "GPX files" ) + u" (*.gpx *.GPX)"_s ) );
71
72 addOutput( new QgsProcessingOutputVectorLayer( u"OUTPUT_LAYER"_s, QObject::tr( "Output layer" ) ) );
73}
74
75QIcon QgsConvertGpxFeatureTypeAlgorithm::icon() const
76{
77 return QgsApplication::getThemeIcon( u"/mIconGps.svg"_s );
78}
79
80QString QgsConvertGpxFeatureTypeAlgorithm::svgIconPath() const
81{
82 return QgsApplication::iconPath( u"/mIconGps.svg"_s );
83}
84
85QString QgsConvertGpxFeatureTypeAlgorithm::shortHelpString() const
86{
87 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)." );
88}
89
90QString QgsConvertGpxFeatureTypeAlgorithm::shortDescription() const
91{
92 return QObject::tr( "Converts GPX features from one type to another." );
93}
94
95QgsConvertGpxFeatureTypeAlgorithm *QgsConvertGpxFeatureTypeAlgorithm::createInstance() const
96{
97 return new QgsConvertGpxFeatureTypeAlgorithm();
98}
99
100
101QVariantMap QgsConvertGpxFeatureTypeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
102{
103 const QString inputPath = parameterAsString( parameters, u"INPUT"_s, context );
104 const QString outputPath = parameterAsString( parameters, u"OUTPUT"_s, context );
105
106 const ConversionType convertType = static_cast<ConversionType>( parameterAsEnum( parameters, u"CONVERSION"_s, context ) );
107
108 QString babelPath = QgsSettingsRegistryCore::settingsGpsBabelPath->value();
109 if ( babelPath.isEmpty() )
110 babelPath = u"gpsbabel"_s;
111
112 QStringList processArgs;
113 QStringList logArgs;
114 createArgumentLists( inputPath, outputPath, convertType, processArgs, logArgs );
115 feedback->pushCommandInfo( QObject::tr( "Conversion command: " ) + babelPath + ' ' + logArgs.join( ' ' ) );
116
117 QgsBlockingProcess babelProcess( babelPath, processArgs );
118 babelProcess.setStdErrHandler( [feedback]( const QByteArray &ba ) {
119 feedback->reportError( ba );
120 } );
121 babelProcess.setStdOutHandler( [feedback]( const QByteArray &ba ) {
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, u"gpx"_s );
155 break;
156 case QgsConvertGpxFeatureTypeAlgorithm::RouteFromWaypoints:
157 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=route", layerName, u"gpx"_s );
158 break;
159 case QgsConvertGpxFeatureTypeAlgorithm::TrackFromWaypoints:
160 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=track", layerName, u"gpx"_s );
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( u"OUTPUT_LAYER"_s, layerId );
173 const QgsProcessingContext::LayerDetails details( layer->name(), context.project(), u"OUTPUT_LAYER"_s, QgsProcessingUtils::LayerHint::Vector );
174 context.addLayerToLoadOnCompletion( layerId, details );
175 context.temporaryLayerStore()->addMapLayer( layer.release() );
176 }
177
178 outputs.insert( u"OUTPUT"_s, 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 : { u"-i"_s, u"gpx"_s, u"-f"_s } )
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 << u"\"%1\""_s.arg( inputPath );
194 processArgs << inputPath;
195
196 QStringList convertStrings;
197 switch ( conversion )
198 {
199 case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromRoute:
200 convertStrings << u"-x"_s << u"transform,wpt=rte,del"_s;
201 break;
202 case QgsConvertGpxFeatureTypeAlgorithm::WaypointsFromTrack:
203 convertStrings << u"-x"_s << u"transform,wpt=trk,del"_s;
204 break;
205 case QgsConvertGpxFeatureTypeAlgorithm::RouteFromWaypoints:
206 convertStrings << u"-x"_s << u"transform,rte=wpt,del"_s;
207 break;
208 case QgsConvertGpxFeatureTypeAlgorithm::TrackFromWaypoints:
209 convertStrings << u"-x"_s << u"transform,trk=wpt,del"_s;
210 break;
211 }
212 logArgs << convertStrings;
213 processArgs << convertStrings;
214
215 for ( const QString &arg : { u"-o"_s, u"gpx"_s, u"-F"_s } )
216 {
217 logArgs << arg;
218 processArgs << arg;
219 }
220
221 logArgs << u"\"%1\""_s.arg( outputPath );
222 processArgs << outputPath;
223}
224
225
226//
227// QgsConvertGpsDataAlgorithm
228//
229
230QString QgsConvertGpsDataAlgorithm::name() const
231{
232 return u"convertgpsdata"_s;
233}
234
235QString QgsConvertGpsDataAlgorithm::displayName() const
236{
237 return QObject::tr( "Convert GPS data" );
238}
239
240QStringList QgsConvertGpsDataAlgorithm::tags() const
241{
242 return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export" ).split( ',' );
243}
244
245QString QgsConvertGpsDataAlgorithm::group() const
246{
247 return QObject::tr( "GPS" );
248}
249
250QString QgsConvertGpsDataAlgorithm::groupId() const
251{
252 return u"gps"_s;
253}
254
255void QgsConvertGpsDataAlgorithm::initAlgorithm( const QVariantMap & )
256{
257 addParameter( new QgsProcessingParameterFile( u"INPUT"_s, QObject::tr( "Input file" ), Qgis::ProcessingFileParameterBehavior::File, QString(), QVariant(), false, QgsApplication::gpsBabelFormatRegistry()->importFileFilter() + u";;%1"_s.arg( QObject::tr( "All files (*.*)" ) ) ) );
258
259 auto formatParam = std::make_unique<QgsProcessingParameterString>( u"FORMAT"_s, QObject::tr( "Format" ) );
260
261 QStringList formats;
262 const QStringList formatNames = QgsApplication::gpsBabelFormatRegistry()->importFormatNames();
263 for ( const QString &format : formatNames )
265
266 std::sort( formats.begin(), formats.end(), []( const QString &a, const QString &b ) {
267 return a.compare( b, Qt::CaseInsensitive ) < 0;
268 } );
269
270 formatParam->setMetadata( { { u"widget_wrapper"_s, QVariantMap( { { u"value_hints"_s, formats } } ) }
271 } );
272 addParameter( formatParam.release() );
273
274 addParameter( new QgsProcessingParameterEnum( u"FEATURE_TYPE"_s, QObject::tr( "Feature type" ), { QObject::tr( "Waypoints" ), QObject::tr( "Routes" ), QObject::tr( "Tracks" ) }, false, 0 ) );
275
276 addParameter( new QgsProcessingParameterFileDestination( u"OUTPUT"_s, QObject::tr( "Output" ), QObject::tr( "GPX files" ) + u" (*.gpx *.GPX)"_s ) );
277
278 addOutput( new QgsProcessingOutputVectorLayer( u"OUTPUT_LAYER"_s, QObject::tr( "Output layer" ) ) );
279}
280
281QIcon QgsConvertGpsDataAlgorithm::icon() const
282{
283 return QgsApplication::getThemeIcon( u"/mIconGps.svg"_s );
284}
285
286QString QgsConvertGpsDataAlgorithm::svgIconPath() const
287{
288 return QgsApplication::iconPath( u"/mIconGps.svg"_s );
289}
290
291QString QgsConvertGpsDataAlgorithm::shortHelpString() const
292{
293 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." );
294}
295
296QString QgsConvertGpsDataAlgorithm::shortDescription() const
297{
298 return QObject::tr( "Converts a GPS data file from a range of formats to the GPX standard format." );
299}
300
301QgsConvertGpsDataAlgorithm *QgsConvertGpsDataAlgorithm::createInstance() const
302{
303 return new QgsConvertGpsDataAlgorithm();
304}
305
306QVariantMap QgsConvertGpsDataAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
307{
308 const QString inputPath = parameterAsString( parameters, u"INPUT"_s, context );
309 const QString outputPath = parameterAsString( parameters, u"OUTPUT"_s, context );
310
311 const Qgis::GpsFeatureType featureType = static_cast<Qgis::GpsFeatureType>( parameterAsEnum( parameters, u"FEATURE_TYPE"_s, context ) );
312
313 QString babelPath = QgsSettingsRegistryCore::settingsGpsBabelPath->value();
314 if ( babelPath.isEmpty() )
315 babelPath = u"gpsbabel"_s;
316
317 const QString formatName = parameterAsString( parameters, u"FORMAT"_s, context );
319 if ( !format ) // second try, match using descriptions instead of names
321
322 if ( !format )
323 {
324 throw QgsProcessingException( QObject::tr( "Unknown GPSBabel format “%1”. Valid formats are: %2" )
325 .arg( formatName, QgsApplication::gpsBabelFormatRegistry()->importFormatNames().join( ", "_L1 ) ) );
326 }
327
328 switch ( featureType )
329 {
332 {
333 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting waypoints." )
334 .arg( formatName ) );
335 }
336 break;
337
340 {
341 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting routes." )
342 .arg( formatName ) );
343 }
344 break;
345
348 {
349 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting tracks." )
350 .arg( formatName ) );
351 }
352 break;
353 }
354
355 // note that for the log we should quote file paths, but for the actual command we don't. That's
356 // because QProcess does this internally for us, and double quoting causes issues
357 const QStringList logCommand = format->importCommand( babelPath, featureType, inputPath, outputPath, Qgis::BabelCommandFlag::QuoteFilePaths );
358 const QStringList processCommand = format->importCommand( babelPath, featureType, inputPath, outputPath );
359 feedback->pushCommandInfo( QObject::tr( "Conversion command: " ) + logCommand.join( ' ' ) );
360
361 QgsBlockingProcess babelProcess( processCommand.value( 0 ), processCommand.mid( 1 ) );
362 babelProcess.setStdErrHandler( [feedback]( const QByteArray &ba ) {
363 feedback->reportError( ba );
364 } );
365 babelProcess.setStdOutHandler( [feedback]( const QByteArray &ba ) {
366 feedback->pushDebugInfo( ba );
367 } );
368
369 const int res = babelProcess.run( feedback );
370 if ( feedback->isCanceled() && res != 0 )
371 {
372 feedback->pushInfo( QObject::tr( "Process was canceled and did not complete" ) );
373 }
374 else if ( !feedback->isCanceled() && babelProcess.exitStatus() == QProcess::CrashExit )
375 {
376 throw QgsProcessingException( QObject::tr( "Process was unexpectedly terminated" ) );
377 }
378 else if ( res == 0 )
379 {
380 feedback->pushInfo( QObject::tr( "Process completed successfully" ) );
381 }
382 else if ( babelProcess.processError() == QProcess::FailedToStart )
383 {
384 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 ) );
385 }
386 else
387 {
388 throw QgsProcessingException( QObject::tr( "Process returned error code %1" ).arg( res ) );
389 }
390
391 std::unique_ptr<QgsVectorLayer> layer;
392 const QString layerName = QgsProviderUtils::suggestLayerNameFromFilePath( outputPath );
393 // add the layer
394 switch ( featureType )
395 {
397 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=waypoint", layerName, u"gpx"_s );
398 break;
400 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=route", layerName, u"gpx"_s );
401 break;
403 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=track", layerName, u"gpx"_s );
404 break;
405 }
406
407 QVariantMap outputs;
408 if ( !layer->isValid() )
409 {
410 feedback->reportError( QObject::tr( "Resulting file is not a valid GPX layer" ) );
411 }
412 else
413 {
414 const QString layerId = layer->id();
415 outputs.insert( u"OUTPUT_LAYER"_s, layerId );
416 const QgsProcessingContext::LayerDetails details( layer->name(), context.project(), u"OUTPUT_LAYER"_s, QgsProcessingUtils::LayerHint::Vector );
417 context.addLayerToLoadOnCompletion( layerId, details );
418 context.temporaryLayerStore()->addMapLayer( layer.release() );
419 }
420
421 outputs.insert( u"OUTPUT"_s, outputPath );
422 return outputs;
423}
424
425//
426// QgsDownloadGpsDataAlgorithm
427//
428
429QString QgsDownloadGpsDataAlgorithm::name() const
430{
431 return u"downloadgpsdata"_s;
432}
433
434QString QgsDownloadGpsDataAlgorithm::displayName() const
435{
436 return QObject::tr( "Download GPS data from device" );
437}
438
439QStringList QgsDownloadGpsDataAlgorithm::tags() const
440{
441 return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export,export,device,serial" ).split( ',' );
442}
443
444QString QgsDownloadGpsDataAlgorithm::group() const
445{
446 return QObject::tr( "GPS" );
447}
448
449QString QgsDownloadGpsDataAlgorithm::groupId() const
450{
451 return u"gps"_s;
452}
453
454void QgsDownloadGpsDataAlgorithm::initAlgorithm( const QVariantMap & )
455{
456 auto deviceParam = std::make_unique<QgsProcessingParameterString>( u"DEVICE"_s, QObject::tr( "Device" ) );
457
458 QStringList deviceNames = QgsApplication::gpsBabelFormatRegistry()->deviceNames();
459 std::sort( deviceNames.begin(), deviceNames.end(), []( const QString &a, const QString &b ) {
460 return a.compare( b, Qt::CaseInsensitive ) < 0;
461 } );
462
463 deviceParam->setMetadata( { { u"widget_wrapper"_s, QVariantMap( { { u"value_hints"_s, deviceNames } } ) }
464 } );
465 addParameter( deviceParam.release() );
466
467
468 const QList<QPair<QString, QString>> devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( u"usb:"_s, u"usb:"_s );
469 auto portParam = std::make_unique<QgsProcessingParameterString>( u"PORT"_s, QObject::tr( "Port" ) );
470
471 QStringList ports;
472 for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
473 ports << it->second;
474 std::sort( ports.begin(), ports.end(), []( const QString &a, const QString &b ) {
475 return a.compare( b, Qt::CaseInsensitive ) < 0;
476 } );
477
478 portParam->setMetadata( { { u"widget_wrapper"_s, QVariantMap( { { u"value_hints"_s, ports } } ) }
479 } );
480 addParameter( portParam.release() );
481
482 addParameter( new QgsProcessingParameterEnum( u"FEATURE_TYPE"_s, QObject::tr( "Feature type" ), { QObject::tr( "Waypoints" ), QObject::tr( "Routes" ), QObject::tr( "Tracks" ) }, false, 0 ) );
483
484 addParameter( new QgsProcessingParameterFileDestination( u"OUTPUT"_s, QObject::tr( "Output" ), QObject::tr( "GPX files" ) + u" (*.gpx *.GPX)"_s ) );
485
486 addOutput( new QgsProcessingOutputVectorLayer( u"OUTPUT_LAYER"_s, QObject::tr( "Output layer" ) ) );
487}
488
489QIcon QgsDownloadGpsDataAlgorithm::icon() const
490{
491 return QgsApplication::getThemeIcon( u"/mIconGps.svg"_s );
492}
493
494QString QgsDownloadGpsDataAlgorithm::svgIconPath() const
495{
496 return QgsApplication::iconPath( u"/mIconGps.svg"_s );
497}
498
499QString QgsDownloadGpsDataAlgorithm::shortHelpString() const
500{
501 return QObject::tr( "This algorithm uses the GPSBabel tool to download data from a GPS device into the GPX standard format." );
502}
503
504QString QgsDownloadGpsDataAlgorithm::shortDescription() const
505{
506 return QObject::tr( "Downloads data from a GPS device into the GPX standard format." );
507}
508
509QgsDownloadGpsDataAlgorithm *QgsDownloadGpsDataAlgorithm::createInstance() const
510{
511 return new QgsDownloadGpsDataAlgorithm();
512}
513
514QVariantMap QgsDownloadGpsDataAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
515{
516 const QString outputPath = parameterAsString( parameters, u"OUTPUT"_s, context );
517 const Qgis::GpsFeatureType featureType = static_cast<Qgis::GpsFeatureType>( parameterAsEnum( parameters, u"FEATURE_TYPE"_s, context ) );
518
519 QString babelPath = QgsSettingsRegistryCore::settingsGpsBabelPath->value();
520 if ( babelPath.isEmpty() )
521 babelPath = u"gpsbabel"_s;
522
523 const QString deviceName = parameterAsString( parameters, u"DEVICE"_s, context );
525 if ( !format )
526 {
527 throw QgsProcessingException( QObject::tr( "Unknown GPSBabel device “%1”. Valid devices are: %2" )
528 .arg( deviceName, QgsApplication::gpsBabelFormatRegistry()->deviceNames().join( ", "_L1 ) ) );
529 }
530
531 const QString portName = parameterAsString( parameters, u"PORT"_s, context );
532 QString inputPort;
533 const QList<QPair<QString, QString>> devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( u"usb:"_s, u"usb:"_s );
534 QStringList validPorts;
535 for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
536 {
537 if ( it->first.compare( portName, Qt::CaseInsensitive ) == 0 || it->second.compare( portName, Qt::CaseInsensitive ) == 0 )
538 {
539 inputPort = it->first;
540 }
541 validPorts << it->first;
542 }
543 if ( inputPort.isEmpty() )
544 {
545 throw QgsProcessingException( QObject::tr( "Unknown port “%1”. Valid ports are: %2" )
546 .arg( portName, validPorts.join( ", "_L1 ) ) );
547 }
548
549 switch ( featureType )
550 {
553 {
554 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting waypoints." )
555 .arg( deviceName ) );
556 }
557 break;
558
561 {
562 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting routes." )
563 .arg( deviceName ) );
564 }
565 break;
566
569 {
570 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting tracks." )
571 .arg( deviceName ) );
572 }
573 break;
574 }
575
576 // note that for the log we should quote file paths, but for the actual command we don't. That's
577 // because QProcess does this internally for us, and double quoting causes issues
578 const QStringList logCommand = format->importCommand( babelPath, featureType, inputPort, outputPath, Qgis::BabelCommandFlag::QuoteFilePaths );
579 const QStringList processCommand = format->importCommand( babelPath, featureType, inputPort, outputPath );
580 feedback->pushCommandInfo( QObject::tr( "Download command: " ) + logCommand.join( ' ' ) );
581
582 QgsBlockingProcess babelProcess( processCommand.value( 0 ), processCommand.mid( 1 ) );
583 babelProcess.setStdErrHandler( [feedback]( const QByteArray &ba ) {
584 feedback->reportError( ba );
585 } );
586 babelProcess.setStdOutHandler( [feedback]( const QByteArray &ba ) {
587 feedback->pushDebugInfo( ba );
588 } );
589
590 const int res = babelProcess.run( feedback );
591 if ( feedback->isCanceled() && res != 0 )
592 {
593 feedback->pushInfo( QObject::tr( "Process was canceled and did not complete" ) );
594 }
595 else if ( !feedback->isCanceled() && babelProcess.exitStatus() == QProcess::CrashExit )
596 {
597 throw QgsProcessingException( QObject::tr( "Process was unexpectedly terminated" ) );
598 }
599 else if ( res == 0 )
600 {
601 feedback->pushInfo( QObject::tr( "Process completed successfully" ) );
602 }
603 else if ( babelProcess.processError() == QProcess::FailedToStart )
604 {
605 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 ) );
606 }
607 else
608 {
609 throw QgsProcessingException( QObject::tr( "Process returned error code %1" ).arg( res ) );
610 }
611
612 std::unique_ptr<QgsVectorLayer> layer;
613 const QString layerName = QgsProviderUtils::suggestLayerNameFromFilePath( outputPath );
614 // add the layer
615 switch ( featureType )
616 {
618 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=waypoint", layerName, u"gpx"_s );
619 break;
621 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=route", layerName, u"gpx"_s );
622 break;
624 layer = std::make_unique<QgsVectorLayer>( outputPath + "?type=track", layerName, u"gpx"_s );
625 break;
626 }
627
628 QVariantMap outputs;
629 if ( !layer->isValid() )
630 {
631 feedback->reportError( QObject::tr( "Resulting file is not a valid GPX layer" ) );
632 }
633 else
634 {
635 const QString layerId = layer->id();
636 outputs.insert( u"OUTPUT_LAYER"_s, layerId );
637 const QgsProcessingContext::LayerDetails details( layer->name(), context.project(), u"OUTPUT_LAYER"_s, QgsProcessingUtils::LayerHint::Vector );
638 context.addLayerToLoadOnCompletion( layerId, details );
639 context.temporaryLayerStore()->addMapLayer( layer.release() );
640 }
641
642 outputs.insert( u"OUTPUT"_s, outputPath );
643 return outputs;
644}
645
646
647//
648// QgsUploadGpsDataAlgorithm
649//
650
651QString QgsUploadGpsDataAlgorithm::name() const
652{
653 return u"uploadgpsdata"_s;
654}
655
656QString QgsUploadGpsDataAlgorithm::displayName() const
657{
658 return QObject::tr( "Upload GPS data to device" );
659}
660
661QStringList QgsUploadGpsDataAlgorithm::tags() const
662{
663 return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export,export,device,serial" ).split( ',' );
664}
665
666QString QgsUploadGpsDataAlgorithm::group() const
667{
668 return QObject::tr( "GPS" );
669}
670
671QString QgsUploadGpsDataAlgorithm::groupId() const
672{
673 return u"gps"_s;
674}
675
676void QgsUploadGpsDataAlgorithm::initAlgorithm( const QVariantMap & )
677{
678 addParameter( new QgsProcessingParameterFile( u"INPUT"_s, QObject::tr( "Input file" ), Qgis::ProcessingFileParameterBehavior::File, QString(), QVariant(), false, QObject::tr( "GPX files" ) + u" (*.gpx *.GPX)"_s ) );
679
680 auto deviceParam = std::make_unique<QgsProcessingParameterString>( u"DEVICE"_s, QObject::tr( "Device" ) );
681
682 QStringList deviceNames = QgsApplication::gpsBabelFormatRegistry()->deviceNames();
683 std::sort( deviceNames.begin(), deviceNames.end(), []( const QString &a, const QString &b ) {
684 return a.compare( b, Qt::CaseInsensitive ) < 0;
685 } );
686
687 deviceParam->setMetadata( { { u"widget_wrapper"_s, QVariantMap( { { u"value_hints"_s, deviceNames } } ) }
688 } );
689 addParameter( deviceParam.release() );
690
691 const QList<QPair<QString, QString>> devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( u"usb:"_s, u"usb:"_s );
692 auto portParam = std::make_unique<QgsProcessingParameterString>( u"PORT"_s, QObject::tr( "Port" ) );
693
694 QStringList ports;
695 for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
696 ports << it->second;
697 std::sort( ports.begin(), ports.end(), []( const QString &a, const QString &b ) {
698 return a.compare( b, Qt::CaseInsensitive ) < 0;
699 } );
700
701 portParam->setMetadata( { { u"widget_wrapper"_s, QVariantMap( { { u"value_hints"_s, ports } } ) }
702 } );
703 addParameter( portParam.release() );
704
705 addParameter( new QgsProcessingParameterEnum( u"FEATURE_TYPE"_s, QObject::tr( "Feature type" ), { QObject::tr( "Waypoints" ), QObject::tr( "Routes" ), QObject::tr( "Tracks" ) }, false, 0 ) );
706}
707
708QIcon QgsUploadGpsDataAlgorithm::icon() const
709{
710 return QgsApplication::getThemeIcon( u"/mIconGps.svg"_s );
711}
712
713QString QgsUploadGpsDataAlgorithm::svgIconPath() const
714{
715 return QgsApplication::iconPath( u"/mIconGps.svg"_s );
716}
717
718QString QgsUploadGpsDataAlgorithm::shortHelpString() const
719{
720 return QObject::tr( "This algorithm uses the GPSBabel tool to upload data to a GPS device from the GPX standard format." );
721}
722
723QString QgsUploadGpsDataAlgorithm::shortDescription() const
724{
725 return QObject::tr( "Uploads data to a GPS device from the GPX standard format." );
726}
727
728QgsUploadGpsDataAlgorithm *QgsUploadGpsDataAlgorithm::createInstance() const
729{
730 return new QgsUploadGpsDataAlgorithm();
731}
732
733QVariantMap QgsUploadGpsDataAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
734{
735 const QString inputPath = parameterAsString( parameters, u"INPUT"_s, context );
736 const Qgis::GpsFeatureType featureType = static_cast<Qgis::GpsFeatureType>( parameterAsEnum( parameters, u"FEATURE_TYPE"_s, context ) );
737
738 QString babelPath = QgsSettingsRegistryCore::settingsGpsBabelPath->value();
739 if ( babelPath.isEmpty() )
740 babelPath = u"gpsbabel"_s;
741
742 const QString deviceName = parameterAsString( parameters, u"DEVICE"_s, context );
744 if ( !format )
745 {
746 throw QgsProcessingException( QObject::tr( "Unknown GPSBabel device “%1”. Valid devices are: %2" )
747 .arg( deviceName, QgsApplication::gpsBabelFormatRegistry()->deviceNames().join( ", "_L1 ) ) );
748 }
749
750 const QString portName = parameterAsString( parameters, u"PORT"_s, context );
751 QString outputPort;
752 const QList<QPair<QString, QString>> devices = QgsGpsDetector::availablePorts() << QPair<QString, QString>( u"usb:"_s, u"usb:"_s );
753 QStringList validPorts;
754 for ( auto it = devices.constBegin(); it != devices.constEnd(); ++it )
755 {
756 if ( it->first.compare( portName, Qt::CaseInsensitive ) == 0 || it->second.compare( portName, Qt::CaseInsensitive ) == 0 )
757 {
758 outputPort = it->first;
759 }
760 validPorts << it->first;
761 }
762 if ( outputPort.isEmpty() )
763 {
764 throw QgsProcessingException( QObject::tr( "Unknown port “%1”. Valid ports are: %2" )
765 .arg( portName, validPorts.join( ", "_L1 ) ) );
766 }
767
768
769 switch ( featureType )
770 {
773 {
774 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support waypoints." )
775 .arg( deviceName ) );
776 }
777 break;
778
781 {
782 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support routes." )
783 .arg( deviceName ) );
784 }
785 break;
786
789 {
790 throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support tracks." )
791 .arg( deviceName ) );
792 }
793 break;
794 }
795
796 // note that for the log we should quote file paths, but for the actual command we don't. That's
797 // because QProcess does this internally for us, and double quoting causes issues
798 const QStringList logCommand = format->exportCommand( babelPath, featureType, inputPath, outputPort, Qgis::BabelCommandFlag::QuoteFilePaths );
799 const QStringList processCommand = format->exportCommand( babelPath, featureType, inputPath, outputPort );
800 feedback->pushCommandInfo( QObject::tr( "Upload command: " ) + logCommand.join( ' ' ) );
801
802 QgsBlockingProcess babelProcess( processCommand.value( 0 ), processCommand.mid( 1 ) );
803 babelProcess.setStdErrHandler( [feedback]( const QByteArray &ba ) {
804 feedback->reportError( ba );
805 } );
806 babelProcess.setStdOutHandler( [feedback]( const QByteArray &ba ) {
807 feedback->pushDebugInfo( ba );
808 } );
809
810 const int res = babelProcess.run( feedback );
811 if ( feedback->isCanceled() && res != 0 )
812 {
813 feedback->pushInfo( QObject::tr( "Process was canceled and did not complete" ) );
814 }
815 else if ( !feedback->isCanceled() && babelProcess.exitStatus() == QProcess::CrashExit )
816 {
817 throw QgsProcessingException( QObject::tr( "Process was unexpectedly terminated" ) );
818 }
819 else if ( res == 0 )
820 {
821 feedback->pushInfo( QObject::tr( "Process completed successfully" ) );
822 }
823 else if ( babelProcess.processError() == QProcess::FailedToStart )
824 {
825 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 ) );
826 }
827 else
828 {
829 throw QgsProcessingException( QObject::tr( "Process returned error code %1" ).arg( res ) );
830 }
831
832 return {};
833}
834
836#endif // process
@ File
Parameter is a single file.
Definition qgis.h:3860
@ QuoteFilePaths
File paths should be enclosed in quotations and escaped.
Definition qgis.h:2072
GpsFeatureType
GPS feature types.
Definition qgis.h:2085
@ Waypoint
Waypoint.
Definition qgis.h:2086
@ Tracks
Format supports tracks.
Definition qgis.h:2057
@ Waypoints
Format supports waypoints.
Definition qgis.h:2055
@ Routes
Format supports routes.
Definition qgis.h:2056
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:55
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.
static QString suggestLayerNameFromFilePath(const QString &path)
Suggests a suitable layer name given only a file path.
static const QgsSettingsEntryString * settingsGpsBabelPath
Settings entry path to GPSBabel executable.