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