19#include <cpl_string.h>
29#include <QDomDocument>
32#include <QMutexLocker>
38using namespace Qt::StringLiterals;
43 GDALDriverH hDriverMem = GDALGetDriverByName(
"PDF" );
49 const char *pHavePoppler = GDALGetMetadataItem( hDriverMem,
"HAVE_POPPLER",
nullptr );
50 if ( pHavePoppler && strstr( pHavePoppler,
"YES" ) )
53 const char *pHavePdfium = GDALGetMetadataItem( hDriverMem,
"HAVE_PDFIUM",
nullptr );
54 if ( pHavePdfium && strstr( pHavePdfium,
"YES" ) )
63 GDALDriverH hDriverMem = GDALGetDriverByName(
"PDF" );
66 return QObject::tr(
"No GDAL PDF driver available." );
69 const char *pHavePoppler = GDALGetMetadataItem( hDriverMem,
"HAVE_POPPLER",
nullptr );
70 if ( pHavePoppler && strstr( pHavePoppler,
"YES" ) )
73 const char *pHavePdfium = GDALGetMetadataItem( hDriverMem,
"HAVE_PDFIUM",
nullptr );
74 if ( pHavePdfium && strstr( pHavePdfium,
"YES" ) )
77 return QObject::tr(
"GDAL PDF driver was not built with PDF read support. A build with PDF read support is required for geospatial PDF creation." );
82 QgsDebugError( u
"GDAL PDF creation error: %1 "_s.arg( msg ) );
83 if ( QStringList *errorList =
static_cast< QStringList *
>( CPLGetErrorHandlerUserData() ) )
85 errorList->append( QString( msg ) );
94 const QString composition = createCompositionXml( components, details );
96 if ( composition.isEmpty() )
100 GDALDriverH driver = GDALGetDriverByName(
"PDF" );
103 mErrorMessage = QObject::tr(
"Cannot load GDAL PDF driver" );
108 QFile file( xmlFilePath );
109 if ( file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
111 QTextStream out( &file );
116 mErrorMessage = QObject::tr(
"Could not create geospatial PDF composition file" );
120 char **papszOptions = CSLSetNameValue(
nullptr,
"COMPOSITION_FILE", xmlFilePath.toUtf8().constData() );
122 QStringList creationErrors;
126 gdal::dataset_unique_ptr outputDataset( GDALCreate( driver, destinationFile.toUtf8().constData(), 0, 0, 0, GDT_Unknown, papszOptions ) );
128 CPLPopErrorHandler();
130 const bool res = outputDataset.get() !=
nullptr;
133 if ( creationErrors.size() == 1 )
135 mErrorMessage = QObject::tr(
"Could not create PDF file: %1" ).arg( creationErrors.at( 0 ) );
137 else if ( !creationErrors.empty() )
139 mErrorMessage = QObject::tr(
"Could not create PDF file. Received errors:\n" );
140 for (
const QString &error : std::as_const( creationErrors ) )
142 mErrorMessage += ( !mErrorMessage.isEmpty() ? u
"\n"_s : QString() ) + error;
147 mErrorMessage = QObject::tr(
"Could not create PDF file, but no error details are available" );
150 outputDataset.reset();
152 CSLDestroy( papszOptions );
166 case QPainter::CompositionMode_SourceOver:
167 case QPainter::CompositionMode_Multiply:
168 case QPainter::CompositionMode_Screen:
169 case QPainter::CompositionMode_Overlay:
170 case QPainter::CompositionMode_Darken:
171 case QPainter::CompositionMode_Lighten:
172 case QPainter::CompositionMode_ColorDodge:
173 case QPainter::CompositionMode_ColorBurn:
174 case QPainter::CompositionMode_HardLight:
175 case QPainter::CompositionMode_SoftLight:
176 case QPainter::CompositionMode_Difference:
177 case QPainter::CompositionMode_Exclusion:
190 QMutexLocker locker( &mMutex );
195 mCollatedFeatures[group][layerId].append( f );
198bool QgsAbstractGeospatialPdfExporter::saveTemporaryLayers()
200 for (
auto groupIt = mCollatedFeatures.constBegin(); groupIt != mCollatedFeatures.constEnd(); ++groupIt )
202 for (
auto it = groupIt->constBegin(); it != groupIt->constEnd(); ++it )
206 VectorComponentDetail detail = componentDetailForLayerId( it.key() );
207 detail.sourceVectorPath = filePath;
208 detail.group = groupIt.key();
216 std::unique_ptr< QgsVectorFileWriter > writer(
220 if ( writer->hasError() )
222 mErrorMessage = writer->errorMessage();
226 for (
const QgsFeature &feature : features )
228 QgsFeature f = feature;
231 mErrorMessage = writer->errorMessage();
236 detail.sourceVectorLayer = layerName;
237 mVectorComponents << detail;
244void sortTreeNodeList( std::vector<std::unique_ptr<TreeNode>> &nodeList, QStringList layerOrder )
246 std::sort( nodeList.begin(), nodeList.end(), [&layerOrder](
const std::unique_ptr< TreeNode > &a,
const std::unique_ptr< TreeNode > &b ) ->
bool {
247 const qsizetype indexA = layerOrder.indexOf( a->mapLayerId );
248 const qsizetype indexB = layerOrder.indexOf( b->mapLayerId );
250 if ( indexA >= 0 && indexB >= 0 )
251 return indexA < indexB;
252 else if ( indexA >= 0 )
254 else if ( indexB >= 0 )
257 return a->name.localeAwareCompare( b->name ) < 0;
261QString QgsAbstractGeospatialPdfExporter::createCompositionXml(
const QList<ComponentLayerDetail> &components,
const ExportDetails &details )
const
264 QDomElement compositionElem = doc.createElement( u
"PDFComposition"_s );
267 createMetadataXmlSection( compositionElem, doc, details );
270 QDomElement page = doc.createElement( u
"Page"_s );
272 const double pageWidthPdfUnits = std::ceil( details.pageSizeMm.width() / 25.4 *
DPI_72 );
273 const double pageHeightPdfUnits = std::ceil( details.pageSizeMm.height() / 25.4 *
DPI_72 );
274 createPageDimensionXmlSection( page, doc, pageWidthPdfUnits, pageHeightPdfUnits );
277 createGeoreferencingXmlSection( page, doc, details, pageWidthPdfUnits, pageHeightPdfUnits );
280 QgsLayerTree *layerTreePointer = layerTree();
281 if ( details.useLayerTreeConfig && layerTreePointer )
283 createLayerTreeAndContentXmlSectionsFromLayerTree( layerTreePointer, compositionElem, page, doc, components, details );
287 createLayerTreeAndContentXmlSections( compositionElem, page, doc, components, details );
290 compositionElem.appendChild( page );
291 doc.appendChild( compositionElem );
294 QTextStream stream( &composition );
295 doc.save( stream, -1 );
300void QgsAbstractGeospatialPdfExporter::createMetadataXmlSection( QDomElement &compositionElem, QDomDocument &doc,
const ExportDetails &details )
const
303 QDomElement metadata = doc.createElement( u
"Metadata"_s );
304 if ( !details.author.isEmpty() )
306 QDomElement author = doc.createElement( u
"Author"_s );
307 author.appendChild( doc.createTextNode( details.author ) );
308 metadata.appendChild( author );
310 if ( !details.producer.isEmpty() )
312 QDomElement producer = doc.createElement( u
"Producer"_s );
313 producer.appendChild( doc.createTextNode( details.producer ) );
314 metadata.appendChild( producer );
316 if ( !details.creator.isEmpty() )
318 QDomElement creator = doc.createElement( u
"Creator"_s );
319 creator.appendChild( doc.createTextNode( details.creator ) );
320 metadata.appendChild( creator );
322 if ( details.creationDateTime.isValid() )
324 QDomElement creationDate = doc.createElement( u
"CreationDate"_s );
325 QString creationDateString = u
"D:%1"_s.arg( details.creationDateTime.toString( u
"yyyyMMddHHmmss"_s ) );
326#if QT_FEATURE_timezone > 0
327 if ( details.creationDateTime.timeZone().isValid() )
329 int offsetFromUtc = details.creationDateTime.timeZone().offsetFromUtc( details.creationDateTime );
330 creationDateString += ( offsetFromUtc >= 0 ) ?
'+' :
'-';
331 offsetFromUtc = std::abs( offsetFromUtc );
332 int offsetHours = offsetFromUtc / 3600;
333 int offsetMins = ( offsetFromUtc % 3600 ) / 60;
334 creationDateString += u
"%1'%2'"_s.arg( offsetHours ).arg( offsetMins );
337 QgsDebugError( u
"Qt is built without timezone support, skipping timezone for pdf export"_s );
339 creationDate.appendChild( doc.createTextNode( creationDateString ) );
340 metadata.appendChild( creationDate );
342 if ( !details.subject.isEmpty() )
344 QDomElement subject = doc.createElement( u
"Subject"_s );
345 subject.appendChild( doc.createTextNode( details.subject ) );
346 metadata.appendChild( subject );
348 if ( !details.title.isEmpty() )
350 QDomElement title = doc.createElement( u
"Title"_s );
351 title.appendChild( doc.createTextNode( details.title ) );
352 metadata.appendChild( title );
354 if ( !details.keywords.empty() )
356 QStringList allKeywords;
357 for (
auto it = details.keywords.constBegin(); it != details.keywords.constEnd(); ++it )
359 allKeywords.append( u
"%1: %2"_s.arg( it.key(), it.value().join(
',' ) ) );
361 QDomElement keywords = doc.createElement( u
"Keywords"_s );
362 keywords.appendChild( doc.createTextNode( allKeywords.join(
';' ) ) );
363 metadata.appendChild( keywords );
365 compositionElem.appendChild( metadata );
368void QgsAbstractGeospatialPdfExporter::createGeoreferencingXmlSection(
369 QDomElement &pageElem, QDomDocument &doc,
const ExportDetails &details,
const double pageWidthPdfUnits,
const double pageHeightPdfUnits
373 for (
const QgsAbstractGeospatialPdfExporter::GeoReferencedSection §ion : details.georeferencedSections )
375 QDomElement georeferencing = doc.createElement( u
"Georeferencing"_s );
376 georeferencing.setAttribute( u
"id"_s, u
"georeferenced_%1"_s.arg( i++ ) );
377 georeferencing.setAttribute( u
"ISO32000ExtensionFormat"_s, details.useIso32000ExtensionFormatGeoreferencing ? u
"true"_s : u
"false"_s );
381 QDomElement srs = doc.createElement( u
"SRS"_s );
384 if ( !section.
crs.
authid().isEmpty() && !section.
crs.
authid().startsWith( u
"user"_s, Qt::CaseInsensitive ) )
386 srs.appendChild( doc.createTextNode( section.
crs.
authid() ) );
392 georeferencing.appendChild( srs );
404 QDomElement boundingPolygon = doc.createElement( u
"BoundingPolygon"_s );
407 QTransform t = QTransform::fromTranslate( 0, pageHeightPdfUnits ).scale( pageWidthPdfUnits / details.pageSizeMm.width(), -pageHeightPdfUnits / details.pageSizeMm.height() );
411 boundingPolygon.appendChild( doc.createTextNode( p.
asWkt() ) );
413 georeferencing.appendChild( boundingPolygon );
422 QDomElement boundingBox = doc.createElement( u
"BoundingBox"_s );
427 georeferencing.appendChild( boundingBox );
432 QDomElement cp1 = doc.createElement( u
"ControlPoint"_s );
434 cp1.setAttribute( u
"y"_s,
qgsDoubleToString( ( details.pageSizeMm.height() - point.pagePoint.y() ) / 25.4 *
DPI_72 ) );
437 georeferencing.appendChild( cp1 );
440 pageElem.appendChild( georeferencing );
444void QgsAbstractGeospatialPdfExporter::createPageDimensionXmlSection( QDomElement &pageElem, QDomDocument &doc,
const double pageWidthPdfUnits,
const double pageHeightPdfUnits )
const
446 QDomElement dpi = doc.createElement( u
"DPI"_s );
449 pageElem.appendChild( dpi );
451 QDomElement width = doc.createElement( u
"Width"_s );
452 width.appendChild( doc.createTextNode(
qgsDoubleToString( pageWidthPdfUnits ) ) );
453 pageElem.appendChild( width );
454 QDomElement height = doc.createElement( u
"Height"_s );
455 height.appendChild( doc.createTextNode(
qgsDoubleToString( pageHeightPdfUnits ) ) );
456 pageElem.appendChild( height );
459void QgsAbstractGeospatialPdfExporter::createLayerTreeAndContentXmlSections(
460 QDomElement &compositionElem, QDomElement &pageElem, QDomDocument &doc,
const QList<ComponentLayerDetail> &components,
const ExportDetails &details
463 QSet< QString > createdLayerIds;
464 std::vector< std::unique_ptr< TreeNode > > rootGroups;
465 std::vector< std::unique_ptr< TreeNode > > rootLayers;
466 QMap< QString, TreeNode * > groupNameMap;
468 QStringList layerTreeGroupOrder = details.layerTreeGroupOrder;
472 for (
auto it = details.customLayerTreeGroups.constBegin(); it != details.customLayerTreeGroups.constEnd(); ++it )
474 if ( layerTreeGroupOrder.contains( it.value() ) )
476 layerTreeGroupOrder.append( it.value() );
480 if ( details.includeFeatures )
484 if ( !component.group.isEmpty() && !layerTreeGroupOrder.contains( component.group ) )
486 layerTreeGroupOrder.append( component.group );
494 if ( !component.group.isEmpty() && !layerTreeGroupOrder.contains( component.group ) )
496 layerTreeGroupOrder.append( component.group );
501 QMap< QString, TreeNode * > groupNameToTreeNode;
502 QMap< QString, TreeNode * > layerIdToTreeNode;
504 auto createGroup = [&details, &groupNameToTreeNode](
const QString &groupName ) -> std::unique_ptr< TreeNode > {
505 auto group = std::make_unique< TreeNode >();
506 const QString
id = QUuid::createUuid().toString();
508 groupNameToTreeNode[groupName] = group.get();
510 group->name = groupName;
511 group->initiallyVisible =
true;
512 if ( details.mutuallyExclusiveGroups.contains( groupName ) )
513 group->mutuallyExclusiveGroupId = u
"__mutually_exclusive_groups__"_s;
517 if ( details.includeFeatures )
521 const QString destinationGroup = details.customLayerTreeGroups.value( component.mapLayerId, component.group );
522 const QString
id = destinationGroup.isEmpty() ? component.mapLayerId : u
"%1_%2"_s.arg( destinationGroup, component.mapLayerId );
524 auto layer = std::make_unique< TreeNode >();
526 layer->name = details.layerIdToPdfLayerTreeNameMap.contains( component.mapLayerId ) ? details.layerIdToPdfLayerTreeNameMap.value( component.mapLayerId ) : component.name;
527 layer->initiallyVisible = details.initialLayerVisibility.value( component.mapLayerId,
true );
528 layer->mapLayerId = component.mapLayerId;
530 layerIdToTreeNode.insert( layer->id, layer.get() );
531 if ( !destinationGroup.isEmpty() )
533 if ( TreeNode *groupNode = groupNameMap.value( destinationGroup ) )
535 groupNode->addChild( std::move( layer ) );
539 std::unique_ptr< TreeNode > group = createGroup( destinationGroup );
540 group->addChild( std::move( layer ) );
541 groupNameMap.insert( destinationGroup, group.get() );
542 rootGroups.emplace_back( std::move( group ) );
547 rootLayers.emplace_back( std::move( layer ) );
550 createdLayerIds.insert(
id );
560 const QString destinationGroup = details.customLayerTreeGroups.value( component.mapLayerId, component.group );
561 const QString
id = destinationGroup.isEmpty() ? component.mapLayerId : u
"%1_%2"_s.arg( destinationGroup, component.mapLayerId );
563 if ( !component.mapLayerId.isEmpty() && createdLayerIds.contains(
id ) )
566 if ( destinationGroup.isEmpty() && component.mapLayerId.isEmpty() )
569 std::unique_ptr< TreeNode > mapLayerNode;
570 if ( !component.mapLayerId.isEmpty() )
572 mapLayerNode = std::make_unique< TreeNode >();
573 mapLayerNode->id = id;
574 mapLayerNode->name = details.layerIdToPdfLayerTreeNameMap.value( component.mapLayerId, component.name );
575 mapLayerNode->initiallyVisible = details.initialLayerVisibility.value( component.mapLayerId,
true );
576 mapLayerNode->mapLayerId = component.mapLayerId;
578 layerIdToTreeNode.insert( mapLayerNode->id, mapLayerNode.get() );
581 if ( !destinationGroup.isEmpty() )
583 if ( TreeNode *groupNode = groupNameMap.value( destinationGroup ) )
586 groupNode->addChild( std::move( mapLayerNode ) );
590 std::unique_ptr< TreeNode > group = createGroup( destinationGroup );
592 group->addChild( std::move( mapLayerNode ) );
593 groupNameMap.insert( destinationGroup, group.get() );
594 rootGroups.emplace_back( std::move( group ) );
600 rootLayers.emplace_back( std::move( mapLayerNode ) );
603 if ( !component.mapLayerId.isEmpty() )
605 createdLayerIds.insert(
id );
609 QDomElement contentElem = doc.createElement( u
"Content"_s );
610 createContentXmlSection( contentElem, doc, groupNameToTreeNode, layerIdToTreeNode, components, details );
612 pageElem.appendChild( contentElem );
615 QDomElement layerTree = doc.createElement( u
"LayerTree"_s );
621 std::sort( rootGroups.begin(), rootGroups.end(), [&layerTreeGroupOrder](
const std::unique_ptr< TreeNode > &a,
const std::unique_ptr< TreeNode > &b ) ->
bool {
622 return layerTreeGroupOrder.indexOf( a->name ) < layerTreeGroupOrder.indexOf( b->name );
626 for (
const std::unique_ptr<TreeNode> &rootGroup : rootGroups )
631 bool haveFoundMutuallyExclusiveGroup =
false;
632 for (
const auto &node : std::as_const( rootGroups ) )
634 if ( !node->mutuallyExclusiveGroupId.isEmpty() )
637 node->initiallyVisible = !haveFoundMutuallyExclusiveGroup;
638 haveFoundMutuallyExclusiveGroup =
true;
640 layerTree.appendChild( node->toElement( doc ) );
644 layerTreeGroupOrder.erase(
645 std::remove_if( layerTreeGroupOrder.begin(), layerTreeGroupOrder.end(), [&details](
const QString &group ) { return details.customLayerTreeGroups.key( group ).isEmpty(); } ),
646 layerTreeGroupOrder.end()
653 for (
const auto &node : std::as_const( rootLayers ) )
655 layerTree.appendChild( node->toElement( doc ) );
658 compositionElem.appendChild( layerTree );
661void QgsAbstractGeospatialPdfExporter::createLayerTreeAndContentXmlSectionsFromLayerTree(
662 const QgsLayerTree *layerTree, QDomElement &compositionElem, QDomElement &pageElem, QDomDocument &doc,
const QList<ComponentLayerDetail> &components,
const ExportDetails &details
665 QMap< QString, TreeNode * > groupNameToTreeNode;
666 QMap< QString, TreeNode * > layerIdToTreeNode;
669 std::unique_ptr< TreeNode > rootPdfNode = createPdfTreeNodes( groupNameToTreeNode, layerIdToTreeNode, layerTree );
670 rootPdfNode->isRootNode =
true;
675 if ( !component.group.isEmpty() && !groupNameToTreeNode.contains( component.group ) )
677 auto pdfTreeGroup = std::make_unique< TreeNode >();
678 const QString
id = QUuid::createUuid().toString();
679 pdfTreeGroup->id = id;
680 pdfTreeGroup->name = component.group;
681 pdfTreeGroup->initiallyVisible =
true;
682 groupNameToTreeNode[pdfTreeGroup->name] = pdfTreeGroup.get();
683 rootPdfNode->addChild( std::move( pdfTreeGroup ) );
687 QDomElement contentElem = doc.createElement( u
"Content"_s );
688 createContentXmlSection( contentElem, doc, groupNameToTreeNode, layerIdToTreeNode, components, details );
690 pageElem.appendChild( contentElem );
693 QDomElement layerTreeElem = doc.createElement( u
"LayerTree"_s );
694 rootPdfNode->toChildrenElements( doc, layerTreeElem );
695 compositionElem.appendChild( layerTreeElem );
698void QgsAbstractGeospatialPdfExporter::createContentXmlSection(
699 QDomElement &contentElem,
701 const QMap< QString, TreeNode * > &groupNameToTreeNode,
702 const QMap< QString, TreeNode * > &layerIdToTreeNode,
703 const QList<ComponentLayerDetail> &components,
704 const ExportDetails &details
708 QDomElement pdfDataset = doc.createElement( u
"PDF"_s );
709 pdfDataset.setAttribute( u
"dataset"_s, component.sourcePdfPath );
710 if ( component.opacity != 1.0 || component.compositionMode != QPainter::CompositionMode_SourceOver )
712 QDomElement blendingElement = doc.createElement( u
"Blending"_s );
713 blendingElement.setAttribute( u
"opacity"_s, component.opacity );
714 blendingElement.setAttribute( u
"function"_s, compositionModeToString( component.compositionMode ) );
716 pdfDataset.appendChild( blendingElement );
724 if ( component.mapLayerId.isEmpty() && component.group.isEmpty() )
726 contentElem.appendChild( createPdfDatasetElement( component ) );
728 else if ( !component.mapLayerId.isEmpty() )
730 const QString destinationGroup = details.customLayerTreeGroups.value( component.mapLayerId, component.group );
731 const QString
id = destinationGroup.isEmpty() ? component.mapLayerId : u
"%1_%2"_s.arg( destinationGroup, component.mapLayerId );
732 if ( TreeNode *treeNode = layerIdToTreeNode.value(
id ) )
734 QDomElement ifLayerOnElement = treeNode->createNestedIfLayerOnElements( doc, contentElem );
735 ifLayerOnElement.appendChild( createPdfDatasetElement( component ) );
738 else if ( TreeNode *groupNode = groupNameToTreeNode.value( component.group ) )
740 QDomElement ifGroupOn = groupNode->createIfLayerOnElement( doc, contentElem );
741 ifGroupOn.appendChild( createPdfDatasetElement( component ) );
746 if ( details.includeFeatures )
750 const QString destinationGroup = details.customLayerTreeGroups.value( component.mapLayerId, component.group );
751 const QString
id = destinationGroup.isEmpty() ? component.mapLayerId : u
"%1_%2"_s.arg( destinationGroup, component.mapLayerId );
752 if ( TreeNode *treeNode = layerIdToTreeNode.value(
id ) )
754 QDomElement ifLayerOnElement = treeNode->createNestedIfLayerOnElements( doc, contentElem );
756 QDomElement vectorDataset = doc.createElement( u
"Vector"_s );
757 vectorDataset.setAttribute( u
"dataset"_s, component.sourceVectorPath );
758 vectorDataset.setAttribute( u
"layer"_s, component.sourceVectorLayer );
759 vectorDataset.setAttribute( u
"visible"_s, u
"false"_s );
760 QDomElement logicalStructure = doc.createElement( u
"LogicalStructure"_s );
761 logicalStructure.setAttribute( u
"displayLayerName"_s, component.name );
762 if ( !component.displayAttribute.isEmpty() )
763 logicalStructure.setAttribute( u
"fieldToDisplay"_s, component.displayAttribute );
764 vectorDataset.appendChild( logicalStructure );
765 ifLayerOnElement.appendChild( vectorDataset );
771std::unique_ptr< TreeNode > QgsAbstractGeospatialPdfExporter::createPdfTreeNodes(
772 QMap< QString, TreeNode * > &groupNameToTreeNode, QMap< QString, TreeNode * > &layerIdToTreeNode,
const QgsLayerTreeGroup *layerTreeGroup
775 auto pdfTreeNodes = std::make_unique< TreeNode >();
776 const QString
id = QUuid::createUuid().toString();
777 pdfTreeNodes->id = id;
778 pdfTreeNodes->name = layerTreeGroup->
name();
781 const QList<QgsLayerTreeNode *> groupChildren = layerTreeGroup->
children();
783 for ( QgsLayerTreeNode *qgisNode : groupChildren )
785 switch ( qgisNode->nodeType() )
789 QgsLayerTreeLayer *layerTreeLayer = qobject_cast<QgsLayerTreeLayer *>( qgisNode );
797 QgsVectorLayer *vectorLayer = qobject_cast< QgsVectorLayer * >( layerTreeLayer->
layer() );
802 auto pdfLayerNode = std::make_unique< TreeNode >();
803 pdfLayerNode->id = layerTreeLayer->
layerId();
804 pdfLayerNode->name = layerTreeLayer->
name();
806 pdfLayerNode->mapLayerId = layerTreeLayer->
layerId();
807 layerIdToTreeNode.insert( pdfLayerNode->id, pdfLayerNode.get() );
808 pdfTreeNodes->addChild( std::move( pdfLayerNode ) );
814 QgsLayerTreeGroup *childLayerTreeGroup = qobject_cast<QgsLayerTreeGroup *>( qgisNode );
817 if ( QgsGroupLayer *groupLayer = childLayerTreeGroup->
groupLayer() )
820 auto pdfLayerNode = std::make_unique< TreeNode >();
821 pdfLayerNode->id = groupLayer->id();
822 pdfLayerNode->name = childLayerTreeGroup->
name();
824 pdfLayerNode->mapLayerId = groupLayer->id();
825 layerIdToTreeNode.insert( pdfLayerNode->id, pdfLayerNode.get() );
826 pdfTreeNodes->addChild( std::move( pdfLayerNode ) );
831 if ( !childLayerTreeGroup->
children().empty() )
833 std::unique_ptr< TreeNode > pdfGroupNode = createPdfTreeNodes( groupNameToTreeNode, layerIdToTreeNode, childLayerTreeGroup );
837 if ( !pdfGroupNode->children.empty() )
839 pdfTreeNodes->addChild( std::move( pdfGroupNode ) );
851 if ( !pdfTreeNodes->children.empty() )
853 groupNameToTreeNode[pdfTreeNodes->name] = pdfTreeNodes.get();
859QString QgsAbstractGeospatialPdfExporter::compositionModeToString( QPainter::CompositionMode mode )
863 case QPainter::CompositionMode_SourceOver:
866 case QPainter::CompositionMode_Multiply:
867 return u
"Multiply"_s;
869 case QPainter::CompositionMode_Screen:
872 case QPainter::CompositionMode_Overlay:
875 case QPainter::CompositionMode_Darken:
878 case QPainter::CompositionMode_Lighten:
881 case QPainter::CompositionMode_ColorDodge:
882 return u
"ColorDodge"_s;
884 case QPainter::CompositionMode_ColorBurn:
885 return u
"ColorBurn"_s;
887 case QPainter::CompositionMode_HardLight:
888 return u
"HardLight"_s;
890 case QPainter::CompositionMode_SoftLight:
891 return u
"SoftLight"_s;
893 case QPainter::CompositionMode_Difference:
894 return u
"Difference"_s;
896 case QPainter::CompositionMode_Exclusion:
897 return u
"Exclusion"_s;
903 QgsDebugError( u
"Unsupported PDF blend mode %1"_s.arg( mode ) );
@ PreferredGdal
Preferred format for conversion of CRS to WKT for use with the GDAL library.
@ NoSymbology
Export only data.
static bool compositionModeSupported(QPainter::CompositionMode mode)
Returns true if the specified composition mode is supported for layers during Geospatial PDF exports.
static QString geospatialPDFAvailabilityExplanation()
Returns a user-friendly, translated string explaining why Geospatial PDF export support is not availa...
static constexpr double DPI_72
Hardcode DPI of 72 to get correct page sizes in outputs.
bool finalize(const QList< QgsAbstractGeospatialPdfExporter::ComponentLayerDetail > &components, const QString &destinationFile, const ExportDetails &details)
To be called after the rendering operation is complete.
void pushRenderedFeature(const QString &layerId, const QgsAbstractGeospatialPdfExporter::RenderedFeature &feature, const QString &group=QString())
Called multiple times during the rendering operation, whenever a feature associated with the specifie...
static bool geospatialPDFCreationAvailable()
Returns true if the current QGIS build is capable of Geospatial PDF support.
QString generateTemporaryFilepath(const QString &filename) const
Returns a file path to use for temporary files required for Geospatial PDF creation.
Represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
Contains information about the context in which a coordinate transform is executed.
bool isEmpty() const override
Returns true if the geometry is empty.
void transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection d=Qgis::TransformDirection::Forward, bool transformZ=false) override
Transforms the geometry using a coordinate transform.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
@ RegeneratePrimaryKey
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
static QString stringToSafeFilename(const QString &string)
Converts a string to a safe filename, replacing characters which are not safe for filenames with an '...
Layer tree group node serves as a container for layers and further groups.
QString name() const override
Returns the group's name.
QgsGroupLayer * groupLayer()
Returns a reference to the associated group layer, if the layer tree group will be treated as group l...
QString layerId() const
Returns the ID for the map layer associated with this node.
QString name() const override
Returns the layer's name.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
@ NodeCustom
Leaf node pointing to a custom object.
@ NodeGroup
Container of other groups and layers.
@ NodeLayer
Leaf node pointing to a layer.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
bool itemVisibilityChecked() const
Returns whether a node is checked (independently of its ancestors or children).
Namespace with helper functions for layer tree operations.
QString asWkt(int precision=17) const override
Returns a WKT representation of the geometry.
Options to pass to QgsVectorFileWriter::writeAsVectorFormat().
QString driverName
OGR driver to use.
Qgis::FeatureSymbologyExport symbologyExport
Symbology to export.
A convenience class for writing vector layers to disk based formats (e.g.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
void sortTreeNodeList(std::vector< std::unique_ptr< TreeNode > > &nodeList, QStringList layerOrder)
Sort a tree node list according to layerOrder.
void CPL_STDCALL collectErrors(CPLErr, int, const char *msg)
QList< QgsFeature > QgsFeatureList
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
Contains details of a particular input component to be used during PDF composition.
Contains details of a control point used during georeferencing Geospatial PDF outputs.
bool includeFeatures
true if feature vector information (such as attributes) should be exported.
QgsRectangle pageBoundsMm
Bounds of the georeferenced section on the page, in millimeters.
QgsCoordinateReferenceSystem crs
Coordinate reference system for georeferenced section.
QList< QgsAbstractGeospatialPdfExporter::ControlPoint > controlPoints
List of control points corresponding to this georeferenced section.
QgsPolygon pageBoundsPolygon
Bounds of the georeferenced section on the page, in millimeters, as a free-form polygon.
Contains information about a feature rendered inside the PDF.
QgsGeometry renderedBounds
Bounds, in PDF units, of rendered feature.
QgsFeature feature
Rendered feature.
Contains information relating to a single PDF layer in the Geospatial PDF export.