QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
qgsabstractgeopdfexporter.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsabtractgeopdfexporter.cpp
3  --------------------------
4  begin : August 2019
5  copyright : (C) 2019 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 /***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
20 #include "qgsfeaturerequest.h"
21 #include "qgslogger.h"
22 #include "qgsgeometry.h"
23 #include "qgsvectorlayer.h"
24 #include "qgsvectorfilewriter.h"
25 
26 #include <gdal.h>
27 #include "qgsgdalutils.h"
28 #include "cpl_string.h"
29 
30 #include <QMutex>
31 #include <QMutexLocker>
32 #include <QDomDocument>
33 #include <QDomElement>
34 
35 
37 {
38 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(3,0,0)
39  return false;
40 #else
41 
42  // test if GDAL has read support in PDF driver
43  GDALDriverH hDriverMem = GDALGetDriverByName( "PDF" );
44  if ( !hDriverMem )
45  {
46  return false;
47  }
48 
49  const char *pHavePoppler = GDALGetMetadataItem( hDriverMem, "HAVE_POPPLER", nullptr );
50  if ( pHavePoppler && strstr( pHavePoppler, "YES" ) )
51  return true;
52 
53  const char *pHavePdfium = GDALGetMetadataItem( hDriverMem, "HAVE_PDFIUM", nullptr );
54  if ( pHavePdfium && strstr( pHavePdfium, "YES" ) )
55  return true;
56 
57  return false;
58 #endif
59 }
60 
62 {
63 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(3,0,0)
64  return QObject::tr( "GeoPDF creation requires GDAL version 3.0 or later." );
65 #else
66  // test if GDAL has read support in PDF driver
67  GDALDriverH hDriverMem = GDALGetDriverByName( "PDF" );
68  if ( !hDriverMem )
69  {
70  return QObject::tr( "No GDAL PDF driver available." );
71  }
72 
73  const char *pHavePoppler = GDALGetMetadataItem( hDriverMem, "HAVE_POPPLER", nullptr );
74  if ( pHavePoppler && strstr( pHavePoppler, "YES" ) )
75  return QString();
76 
77  const char *pHavePdfium = GDALGetMetadataItem( hDriverMem, "HAVE_PDFIUM", nullptr );
78  if ( pHavePdfium && strstr( pHavePdfium, "YES" ) )
79  return QString();
80 
81  return QObject::tr( "GDAL PDF driver was not built with PDF read support. A build with PDF read support is required for GeoPDF creation." );
82 #endif
83 }
84 
85 bool QgsAbstractGeoPdfExporter::finalize( const QList<ComponentLayerDetail> &components, const QString &destinationFile, const ExportDetails &details )
86 {
87  if ( details.includeFeatures && !saveTemporaryLayers() )
88  return false;
89 
90 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(3,0,0)
91  Q_UNUSED( components )
92  Q_UNUSED( destinationFile )
93  return false;
94 #else
95  const QString composition = createCompositionXml( components, details );
96  QgsDebugMsg( composition );
97  if ( composition.isEmpty() )
98  return false;
99 
100  // do the creation!
101  GDALDriverH driver = GDALGetDriverByName( "PDF" );
102  if ( !driver )
103  {
104  mErrorMessage = QObject::tr( "Cannot load GDAL PDF driver" );
105  return false;
106  }
107 
108  const QString xmlFilePath = generateTemporaryFilepath( QStringLiteral( "composition.xml" ) );
109  QFile file( xmlFilePath );
110  if ( file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
111  {
112  QTextStream out( &file );
113  out << composition;
114  }
115  else
116  {
117  mErrorMessage = QObject::tr( "Could not create GeoPDF composition file" );
118  return false;
119  }
120 
121  char **papszOptions = CSLSetNameValue( nullptr, "COMPOSITION_FILE", xmlFilePath.toUtf8().constData() );
122 
123  // return a non-null (fake) dataset in case of success, nullptr otherwise.
124  gdal::dataset_unique_ptr outputDataset( GDALCreate( driver, destinationFile.toUtf8().constData(), 0, 0, 0, GDT_Unknown, papszOptions ) );
125  bool res = outputDataset.get();
126  outputDataset.reset();
127 
128  CSLDestroy( papszOptions );
129 
130  return res;
131 #endif
132 }
133 
134 QString QgsAbstractGeoPdfExporter::generateTemporaryFilepath( const QString &filename ) const
135 {
136  return mTemporaryDir.filePath( filename );
137 }
138 
139 
140 void QgsAbstractGeoPdfExporter::pushRenderedFeature( const QString &layerId, const QgsAbstractGeoPdfExporter::RenderedFeature &feature, const QString &group )
141 {
142  // because map layers may be rendered in parallel, we need a mutex here
143  QMutexLocker locker( &mMutex );
144 
145  // collate all the features which belong to the same layer, replacing their geometries with the rendered feature bounds
146  QgsFeature f = feature.feature;
147  f.setGeometry( feature.renderedBounds );
148  mCollatedFeatures[ group ][ layerId ].append( f );
149 }
150 
151 bool QgsAbstractGeoPdfExporter::saveTemporaryLayers()
152 {
153  for ( auto groupIt = mCollatedFeatures.constBegin(); groupIt != mCollatedFeatures.constEnd(); ++groupIt )
154  {
155  for ( auto it = groupIt->constBegin(); it != groupIt->constEnd(); ++it )
156  {
157  const QString filePath = generateTemporaryFilepath( it.key() + groupIt.key() + QStringLiteral( ".gpkg" ) );
158 
159  VectorComponentDetail detail = componentDetailForLayerId( it.key() );
160  detail.sourceVectorPath = filePath;
161  detail.group = groupIt.key();
162 
163  // write out features to disk
164  const QgsFeatureList features = it.value();
165  QString layerName;
167  saveOptions.driverName = QStringLiteral( "GPKG" );
169  std::unique_ptr< QgsVectorFileWriter > writer( QgsVectorFileWriter::create( filePath, features.first().fields(), features.first().geometry().wkbType(), QgsCoordinateReferenceSystem(), QgsCoordinateTransformContext(), saveOptions, QgsFeatureSink::RegeneratePrimaryKey, nullptr, &layerName ) );
170  if ( writer->hasError() )
171  {
172  mErrorMessage = writer->errorMessage();
173  QgsDebugMsg( mErrorMessage );
174  return false;
175  }
176  for ( const QgsFeature &feature : features )
177  {
178  QgsFeature f = feature;
179  if ( !writer->addFeature( f, QgsFeatureSink::FastInsert ) )
180  {
181  mErrorMessage = writer->errorMessage();
182  QgsDebugMsg( mErrorMessage );
183  return false;
184  }
185  }
186  detail.sourceVectorLayer = layerName;
187  mVectorComponents << detail;
188  }
189  }
190  return true;
191 }
192 
193 QString QgsAbstractGeoPdfExporter::createCompositionXml( const QList<ComponentLayerDetail> &components, const ExportDetails &details )
194 {
195  QDomDocument doc;
196 
197  QDomElement compositionElem = doc.createElement( QStringLiteral( "PDFComposition" ) );
198 
199  // metadata tags
200  QDomElement metadata = doc.createElement( QStringLiteral( "Metadata" ) );
201  if ( !details.author.isEmpty() )
202  {
203  QDomElement author = doc.createElement( QStringLiteral( "Author" ) );
204  author.appendChild( doc.createTextNode( details.author ) );
205  metadata.appendChild( author );
206  }
207  if ( !details.producer.isEmpty() )
208  {
209  QDomElement producer = doc.createElement( QStringLiteral( "Producer" ) );
210  producer.appendChild( doc.createTextNode( details.producer ) );
211  metadata.appendChild( producer );
212  }
213  if ( !details.creator.isEmpty() )
214  {
215  QDomElement creator = doc.createElement( QStringLiteral( "Creator" ) );
216  creator.appendChild( doc.createTextNode( details.creator ) );
217  metadata.appendChild( creator );
218  }
219  if ( details.creationDateTime.isValid() )
220  {
221  QDomElement creationDate = doc.createElement( QStringLiteral( "CreationDate" ) );
222  QString creationDateString = QStringLiteral( "D:%1" ).arg( details.creationDateTime.toString( QStringLiteral( "yyyyMMddHHmmss" ) ) );
223  if ( details.creationDateTime.timeZone().isValid() )
224  {
225  int offsetFromUtc = details.creationDateTime.timeZone().offsetFromUtc( details.creationDateTime );
226  creationDateString += ( offsetFromUtc >= 0 ) ? '+' : '-';
227  offsetFromUtc = std::abs( offsetFromUtc );
228  int offsetHours = offsetFromUtc / 3600;
229  int offsetMins = ( offsetFromUtc % 3600 ) / 60;
230  creationDateString += QStringLiteral( "%1'%2'" ).arg( offsetHours ).arg( offsetMins );
231  }
232  creationDate.appendChild( doc.createTextNode( creationDateString ) );
233  metadata.appendChild( creationDate );
234  }
235  if ( !details.subject.isEmpty() )
236  {
237  QDomElement subject = doc.createElement( QStringLiteral( "Subject" ) );
238  subject.appendChild( doc.createTextNode( details.subject ) );
239  metadata.appendChild( subject );
240  }
241  if ( !details.title.isEmpty() )
242  {
243  QDomElement title = doc.createElement( QStringLiteral( "Title" ) );
244  title.appendChild( doc.createTextNode( details.title ) );
245  metadata.appendChild( title );
246  }
247  if ( !details.keywords.empty() )
248  {
249  QStringList allKeywords;
250  for ( auto it = details.keywords.constBegin(); it != details.keywords.constEnd(); ++it )
251  {
252  allKeywords.append( QStringLiteral( "%1: %2" ).arg( it.key(), it.value().join( ',' ) ) );
253  }
254  QDomElement keywords = doc.createElement( QStringLiteral( "Keywords" ) );
255  keywords.appendChild( doc.createTextNode( allKeywords.join( ';' ) ) );
256  metadata.appendChild( keywords );
257  }
258  compositionElem.appendChild( metadata );
259 
260  // layertree
261  QDomElement layerTree = doc.createElement( QStringLiteral( "LayerTree" ) );
262  //layerTree.setAttribute( QStringLiteral("displayOnlyOnVisiblePages"), QStringLiteral("true"));
263  QMap< QString, QSet< QString > > createdLayerIds;
264  QMap< QString, QDomElement > groupLayerMap;
265  QMap< QString, QString > customGroupNamesToIds;
266  if ( details.includeFeatures )
267  {
268  for ( const VectorComponentDetail &component : qgis::as_const( mVectorComponents ) )
269  {
270  if ( details.customLayerTreeGroups.contains( component.mapLayerId ) )
271  continue;
272 
273  QDomElement layer = doc.createElement( QStringLiteral( "Layer" ) );
274  layer.setAttribute( QStringLiteral( "id" ), component.group.isEmpty() ? component.mapLayerId : QStringLiteral( "%1_%2" ).arg( component.group, component.mapLayerId ) );
275  layer.setAttribute( QStringLiteral( "name" ), component.name );
276  layer.setAttribute( QStringLiteral( "initiallyVisible" ), QStringLiteral( "true" ) );
277 
278  if ( !component.group.isEmpty() )
279  {
280  if ( groupLayerMap.contains( component.group ) )
281  {
282  groupLayerMap[ component.group ].appendChild( layer );
283  }
284  else
285  {
286  QDomElement group = doc.createElement( QStringLiteral( "Layer" ) );
287  group.setAttribute( QStringLiteral( "id" ), QStringLiteral( "group_%1" ).arg( component.group ) );
288  group.setAttribute( QStringLiteral( "name" ), component.group );
289  group.setAttribute( QStringLiteral( "initiallyVisible" ), groupLayerMap.empty() ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
290  group.setAttribute( QStringLiteral( "mutuallyExclusiveGroupId" ), QStringLiteral( "__mutually_exclusive_groups__" ) );
291  layerTree.appendChild( group );
292  group.appendChild( layer );
293  groupLayerMap[ component.group ] = group;
294  }
295  }
296  else
297  {
298  layerTree.appendChild( layer );
299  }
300 
301  createdLayerIds[ component.group ].insert( component.mapLayerId );
302  }
303  }
304  // some PDF components may not be linked to vector components - e.g. layers with labels but no features
305  for ( const ComponentLayerDetail &component : components )
306  {
307  if ( component.mapLayerId.isEmpty() || createdLayerIds.value( component.group ).contains( component.mapLayerId ) )
308  continue;
309 
310  if ( details.customLayerTreeGroups.contains( component.mapLayerId ) )
311  continue;
312 
313  QDomElement layer = doc.createElement( QStringLiteral( "Layer" ) );
314  layer.setAttribute( QStringLiteral( "id" ), component.group.isEmpty() ? component.mapLayerId : QStringLiteral( "%1_%2" ).arg( component.group, component.mapLayerId ) );
315  layer.setAttribute( QStringLiteral( "name" ), component.name );
316  layer.setAttribute( QStringLiteral( "initiallyVisible" ), QStringLiteral( "true" ) );
317 
318  if ( !component.group.isEmpty() )
319  {
320  if ( groupLayerMap.contains( component.group ) )
321  {
322  groupLayerMap[ component.group ].appendChild( layer );
323  }
324  else
325  {
326  QDomElement group = doc.createElement( QStringLiteral( "Layer" ) );
327  group.setAttribute( QStringLiteral( "id" ), QStringLiteral( "group_%1" ).arg( component.group ) );
328  group.setAttribute( QStringLiteral( "name" ), component.group );
329  group.setAttribute( QStringLiteral( "initiallyVisible" ), groupLayerMap.empty() ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
330  group.setAttribute( QStringLiteral( "mutuallyExclusiveGroupId" ), QStringLiteral( "__mutually_exclusive_groups__" ) );
331  layerTree.appendChild( group );
332  group.appendChild( layer );
333  groupLayerMap[ component.group ] = group;
334  }
335  }
336  else
337  {
338  layerTree.appendChild( layer );
339  }
340 
341  createdLayerIds[ component.group ].insert( component.mapLayerId );
342  }
343 
344  // create custom layer tree entries
345  for ( auto it = details.customLayerTreeGroups.constBegin(); it != details.customLayerTreeGroups.constEnd(); ++it )
346  {
347  if ( customGroupNamesToIds.contains( it.value() ) )
348  continue;
349 
350  QDomElement layer = doc.createElement( QStringLiteral( "Layer" ) );
351  const QString id = QUuid::createUuid().toString();
352  customGroupNamesToIds[ it.value() ] = id;
353  layer.setAttribute( QStringLiteral( "id" ), id );
354  layer.setAttribute( QStringLiteral( "name" ), it.value() );
355  layer.setAttribute( QStringLiteral( "initiallyVisible" ), QStringLiteral( "true" ) );
356  layerTree.appendChild( layer );
357  }
358 
359  compositionElem.appendChild( layerTree );
360 
361  // pages
362  QDomElement page = doc.createElement( QStringLiteral( "Page" ) );
363  QDomElement dpi = doc.createElement( QStringLiteral( "DPI" ) );
364  dpi.appendChild( doc.createTextNode( QString::number( details.dpi ) ) );
365  page.appendChild( dpi );
366  // assumes DPI of 72, which is an assumption on GDALs/PDF side. It's only related to the PDF coordinate space and doesn't affect the actual output DPI!
367  QDomElement width = doc.createElement( QStringLiteral( "Width" ) );
368  const double pageWidthPdfUnits = std::ceil( details.pageSizeMm.width() / 25.4 * 72 );
369  width.appendChild( doc.createTextNode( QString::number( pageWidthPdfUnits ) ) );
370  page.appendChild( width );
371  QDomElement height = doc.createElement( QStringLiteral( "Height" ) );
372  const double pageHeightPdfUnits = std::ceil( details.pageSizeMm.height() / 25.4 * 72 );
373  height.appendChild( doc.createTextNode( QString::number( pageHeightPdfUnits ) ) );
374  page.appendChild( height );
375 
376 
377  // georeferencing
378  int i = 0;
380  {
381  QDomElement georeferencing = doc.createElement( QStringLiteral( "Georeferencing" ) );
382  georeferencing.setAttribute( QStringLiteral( "id" ), QStringLiteral( "georeferenced_%1" ).arg( i++ ) );
383  georeferencing.setAttribute( QStringLiteral( "OGCBestPracticeFormat" ), details.useOgcBestPracticeFormatGeoreferencing ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
384  georeferencing.setAttribute( QStringLiteral( "ISO32000ExtensionFormat" ), details.useIso32000ExtensionFormatGeoreferencing ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
385 
386  if ( section.crs.isValid() )
387  {
388  QDomElement srs = doc.createElement( QStringLiteral( "SRS" ) );
389  // not currently used by GDAL or the PDF spec, but exposed in the GDAL XML schema. Maybe something we'll need to consider down the track...
390  // srs.setAttribute( QStringLiteral( "dataAxisToSRSAxisMapping" ), QStringLiteral( "2,1" ) );
391  if ( !section.crs.authid().startsWith( QStringLiteral( "user" ), Qt::CaseInsensitive ) )
392  {
393  srs.appendChild( doc.createTextNode( section.crs.authid() ) );
394  }
395  else
396  {
397  srs.appendChild( doc.createTextNode( section.crs.toWkt( QgsCoordinateReferenceSystem::WKT2_2018 ) ) );
398  }
399  georeferencing.appendChild( srs );
400  }
401 
402  if ( !section.pageBoundsPolygon.isEmpty() )
403  {
404  /*
405  Define a polygon / neatline in PDF units into which the
406  Measure tool will display coordinates.
407  If not specified, BoundingBox will be used instead.
408  If none of BoundingBox and BoundingPolygon are specified,
409  the whole PDF page will be assumed to be georeferenced.
410  */
411  QDomElement boundingPolygon = doc.createElement( QStringLiteral( "BoundingPolygon" ) );
412 
413  // transform to PDF coordinate space
414  QTransform t = QTransform::fromTranslate( 0, pageHeightPdfUnits ).scale( pageWidthPdfUnits / details.pageSizeMm.width(),
415  -pageHeightPdfUnits / details.pageSizeMm.height() );
416 
417  QgsPolygon p = section.pageBoundsPolygon;
418  p.transform( t );
419  boundingPolygon.appendChild( doc.createTextNode( p.asWkt() ) );
420 
421  georeferencing.appendChild( boundingPolygon );
422  }
423  else
424  {
425  /* Define the viewport where georeferenced coordinates are available.
426  If not specified, the extent of BoundingPolygon will be used instead.
427  If none of BoundingBox and BoundingPolygon are specified,
428  the whole PDF page will be assumed to be georeferenced.
429  */
430  QDomElement boundingBox = doc.createElement( QStringLiteral( "BoundingBox" ) );
431  boundingBox.setAttribute( QStringLiteral( "x1" ), QString::number( section.pageBoundsMm.xMinimum() / 25.4 * 72 ) );
432  boundingBox.setAttribute( QStringLiteral( "y1" ), QString::number( section.pageBoundsMm.yMinimum() / 25.4 * 72 ) );
433  boundingBox.setAttribute( QStringLiteral( "x2" ), QString::number( section.pageBoundsMm.xMaximum() / 25.4 * 72 ) );
434  boundingBox.setAttribute( QStringLiteral( "y2" ), QString::number( section.pageBoundsMm.yMaximum() / 25.4 * 72 ) );
435  georeferencing.appendChild( boundingBox );
436  }
437 
438  for ( const ControlPoint &point : section.controlPoints )
439  {
440  QDomElement cp1 = doc.createElement( QStringLiteral( "ControlPoint" ) );
441  cp1.setAttribute( QStringLiteral( "x" ), QString::number( point.pagePoint.x() / 25.4 * 72 ) );
442  cp1.setAttribute( QStringLiteral( "y" ), QString::number( ( details.pageSizeMm.height() - point.pagePoint.y() ) / 25.4 * 72 ) );
443  cp1.setAttribute( QStringLiteral( "GeoX" ), QString::number( point.geoPoint.x() ) );
444  cp1.setAttribute( QStringLiteral( "GeoY" ), QString::number( point.geoPoint.y() ) );
445  georeferencing.appendChild( cp1 );
446  }
447 
448  page.appendChild( georeferencing );
449  }
450 
451  // content
452  QDomElement content = doc.createElement( QStringLiteral( "Content" ) );
453  for ( const ComponentLayerDetail &component : components )
454  {
455  if ( component.mapLayerId.isEmpty() )
456  {
457  QDomElement pdfDataset = doc.createElement( QStringLiteral( "PDF" ) );
458  pdfDataset.setAttribute( QStringLiteral( "dataset" ), component.sourcePdfPath );
459  content.appendChild( pdfDataset );
460  }
461  else if ( !component.group.isEmpty() )
462  {
463  // if content belongs to a group, we need nested "IfLayerOn" elements, one for the group and one for the layer
464  QDomElement ifGroupOn = doc.createElement( QStringLiteral( "IfLayerOn" ) );
465  ifGroupOn.setAttribute( QStringLiteral( "layerId" ), QStringLiteral( "group_%1" ).arg( component.group ) );
466  QDomElement ifLayerOn = doc.createElement( QStringLiteral( "IfLayerOn" ) );
467  if ( details.customLayerTreeGroups.contains( component.mapLayerId ) )
468  ifLayerOn.setAttribute( QStringLiteral( "layerId" ), customGroupNamesToIds.value( details.customLayerTreeGroups.value( component.mapLayerId ) ) );
469  else if ( component.group.isEmpty() )
470  ifLayerOn.setAttribute( QStringLiteral( "layerId" ), component.mapLayerId );
471  else
472  ifLayerOn.setAttribute( QStringLiteral( "layerId" ), QStringLiteral( "%1_%2" ).arg( component.group, component.mapLayerId ) );
473  QDomElement pdfDataset = doc.createElement( QStringLiteral( "PDF" ) );
474  pdfDataset.setAttribute( QStringLiteral( "dataset" ), component.sourcePdfPath );
475  ifLayerOn.appendChild( pdfDataset );
476  ifGroupOn.appendChild( ifLayerOn );
477  content.appendChild( ifGroupOn );
478  }
479  else
480  {
481  QDomElement ifLayerOn = doc.createElement( QStringLiteral( "IfLayerOn" ) );
482  if ( details.customLayerTreeGroups.contains( component.mapLayerId ) )
483  ifLayerOn.setAttribute( QStringLiteral( "layerId" ), customGroupNamesToIds.value( details.customLayerTreeGroups.value( component.mapLayerId ) ) );
484  else if ( component.group.isEmpty() )
485  ifLayerOn.setAttribute( QStringLiteral( "layerId" ), component.mapLayerId );
486  else
487  ifLayerOn.setAttribute( QStringLiteral( "layerId" ), QStringLiteral( "%1_%2" ).arg( component.group, component.mapLayerId ) );
488  QDomElement pdfDataset = doc.createElement( QStringLiteral( "PDF" ) );
489  pdfDataset.setAttribute( QStringLiteral( "dataset" ), component.sourcePdfPath );
490  ifLayerOn.appendChild( pdfDataset );
491  content.appendChild( ifLayerOn );
492  }
493  }
494 
495  // vector datasets (we "draw" these on top, just for debugging... but they are invisible, so are never really drawn!)
496  if ( details.includeFeatures )
497  {
498  for ( const VectorComponentDetail &component : qgis::as_const( mVectorComponents ) )
499  {
500  QDomElement ifLayerOn = doc.createElement( QStringLiteral( "IfLayerOn" ) );
501  if ( details.customLayerTreeGroups.contains( component.mapLayerId ) )
502  ifLayerOn.setAttribute( QStringLiteral( "layerId" ), customGroupNamesToIds.value( details.customLayerTreeGroups.value( component.mapLayerId ) ) );
503  else if ( component.group.isEmpty() )
504  ifLayerOn.setAttribute( QStringLiteral( "layerId" ), component.mapLayerId );
505  else
506  ifLayerOn.setAttribute( QStringLiteral( "layerId" ), QStringLiteral( "%1_%2" ).arg( component.group, component.mapLayerId ) );
507  QDomElement vectorDataset = doc.createElement( QStringLiteral( "Vector" ) );
508  vectorDataset.setAttribute( QStringLiteral( "dataset" ), component.sourceVectorPath );
509  vectorDataset.setAttribute( QStringLiteral( "layer" ), component.sourceVectorLayer );
510  vectorDataset.setAttribute( QStringLiteral( "visible" ), QStringLiteral( "false" ) );
511  QDomElement logicalStructure = doc.createElement( QStringLiteral( "LogicalStructure" ) );
512  logicalStructure.setAttribute( QStringLiteral( "displayLayerName" ), component.name );
513  if ( !component.displayAttribute.isEmpty() )
514  logicalStructure.setAttribute( QStringLiteral( "fieldToDisplay" ), component.displayAttribute );
515  vectorDataset.appendChild( logicalStructure );
516  ifLayerOn.appendChild( vectorDataset );
517  content.appendChild( ifLayerOn );
518  }
519  }
520 
521  page.appendChild( content );
522  compositionElem.appendChild( page );
523 
524  doc.appendChild( compositionElem );
525 
526  QString composition;
527  QTextStream stream( &composition );
528  doc.save( stream, -1 );
529 
530  return composition;
531 }
532 
QList< QgsAbstractGeoPdfExporter::GeoReferencedSection > georeferencedSections
List of georeferenced sections.
bool includeFeatures
true if feature vector information (such as attributes) should be exported.
static QgsVectorFileWriter * create(const QString &fileName, const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &srs, const QgsCoordinateTransformContext &transformContext, const QgsVectorFileWriter::SaveVectorOptions &options, QgsFeatureSink::SinkFlags sinkFlags=nullptr, QString *newFilename=nullptr, QString *newLayer=nullptr)
Create a new vector file writer.
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
void pushRenderedFeature(const QString &layerId, const QgsAbstractGeoPdfExporter::RenderedFeature &feature, const QString &group=QString())
Called multiple times during the rendering operation, whenever a feature associated with the specifie...
QString asWkt(int precision=17) const override
Returns a WKT representation of the geometry.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:571
double y
Definition: qgspointxy.h:48
QgsGeometry renderedBounds
Bounds, in PDF units, of rendered feature.
QgsLayerTree * layerTree(const QgsWmsRenderContext &context)
QgsAbstractMetadataBase::KeywordMap keywords
Metadata keyword map.
QSizeF pageSizeMm
Page size, in millimeters.
QMap< QString, QString > customLayerTreeGroups
Optional map of map layer ID to custom logical layer tree group in created PDF file.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
QDateTime creationDateTime
Metadata creation datetime.
QgsVectorFileWriter::SymbologyExport symbologyExport
Symbology to export.
void transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d=QgsCoordinateTransform::ForwardTransform, bool transformZ=false) override SIP_THROW(QgsCsException)
Transforms the geometry using a coordinate transform.
Contains details of a control point used during georeferencing GeoPDF outputs.
Options to pass to writeAsVectorFormat()
bool useOgcBestPracticeFormatGeoreferencing
true if OGC "best practice" format georeferencing should be used.
QList< QgsAbstractGeoPdfExporter::ControlPoint > controlPoints
List of control points corresponding to this georeferenced section.
QgsCoordinateReferenceSystem crs
Coordinate reference system for georeferenced section.
Contains information about a feature rendered inside the PDF.
QString sourceVectorPath
File path to the (already created) vector dataset to use as the source for this component layer...
Contains information relating to a single PDF layer in the GeoPDF export.
QgsRectangle pageBoundsMm
Bounds of the georeferenced section on the page, in millimeters.
Contains information about the context in which a coordinate transform is executed.
double x
Definition: qgspointxy.h:47
Full WKT2 string, conforming to ISO 19162:2018 / OGC 18-010, with all possible nodes and new keyword ...
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
Contains details of a particular input component to be used during PDF composition.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
QString generateTemporaryFilepath(const QString &filename) const
Returns a file path to use for temporary files required for GeoPDF creation.
bool finalize(const QList< QgsAbstractGeoPdfExporter::ComponentLayerDetail > &components, const QString &destinationFile, const ExportDetails &details)
To be called after the rendering operation is complete.
bool useIso32000ExtensionFormatGeoreferencing
true if ISO3200 extension format georeferencing should be used.
QgsPolygon pageBoundsPolygon
Bounds of the georeferenced section on the page, in millimeters, as a free-form polygon.
This class represents a coordinate reference system (CRS).
void setGeometry(const QgsGeometry &geometry)
Set the feature&#39;s geometry.
Definition: qgsfeature.cpp:137
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:172
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
Definition: qgsogrutils.h:134
QgsPointXY pagePoint
Coordinate on the page of the control point, in millimeters.
static QString geoPDFAvailabilityExplanation()
Returns a user-friendly, translated string explaining why GeoPDF export support is not available on t...
Polygon geometry type.
Definition: qgspolygon.h:31
QgsPointXY geoPoint
Georeferenced coordinate of the control point, in CRS units.
bool isEmpty() const override
Returns true if the geometry is empty.
QString authid() const
Returns the authority identifier for the CRS.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
static bool geoPDFCreationAvailable()
Returns true if the current QGIS build is capable of GeoPDF support.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.