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