QGIS API Documentation  3.23.0-Master (b5237dafc3)
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"
28 #include "qgsbabelformatregistry.h"
29 #include "qgsbabelformat.h"
30 #include "qgsgpsdetector.h"
31 #include "qgsbabelgpsdevice.h"
32 
34 
35 QString QgsConvertGpxFeatureTypeAlgorithm::name() const
36 {
37  return QStringLiteral( "convertgpxfeaturetype" );
38 }
39 
40 QString QgsConvertGpxFeatureTypeAlgorithm::displayName() const
41 {
42  return QObject::tr( "Convert GPX feature type" );
43 }
44 
45 QStringList QgsConvertGpxFeatureTypeAlgorithm::tags() const
46 {
47  return QObject::tr( "gps,tools,babel,tracks,waypoints,routes" ).split( ',' );
48 }
49 
50 QString QgsConvertGpxFeatureTypeAlgorithm::group() const
51 {
52  return QObject::tr( "GPS" );
53 }
54 
55 QString QgsConvertGpxFeatureTypeAlgorithm::groupId() const
56 {
57  return QStringLiteral( "gps" );
58 }
59 
60 void 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 
78 QIcon QgsConvertGpxFeatureTypeAlgorithm::icon() const
79 {
80  return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
81 }
82 
83 QString QgsConvertGpxFeatureTypeAlgorithm::svgIconPath() const
84 {
85  return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
86 }
87 
88 QString 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 
93 QgsConvertGpxFeatureTypeAlgorithm *QgsConvertGpxFeatureTypeAlgorithm::createInstance() const
94 {
95  return new QgsConvertGpxFeatureTypeAlgorithm();
96 }
97 
98 
99 QVariantMap 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 
184 void 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 
233 QString QgsConvertGpsDataAlgorithm::name() const
234 {
235  return QStringLiteral( "convertgpsdata" );
236 }
237 
238 QString QgsConvertGpsDataAlgorithm::displayName() const
239 {
240  return QObject::tr( "Convert GPS data" );
241 }
242 
243 QStringList QgsConvertGpsDataAlgorithm::tags() const
244 {
245  return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export" ).split( ',' );
246 }
247 
248 QString QgsConvertGpsDataAlgorithm::group() const
249 {
250  return QObject::tr( "GPS" );
251 }
252 
253 QString QgsConvertGpsDataAlgorithm::groupId() const
254 {
255  return QStringLiteral( "gps" );
256 }
257 
258 void 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 
295 QIcon QgsConvertGpsDataAlgorithm::icon() const
296 {
297  return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
298 }
299 
300 QString QgsConvertGpsDataAlgorithm::svgIconPath() const
301 {
302  return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
303 }
304 
305 QString 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 
310 QgsConvertGpsDataAlgorithm *QgsConvertGpsDataAlgorithm::createInstance() const
311 {
312  return new QgsConvertGpsDataAlgorithm();
313 }
314 
315 QVariantMap 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 
351  if ( !( format->capabilities() & Qgis::BabelFormatCapability::Routes ) )
352  {
353  throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting routes." )
354  .arg( formatName ) );
355  }
356  break;
357 
359  if ( !( format->capabilities() & Qgis::BabelFormatCapability::Tracks ) )
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 
443 QString QgsDownloadGpsDataAlgorithm::name() const
444 {
445  return QStringLiteral( "downloadgpsdata" );
446 }
447 
448 QString QgsDownloadGpsDataAlgorithm::displayName() const
449 {
450  return QObject::tr( "Download GPS data from device" );
451 }
452 
453 QStringList QgsDownloadGpsDataAlgorithm::tags() const
454 {
455  return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export,export,device,serial" ).split( ',' );
456 }
457 
458 QString QgsDownloadGpsDataAlgorithm::group() const
459 {
460  return QObject::tr( "GPS" );
461 }
462 
463 QString QgsDownloadGpsDataAlgorithm::groupId() const
464 {
465  return QStringLiteral( "gps" );
466 }
467 
468 void 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 
518 QIcon QgsDownloadGpsDataAlgorithm::icon() const
519 {
520  return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
521 }
522 
523 QString QgsDownloadGpsDataAlgorithm::svgIconPath() const
524 {
525  return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
526 }
527 
528 QString 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 
533 QgsDownloadGpsDataAlgorithm *QgsDownloadGpsDataAlgorithm::createInstance() const
534 {
535  return new QgsDownloadGpsDataAlgorithm();
536 }
537 
538 QVariantMap 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 
586  if ( !( format->capabilities() & Qgis::BabelFormatCapability::Routes ) )
587  {
588  throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support converting routes." )
589  .arg( deviceName ) );
590  }
591  break;
592 
594  if ( !( format->capabilities() & Qgis::BabelFormatCapability::Tracks ) )
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 
679 QString QgsUploadGpsDataAlgorithm::name() const
680 {
681  return QStringLiteral( "uploadgpsdata" );
682 }
683 
684 QString QgsUploadGpsDataAlgorithm::displayName() const
685 {
686  return QObject::tr( "Upload GPS data to device" );
687 }
688 
689 QStringList QgsUploadGpsDataAlgorithm::tags() const
690 {
691  return QObject::tr( "gps,tools,babel,tracks,waypoints,routes,gpx,import,export,export,device,serial" ).split( ',' );
692 }
693 
694 QString QgsUploadGpsDataAlgorithm::group() const
695 {
696  return QObject::tr( "GPS" );
697 }
698 
699 QString QgsUploadGpsDataAlgorithm::groupId() const
700 {
701  return QStringLiteral( "gps" );
702 }
703 
704 void 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 
753 QIcon QgsUploadGpsDataAlgorithm::icon() const
754 {
755  return QgsApplication::getThemeIcon( QStringLiteral( "/mIconGps.svg" ) );
756 }
757 
758 QString QgsUploadGpsDataAlgorithm::svgIconPath() const
759 {
760  return QgsApplication::iconPath( QStringLiteral( "/mIconGps.svg" ) );
761 }
762 
763 QString 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 
768 QgsUploadGpsDataAlgorithm *QgsUploadGpsDataAlgorithm::createInstance() const
769 {
770  return new QgsUploadGpsDataAlgorithm();
771 }
772 
773 QVariantMap 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 
822  if ( !( format->capabilities() & Qgis::BabelFormatCapability::Routes ) )
823  {
824  throw QgsProcessingException( QObject::tr( "The GPSBabel format “%1” does not support routes." )
825  .arg( deviceName ) );
826  }
827  break;
828 
830  if ( !( format->capabilities() & Qgis::BabelFormatCapability::Tracks ) )
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:646
@ 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.
QgsMapLayerStore * temporaryLayerStore()
Returns a reference to the layer store used for storing temporary layers during algorithm execution.
QgsProject * project() const
Returns the project in which the algorithm is being 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.
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.
QString value(const QString &dynamicKeyPart=QString(), bool useDefaultValueOverride=false, const QString &defaultValueOverride=QString()) const
Returns settings value.
static const QgsSettingsEntryString settingsGpsBabelPath
Settings entry path to GPSBabel executable.