QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 #include "qgslayertree.h"
26 
27 #include <gdal.h>
28 #include "qgsgdalutils.h"
29 #include "cpl_string.h"
31 
32 #include <QMutex>
33 #include <QMutexLocker>
34 #include <QDomDocument>
35 #include <QDomElement>
36 
38 class QgsGeoPdfRenderedFeatureHandler: public QgsRenderedFeatureHandlerInterface
39 {
40  public:
41 
42  QgsGeoPdfRenderedFeatureHandler( QgsLayoutItemMap *map, QgsLayoutGeoPdfExporter *exporter, const QStringList &layerIds )
43  : mExporter( exporter )
44  , mMap( map )
45  , mLayerIds( layerIds )
46  {
47  // get page size
48  const QgsLayoutSize pageSize = map->layout()->pageCollection()->page( map->page() )->pageSize();
49  QSizeF pageSizeLayoutUnits = map->layout()->convertToLayoutUnits( pageSize );
50  const QgsLayoutSize pageSizeInches = map->layout()->renderContext().measurementConverter().convert( pageSize, QgsUnitTypes::LayoutInches );
51 
52  // PDF assumes 72 dpi -- this is hardcoded!!
53  const double pageHeightPdfUnits = pageSizeInches.height() * 72;
54  const double pageWidthPdfUnits = pageSizeInches.width() * 72;
55 
56  QTransform mapTransform;
57  QPolygonF mapRectPoly = QPolygonF( QRectF( 0, 0, map->rect().width(), map->rect().height() ) );
58  //workaround QT Bug #21329
59  mapRectPoly.pop_back();
60 
61  QPolygonF mapRectInLayout = map->mapToScene( mapRectPoly );
62 
63  //create transform from layout coordinates to map coordinates
64  QTransform::quadToQuad( mapRectPoly, mapRectInLayout, mMapToLayoutTransform );
65 
66  // and a transform to PDF coordinate space
67  mLayoutToPdfTransform = QTransform::fromTranslate( 0, pageHeightPdfUnits ).scale( pageWidthPdfUnits / pageSizeLayoutUnits.width(),
68  -pageHeightPdfUnits / pageSizeLayoutUnits.height() );
69  }
70 
71  void handleRenderedFeature( const QgsFeature &feature, const QgsGeometry &renderedBounds, const QgsRenderedFeatureHandlerInterface::RenderedFeatureContext &context ) override
72  {
73  // is it a hack retrieving the layer ID from an expression context like this? possibly... BUT
74  // the alternative is adding a layer ID member to QgsRenderContext, and that's just asking for people to abuse it
75  // and use it to retrieve QgsMapLayers mid-way through a render operation. Lesser of two evils it is!
76  const QString layerId = context.renderContext.expressionContext().variable( QStringLiteral( "layer_id" ) ).toString();
77  if ( !mLayerIds.contains( layerId ) )
78  return;
79 
80  const QString theme = ( mMap->mExportThemes.isEmpty() || mMap->mExportThemeIt == mMap->mExportThemes.end() ) ? QString() : *mMap->mExportThemeIt;
81 
82  // transform from pixels to map item coordinates
83  QTransform pixelToMapItemTransform = QTransform::fromScale( 1.0 / context.renderContext.scaleFactor(), 1.0 / context.renderContext.scaleFactor() );
84  QgsGeometry transformed = renderedBounds;
85  transformed.transform( pixelToMapItemTransform );
86  // transform from map item coordinates to page coordinates
87  transformed.transform( mMapToLayoutTransform );
88  // ...and then to PDF coordinate space
89  transformed.transform( mLayoutToPdfTransform );
90 
91  // always convert to multitype, to make things consistent
92  transformed.convertToMultiType();
93 
94  mExporter->pushRenderedFeature( layerId, QgsLayoutGeoPdfExporter::RenderedFeature( feature, transformed ), theme );
95  }
96 
97  QSet<QString> usedAttributes( QgsVectorLayer *, const QgsRenderContext & ) const override
98  {
99  return QSet< QString >() << QgsFeatureRequest::ALL_ATTRIBUTES;
100  }
101 
102  private:
103  QTransform mMapToLayoutTransform;
104  QTransform mLayoutToPdfTransform;
105  QgsLayoutGeoPdfExporter *mExporter = nullptr;
106  QgsLayoutItemMap *mMap = nullptr;
107  QStringList mLayerIds;
108 };
110 
112  : mLayout( layout )
113 {
114  // build a list of exportable feature layers in advance
115  QStringList exportableLayerIds;
116  const QMap< QString, QgsMapLayer * > layers = mLayout->project()->mapLayers( true );
117  for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
118  {
119  if ( QgsMapLayer *ml = it.value() )
120  {
121  const QVariant visibility = ml->customProperty( QStringLiteral( "geopdf/initiallyVisible" ), true );
122  mInitialLayerVisibility.insert( ml->id(), !visibility.isValid() ? true : visibility.toBool() );
123  if ( ml->type() == QgsMapLayerType::VectorLayer )
124  {
125  const QVariant v = ml->customProperty( QStringLiteral( "geopdf/includeFeatures" ) );
126  if ( !v.isValid() || v.toBool() )
127  {
128  exportableLayerIds << ml->id();
129  }
130  }
131 
132  const QString groupName = ml->customProperty( QStringLiteral( "geopdf/groupName" ) ).toString();
133  if ( !groupName.isEmpty() )
134  mCustomLayerTreeGroups.insert( ml->id(), groupName );
135  }
136  }
137 
138  // on construction, we install a rendered feature handler on layout item maps
139  QList< QgsLayoutItemMap * > maps;
140  mLayout->layoutItems( maps );
141  for ( QgsLayoutItemMap *map : qgis::as_const( maps ) )
142  {
143  QgsGeoPdfRenderedFeatureHandler *handler = new QgsGeoPdfRenderedFeatureHandler( map, this, exportableLayerIds );
144  mMapHandlers.insert( map, handler );
145  map->addRenderedFeatureHandler( handler );
146  }
147 
148  // start with project layer order, and then apply custom layer order if set
149  QStringList geoPdfLayerOrder;
150  const QString presetLayerOrder = mLayout->customProperty( QStringLiteral( "pdfLayerOrder" ) ).toString();
151  if ( !presetLayerOrder.isEmpty() )
152  geoPdfLayerOrder = presetLayerOrder.split( QStringLiteral( "~~~" ) );
153 
154  QList< QgsMapLayer * > layerOrder = mLayout->project()->layerTreeRoot()->layerOrder();
155  for ( auto it = geoPdfLayerOrder.rbegin(); it != geoPdfLayerOrder.rend(); ++it )
156  {
157  for ( int i = 0; i < layerOrder.size(); ++i )
158  {
159  if ( layerOrder.at( i )->id() == *it )
160  {
161  layerOrder.move( i, 0 );
162  break;
163  }
164  }
165  }
166 
167  for ( const QgsMapLayer *layer : layerOrder )
168  mLayerOrder << layer->id();
169 }
170 
172 {
173  // cleanup - remove rendered feature handler from all maps
174  for ( auto it = mMapHandlers.constBegin(); it != mMapHandlers.constEnd(); ++it )
175  {
176  it.key()->removeRenderedFeatureHandler( it.value() );
177  delete it.value();
178  }
179 }
180 
181 QgsAbstractGeoPdfExporter::VectorComponentDetail QgsLayoutGeoPdfExporter::componentDetailForLayerId( const QString &layerId )
182 {
183  QgsProject *project = mLayout->project();
184  VectorComponentDetail detail;
185  const QgsMapLayer *layer = project->mapLayer( layerId );
186  detail.name = layer ? layer->name() : layerId;
187  detail.mapLayerId = layerId;
188  if ( const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer ) )
189  {
190  detail.displayAttribute = vl->displayField();
191  }
192  return detail;
193 }
194 
QVariant variable(const QString &name) const
Fetches a matching variable from the context.
static const QString ALL_ATTRIBUTES
A special attribute that if set matches all attributes.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:56
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
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.
bool convertToMultiType()
Converts single type geometry into multitype geometry e.g.
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
Handles GeoPDF export specific setup, cleanup and processing steps.
QStringList layerOrder() const
Optional list of map layer IDs in the order they should be shown in the generated GeoPDF layer tree.
QgsLayoutGeoPdfExporter(QgsLayout *layout)
Constructor for QgsLayoutGeoPdfExporter, associated with the specified layout.
Layout graphical items for displaying a map.
void addRenderedFeatureHandler(QgsRenderedFeatureHandlerInterface *handler)
Adds a rendered feature handler to use while rendering the map.
QgsLayoutSize pageSize() const
Returns the size of the page.
int page() const
Returns the page the item is currently on, with the first page returning 0.
QgsLayoutMeasurement convert(QgsLayoutMeasurement measurement, QgsUnitTypes::LayoutUnit targetUnits) const
Converts a measurement from one unit to another.
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.
const QgsLayoutMeasurementConverter & measurementConverter() const
Returns the layout measurement converter to be used in the layout.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:41
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
double width() const
Returns the width of the size.
Definition: qgslayoutsize.h:76
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:50
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout's render context, which stores information relating to the current ...
Definition: qgslayout.cpp:359
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
Definition: qgslayout.cpp:459
void layoutItems(QList< T * > &itemList) const
Returns a list of layout items of a specific type.
Definition: qgslayout.h:121
double convertToLayoutUnits(QgsLayoutMeasurement measurement) const
Converts a measurement into the layout's native units.
Definition: qgslayout.cpp:329
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from the layout.
Definition: qgslayout.cpp:415
QgsProject * project() const
The project associated with the layout.
Definition: qgslayout.cpp:132
Base class for all map layer types.
Definition: qgsmaplayer.h:85
QString name
Definition: qgsmaplayer.h:88
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:99
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
QgsExpressionContext & expressionContext()
Gets the expression context.
An interface for classes which provider custom handlers for features rendered as part of a map render...
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.
virtual QSet< QString > usedAttributes(QgsVectorLayer *layer, const QgsRenderContext &context) const
Returns a list of attributes required by this handler, for the specified layer.
@ LayoutInches
Inches.
Definition: qgsunittypes.h:185
Represents a vector layer which manages a vector based data sets.
Contains information about a feature rendered inside the PDF.
Contains information relating to a single PDF layer in the GeoPDF export.
const QgsRenderContext & renderContext
The render context which was used while rendering feature.