QGIS API Documentation 3.99.0-Master (752b475928d)
Loading...
Searching...
No Matches
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"
18
21#include "qgsdxfexport.h"
23#include "qgsprofilerenderer.h"
24#include "qgsvectorfilewriter.h"
25#include "qgsvectorlayer.h"
26
27#include <QFileInfo>
28#include <QThread>
29
30#include "moc_qgsprofileexporter.cpp"
31
32QgsProfileExporter::QgsProfileExporter( const QList<QgsAbstractProfileSource *> &sources, const QgsProfileRequest &request, Qgis::ProfileExportType type )
33 : mType( type )
34 , mRequest( request )
35{
36 for ( QgsAbstractProfileSource *source : sources )
37 {
38 if ( source )
39 {
40 if ( std::unique_ptr< QgsAbstractProfileGenerator > generator{ source->createProfileGenerator( mRequest ) } )
41 mGenerators.emplace_back( std::move( generator ) );
42 }
43 }
44}
45
47
49{
50 if ( mGenerators.empty() )
51 return;
52
53 QgsProfilePlotRenderer renderer( std::move( mGenerators ), mRequest );
54 renderer.startGeneration();
55 renderer.waitForFinished();
56
57 mFeatures = renderer.asFeatures( mType, feedback );
58}
59
60QList< QgsVectorLayer *> QgsProfileExporter::toLayers()
61{
62 if ( mFeatures.empty() )
63 return {};
64
65 // collect all features with the same geometry types together
66 QHash< quint32, QVector< QgsAbstractProfileResults::Feature > > featuresByGeometryType;
67 for ( const QgsAbstractProfileResults::Feature &feature : std::as_const( mFeatures ) )
68 {
69 featuresByGeometryType[static_cast< quint32 >( feature.geometry.wkbType() )].append( feature );
70 }
71
72 // generate a new memory provider layer for each geometry type
73 QList< QgsVectorLayer * > res;
74 for ( auto wkbTypeIt = featuresByGeometryType.constBegin(); wkbTypeIt != featuresByGeometryType.constEnd(); ++wkbTypeIt )
75 {
76 // first collate a master list of fields for this geometry type
77 QgsFields outputFields;
78 outputFields.append( QgsField( QStringLiteral( "layer" ), QMetaType::Type::QString ) );
79
80 for ( const QgsAbstractProfileResults::Feature &feature : std::as_const( wkbTypeIt.value() ) )
81 {
82 for ( auto attributeIt = feature.attributes.constBegin(); attributeIt != feature.attributes.constEnd(); ++attributeIt )
83 {
84 const int existingFieldIndex = outputFields.lookupField( attributeIt.key() );
85 if ( existingFieldIndex < 0 )
86 {
87 outputFields.append( QgsField( attributeIt.key(), static_cast<QMetaType::Type>( attributeIt.value().userType() ) ) );
88 }
89 else
90 {
91 if ( outputFields.at( existingFieldIndex ).type() != QMetaType::Type::QString && outputFields.at( existingFieldIndex ).type() != attributeIt.value().userType() )
92 {
93 // attribute type mismatch across fields, just promote to string types to be flexible
94 outputFields[ existingFieldIndex ].setType( QMetaType::Type::QString );
95 }
96 }
97 }
98 }
99
100 // note -- 2d profiles have no CRS associated, the coordinate values are not location based!
101 std::unique_ptr< QgsVectorLayer > outputLayer( QgsMemoryProviderUtils::createMemoryLayer(
102 QStringLiteral( "profile" ),
103 outputFields,
104 static_cast< Qgis::WkbType >( wkbTypeIt.key() ),
106 false ) );
107
108 QList< QgsFeature > featuresToAdd;
109 featuresToAdd.reserve( wkbTypeIt.value().size() );
110 for ( const QgsAbstractProfileResults::Feature &feature : std::as_const( wkbTypeIt.value() ) )
111 {
112 QgsFeature out( outputFields );
113 out.setAttribute( 0, feature.layerIdentifier );
114 out.setGeometry( feature.geometry );
115 for ( auto attributeIt = feature.attributes.constBegin(); attributeIt != feature.attributes.constEnd(); ++attributeIt )
116 {
117 const int outputFieldIndex = outputFields.lookupField( attributeIt.key() );
118 const QgsField &targetField = outputFields.at( outputFieldIndex );
119 QVariant value = attributeIt.value();
120 targetField.convertCompatible( value );
121 out.setAttribute( outputFieldIndex, value );
122 }
123 featuresToAdd << out;
124 }
125
126 if ( !outputLayer->dataProvider()->addFeatures( featuresToAdd, QgsFeatureSink::FastInsert ) )
127 {
128 QgsDebugError( QStringLiteral( "Error exporting feature: %1" ).arg( outputLayer->dataProvider()->lastError() ) );
129 }
130 res << outputLayer.release();
131 }
132 return res;
133}
134
135//
136// QgsProfileExporterTask
137//
138
139QgsProfileExporterTask::QgsProfileExporterTask( const QList<QgsAbstractProfileSource *> &sources,
140 const QgsProfileRequest &request,
142 const QString &destination,
143 const QgsCoordinateTransformContext &transformContext
144 )
145 : QgsTask( tr( "Exporting elevation profile" ), QgsTask::CanCancel )
146 , mDestination( destination )
147 , mTransformContext( transformContext )
148{
149 mExporter = std::make_unique< QgsProfileExporter >( sources, request, type );
150}
151
153{
154 mFeedback = std::make_unique< QgsFeedback >();
155
156 mExporter->run( mFeedback.get() );
157
158 mLayers = mExporter->toLayers();
159
160 if ( mFeedback->isCanceled() )
161 {
162 mResult = ExportResult::Canceled;
163 return true;
164 }
165
166 if ( !mDestination.isEmpty() && !mLayers.empty() )
167 {
168 const QFileInfo destinationFileInfo( mDestination );
169 const QString fileExtension = destinationFileInfo.completeSuffix();
170 const QString driverName = QgsVectorFileWriter::driverForExtension( fileExtension );
171
172 if ( driverName == QLatin1String( "DXF" ) )
173 {
174 // DXF gets special handling -- we use the inbuilt QgsDxfExport class
175 QgsDxfExport dxf;
176 QList< QgsDxfExport::DxfLayer > dxfLayers;
177 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
178 {
179 QgsDxfExport::DxfLayer dxfLayer( layer );
180 dxfLayers.append( dxfLayer );
181 if ( layer->crs().isValid() )
182 dxf.setDestinationCrs( layer->crs() );
183 }
184 dxf.addLayers( dxfLayers );
185 QFile dxfFile( mDestination );
186 switch ( dxf.writeToFile( &dxfFile, QStringLiteral( "UTF-8" ) ) )
187 {
189 mResult = ExportResult::Success;
190 mCreatedFiles.append( mDestination );
191 break;
192
196 break;
197
200 break;
201 }
202 }
203 else
204 {
205 // use vector file writer
206 const bool outputFormatIsMultiLayer = QgsVectorFileWriter::supportedFormatExtensions( QgsVectorFileWriter::SupportsMultipleLayers ).contains( fileExtension );
207
208 int layerCount = 1;
209 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
210 {
211 QString thisLayerFilename;
213 if ( outputFormatIsMultiLayer )
214 {
215 thisLayerFilename = mDestination;
218 if ( mLayers.size() > 1 )
219 options.layerName = QStringLiteral( "profile_%1" ).arg( layerCount );
220 }
221 else
222 {
224 if ( mLayers.size() > 1 )
225 {
226 thisLayerFilename = QStringLiteral( "%1/%2_%3.%4" ).arg( destinationFileInfo.path(), destinationFileInfo.baseName() ).arg( layerCount ).arg( fileExtension );
227 }
228 else
229 {
230 thisLayerFilename = mDestination;
231 }
232 }
233 options.driverName = driverName;
234 options.feedback = mFeedback.get();
235 options.fileEncoding = QStringLiteral( "UTF-8" );
236 QString newFileName;
238 layer,
239 thisLayerFilename,
240 mTransformContext,
241 options,
242 &mError,
243 &newFileName
244 );
245 switch ( result )
246 {
248 mResult = ExportResult::Success;
249 if ( !mCreatedFiles.contains( newFileName ) )
250 mCreatedFiles.append( newFileName );
251 break;
252
257 break;
258
266 break;
267
268
270 mResult = ExportResult::Canceled;
271 break;
272 }
273
274 if ( mResult != ExportResult::Success )
275 break;
276 layerCount += 1;
277 }
278 }
279 }
280 else if ( mLayers.empty() )
281 {
282 mResult = ExportResult::Empty;
283 }
284
285 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
286 {
287 layer->moveToThread( nullptr );
288 }
289
290 mExporter.reset();
291 return true;
292}
293
295{
296 if ( mFeedback )
297 mFeedback->cancel();
298
300}
301
302QList<QgsVectorLayer *> QgsProfileExporterTask::takeLayers()
303{
304 QList<QgsVectorLayer *> res;
305 res.reserve( mLayers.size() );
306 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
307 {
308 layer->moveToThread( QThread::currentThread() );
309 res.append( layer );
310 }
311 mLayers.clear();
312 return res;
313}
314
ProfileExportType
Types of export for elevation profiles.
Definition qgis.h:4233
@ Profile2D
Export profiles as 2D profile lines, with elevation stored in exported geometry Y dimension and dista...
Definition qgis.h:4235
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:277
Interface for classes which can generate elevation profiles.
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.
@ 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:58
Q_INVOKABLE bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
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:54
QMetaType::Type type
Definition qgsfield.h:61
bool convertCompatible(QVariant &v, QString *errorMessage=nullptr) const
Converts the provided variant to a compatible format.
Definition qgsfield.cpp:475
Container of fields for a vector layer.
Definition qgsfields.h:46
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:73
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
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...
virtual void cancel()
Notifies the task that it should terminate.
QgsTask(const QString &description=QString(), QgsTask::Flags flags=AllFlags)
Constructor for QgsTask.
@ CanCancel
Task can be canceled.
Options to pass to QgsVectorFileWriter::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.
static QStringList supportedFormatExtensions(VectorFormatOptions options=SortRecommended)
Returns a list of file extensions for supported formats, e.g "shp", "gpkg".
@ CreateOrOverwriteLayer
Create or overwrite layer.
@ CreateOrOverwriteFile
Create or overwrite file.
Represents a vector layer which manages a vector based dataset.
#define QgsDebugError(str)
Definition qgslogger.h:57
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...