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