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