QGIS API Documentation 3.99.0-Master (09f76ad7019)
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 <QString>
29#include <QThread>
30
31#include "moc_qgsprofileexporter.cpp"
32
33using namespace Qt::StringLiterals;
34
35QgsProfileExporter::QgsProfileExporter( const QList<QgsAbstractProfileSource *> &sources, const QgsProfileRequest &request, Qgis::ProfileExportType type )
36 : mType( type )
37 , mRequest( request )
38{
39 for ( QgsAbstractProfileSource *source : sources )
40 {
41 if ( source )
42 {
43 if ( std::unique_ptr< QgsAbstractProfileGenerator > generator{ source->createProfileGenerator( mRequest ) } )
44 mGenerators.emplace_back( std::move( generator ) );
45 }
46 }
47}
48
50
52{
53 if ( mGenerators.empty() )
54 return;
55
56 QgsProfilePlotRenderer renderer( std::move( mGenerators ), mRequest );
57 renderer.startGeneration();
58 renderer.waitForFinished();
59
60 mFeatures = renderer.asFeatures( mType, feedback );
61}
62
63QList< QgsVectorLayer *> QgsProfileExporter::toLayers()
64{
65 if ( mFeatures.empty() )
66 return {};
67
68 // collect all features with the same geometry types together
69 QHash< quint32, QVector< QgsAbstractProfileResults::Feature > > featuresByGeometryType;
70 for ( const QgsAbstractProfileResults::Feature &feature : std::as_const( mFeatures ) )
71 {
72 featuresByGeometryType[static_cast< quint32 >( feature.geometry.wkbType() )].append( feature );
73 }
74
75 // generate a new memory provider layer for each geometry type
76 QList< QgsVectorLayer * > res;
77 for ( auto wkbTypeIt = featuresByGeometryType.constBegin(); wkbTypeIt != featuresByGeometryType.constEnd(); ++wkbTypeIt )
78 {
79 // first collate a master list of fields for this geometry type
80 QgsFields outputFields;
81 outputFields.append( QgsField( u"layer"_s, QMetaType::Type::QString ) );
82
83 for ( const QgsAbstractProfileResults::Feature &feature : std::as_const( wkbTypeIt.value() ) )
84 {
85 for ( auto attributeIt = feature.attributes.constBegin(); attributeIt != feature.attributes.constEnd(); ++attributeIt )
86 {
87 const int existingFieldIndex = outputFields.lookupField( attributeIt.key() );
88 if ( existingFieldIndex < 0 )
89 {
90 outputFields.append( QgsField( attributeIt.key(), static_cast<QMetaType::Type>( attributeIt.value().userType() ) ) );
91 }
92 else
93 {
94 if ( outputFields.at( existingFieldIndex ).type() != QMetaType::Type::QString && outputFields.at( existingFieldIndex ).type() != attributeIt.value().userType() )
95 {
96 // attribute type mismatch across fields, just promote to string types to be flexible
97 outputFields[ existingFieldIndex ].setType( QMetaType::Type::QString );
98 }
99 }
100 }
101 }
102
103 // note -- 2d profiles have no CRS associated, the coordinate values are not location based!
104 std::unique_ptr< QgsVectorLayer > outputLayer( QgsMemoryProviderUtils::createMemoryLayer(
105 u"profile"_s,
106 outputFields,
107 static_cast< Qgis::WkbType >( wkbTypeIt.key() ),
109 false ) );
110
111 QList< QgsFeature > featuresToAdd;
112 featuresToAdd.reserve( wkbTypeIt.value().size() );
113 for ( const QgsAbstractProfileResults::Feature &feature : std::as_const( wkbTypeIt.value() ) )
114 {
115 QgsFeature out( outputFields );
116 out.setAttribute( 0, feature.layerIdentifier );
117 out.setGeometry( feature.geometry );
118 for ( auto attributeIt = feature.attributes.constBegin(); attributeIt != feature.attributes.constEnd(); ++attributeIt )
119 {
120 const int outputFieldIndex = outputFields.lookupField( attributeIt.key() );
121 const QgsField &targetField = outputFields.at( outputFieldIndex );
122 QVariant value = attributeIt.value();
123 targetField.convertCompatible( value );
124 out.setAttribute( outputFieldIndex, value );
125 }
126 featuresToAdd << out;
127 }
128
129 if ( !outputLayer->dataProvider()->addFeatures( featuresToAdd, QgsFeatureSink::FastInsert ) )
130 {
131 QgsDebugError( u"Error exporting feature: %1"_s.arg( outputLayer->dataProvider()->lastError() ) );
132 }
133 res << outputLayer.release();
134 }
135 return res;
136}
137
138//
139// QgsProfileExporterTask
140//
141
142QgsProfileExporterTask::QgsProfileExporterTask( const QList<QgsAbstractProfileSource *> &sources,
143 const QgsProfileRequest &request,
145 const QString &destination,
146 const QgsCoordinateTransformContext &transformContext
147 )
148 : QgsTask( tr( "Exporting elevation profile" ), QgsTask::CanCancel )
149 , mDestination( destination )
150 , mTransformContext( transformContext )
151{
152 mExporter = std::make_unique< QgsProfileExporter >( sources, request, type );
153}
154
156{
157 mFeedback = std::make_unique< QgsFeedback >();
158
159 mExporter->run( mFeedback.get() );
160
161 mLayers = mExporter->toLayers();
162
163 if ( mFeedback->isCanceled() )
164 {
165 mResult = ExportResult::Canceled;
166 return true;
167 }
168
169 if ( !mDestination.isEmpty() && !mLayers.empty() )
170 {
171 const QFileInfo destinationFileInfo( mDestination );
172 const QString fileExtension = destinationFileInfo.completeSuffix();
173 const QString driverName = QgsVectorFileWriter::driverForExtension( fileExtension );
174
175 if ( driverName == "DXF"_L1 )
176 {
177 // DXF gets special handling -- we use the inbuilt QgsDxfExport class
178 QgsDxfExport dxf;
179 QList< QgsDxfExport::DxfLayer > dxfLayers;
180 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
181 {
182 QgsDxfExport::DxfLayer dxfLayer( layer );
183 dxfLayers.append( dxfLayer );
184 if ( layer->crs().isValid() )
185 dxf.setDestinationCrs( layer->crs() );
186 }
187 dxf.addLayers( dxfLayers );
188 QFile dxfFile( mDestination );
189 switch ( dxf.writeToFile( &dxfFile, u"UTF-8"_s ) )
190 {
192 mResult = ExportResult::Success;
193 mCreatedFiles.append( mDestination );
194 break;
195
199 break;
200
203 break;
204 }
205 }
206 else
207 {
208 // use vector file writer
209 const bool outputFormatIsMultiLayer = QgsVectorFileWriter::supportedFormatExtensions( QgsVectorFileWriter::SupportsMultipleLayers ).contains( fileExtension );
210
211 int layerCount = 1;
212 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
213 {
214 QString thisLayerFilename;
216 if ( outputFormatIsMultiLayer )
217 {
218 thisLayerFilename = mDestination;
221 if ( mLayers.size() > 1 )
222 options.layerName = u"profile_%1"_s.arg( layerCount );
223 }
224 else
225 {
227 if ( mLayers.size() > 1 )
228 {
229 thisLayerFilename = u"%1/%2_%3.%4"_s.arg( destinationFileInfo.path(), destinationFileInfo.baseName() ).arg( layerCount ).arg( fileExtension );
230 }
231 else
232 {
233 thisLayerFilename = mDestination;
234 }
235 }
236 options.driverName = driverName;
237 options.feedback = mFeedback.get();
238 options.fileEncoding = u"UTF-8"_s;
239 QString newFileName;
241 layer,
242 thisLayerFilename,
243 mTransformContext,
244 options,
245 &mError,
246 &newFileName
247 );
248 switch ( result )
249 {
251 mResult = ExportResult::Success;
252 if ( !mCreatedFiles.contains( newFileName ) )
253 mCreatedFiles.append( newFileName );
254 break;
255
260 break;
261
269 break;
270
271
273 mResult = ExportResult::Canceled;
274 break;
275 }
276
277 if ( mResult != ExportResult::Success )
278 break;
279 layerCount += 1;
280 }
281 }
282 }
283 else if ( mLayers.empty() )
284 {
285 mResult = ExportResult::Empty;
286 }
287
288 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
289 {
290 layer->moveToThread( nullptr );
291 }
292
293 mExporter.reset();
294 return true;
295}
296
298{
299 if ( mFeedback )
300 mFeedback->cancel();
301
303}
304
305QList<QgsVectorLayer *> QgsProfileExporterTask::takeLayers()
306{
307 QList<QgsVectorLayer *> res;
308 res.reserve( mLayers.size() );
309 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
310 {
311 layer->moveToThread( QThread::currentThread() );
312 res.append( layer );
313 }
314 mLayers.clear();
315 return res;
316}
317
ProfileExportType
Types of export for elevation profiles.
Definition qgis.h:4327
@ Profile2D
Export profiles as 2D profile lines, with elevation stored in exported geometry Y dimension and dista...
Definition qgis.h:4329
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:280
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:60
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:56
QMetaType::Type type
Definition qgsfield.h:63
bool convertCompatible(QVariant &v, QString *errorMessage=nullptr) const
Converts the provided variant to a compatible format.
Definition qgsfield.cpp:479
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:76
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:59
Encapsulates information about a feature exported from the profile results.
Encapsulates the properties of a vector layer containing features that will be exported to the DXF fi...