QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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 QgsGeospatialPdfRenderedFeatureHandler( QgsLayoutItemMap *map, QgsLayoutGeospatialPdfExporter *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, Qgis::LayoutUnit::Inches );
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(), -pageHeightPdfUnits / pageSizeLayoutUnits.height() );
68 }
69
70 void handleRenderedFeature( const QgsFeature &feature, const QgsGeometry &renderedBounds, const QgsRenderedFeatureHandlerInterface::RenderedFeatureContext &context ) override
71 {
72 // is it a hack retrieving the layer ID from an expression context like this? possibly... BUT
73 // the alternative is adding a layer ID member to QgsRenderContext, and that's just asking for people to abuse it
74 // and use it to retrieve QgsMapLayers mid-way through a render operation. Lesser of two evils it is!
75 const QString layerId = context.renderContext.expressionContext().variable( u"layer_id"_s ).toString();
76 if ( !mLayerIds.contains( layerId ) )
77 return;
78
79 const QString theme = ( mMap->mExportThemes.isEmpty() || mMap->mExportThemeIt == mMap->mExportThemes.end() ) ? QString() : *mMap->mExportThemeIt;
80
81 // transform from pixels to map item coordinates
82 QTransform pixelToMapItemTransform = QTransform::fromScale( 1.0 / context.renderContext.scaleFactor(), 1.0 / context.renderContext.scaleFactor() );
83 QgsGeometry transformed = renderedBounds;
84 transformed.transform( pixelToMapItemTransform );
85 // transform from map item coordinates to page coordinates
86 transformed.transform( mMapToLayoutTransform );
87 // ...and then to PDF coordinate space
88 transformed.transform( mLayoutToPdfTransform );
89
90 // always convert to multitype, to make things consistent
91 transformed.convertToMultiType();
92
93 mExporter->pushRenderedFeature( layerId, QgsLayoutGeospatialPdfExporter::RenderedFeature( feature, transformed ), theme );
94 }
95
96 QSet<QString> usedAttributes( QgsVectorLayer *, const QgsRenderContext & ) const override { return QSet< QString >() << QgsFeatureRequest::ALL_ATTRIBUTES; }
97
98 private:
99 QTransform mMapToLayoutTransform;
100 QTransform mLayoutToPdfTransform;
101 QgsLayoutGeospatialPdfExporter *mExporter = nullptr;
102 QgsLayoutItemMap *mMap = nullptr;
103 QStringList mLayerIds;
104};
106
108 : mLayout( layout )
109{
110 // build a list of exportable feature layers in advance
111 QStringList exportableLayerIds;
112 const QMap< QString, QgsMapLayer * > layers = mLayout->project()->mapLayers( true );
113 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
114 {
115 if ( QgsMapLayer *ml = it.value() )
116 {
117 const QVariant visibility = ml->customProperty( u"geopdf/initiallyVisible"_s, true );
118 mInitialLayerVisibility.insert( ml->id(), !visibility.isValid() ? true : visibility.toBool() );
119 if ( ml->type() == Qgis::LayerType::Vector )
120 {
121 const QVariant v = ml->customProperty( u"geopdf/includeFeatures"_s );
122 if ( !v.isValid() || v.toBool() )
123 {
124 exportableLayerIds << ml->id();
125 }
126 }
127
128 const QString groupName = ml->customProperty( u"geopdf/groupName"_s ).toString();
129 if ( !groupName.isEmpty() )
130 mCustomLayerTreeGroups.insert( ml->id(), groupName );
131 }
132 }
133
134 // on construction, we install a rendered feature handler on layout item maps
135 QList< QgsLayoutItemMap * > maps;
136 mLayout->layoutItems( maps );
137 for ( QgsLayoutItemMap *map : std::as_const( maps ) )
138 {
139 QgsGeospatialPdfRenderedFeatureHandler *handler = new QgsGeospatialPdfRenderedFeatureHandler( map, this, exportableLayerIds );
140 mMapHandlers.insert( map, handler );
141 map->addRenderedFeatureHandler( handler );
142 }
143
144 mLayerTreeGroupOrder = mLayout->customProperty( u"pdfGroupOrder"_s ).toStringList();
145
146 // start with project layer order, and then apply custom layer order if set
147 QStringList geospatialPdfLayerOrder;
148 const QString presetLayerOrder = mLayout->customProperty( u"pdfLayerOrder"_s ).toString();
149 if ( !presetLayerOrder.isEmpty() )
150 geospatialPdfLayerOrder = presetLayerOrder.split( u"~~~"_s );
151
152 QList< QgsMapLayer * > layerOrder = mLayout->project()->layerTreeRoot()->layerOrder();
153 for ( auto it = geospatialPdfLayerOrder.rbegin(); it != geospatialPdfLayerOrder.rend(); ++it )
154 {
155 for ( int i = 0; i < layerOrder.size(); ++i )
156 {
157 if ( layerOrder.at( i )->id() == *it )
158 {
159 layerOrder.move( i, 0 );
160 break;
161 }
162 }
163 }
164
165 for ( const QgsMapLayer *layer : layerOrder )
166 mLayerOrder << layer->id();
167}
168
170{
171 // cleanup - remove rendered feature handler from all maps
172 for ( auto it = mMapHandlers.constBegin(); it != mMapHandlers.constEnd(); ++it )
173 {
174 it.key()->removeRenderedFeatureHandler( it.value() );
175 delete it.value();
176 }
177}
178
179QgsAbstractGeospatialPdfExporter::VectorComponentDetail QgsLayoutGeospatialPdfExporter::componentDetailForLayerId( const QString &layerId )
180{
181 QgsProject *project = mLayout->project();
182 VectorComponentDetail detail;
183 const QgsMapLayer *layer = project->mapLayer( layerId );
184 detail.name = layer ? layer->name() : layerId;
185 detail.mapLayerId = layerId;
186 if ( const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer ) )
187 {
188 detail.displayAttribute = vl->displayField();
189 }
190 return detail;
191}
@ Inches
Inches.
Definition qgis.h:5364
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.