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