Branch data Line data Source code
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 : : 17 : : #include "qgslayoutgeopdfexporter.h" 18 : : #include "qgsrenderedfeaturehandlerinterface.h" 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" 30 : : #include "qgslayoutpagecollection.h" 31 : : 32 : : #include <QMutex> 33 : : #include <QMutexLocker> 34 : : #include <QDomDocument> 35 : : #include <QDomElement> 36 : : 37 : : ///@cond PRIVATE 38 : 0 : class QgsGeoPdfRenderedFeatureHandler: public QgsRenderedFeatureHandlerInterface 39 : : { 40 : : public: 41 : : 42 : 0 : QgsGeoPdfRenderedFeatureHandler( QgsLayoutItemMap *map, QgsLayoutGeoPdfExporter *exporter, const QStringList &layerIds ) 43 : 0 : : mExporter( exporter ) 44 : 0 : , mMap( map ) 45 : 0 : , mLayerIds( layerIds ) 46 : 0 : { 47 : : // get page size 48 : 0 : const QgsLayoutSize pageSize = map->layout()->pageCollection()->page( map->page() )->pageSize(); 49 : 0 : QSizeF pageSizeLayoutUnits = map->layout()->convertToLayoutUnits( pageSize ); 50 : 0 : const QgsLayoutSize pageSizeInches = map->layout()->renderContext().measurementConverter().convert( pageSize, QgsUnitTypes::LayoutInches ); 51 : : 52 : : // PDF assumes 72 dpi -- this is hardcoded!! 53 : 0 : const double pageHeightPdfUnits = pageSizeInches.height() * 72; 54 : 0 : const double pageWidthPdfUnits = pageSizeInches.width() * 72; 55 : : 56 : 0 : QTransform mapTransform; 57 : 0 : QPolygonF mapRectPoly = QPolygonF( QRectF( 0, 0, map->rect().width(), map->rect().height() ) ); 58 : : //workaround QT Bug #21329 59 : 0 : mapRectPoly.pop_back(); 60 : : 61 : 0 : QPolygonF mapRectInLayout = map->mapToScene( mapRectPoly ); 62 : : 63 : : //create transform from layout coordinates to map coordinates 64 : 0 : QTransform::quadToQuad( mapRectPoly, mapRectInLayout, mMapToLayoutTransform ); 65 : : 66 : : // and a transform to PDF coordinate space 67 : 0 : mLayoutToPdfTransform = QTransform::fromTranslate( 0, pageHeightPdfUnits ).scale( pageWidthPdfUnits / pageSizeLayoutUnits.width(), 68 : 0 : -pageHeightPdfUnits / pageSizeLayoutUnits.height() ); 69 : 0 : } 70 : : 71 : 0 : 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 : 0 : const QString layerId = context.renderContext.expressionContext().variable( QStringLiteral( "layer_id" ) ).toString(); 77 : 0 : if ( !mLayerIds.contains( layerId ) ) 78 : 0 : return; 79 : : 80 : 0 : const QString theme = ( mMap->mExportThemes.isEmpty() || mMap->mExportThemeIt == mMap->mExportThemes.end() ) ? QString() : *mMap->mExportThemeIt; 81 : : 82 : : // transform from pixels to map item coordinates 83 : 0 : QTransform pixelToMapItemTransform = QTransform::fromScale( 1.0 / context.renderContext.scaleFactor(), 1.0 / context.renderContext.scaleFactor() ); 84 : 0 : QgsGeometry transformed = renderedBounds; 85 : 0 : transformed.transform( pixelToMapItemTransform ); 86 : : // transform from map item coordinates to page coordinates 87 : 0 : transformed.transform( mMapToLayoutTransform ); 88 : : // ...and then to PDF coordinate space 89 : 0 : transformed.transform( mLayoutToPdfTransform ); 90 : : 91 : : // always convert to multitype, to make things consistent 92 : 0 : transformed.convertToMultiType(); 93 : : 94 : 0 : mExporter->pushRenderedFeature( layerId, QgsLayoutGeoPdfExporter::RenderedFeature( feature, transformed ), theme ); 95 : 0 : } 96 : : 97 : 0 : QSet<QString> usedAttributes( QgsVectorLayer *, const QgsRenderContext & ) const override 98 : : { 99 : 0 : return QSet< QString >() << QgsFeatureRequest::ALL_ATTRIBUTES; 100 : 0 : } 101 : : 102 : : private: 103 : : QTransform mMapToLayoutTransform; 104 : : QTransform mLayoutToPdfTransform; 105 : : QgsLayoutGeoPdfExporter *mExporter = nullptr; 106 : : QgsLayoutItemMap *mMap = nullptr; 107 : : QStringList mLayerIds; 108 : : }; 109 : : ///@endcond 110 : : 111 : 0 : QgsLayoutGeoPdfExporter::QgsLayoutGeoPdfExporter( QgsLayout *layout ) 112 : 0 : : mLayout( layout ) 113 : 0 : { 114 : : // build a list of exportable feature layers in advance 115 : 0 : QStringList exportableLayerIds; 116 : 0 : const QMap< QString, QgsMapLayer * > layers = mLayout->project()->mapLayers( true ); 117 : 0 : for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it ) 118 : : { 119 : 0 : if ( QgsMapLayer *ml = it.value() ) 120 : : { 121 : 0 : const QVariant visibility = ml->customProperty( QStringLiteral( "geopdf/initiallyVisible" ), true ); 122 : 0 : mInitialLayerVisibility.insert( ml->id(), !visibility.isValid() ? true : visibility.toBool() ); 123 : 0 : if ( ml->type() == QgsMapLayerType::VectorLayer ) 124 : : { 125 : 0 : const QVariant v = ml->customProperty( QStringLiteral( "geopdf/includeFeatures" ) ); 126 : 0 : if ( !v.isValid() || v.toBool() ) 127 : : { 128 : 0 : exportableLayerIds << ml->id(); 129 : 0 : } 130 : 0 : } 131 : : 132 : 0 : const QString groupName = ml->customProperty( QStringLiteral( "geopdf/groupName" ) ).toString(); 133 : 0 : if ( !groupName.isEmpty() ) 134 : 0 : mCustomLayerTreeGroups.insert( ml->id(), groupName ); 135 : 0 : } 136 : 0 : } 137 : : 138 : : // on construction, we install a rendered feature handler on layout item maps 139 : 0 : QList< QgsLayoutItemMap * > maps; 140 : 0 : mLayout->layoutItems( maps ); 141 : 0 : for ( QgsLayoutItemMap *map : std::as_const( maps ) ) 142 : : { 143 : 0 : QgsGeoPdfRenderedFeatureHandler *handler = new QgsGeoPdfRenderedFeatureHandler( map, this, exportableLayerIds ); 144 : 0 : mMapHandlers.insert( map, handler ); 145 : 0 : map->addRenderedFeatureHandler( handler ); 146 : : } 147 : : 148 : : // start with project layer order, and then apply custom layer order if set 149 : 0 : QStringList geoPdfLayerOrder; 150 : 0 : const QString presetLayerOrder = mLayout->customProperty( QStringLiteral( "pdfLayerOrder" ) ).toString(); 151 : 0 : if ( !presetLayerOrder.isEmpty() ) 152 : 0 : geoPdfLayerOrder = presetLayerOrder.split( QStringLiteral( "~~~" ) ); 153 : : 154 : 0 : QList< QgsMapLayer * > layerOrder = mLayout->project()->layerTreeRoot()->layerOrder(); 155 : 0 : for ( auto it = geoPdfLayerOrder.rbegin(); it != geoPdfLayerOrder.rend(); ++it ) 156 : : { 157 : 0 : for ( int i = 0; i < layerOrder.size(); ++i ) 158 : : { 159 : 0 : if ( layerOrder.at( i )->id() == *it ) 160 : : { 161 : 0 : layerOrder.move( i, 0 ); 162 : 0 : break; 163 : : } 164 : 0 : } 165 : 0 : } 166 : : 167 : 0 : for ( const QgsMapLayer *layer : layerOrder ) 168 : 0 : mLayerOrder << layer->id(); 169 : 0 : } 170 : : 171 : 0 : QgsLayoutGeoPdfExporter::~QgsLayoutGeoPdfExporter() 172 : 0 : { 173 : : // cleanup - remove rendered feature handler from all maps 174 : 0 : for ( auto it = mMapHandlers.constBegin(); it != mMapHandlers.constEnd(); ++it ) 175 : : { 176 : 0 : it.key()->removeRenderedFeatureHandler( it.value() ); 177 : 0 : delete it.value(); 178 : 0 : } 179 : 0 : } 180 : : 181 : 0 : QgsAbstractGeoPdfExporter::VectorComponentDetail QgsLayoutGeoPdfExporter::componentDetailForLayerId( const QString &layerId ) 182 : : { 183 : 0 : QgsProject *project = mLayout->project(); 184 : 0 : VectorComponentDetail detail; 185 : 0 : const QgsMapLayer *layer = project->mapLayer( layerId ); 186 : 0 : detail.name = layer ? layer->name() : layerId; 187 : 0 : detail.mapLayerId = layerId; 188 : 0 : if ( const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer ) ) 189 : : { 190 : 0 : detail.displayAttribute = vl->displayField(); 191 : 0 : } 192 : 0 : return detail; 193 : 0 : } 194 : :