QGIS API Documentation 3.99.0-Master (09f76ad7019)
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#include <QString>
35
36using namespace Qt::StringLiterals;
37
39class QgsGeospatialPdfRenderedFeatureHandler: public QgsRenderedFeatureHandlerInterface
40{
41 public:
42
43 QgsGeospatialPdfRenderedFeatureHandler( QgsLayoutItemMap *map, QgsLayoutGeospatialPdfExporter *exporter, const QStringList &layerIds )
44 : mExporter( exporter )
45 , mMap( map )
46 , mLayerIds( layerIds )
47 {
48 // get page size
49 const QgsLayoutSize pageSize = map->layout()->pageCollection()->page( map->page() )->pageSize();
50 QSizeF pageSizeLayoutUnits = map->layout()->convertToLayoutUnits( pageSize );
51 const QgsLayoutSize pageSizeInches = map->layout()->renderContext().measurementConverter().convert( pageSize, Qgis::LayoutUnit::Inches );
52
53 // PDF assumes 72 dpi -- this is hardcoded!!
54 const double pageHeightPdfUnits = pageSizeInches.height() * 72;
55 const double pageWidthPdfUnits = pageSizeInches.width() * 72;
56
57 QTransform mapTransform;
58 QPolygonF mapRectPoly = QPolygonF( QRectF( 0, 0, map->rect().width(), map->rect().height() ) );
59 //workaround QT Bug #21329
60 mapRectPoly.pop_back();
61
62 QPolygonF mapRectInLayout = map->mapToScene( mapRectPoly );
63
64 //create transform from layout coordinates to map coordinates
65 QTransform::quadToQuad( mapRectPoly, mapRectInLayout, mMapToLayoutTransform );
66
67 // and a transform to PDF coordinate space
68 mLayoutToPdfTransform = QTransform::fromTranslate( 0, pageHeightPdfUnits ).scale( pageWidthPdfUnits / pageSizeLayoutUnits.width(),
69 -pageHeightPdfUnits / pageSizeLayoutUnits.height() );
70 }
71
72 void handleRenderedFeature( const QgsFeature &feature, const QgsGeometry &renderedBounds, const QgsRenderedFeatureHandlerInterface::RenderedFeatureContext &context ) override
73 {
74 // is it a hack retrieving the layer ID from an expression context like this? possibly... BUT
75 // the alternative is adding a layer ID member to QgsRenderContext, and that's just asking for people to abuse it
76 // and use it to retrieve QgsMapLayers mid-way through a render operation. Lesser of two evils it is!
77 const QString layerId = context.renderContext.expressionContext().variable( u"layer_id"_s ).toString();
78 if ( !mLayerIds.contains( layerId ) )
79 return;
80
81 const QString theme = ( mMap->mExportThemes.isEmpty() || mMap->mExportThemeIt == mMap->mExportThemes.end() ) ? QString() : *mMap->mExportThemeIt;
82
83 // transform from pixels to map item coordinates
84 QTransform pixelToMapItemTransform = QTransform::fromScale( 1.0 / context.renderContext.scaleFactor(), 1.0 / context.renderContext.scaleFactor() );
85 QgsGeometry transformed = renderedBounds;
86 transformed.transform( pixelToMapItemTransform );
87 // transform from map item coordinates to page coordinates
88 transformed.transform( mMapToLayoutTransform );
89 // ...and then to PDF coordinate space
90 transformed.transform( mLayoutToPdfTransform );
91
92 // always convert to multitype, to make things consistent
93 transformed.convertToMultiType();
94
95 mExporter->pushRenderedFeature( layerId, QgsLayoutGeospatialPdfExporter::RenderedFeature( feature, transformed ), theme );
96 }
97
98 QSet<QString> usedAttributes( QgsVectorLayer *, const QgsRenderContext & ) const override
99 {
100 return QSet< QString >() << QgsFeatureRequest::ALL_ATTRIBUTES;
101 }
102
103 private:
104 QTransform mMapToLayoutTransform;
105 QTransform mLayoutToPdfTransform;
106 QgsLayoutGeospatialPdfExporter *mExporter = nullptr;
107 QgsLayoutItemMap *mMap = nullptr;
108 QStringList mLayerIds;
109};
111
113 : mLayout( layout )
114{
115 // build a list of exportable feature layers in advance
116 QStringList exportableLayerIds;
117 const QMap< QString, QgsMapLayer * > layers = mLayout->project()->mapLayers( true );
118 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
119 {
120 if ( QgsMapLayer *ml = it.value() )
121 {
122 const QVariant visibility = ml->customProperty( u"geopdf/initiallyVisible"_s, true );
123 mInitialLayerVisibility.insert( ml->id(), !visibility.isValid() ? true : visibility.toBool() );
124 if ( ml->type() == Qgis::LayerType::Vector )
125 {
126 const QVariant v = ml->customProperty( u"geopdf/includeFeatures"_s );
127 if ( !v.isValid() || v.toBool() )
128 {
129 exportableLayerIds << ml->id();
130 }
131 }
132
133 const QString groupName = ml->customProperty( u"geopdf/groupName"_s ).toString();
134 if ( !groupName.isEmpty() )
135 mCustomLayerTreeGroups.insert( ml->id(), groupName );
136 }
137 }
138
139 // on construction, we install a rendered feature handler on layout item maps
140 QList< QgsLayoutItemMap * > maps;
141 mLayout->layoutItems( maps );
142 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
143 {
144 QgsGeospatialPdfRenderedFeatureHandler *handler = new QgsGeospatialPdfRenderedFeatureHandler( map, this, exportableLayerIds );
145 mMapHandlers.insert( map, handler );
146 map->addRenderedFeatureHandler( handler );
147 }
148
149 mLayerTreeGroupOrder = mLayout->customProperty( u"pdfGroupOrder"_s ).toStringList();
150
151 // start with project layer order, and then apply custom layer order if set
152 QStringList geospatialPdfLayerOrder;
153 const QString presetLayerOrder = mLayout->customProperty( u"pdfLayerOrder"_s ).toString();
154 if ( !presetLayerOrder.isEmpty() )
155 geospatialPdfLayerOrder = presetLayerOrder.split( u"~~~"_s );
156
157 QList< QgsMapLayer * > layerOrder = mLayout->project()->layerTreeRoot()->layerOrder();
158 for ( auto it = geospatialPdfLayerOrder.rbegin(); it != geospatialPdfLayerOrder.rend(); ++it )
159 {
160 for ( int i = 0; i < layerOrder.size(); ++i )
161 {
162 if ( layerOrder.at( i )->id() == *it )
163 {
164 layerOrder.move( i, 0 );
165 break;
166 }
167 }
168 }
169
170 for ( const QgsMapLayer *layer : layerOrder )
171 mLayerOrder << layer->id();
172}
173
175{
176 // cleanup - remove rendered feature handler from all maps
177 for ( auto it = mMapHandlers.constBegin(); it != mMapHandlers.constEnd(); ++it )
178 {
179 it.key()->removeRenderedFeatureHandler( it.value() );
180 delete it.value();
181 }
182}
183
184QgsAbstractGeospatialPdfExporter::VectorComponentDetail QgsLayoutGeospatialPdfExporter::componentDetailForLayerId( const QString &layerId )
185{
186 QgsProject *project = mLayout->project();
187 VectorComponentDetail detail;
188 const QgsMapLayer *layer = project->mapLayer( layerId );
189 detail.name = layer ? layer->name() : layerId;
190 detail.mapLayerId = layerId;
191 if ( const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer ) )
192 {
193 detail.displayAttribute = vl->displayField();
194 }
195 return detail;
196}
197
@ Inches
Inches.
Definition qgis.h:5314
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:83
QString name
Definition qgsmaplayer.h:87
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:113
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.