QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsprofileexporter.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsprofileexporter.cpp
3 ---------------
4 begin : May 2023
5 copyright : (C) 2023 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#include "qgsprofileexporter.h"
20#include "qgsdxfexport.h"
21#include "qgsprofilerenderer.h"
23#include "qgsvectorlayer.h"
24#include "qgsvectorfilewriter.h"
25
26#include <QThread>
27#include <QFileInfo>
28
29QgsProfileExporter::QgsProfileExporter( const QList<QgsAbstractProfileSource *> &sources, const QgsProfileRequest &request, Qgis::ProfileExportType type )
30 : mType( type )
31 , mRequest( request )
32{
33 for ( QgsAbstractProfileSource *source : sources )
34 {
35 if ( source )
36 {
37 if ( std::unique_ptr< QgsAbstractProfileGenerator > generator{ source->createProfileGenerator( mRequest ) } )
38 mGenerators.emplace_back( std::move( generator ) );
39 }
40 }
41}
42
44
46{
47 if ( mGenerators.empty() )
48 return;
49
50 QgsProfilePlotRenderer renderer( std::move( mGenerators ), mRequest );
51 renderer.startGeneration();
52 renderer.waitForFinished();
53
54 mFeatures = renderer.asFeatures( mType, feedback );
55}
56
57QList< QgsVectorLayer *> QgsProfileExporter::toLayers()
58{
59 if ( mFeatures.empty() )
60 return {};
61
62 // collect all features with the same geometry types together
63 QHash< quint32, QVector< QgsAbstractProfileResults::Feature > > featuresByGeometryType;
64 for ( const QgsAbstractProfileResults::Feature &feature : std::as_const( mFeatures ) )
65 {
66 featuresByGeometryType[static_cast< quint32 >( feature.geometry.wkbType() )].append( feature );
67 }
68
69 // generate a new memory provider layer for each geometry type
70 QList< QgsVectorLayer * > res;
71 for ( auto wkbTypeIt = featuresByGeometryType.constBegin(); wkbTypeIt != featuresByGeometryType.constEnd(); ++wkbTypeIt )
72 {
73 // first collate a master list of fields for this geometry type
74 QgsFields outputFields;
75 outputFields.append( QgsField( QStringLiteral( "layer" ), QVariant::String ) );
76
77 for ( const QgsAbstractProfileResults::Feature &feature : std::as_const( wkbTypeIt.value() ) )
78 {
79 for ( auto attributeIt = feature.attributes.constBegin(); attributeIt != feature.attributes.constEnd(); ++attributeIt )
80 {
81 const int existingFieldIndex = outputFields.lookupField( attributeIt.key() );
82 if ( existingFieldIndex < 0 )
83 {
84 outputFields.append( QgsField( attributeIt.key(), attributeIt.value().type() ) );
85 }
86 else
87 {
88 if ( outputFields.at( existingFieldIndex ).type() != QVariant::String && outputFields.at( existingFieldIndex ).type() != attributeIt.value().type() )
89 {
90 // attribute type mismatch across fields, just promote to string types to be flexible
91 outputFields[ existingFieldIndex ].setType( QVariant::String );
92 }
93 }
94 }
95 }
96
97 // note -- 2d profiles have no CRS associated, the coordinate values are not location based!
98 std::unique_ptr< QgsVectorLayer > outputLayer( QgsMemoryProviderUtils::createMemoryLayer(
99 QStringLiteral( "profile" ),
100 outputFields,
101 static_cast< Qgis::WkbType >( wkbTypeIt.key() ),
103 false ) );
104
105 QList< QgsFeature > featuresToAdd;
106 featuresToAdd.reserve( wkbTypeIt.value().size() );
107 for ( const QgsAbstractProfileResults::Feature &feature : std::as_const( wkbTypeIt.value() ) )
108 {
109 QgsFeature out( outputFields );
110 out.setAttribute( 0, feature.layerIdentifier );
111 out.setGeometry( feature.geometry );
112 for ( auto attributeIt = feature.attributes.constBegin(); attributeIt != feature.attributes.constEnd(); ++attributeIt )
113 {
114 const int outputFieldIndex = outputFields.lookupField( attributeIt.key() );
115 const QgsField &targetField = outputFields.at( outputFieldIndex );
116 QVariant value = attributeIt.value();
117 targetField.convertCompatible( value );
118 out.setAttribute( outputFieldIndex, value );
119 }
120 featuresToAdd << out;
121 }
122
123 outputLayer->dataProvider()->addFeatures( featuresToAdd, QgsFeatureSink::FastInsert );
124 res << outputLayer.release();
125 }
126 return res;
127}
128
129//
130// QgsProfileExporterTask
131//
132
133QgsProfileExporterTask::QgsProfileExporterTask( const QList<QgsAbstractProfileSource *> &sources,
134 const QgsProfileRequest &request,
136 const QString &destination,
137 const QgsCoordinateTransformContext &transformContext
138 )
139 : QgsTask( tr( "Exporting elevation profile" ), QgsTask::CanCancel )
140 , mDestination( destination )
141 , mTransformContext( transformContext )
142{
143 mExporter = std::make_unique< QgsProfileExporter >( sources, request, type );
144}
145
147{
148 mFeedback = std::make_unique< QgsFeedback >();
149
150 mExporter->run( mFeedback.get() );
151
152 mLayers = mExporter->toLayers();
153
154 if ( mFeedback->isCanceled() )
155 {
156 mResult = ExportResult::Canceled;
157 return true;
158 }
159
160 if ( !mDestination.isEmpty() && !mLayers.empty() )
161 {
162 const QFileInfo destinationFileInfo( mDestination );
163 const QString fileExtension = destinationFileInfo.completeSuffix();
164 const QString driverName = QgsVectorFileWriter::driverForExtension( fileExtension );
165
166 if ( driverName == QLatin1String( "DXF" ) )
167 {
168 // DXF gets special handling -- we use the inbuilt QgsDxfExport class
169 QgsDxfExport dxf;
170 QList< QgsDxfExport::DxfLayer > dxfLayers;
171 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
172 {
173 QgsDxfExport::DxfLayer dxfLayer( layer );
174 dxfLayers.append( dxfLayer );
175 if ( layer->crs().isValid() )
176 dxf.setDestinationCrs( layer->crs() );
177 }
178 dxf.addLayers( dxfLayers );
179 QFile dxfFile( mDestination );
180 switch ( dxf.writeToFile( &dxfFile, QStringLiteral( "UTF-8" ) ) )
181 {
183 mResult = ExportResult::Success;
184 mCreatedFiles.append( mDestination );
185 break;
186
190 break;
191
194 break;
195 }
196 }
197 else
198 {
199 // use vector file writer
200 const bool outputFormatIsMultiLayer = QgsVectorFileWriter::supportedFormatExtensions( QgsVectorFileWriter::SupportsMultipleLayers ).contains( fileExtension );
201
202 int layerCount = 1;
203 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
204 {
205 QString thisLayerFilename;
207 if ( outputFormatIsMultiLayer )
208 {
209 thisLayerFilename = mDestination;
210 options.actionOnExistingFile = layerCount == 1 ? QgsVectorFileWriter::ActionOnExistingFile::CreateOrOverwriteFile
211 : QgsVectorFileWriter::ActionOnExistingFile::CreateOrOverwriteLayer;
212 if ( mLayers.size() > 1 )
213 options.layerName = QStringLiteral( "profile_%1" ).arg( layerCount );
214 }
215 else
216 {
217 options.actionOnExistingFile = QgsVectorFileWriter::ActionOnExistingFile::CreateOrOverwriteFile;
218 if ( mLayers.size() > 1 )
219 {
220 thisLayerFilename = QStringLiteral( "%1/%2_%3.%4" ).arg( destinationFileInfo.path(), destinationFileInfo.baseName() ).arg( layerCount ).arg( fileExtension );
221 }
222 else
223 {
224 thisLayerFilename = mDestination;
225 }
226 }
227 options.driverName = driverName;
228 options.feedback = mFeedback.get();
229 options.fileEncoding = QStringLiteral( "UTF-8" );
230 QString newFileName;
232 layer,
233 thisLayerFilename,
234 mTransformContext,
235 options,
236 &mError,
237 &newFileName
238 );
239 switch ( result )
240 {
242 mResult = ExportResult::Success;
243 if ( !mCreatedFiles.contains( newFileName ) )
244 mCreatedFiles.append( newFileName );
245 break;
246
251 break;
252
260 break;
261
262
264 mResult = ExportResult::Canceled;
265 break;
266 }
267
268 if ( mResult != ExportResult::Success )
269 break;
270 layerCount += 1;
271 }
272 }
273 }
274 else if ( mLayers.empty() )
275 {
276 mResult = ExportResult::Empty;
277 }
278
279 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
280 {
281 layer->moveToThread( nullptr );
282 }
283
284 mExporter.reset();
285 return true;
286}
287
289{
290 if ( mFeedback )
291 mFeedback->cancel();
292
294}
295
296QList<QgsVectorLayer *> QgsProfileExporterTask::takeLayers()
297{
298 QList<QgsVectorLayer *> res;
299 res.reserve( mLayers.size() );
300 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
301 {
302 layer->moveToThread( QThread::currentThread() );
303 res.append( layer );
304 }
305 mLayers.clear();
306 return res;
307}
308
310{
311 return mResult;
312}
ProfileExportType
Types of export for elevation profiles.
Definition: qgis.h:3475
@ Profile2D
Export profiles as 2D profile lines, with elevation stored in exported geometry Y dimension and dista...
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition: qgis.h:182
Interface for classes which can generate elevation profiles.
This class represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
Exports QGIS layers to the DXF format.
Definition: qgsdxfexport.h:66
@ DeviceNotWritableError
Device not writable error.
@ Success
Successful export.
@ EmptyExtentError
Empty extent, no extent given and no extent could be derived from layers.
@ InvalidDeviceError
Invalid device error.
ExportResult writeToFile(QIODevice *d, const QString &codec)
Export to a dxf file in the given encoding.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Set destination CRS.
void addLayers(const QList< QgsDxfExport::DxfLayer > &layers)
Add layers to export.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
Definition: qgsfeature.cpp:262
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:167
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:53
bool convertCompatible(QVariant &v, QString *errorMessage=nullptr) const
Converts the provided variant to a compatible format.
Definition: qgsfield.cpp:452
QVariant::Type type
Definition: qgsfield.h:60
Container of fields for a vector layer.
Definition: qgsfields.h:45
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
Definition: qgsfields.cpp:59
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:359
static QgsVectorLayer * createMemoryLayer(const QString &name, const QgsFields &fields, Qgis::WkbType geometryType=Qgis::WkbType::NoGeometry, const QgsCoordinateReferenceSystem &crs=QgsCoordinateReferenceSystem(), bool loadDefaultStyle=true) SIP_FACTORY
Creates a new memory layer using the specified parameters.
ExportResult
Results of exporting the profile.
@ LayerExportFailed
Generic error when outputting to files.
@ DxfExportFailed
Generic error when outputting to DXF.
@ DeviceError
Could not open output file device.
QgsProfileExporterTask(const QList< QgsAbstractProfileSource * > &sources, const QgsProfileRequest &request, Qgis::ProfileExportType type, const QString &destination, const QgsCoordinateTransformContext &transformContext)
Constructor for QgsProfileExporterTask, saving results to the specified destination file.
bool run() override
Performs the task's operation.
QgsProfileExporterTask::ExportResult result() const
Returns the result of the export operation.
void cancel() override
Notifies the task that it should terminate.
QList< QgsVectorLayer * > takeLayers()
Returns a list of vector layer containing the exported profile results.
void run(QgsFeedback *feedback=nullptr)
Runs the profile generation.
QgsProfileExporter(const QList< QgsAbstractProfileSource * > &sources, const QgsProfileRequest &request, Qgis::ProfileExportType type)
Constructor for QgsProfileExporter, using the provided list of profile sources to generate the result...
QList< QgsVectorLayer * > toLayers()
Returns a list of vector layer containing the exported profile results.
Generates and renders elevation profile plots.
QVector< QgsAbstractProfileResults::Feature > asFeatures(Qgis::ProfileExportType type, QgsFeedback *feedback=nullptr)
Exports the profile results as a set of features.
void startGeneration()
Start the generation job and immediately return.
void waitForFinished()
Block until the current job has finished.
Encapsulates properties and constraints relating to fetching elevation profiles from different source...
QgsCoordinateReferenceSystem crs() const
Returns the desired Coordinate Reference System for the profile.
Abstract base class for long running background tasks.
virtual void cancel()
Notifies the task that it should terminate.
Options to pass to writeAsVectorFormat()
QString layerName
Layer name. If let empty, it will be derived from the filename.
QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile
Action on existing file.
QgsFeedback * feedback
Optional feedback object allowing cancellation of layer save.
static QgsVectorFileWriter::WriterError writeAsVectorFormatV3(QgsVectorLayer *layer, const QString &fileName, const QgsCoordinateTransformContext &transformContext, const QgsVectorFileWriter::SaveVectorOptions &options, QString *errorMessage=nullptr, QString *newFilename=nullptr, QString *newLayer=nullptr)
Writes a layer out to a vector file.
@ Canceled
Writing was interrupted by manual cancellation.
@ ErrSavingMetadata
Metadata saving failed.
static QString driverForExtension(const QString &extension)
Returns the OGR driver name for a specified file extension.
@ SupportsMultipleLayers
Filter to only formats which support multiple layers (since QGIS 3.32)
static QStringList supportedFormatExtensions(VectorFormatOptions options=SortRecommended)
Returns a list of file extensions for supported formats, e.g "shp", "gpkg".
Represents a vector layer which manages a vector based data sets.
Encapsulates information about a feature exported from the profile results.
Layers and optional attribute index to split into multiple layers using attribute value as layer name...
Definition: qgsdxfexport.h:75