QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
qgslayoutgeopdfexporter.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutgeopdfexporter.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 
19 #include "qgsfeaturerequest.h"
20 #include "qgslayout.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"
30 
31 #include <QMutex>
32 #include <QMutexLocker>
33 #include <QDomDocument>
34 #include <QDomElement>
35 
37 class QgsGeoPdfRenderedFeatureHandler: public QgsRenderedFeatureHandlerInterface
38 {
39  public:
40 
41  QgsGeoPdfRenderedFeatureHandler( QgsLayoutItemMap *map, QgsLayoutGeoPdfExporter *exporter, const QStringList &layerIds )
42  : mExporter( exporter )
43  , mMap( map )
44  , mLayerIds( layerIds )
45  {
46  // get page size
47  const QgsLayoutSize pageSize = map->layout()->pageCollection()->page( map->page() )->pageSize();
48  QSizeF pageSizeLayoutUnits = map->layout()->convertToLayoutUnits( pageSize );
49  const QgsLayoutSize pageSizeInches = map->layout()->renderContext().measurementConverter().convert( pageSize, QgsUnitTypes::LayoutInches );
50 
51  // PDF assumes 72 dpi -- this is hardcoded!!
52  const double pageHeightPdfUnits = pageSizeInches.height() * 72;
53  const double pageWidthPdfUnits = pageSizeInches.width() * 72;
54 
55  QTransform mapTransform;
56  QPolygonF mapRectPoly = QPolygonF( QRectF( 0, 0, map->rect().width(), map->rect().height() ) );
57  //workaround QT Bug #21329
58  mapRectPoly.pop_back();
59 
60  QPolygonF mapRectInLayout = map->mapToScene( mapRectPoly );
61 
62  //create transform from layout coordinates to map coordinates
63  QTransform::quadToQuad( mapRectPoly, mapRectInLayout, mMapToLayoutTransform );
64 
65  // and a transform to PDF coordinate space
66  mLayoutToPdfTransform = QTransform::fromTranslate( 0, pageHeightPdfUnits ).scale( pageWidthPdfUnits / pageSizeLayoutUnits.width(),
67  -pageHeightPdfUnits / pageSizeLayoutUnits.height() );
68  }
69 
70  void handleRenderedFeature( const QgsFeature &feature, const QgsGeometry &renderedBounds, const QgsRenderedFeatureHandlerInterface::RenderedFeatureContext &context ) override
71  {
72  // is it a hack retrieving the layer ID from an expression context like this? possibly... BUT
73  // the alternative is adding a layer ID member to QgsRenderContext, and that's just asking for people to abuse it
74  // and use it to retrieve QgsMapLayers mid-way through a render operation. Lesser of two evils it is!
75  const QString layerId = context.renderContext.expressionContext().variable( QStringLiteral( "layer_id" ) ).toString();
76  if ( !mLayerIds.contains( layerId ) )
77  return;
78 
79  const QString theme = ( mMap->mExportThemes.isEmpty() || mMap->mExportThemeIt == mMap->mExportThemes.end() ) ? QString() : *mMap->mExportThemeIt;
80 
81  // transform from pixels to map item coordinates
82  QTransform pixelToMapItemTransform = QTransform::fromScale( 1.0 / context.renderContext.scaleFactor(), 1.0 / context.renderContext.scaleFactor() );
83  QgsGeometry transformed = renderedBounds;
84  transformed.transform( pixelToMapItemTransform );
85  // transform from map item coordinates to page coordinates
86  transformed.transform( mMapToLayoutTransform );
87  // ...and then to PDF coordinate space
88  transformed.transform( mLayoutToPdfTransform );
89 
90  // always convert to multitype, to make things consistent
91  transformed.convertToMultiType();
92 
93  mExporter->pushRenderedFeature( layerId, QgsLayoutGeoPdfExporter::RenderedFeature( feature, transformed ), theme );
94  }
95 
96  QSet<QString> usedAttributes( QgsVectorLayer *, const QgsRenderContext & ) const override
97  {
98  return QSet< QString >() << QgsFeatureRequest::ALL_ATTRIBUTES;
99  }
100 
101  private:
102  QTransform mMapToLayoutTransform;
103  QTransform mLayoutToPdfTransform;
104  QgsLayoutGeoPdfExporter *mExporter = nullptr;
105  QgsLayoutItemMap *mMap = nullptr;
106  QStringList mLayerIds;
107 };
109 
111  : mLayout( layout )
112 {
113  // build a list of exportable feature layers in advance
114  QStringList exportableLayerIds;
115  const QMap< QString, QgsMapLayer * > layers = mLayout->project()->mapLayers( true );
116  for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
117  {
118  if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( it.value() ) )
119  {
120  const QVariant v = vl->customProperty( QStringLiteral( "geopdf/includeFeatures" ) );
121  if ( !v.isValid() || v.toBool() )
122  {
123  exportableLayerIds << vl->id();
124  }
125 
126  const QString groupName = vl->customProperty( QStringLiteral( "geopdf/groupName" ) ).toString();
127  if ( !groupName.isEmpty() )
128  mCustomLayerTreeGroups.insert( vl->id(), groupName );
129  }
130  }
131 
132  // on construction, we install a rendered feature handler on layout item maps
133  QList< QgsLayoutItemMap * > maps;
134  mLayout->layoutItems( maps );
135  for ( QgsLayoutItemMap *map : qgis::as_const( maps ) )
136  {
137  QgsGeoPdfRenderedFeatureHandler *handler = new QgsGeoPdfRenderedFeatureHandler( map, this, exportableLayerIds );
138  mMapHandlers.insert( map, handler );
139  map->addRenderedFeatureHandler( handler );
140  }
141 }
142 
144 {
145  // cleanup - remove rendered feature handler from all maps
146  for ( auto it = mMapHandlers.constBegin(); it != mMapHandlers.constEnd(); ++it )
147  {
148  it.key()->removeRenderedFeatureHandler( it.value() );
149  delete it.value();
150  }
151 }
152 
153 QgsAbstractGeoPdfExporter::VectorComponentDetail QgsLayoutGeoPdfExporter::componentDetailForLayerId( const QString &layerId )
154 {
155  QgsProject *project = mLayout->project();
156  VectorComponentDetail detail;
157  const QgsMapLayer *layer = project->mapLayer( layerId );
158  detail.name = layer ? layer->name() : layerId;
159  detail.mapLayerId = layerId;
160  if ( const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer ) )
161  {
162  detail.displayAttribute = vl->displayField();
163  }
164  return detail;
165 }
166 
const QgsRenderContext & renderContext
The render context which was used while rendering feature.
Base class for all map layer types.
Definition: qgsmaplayer.h:79
virtual void handleRenderedFeature(const QgsFeature &feature, const QgsGeometry &renderedBounds, const QgsRenderedFeatureHandlerInterface::RenderedFeatureContext &context)=0
Called whenever a feature is rendered during a map render job.
OperationResult transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection direction=QgsCoordinateTransform::ForwardTransform, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
QgsLayoutMeasurement convert(QgsLayoutMeasurement measurement, QgsUnitTypes::LayoutUnit targetUnits) const
Converts a measurement from one unit to another.
Handles GeoPDF export specific setup, cleanup and processing steps.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:122
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
QgsLayoutGeoPdfExporter(QgsLayout *layout)
Constructor for QgsLayoutGeoPdfExporter, associated with the specified layout.
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout&#39;s render context, which stores information relating to the current ...
Definition: qgslayout.cpp:358
QVariant variable(const QString &name) const
Fetches a matching variable from the context.
void layoutItems(QList< T *> &itemList) const
Returns a list of layout items of a specific type.
Definition: qgslayout.h:121
Layout graphical items for displaying a map.
const QgsLayout * layout() const
Returns the layout the object is attached to.
QgsLayoutItemPage * page(int pageNumber)
Returns a specific page (by pageNumber) from the collection.
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout&#39;s page collection, which stores and manages page items in the layout...
Definition: qgslayout.cpp:458
An interface for classes which provider custom handlers for features rendered as part of a map render...
Contains information about a feature rendered inside the PDF.
static const QString ALL_ATTRIBUTES
A special attribute that if set matches all attributes.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts, annotations, canvases, etc.
Definition: qgsproject.h:89
int page() const
Returns the page the item is currently on, with the first page returning 0.
Contains information relating to a single PDF layer in the GeoPDF export.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
QgsExpressionContext & expressionContext()
Gets the expression context.
virtual QSet< QString > usedAttributes(QgsVectorLayer *layer, const QgsRenderContext &context) const
Returns a list of attributes required by this handler, for the specified layer.
Contains information about the context of a rendering operation.
const QgsLayoutMeasurementConverter & measurementConverter() const
Returns the layout measurement converter to be used in the layout.
QgsProject * project() const
The project associated with the layout.
Definition: qgslayout.cpp:131
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QString name
Definition: qgsmaplayer.h:83
double convertToLayoutUnits(QgsLayoutMeasurement measurement) const
Converts a measurement into the layout&#39;s native units.
Definition: qgslayout.cpp:328
void addRenderedFeatureHandler(QgsRenderedFeatureHandlerInterface *handler)
Adds a rendered feature handler to use while rendering the map.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:40
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
Represents a vector layer which manages a vector based data sets.
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
QString name
User-friendly name for the generated PDF layer.
double width() const
Returns the width of the size.
Definition: qgslayoutsize.h:76