QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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
18
19#include <gdal.h>
20
21#include "qgsfeaturerequest.h"
22#include "qgsgeometry.h"
23#include "qgslayertree.h"
24#include "qgslayout.h"
28#include "qgsvectorlayer.h"
29
30#include <QDomDocument>
31#include <QDomElement>
32#include <QMutex>
33#include <QMutexLocker>
34
36class QgsGeospatialPdfRenderedFeatureHandler: public QgsRenderedFeatureHandlerInterface
37{
38 public:
39
40 QgsGeospatialPdfRenderedFeatureHandler( QgsLayoutItemMap *map, QgsLayoutGeospatialPdfExporter *exporter, const QStringList &layerIds )
41 : mExporter( exporter )
42 , mMap( map )
43 , mLayerIds( layerIds )
44 {
45 // get page size
46 const QgsLayoutSize pageSize = map->layout()->pageCollection()->page( map->page() )->pageSize();
47 QSizeF pageSizeLayoutUnits = map->layout()->convertToLayoutUnits( pageSize );
48 const QgsLayoutSize pageSizeInches = map->layout()->renderContext().measurementConverter().convert( pageSize, Qgis::LayoutUnit::Inches );
49
50 // PDF assumes 72 dpi -- this is hardcoded!!
51 const double pageHeightPdfUnits = pageSizeInches.height() * 72;
52 const double pageWidthPdfUnits = pageSizeInches.width() * 72;
53
54 QTransform mapTransform;
55 QPolygonF mapRectPoly = QPolygonF( QRectF( 0, 0, map->rect().width(), map->rect().height() ) );
56 //workaround QT Bug #21329
57 mapRectPoly.pop_back();
58
59 QPolygonF mapRectInLayout = map->mapToScene( mapRectPoly );
60
61 //create transform from layout coordinates to map coordinates
62 QTransform::quadToQuad( mapRectPoly, mapRectInLayout, mMapToLayoutTransform );
63
64 // and a transform to PDF coordinate space
65 mLayoutToPdfTransform = QTransform::fromTranslate( 0, pageHeightPdfUnits ).scale( pageWidthPdfUnits / pageSizeLayoutUnits.width(),
66 -pageHeightPdfUnits / pageSizeLayoutUnits.height() );
67 }
68
69 void handleRenderedFeature( const QgsFeature &feature, const QgsGeometry &renderedBounds, const QgsRenderedFeatureHandlerInterface::RenderedFeatureContext &context ) override
70 {
71 // is it a hack retrieving the layer ID from an expression context like this? possibly... BUT
72 // the alternative is adding a layer ID member to QgsRenderContext, and that's just asking for people to abuse it
73 // and use it to retrieve QgsMapLayers mid-way through a render operation. Lesser of two evils it is!
74 const QString layerId = context.renderContext.expressionContext().variable( QStringLiteral( "layer_id" ) ).toString();
75 if ( !mLayerIds.contains( layerId ) )
76 return;
77
78 const QString theme = ( mMap->mExportThemes.isEmpty() || mMap->mExportThemeIt == mMap->mExportThemes.end() ) ? QString() : *mMap->mExportThemeIt;
79
80 // transform from pixels to map item coordinates
81 QTransform pixelToMapItemTransform = QTransform::fromScale( 1.0 / context.renderContext.scaleFactor(), 1.0 / context.renderContext.scaleFactor() );
82 QgsGeometry transformed = renderedBounds;
83 transformed.transform( pixelToMapItemTransform );
84 // transform from map item coordinates to page coordinates
85 transformed.transform( mMapToLayoutTransform );
86 // ...and then to PDF coordinate space
87 transformed.transform( mLayoutToPdfTransform );
88
89 // always convert to multitype, to make things consistent
90 transformed.convertToMultiType();
91
92 mExporter->pushRenderedFeature( layerId, QgsLayoutGeospatialPdfExporter::RenderedFeature( feature, transformed ), theme );
93 }
94
95 QSet<QString> usedAttributes( QgsVectorLayer *, const QgsRenderContext & ) const override
96 {
97 return QSet< QString >() << QgsFeatureRequest::ALL_ATTRIBUTES;
98 }
99
100 private:
101 QTransform mMapToLayoutTransform;
102 QTransform mLayoutToPdfTransform;
103 QgsLayoutGeospatialPdfExporter *mExporter = nullptr;
104 QgsLayoutItemMap *mMap = nullptr;
105 QStringList mLayerIds;
106};
108
110 : mLayout( layout )
111{
112 // build a list of exportable feature layers in advance
113 QStringList exportableLayerIds;
114 const QMap< QString, QgsMapLayer * > layers = mLayout->project()->mapLayers( true );
115 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
116 {
117 if ( QgsMapLayer *ml = it.value() )
118 {
119 const QVariant visibility = ml->customProperty( QStringLiteral( "geopdf/initiallyVisible" ), true );
120 mInitialLayerVisibility.insert( ml->id(), !visibility.isValid() ? true : visibility.toBool() );
121 if ( ml->type() == Qgis::LayerType::Vector )
122 {
123 const QVariant v = ml->customProperty( QStringLiteral( "geopdf/includeFeatures" ) );
124 if ( !v.isValid() || v.toBool() )
125 {
126 exportableLayerIds << ml->id();
127 }
128 }
129
130 const QString groupName = ml->customProperty( QStringLiteral( "geopdf/groupName" ) ).toString();
131 if ( !groupName.isEmpty() )
132 mCustomLayerTreeGroups.insert( ml->id(), groupName );
133 }
134 }
135
136 // on construction, we install a rendered feature handler on layout item maps
137 QList< QgsLayoutItemMap * > maps;
138 mLayout->layoutItems( maps );
139 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
140 {
141 QgsGeospatialPdfRenderedFeatureHandler *handler = new QgsGeospatialPdfRenderedFeatureHandler( map, this, exportableLayerIds );
142 mMapHandlers.insert( map, handler );
143 map->addRenderedFeatureHandler( handler );
144 }
145
146 mLayerTreeGroupOrder = mLayout->customProperty( QStringLiteral( "pdfGroupOrder" ) ).toStringList();
147
148 // start with project layer order, and then apply custom layer order if set
149 QStringList geospatialPdfLayerOrder;
150 const QString presetLayerOrder = mLayout->customProperty( QStringLiteral( "pdfLayerOrder" ) ).toString();
151 if ( !presetLayerOrder.isEmpty() )
152 geospatialPdfLayerOrder = presetLayerOrder.split( QStringLiteral( "~~~" ) );
153
154 QList< QgsMapLayer * > layerOrder = mLayout->project()->layerTreeRoot()->layerOrder();
155 for ( auto it = geospatialPdfLayerOrder.rbegin(); it != geospatialPdfLayerOrder.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
181QgsAbstractGeospatialPdfExporter::VectorComponentDetail QgsLayoutGeospatialPdfExporter::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
@ Inches
Inches.
Definition qgis.h:5207
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.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
bool convertToMultiType()
Converts single type geometry into multitype geometry e.g.
QgsLayoutGeospatialPdfExporter(QgsLayout *layout)
Constructor for QgsLayoutGeospatialPdfExporter, 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.
double scale() const
Returns the map scale.
int page() const
Returns the page the item is currently on, with the first page returning 0.
QgsLayoutMeasurement convert(QgsLayoutMeasurement measurement, Qgis::LayoutUnit targetUnits) const
Converts a measurement from one unit to another.
const QgsLayout * layout() const
Returns the layout the object is attached to.
const QgsLayoutMeasurementConverter & measurementConverter() const
Returns the layout measurement converter to be used in the layout.
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 ...
double convertToLayoutUnits(QgsLayoutMeasurement measurement) const
Converts a measurement into the layout's native units.
QgsProject * project() const
The project associated with the layout.
Base class for all map layer types.
Definition qgsmaplayer.h:80
QString name
Definition qgsmaplayer.h:84
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:109
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
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 provide custom handlers for features rendered as part of a map render ...
virtual QSet< QString > usedAttributes(QgsVectorLayer *layer, const QgsRenderContext &context) const
Returns a list of attributes required by this handler, for the specified layer.
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.
Represents a vector layer which manages a vector based dataset.
Contains information relating to a single PDF layer in the Geospatial PDF export.
const QgsRenderContext & renderContext
The render context which was used while rendering feature.