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