27 #include "cpl_string.h" 30 #include <QMutexLocker> 31 #include <QDomDocument> 32 #include <QDomElement> 37 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(3,0,0) 42 GDALDriverH hDriverMem = GDALGetDriverByName(
"PDF" );
48 const char *pHavePoppler = GDALGetMetadataItem( hDriverMem,
"HAVE_POPPLER",
nullptr );
49 if ( pHavePoppler && strstr( pHavePoppler,
"YES" ) )
52 const char *pHavePdfium = GDALGetMetadataItem( hDriverMem,
"HAVE_PDFIUM",
nullptr );
53 if ( pHavePdfium && strstr( pHavePdfium,
"YES" ) )
62 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(3,0,0) 63 return QObject::tr(
"GeoPDF creation requires GDAL version 3.0 or later." );
66 GDALDriverH hDriverMem = GDALGetDriverByName(
"PDF" );
69 return QObject::tr(
"No GDAL PDF driver available." );
72 const char *pHavePoppler = GDALGetMetadataItem( hDriverMem,
"HAVE_POPPLER",
nullptr );
73 if ( pHavePoppler && strstr( pHavePoppler,
"YES" ) )
76 const char *pHavePdfium = GDALGetMetadataItem( hDriverMem,
"HAVE_PDFIUM",
nullptr );
77 if ( pHavePdfium && strstr( pHavePdfium,
"YES" ) )
80 return QObject::tr(
"GDAL PDF driver was not built with PDF read support. A build with PDF read support is required for GeoPDF creation." );
89 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(3,0,0) 90 Q_UNUSED( components )
91 Q_UNUSED( destinationFile )
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 GeoPDF composition file" );
120 char **papszOptions = CSLSetNameValue(
nullptr,
"COMPOSITION_FILE", xmlFilePath.toUtf8().constData() );
123 gdal::dataset_unique_ptr outputDataset( GDALCreate( driver, destinationFile.toUtf8().constData(), 0, 0, 0, GDT_Unknown, papszOptions ) );
124 bool res = outputDataset.get();
125 outputDataset.reset();
127 CSLDestroy( papszOptions );
135 return mTemporaryDir.filePath( filename );
142 QMutexLocker locker( &mMutex );
147 mCollatedFeatures[ group ][ layerId ].append( f );
150 bool QgsAbstractGeoPdfExporter::saveTemporaryLayers()
152 for (
auto groupIt = mCollatedFeatures.constBegin(); groupIt != mCollatedFeatures.constEnd(); ++groupIt )
154 for (
auto it = groupIt->constBegin(); it != groupIt->constEnd(); ++it )
160 detail.group = groupIt.key();
165 QgsVectorFileWriter writer( filePath, QString(), features.first().fields(), features.first().geometry().wkbType(),
QgsCoordinateReferenceSystem(), QStringLiteral(
"GPKG" ), QStringList(), QStringList(),
nullptr,
QgsVectorFileWriter::NoSymbology,
QgsFeatureSink::RegeneratePrimaryKey, &layerName );
166 if ( writer.hasError() )
168 mErrorMessage = writer.errorMessage();
177 mErrorMessage = writer.errorMessage();
182 detail.sourceVectorLayer = layerName;
183 mVectorComponents << detail;
189 QString QgsAbstractGeoPdfExporter::createCompositionXml(
const QList<ComponentLayerDetail> &components,
const ExportDetails &details )
193 QDomElement compositionElem = doc.createElement( QStringLiteral(
"PDFComposition" ) );
196 QDomElement metadata = doc.createElement( QStringLiteral(
"Metadata" ) );
197 if ( !details.
author.isEmpty() )
199 QDomElement author = doc.createElement( QStringLiteral(
"Author" ) );
200 author.appendChild( doc.createTextNode( details.
author ) );
201 metadata.appendChild( author );
205 QDomElement producer = doc.createElement( QStringLiteral(
"Producer" ) );
206 producer.appendChild( doc.createTextNode( details.
producer ) );
207 metadata.appendChild( producer );
209 if ( !details.
creator.isEmpty() )
211 QDomElement creator = doc.createElement( QStringLiteral(
"Creator" ) );
212 creator.appendChild( doc.createTextNode( details.
creator ) );
213 metadata.appendChild( creator );
217 QDomElement creationDate = doc.createElement( QStringLiteral(
"CreationDate" ) );
218 QString creationDateString = QStringLiteral(
"D:%1" ).arg( details.
creationDateTime.toString( QStringLiteral(
"yyyyMMddHHmmss" ) ) );
222 creationDateString += ( offsetFromUtc >= 0 ) ?
'+' :
'-';
223 offsetFromUtc = std::abs( offsetFromUtc );
224 int offsetHours = offsetFromUtc / 3600;
225 int offsetMins = ( offsetFromUtc % 3600 ) / 60;
226 creationDateString += QStringLiteral(
"%1'%2'" ).arg( offsetHours ).arg( offsetMins );
228 creationDate.appendChild( doc.createTextNode( creationDateString ) );
229 metadata.appendChild( creationDate );
231 if ( !details.
subject.isEmpty() )
233 QDomElement subject = doc.createElement( QStringLiteral(
"Subject" ) );
234 subject.appendChild( doc.createTextNode( details.
subject ) );
235 metadata.appendChild( subject );
237 if ( !details.
title.isEmpty() )
239 QDomElement title = doc.createElement( QStringLiteral(
"Title" ) );
240 title.appendChild( doc.createTextNode( details.
title ) );
241 metadata.appendChild( title );
245 QStringList allKeywords;
246 for (
auto it = details.
keywords.constBegin(); it != details.
keywords.constEnd(); ++it )
248 allKeywords.append( QStringLiteral(
"%1: %2" ).arg( it.key(), it.value().join(
',' ) ) );
250 QDomElement keywords = doc.createElement( QStringLiteral(
"Keywords" ) );
251 keywords.appendChild( doc.createTextNode( allKeywords.join(
';' ) ) );
252 metadata.appendChild( keywords );
254 compositionElem.appendChild( metadata );
257 QDomElement
layerTree = doc.createElement( QStringLiteral(
"LayerTree" ) );
259 QMap< QString, QSet< QString > > createdLayerIds;
260 QMap< QString, QDomElement > groupLayerMap;
261 QMap< QString, QString > customGroupNamesToIds;
269 QDomElement layer = doc.createElement( QStringLiteral(
"Layer" ) );
270 layer.setAttribute( QStringLiteral(
"id" ), component.group.isEmpty() ? component.mapLayerId : QStringLiteral(
"%1_%2" ).arg( component.group, component.mapLayerId ) );
271 layer.setAttribute( QStringLiteral(
"name" ), component.name );
272 layer.setAttribute( QStringLiteral(
"initiallyVisible" ), QStringLiteral(
"true" ) );
274 if ( !component.group.isEmpty() )
276 if ( groupLayerMap.contains( component.group ) )
278 groupLayerMap[ component.group ].appendChild( layer );
282 QDomElement group = doc.createElement( QStringLiteral(
"Layer" ) );
283 group.setAttribute( QStringLiteral(
"id" ), QStringLiteral(
"group_%1" ).arg( component.group ) );
284 group.setAttribute( QStringLiteral(
"name" ), component.group );
285 group.setAttribute( QStringLiteral(
"initiallyVisible" ), groupLayerMap.empty() ? QStringLiteral(
"true" ) : QStringLiteral(
"false" ) );
286 group.setAttribute( QStringLiteral(
"mutuallyExclusiveGroupId" ), QStringLiteral(
"__mutually_exclusive_groups__" ) );
287 layerTree.appendChild( group );
288 group.appendChild( layer );
289 groupLayerMap[ component.group ] = group;
294 layerTree.appendChild( layer );
297 createdLayerIds[ component.group ].insert( component.mapLayerId );
303 if ( component.mapLayerId.isEmpty() || createdLayerIds.value( component.group ).contains( component.mapLayerId ) )
309 QDomElement layer = doc.createElement( QStringLiteral(
"Layer" ) );
310 layer.setAttribute( QStringLiteral(
"id" ), component.group.isEmpty() ? component.mapLayerId : QStringLiteral(
"%1_%2" ).arg( component.group, component.mapLayerId ) );
311 layer.setAttribute( QStringLiteral(
"name" ), component.name );
312 layer.setAttribute( QStringLiteral(
"initiallyVisible" ), QStringLiteral(
"true" ) );
314 if ( !component.group.isEmpty() )
316 if ( groupLayerMap.contains( component.group ) )
318 groupLayerMap[ component.group ].appendChild( layer );
322 QDomElement group = doc.createElement( QStringLiteral(
"Layer" ) );
323 group.setAttribute( QStringLiteral(
"id" ), QStringLiteral(
"group_%1" ).arg( component.group ) );
324 group.setAttribute( QStringLiteral(
"name" ), component.group );
325 group.setAttribute( QStringLiteral(
"initiallyVisible" ), groupLayerMap.empty() ? QStringLiteral(
"true" ) : QStringLiteral(
"false" ) );
326 group.setAttribute( QStringLiteral(
"mutuallyExclusiveGroupId" ), QStringLiteral(
"__mutually_exclusive_groups__" ) );
327 layerTree.appendChild( group );
328 group.appendChild( layer );
329 groupLayerMap[ component.group ] = group;
334 layerTree.appendChild( layer );
337 createdLayerIds[ component.group ].insert( component.mapLayerId );
343 if ( customGroupNamesToIds.contains( it.value() ) )
346 QDomElement layer = doc.createElement( QStringLiteral(
"Layer" ) );
347 const QString
id = QUuid::createUuid().toString();
348 customGroupNamesToIds[ it.value() ] = id;
349 layer.setAttribute( QStringLiteral(
"id" ),
id );
350 layer.setAttribute( QStringLiteral(
"name" ), it.value() );
351 layer.setAttribute( QStringLiteral(
"initiallyVisible" ), QStringLiteral(
"true" ) );
352 layerTree.appendChild( layer );
355 compositionElem.appendChild( layerTree );
358 QDomElement page = doc.createElement( QStringLiteral(
"Page" ) );
359 QDomElement dpi = doc.createElement( QStringLiteral(
"DPI" ) );
360 dpi.appendChild( doc.createTextNode( QString::number( details.
dpi ) ) );
361 page.appendChild( dpi );
363 QDomElement width = doc.createElement( QStringLiteral(
"Width" ) );
364 const double pageWidthPdfUnits = std::ceil( details.
pageSizeMm.width() / 25.4 * 72 );
365 width.appendChild( doc.createTextNode( QString::number( pageWidthPdfUnits ) ) );
366 page.appendChild( width );
367 QDomElement height = doc.createElement( QStringLiteral(
"Height" ) );
368 const double pageHeightPdfUnits = std::ceil( details.
pageSizeMm.height() / 25.4 * 72 );
369 height.appendChild( doc.createTextNode( QString::number( pageHeightPdfUnits ) ) );
370 page.appendChild( height );
377 QDomElement georeferencing = doc.createElement( QStringLiteral(
"Georeferencing" ) );
378 georeferencing.setAttribute( QStringLiteral(
"id" ), QStringLiteral(
"georeferenced_%1" ).arg( i++ ) );
384 QDomElement srs = doc.createElement( QStringLiteral(
"SRS" ) );
387 if ( !section.
crs.
authid().startsWith( QStringLiteral(
"user" ), Qt::CaseInsensitive ) )
389 srs.appendChild( doc.createTextNode( section.
crs.
authid() ) );
395 georeferencing.appendChild( srs );
407 QDomElement boundingPolygon = doc.createElement( QStringLiteral(
"BoundingPolygon" ) );
410 QTransform t = QTransform::fromTranslate( 0, pageHeightPdfUnits ).scale( pageWidthPdfUnits / details.
pageSizeMm.width(),
411 -pageHeightPdfUnits / details.
pageSizeMm.height() );
415 boundingPolygon.appendChild( doc.createTextNode( p.
asWkt() ) );
417 georeferencing.appendChild( boundingPolygon );
426 QDomElement boundingBox = doc.createElement( QStringLiteral(
"BoundingBox" ) );
427 boundingBox.setAttribute( QStringLiteral(
"x1" ), QString::number( section.
pageBoundsMm.
xMinimum() / 25.4 * 72 ) );
428 boundingBox.setAttribute( QStringLiteral(
"y1" ), QString::number( section.
pageBoundsMm.
yMinimum() / 25.4 * 72 ) );
429 boundingBox.setAttribute( QStringLiteral(
"x2" ), QString::number( section.
pageBoundsMm.
xMaximum() / 25.4 * 72 ) );
430 boundingBox.setAttribute( QStringLiteral(
"y2" ), QString::number( section.
pageBoundsMm.
yMaximum() / 25.4 * 72 ) );
431 georeferencing.appendChild( boundingBox );
436 QDomElement cp1 = doc.createElement( QStringLiteral(
"ControlPoint" ) );
437 cp1.setAttribute( QStringLiteral(
"x" ), QString::number( point.
pagePoint.
x() / 25.4 * 72 ) );
438 cp1.setAttribute( QStringLiteral(
"y" ), QString::number( ( details.
pageSizeMm.height() - point.
pagePoint.
y() ) / 25.4 * 72 ) );
439 cp1.setAttribute( QStringLiteral(
"GeoX" ), QString::number( point.
geoPoint.
x() ) );
440 cp1.setAttribute( QStringLiteral(
"GeoY" ), QString::number( point.
geoPoint.
y() ) );
441 georeferencing.appendChild( cp1 );
444 page.appendChild( georeferencing );
448 QDomElement content = doc.createElement( QStringLiteral(
"Content" ) );
451 if ( component.mapLayerId.isEmpty() )
453 QDomElement pdfDataset = doc.createElement( QStringLiteral(
"PDF" ) );
454 pdfDataset.setAttribute( QStringLiteral(
"dataset" ), component.sourcePdfPath );
455 content.appendChild( pdfDataset );
457 else if ( !component.group.isEmpty() )
460 QDomElement ifGroupOn = doc.createElement( QStringLiteral(
"IfLayerOn" ) );
461 ifGroupOn.setAttribute( QStringLiteral(
"layerId" ), QStringLiteral(
"group_%1" ).arg( component.group ) );
462 QDomElement ifLayerOn = doc.createElement( QStringLiteral(
"IfLayerOn" ) );
464 ifLayerOn.setAttribute( QStringLiteral(
"layerId" ), customGroupNamesToIds.value( details.
customLayerTreeGroups.value( component.mapLayerId ) ) );
465 else if ( component.group.isEmpty() )
466 ifLayerOn.setAttribute( QStringLiteral(
"layerId" ), component.mapLayerId );
468 ifLayerOn.setAttribute( QStringLiteral(
"layerId" ), QStringLiteral(
"%1_%2" ).arg( component.group, component.mapLayerId ) );
469 QDomElement pdfDataset = doc.createElement( QStringLiteral(
"PDF" ) );
470 pdfDataset.setAttribute( QStringLiteral(
"dataset" ), component.sourcePdfPath );
471 ifLayerOn.appendChild( pdfDataset );
472 ifGroupOn.appendChild( ifLayerOn );
473 content.appendChild( ifGroupOn );
477 QDomElement ifLayerOn = doc.createElement( QStringLiteral(
"IfLayerOn" ) );
479 ifLayerOn.setAttribute( QStringLiteral(
"layerId" ), customGroupNamesToIds.value( details.
customLayerTreeGroups.value( component.mapLayerId ) ) );
480 else if ( component.group.isEmpty() )
481 ifLayerOn.setAttribute( QStringLiteral(
"layerId" ), component.mapLayerId );
483 ifLayerOn.setAttribute( QStringLiteral(
"layerId" ), QStringLiteral(
"%1_%2" ).arg( component.group, component.mapLayerId ) );
484 QDomElement pdfDataset = doc.createElement( QStringLiteral(
"PDF" ) );
485 pdfDataset.setAttribute( QStringLiteral(
"dataset" ), component.sourcePdfPath );
486 ifLayerOn.appendChild( pdfDataset );
487 content.appendChild( ifLayerOn );
496 QDomElement ifLayerOn = doc.createElement( QStringLiteral(
"IfLayerOn" ) );
498 ifLayerOn.setAttribute( QStringLiteral(
"layerId" ), customGroupNamesToIds.value( details.
customLayerTreeGroups.value( component.mapLayerId ) ) );
499 else if ( component.group.isEmpty() )
500 ifLayerOn.setAttribute( QStringLiteral(
"layerId" ), component.mapLayerId );
502 ifLayerOn.setAttribute( QStringLiteral(
"layerId" ), QStringLiteral(
"%1_%2" ).arg( component.group, component.mapLayerId ) );
503 QDomElement vectorDataset = doc.createElement( QStringLiteral(
"Vector" ) );
504 vectorDataset.setAttribute( QStringLiteral(
"dataset" ), component.sourceVectorPath );
505 vectorDataset.setAttribute( QStringLiteral(
"layer" ), component.sourceVectorLayer );
506 vectorDataset.setAttribute( QStringLiteral(
"visible" ), QStringLiteral(
"false" ) );
507 QDomElement logicalStructure = doc.createElement( QStringLiteral(
"LogicalStructure" ) );
508 logicalStructure.setAttribute( QStringLiteral(
"displayLayerName" ), component.name );
509 if ( !component.displayAttribute.isEmpty() )
510 logicalStructure.setAttribute( QStringLiteral(
"fieldToDisplay" ), component.displayAttribute );
511 vectorDataset.appendChild( logicalStructure );
512 ifLayerOn.appendChild( vectorDataset );
513 content.appendChild( ifLayerOn );
517 page.appendChild( content );
518 compositionElem.appendChild( page );
520 doc.appendChild( compositionElem );
523 QTextStream stream( &composition );
524 doc.save( stream, -1 );
QList< QgsAbstractGeoPdfExporter::GeoReferencedSection > georeferencedSections
List of georeferenced sections.
bool includeFeatures
true if feature vector information (such as attributes) should be exported.
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
QString author
Metadata author tag.
void pushRenderedFeature(const QString &layerId, const QgsAbstractGeoPdfExporter::RenderedFeature &feature, const QString &group=QString())
Called multiple times during the rendering operation, whenever a feature associated with the specifie...
QString asWkt(int precision=17) const override
Returns a WKT representation of the geometry.
QList< QgsFeature > QgsFeatureList
QgsGeometry renderedBounds
Bounds, in PDF units, of rendered feature.
QgsLayerTree * layerTree(const QgsWmsRenderContext &context)
QgsAbstractMetadataBase::KeywordMap keywords
Metadata keyword map.
QSizeF pageSizeMm
Page size, in millimeters.
QMap< QString, QString > customLayerTreeGroups
Optional map of map layer ID to custom logical layer tree group in created PDF file.
A convenience class for writing vector files to disk.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
QDateTime creationDateTime
Metadata creation datetime.
void transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d=QgsCoordinateTransform::ForwardTransform, bool transformZ=false) override SIP_THROW(QgsCsException)
Transforms the geometry using a coordinate transform.
Contains details of a control point used during georeferencing GeoPDF outputs.
bool useOgcBestPracticeFormatGeoreferencing
true if OGC "best practice" format georeferencing should be used.
QString creator
Metadata creator tag.
QgsFeature feature
Rendered feature.
QList< QgsAbstractGeoPdfExporter::ControlPoint > controlPoints
List of control points corresponding to this georeferenced section.
QgsCoordinateReferenceSystem crs
Coordinate reference system for georeferenced section.
Contains information about a feature rendered inside the PDF.
QString sourceVectorPath
File path to the (already created) vector dataset to use as the source for this component layer...
QString subject
Metadata subject tag.
Contains information relating to a single PDF layer in the GeoPDF export.
QgsRectangle pageBoundsMm
Bounds of the georeferenced section on the page, in millimeters.
Full WKT2 string, conforming to ISO 19162:2018 / OGC 18-010, with all possible nodes and new keyword ...
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Contains details of a particular input component to be used during PDF composition.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
QString generateTemporaryFilepath(const QString &filename) const
Returns a file path to use for temporary files required for GeoPDF creation.
bool finalize(const QList< QgsAbstractGeoPdfExporter::ComponentLayerDetail > &components, const QString &destinationFile, const ExportDetails &details)
To be called after the rendering operation is complete.
bool useIso32000ExtensionFormatGeoreferencing
true if ISO3200 extension format georeferencing should be used.
QgsPolygon pageBoundsPolygon
Bounds of the georeferenced section on the page, in millimeters, as a free-form polygon.
QString title
Metadata title tag.
This class represents a coordinate reference system (CRS).
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
QString producer
Metadata producer tag.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
QgsPointXY pagePoint
Coordinate on the page of the control point, in millimeters.
static QString geoPDFAvailabilityExplanation()
Returns a user-friendly, translated string explaining why GeoPDF export support is not available on t...
QgsPointXY geoPoint
Georeferenced coordinate of the control point, in CRS units.
bool isEmpty() const override
Returns true if the geometry is empty.
QString authid() const
Returns the authority identifier for the CRS.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
static bool geoPDFCreationAvailable()
Returns true if the current QGIS build is capable of GeoPDF support.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.