QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
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 
QgsLayoutObject::layout
const QgsLayout * layout() const
Returns the layout the object is attached to.
Definition: qgslayoutobject.cpp:126
QgsLayoutGeoPdfExporter::QgsLayoutGeoPdfExporter
QgsLayoutGeoPdfExporter(QgsLayout *layout)
Constructor for QgsLayoutGeoPdfExporter, associated with the specified layout.
Definition: qgslayoutgeopdfexporter.cpp:111
QgsLayoutItemPage::pageSize
QgsLayoutSize pageSize() const
Returns the size of the page.
Definition: qgslayoutitempage.cpp:104
QgsLayoutRenderContext::measurementConverter
const QgsLayoutMeasurementConverter & measurementConverter() const
Returns the layout measurement converter to be used in the layout.
Definition: qgslayoutrendercontext.h:129
QgsGeometry::transform
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.
Definition: qgsgeometry.cpp:2813
qgsfeaturerequest.h
QgsLayoutSize::width
double width() const
Returns the width of the size.
Definition: qgslayoutsize.h:76
QgsRenderContext::expressionContext
QgsExpressionContext & expressionContext()
Gets the expression context.
Definition: qgsrendercontext.h:596
QgsRenderedFeatureHandlerInterface::handleRenderedFeature
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.
QgsMapLayerType::VectorLayer
@ VectorLayer
QgsLayout::layoutItems
void layoutItems(QList< T * > &itemList) const
Returns a list of layout items of a specific type.
Definition: qgslayout.h:121
QgsProject::mapLayers
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
Definition: qgsproject.cpp:3436
QgsLayoutGeoPdfExporter
Handles GeoPDF export specific setup, cleanup and processing steps.
Definition: qgslayoutgeopdfexporter.h:48
qgsgdalutils.h
qgsrenderedfeaturehandlerinterface.h
QgsRenderContext
Contains information about the context of a rendering operation.
Definition: qgsrendercontext.h:58
QgsLayoutItemMap::addRenderedFeatureHandler
void addRenderedFeatureHandler(QgsRenderedFeatureHandlerInterface *handler)
Adds a rendered feature handler to use while rendering the map.
Definition: qgslayoutitemmap.cpp:1763
qgslayoutgeopdfexporter.h
QgsLayoutItem::page
int page() const
Returns the page the item is currently on, with the first page returning 0.
Definition: qgslayoutitem.cpp:541
QgsRenderContext::scaleFactor
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
Definition: qgsrendercontext.h:333
QgsProject::mapLayer
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
Definition: qgsproject.cpp:3208
QgsRenderedFeatureHandlerInterface::RenderedFeatureContext::renderContext
const QgsRenderContext & renderContext
The render context which was used while rendering feature.
Definition: qgsrenderedfeaturehandlerinterface.h:65
QgsExpressionContext::variable
QVariant variable(const QString &name) const
Fetches a matching variable from the context.
Definition: qgsexpressioncontext.cpp:296
QgsLayoutMeasurementConverter::convert
QgsLayoutMeasurement convert(QgsLayoutMeasurement measurement, QgsUnitTypes::LayoutUnit targetUnits) const
Converts a measurement from one unit to another.
Definition: qgslayoutmeasurementconverter.cpp:21
QgsProject
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:95
QgsRenderedFeatureHandlerInterface
An interface for classes which provider custom handlers for features rendered as part of a map render...
Definition: qgsrenderedfeaturehandlerinterface.h:47
QgsUnitTypes::LayoutInches
@ LayoutInches
Inches.
Definition: qgsunittypes.h:185
QgsFeatureRequest::ALL_ATTRIBUTES
static const QString ALL_ATTRIBUTES
A special attribute that if set matches all attributes.
Definition: qgsfeaturerequest.h:282
QgsLayoutGeoPdfExporter::layerOrder
QStringList layerOrder() const
Optional list of map layer IDs in the order they should be shown in the generated GeoPDF layer tree.
Definition: qgslayoutgeopdfexporter.h:77
QgsProject::layerTreeRoot
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
Definition: qgsproject.cpp:3100
QgsAbstractGeoPdfExporter::VectorComponentDetail
Contains information relating to a single PDF layer in the GeoPDF export.
Definition: qgsabstractgeopdfexporter.h:328
QgsLayout::renderContext
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout's render context, which stores information relating to the current ...
Definition: qgslayout.cpp:359
QgsLayout::customProperty
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from the layout.
Definition: qgslayout.cpp:415
QgsAbstractGeoPdfExporter::RenderedFeature
Contains information about a feature rendered inside the PDF.
Definition: qgsabstractgeopdfexporter.h:85
QgsLayerTree::layerOrder
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
Definition: qgslayertree.cpp:75
QgsLayoutPageCollection::page
QgsLayoutItemPage * page(int pageNumber)
Returns a specific page (by pageNumber) from the collection.
Definition: qgslayoutpagecollection.cpp:460
qgslayertree.h
qgslayout.h
QgsLayoutSize::height
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
qgsvectorlayer.h
QgsLayoutItemMap
Layout graphical items for displaying a map.
Definition: qgslayoutitemmap.h:318
QgsRenderedFeatureHandlerInterface::RenderedFeatureContext
Definition: qgsrenderedfeaturehandlerinterface.h:52
qgsgeometry.h
QgsLayout::pageCollection
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
Definition: qgslayout.cpp:459
QgsGeometry
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
QgsLayout
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:50
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:387
QgsMapLayer
Base class for all map layer types.
Definition: qgsmaplayer.h:83
QgsGeometry::convertToMultiType
bool convertToMultiType()
Converts single type geometry into multitype geometry e.g.
Definition: qgsgeometry.cpp:1453
QgsMapLayer::name
QString name
Definition: qgsmaplayer.h:86
qgslayoutpagecollection.h
QgsLayoutSize
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:41
QgsFeature
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:56
qgslogger.h
QgsLayout::project
QgsProject * project() const
The project associated with the layout.
Definition: qgslayout.cpp:132
qgsvectorfilewriter.h
QgsRenderedFeatureHandlerInterface::usedAttributes
virtual QSet< QString > usedAttributes(QgsVectorLayer *layer, const QgsRenderContext &context) const
Returns a list of attributes required by this handler, for the specified layer.
Definition: qgsrenderedfeaturehandlerinterface.h:93
QgsLayout::convertToLayoutUnits
double convertToLayoutUnits(QgsLayoutMeasurement measurement) const
Converts a measurement into the layout's native units.
Definition: qgslayout.cpp:329
QgsLayoutGeoPdfExporter::~QgsLayoutGeoPdfExporter
~QgsLayoutGeoPdfExporter() override
Definition: qgslayoutgeopdfexporter.cpp:171