Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[api] Add method for retrieving label results from QgsLayoutExporter
Allows plugins and scripts to collect statistics about the labeling
results from all map items included in a layout export
  • Loading branch information
nyalldawson committed Jun 9, 2021
1 parent f0a7cb5 commit 2a57287
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 3 deletions.
10 changes: 10 additions & 0 deletions python/core/auto_generated/layout/qgslayoutexporter.sip.in
Expand Up @@ -348,6 +348,16 @@ Returns the file name corresponding to the last error encountered during
an export.
%End

QMap< QString, QgsLabelingResults * > labelingResults();
%Docstring
Returns the labeling results for all map items included in the export. Map keys are the item UUIDs (see :py:func:`QgsLayoutItem.uuid()`).

Ownership of the results remains with the layout exporter.

.. versionadded:: 3.20
%End


bool georeferenceOutput( const QString &file, QgsLayoutItemMap *referenceMap = 0,
const QRectF &exportRegion = QRectF(), double dpi = -1 ) const;
%Docstring
Expand Down
37 changes: 36 additions & 1 deletion src/core/layout/qgslayoutexporter.cpp
Expand Up @@ -28,6 +28,7 @@
#include "qgslayoutgeopdfexporter.h"
#include "qgslinestring.h"
#include "qgsmessagelog.h"
#include "qgslabelingresults.h"
#include <QImageWriter>
#include <QSize>
#include <QSvgGenerator>
Expand Down Expand Up @@ -150,6 +151,11 @@ QgsLayoutExporter::QgsLayoutExporter( QgsLayout *layout )

}

QgsLayoutExporter::~QgsLayoutExporter()
{
qDeleteAll( mLabelingResults );
}

QgsLayout *QgsLayoutExporter::layout() const
{
return mLayout;
Expand Down Expand Up @@ -462,6 +468,7 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToImage( const QString
}

}
captureLabelingResults();
return Success;
}

Expand Down Expand Up @@ -699,6 +706,7 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToPdf( const QString &f
georeferenceOutputPrivate( filePath, nullptr, QRectF(), settings.dpi, shouldAppendGeoreference, settings.exportMetadata );
}
}
captureLabelingResults();
return result;
}

Expand Down Expand Up @@ -879,6 +887,7 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::print( QPrinter &printer, con
ExportResult result = printPrivate( printer, p, false, settings.dpi, settings.rasterizeWholeImage );
p.end();

captureLabelingResults();
return result;
}

Expand Down Expand Up @@ -1132,7 +1141,7 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToSvg( const QString &f
}
}
}

captureLabelingResults();
return Success;
}

Expand Down Expand Up @@ -1187,6 +1196,18 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToSvg( QgsAbstractLayou

}

QMap<QString, QgsLabelingResults *> QgsLayoutExporter::labelingResults()
{
return mLabelingResults;
}

QMap<QString, QgsLabelingResults *> QgsLayoutExporter::takeLabelingResults()
{
QMap<QString, QgsLabelingResults *> res;
std::swap( mLabelingResults, res );
return res;
}

void QgsLayoutExporter::preparePrintAsPdf( QgsLayout *layout, QPrinter &printer, const QString &filePath )
{
printer.setOutputFileName( filePath );
Expand Down Expand Up @@ -2014,6 +2035,20 @@ QString QgsLayoutExporter::generateFileName( const PageExportDetails &details )
}
}

void QgsLayoutExporter::captureLabelingResults()
{
qDeleteAll( mLabelingResults );
mLabelingResults.clear();

QList< QgsLayoutItemMap * > maps;
mLayout->layoutItems( maps );

for ( QgsLayoutItemMap *map : std::as_const( maps ) )
{
mLabelingResults[ map->uuid() ] = map->mExportLabelingResults.release();
}
}

bool QgsLayoutExporter::saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat, QgsProject *projectForMetadata )
{
QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() );
Expand Down
29 changes: 28 additions & 1 deletion src/core/layout/qgslayoutexporter.h
Expand Up @@ -34,6 +34,7 @@ class QPainter;
class QgsLayoutItemMap;
class QgsAbstractLayoutIterator;
class QgsFeedback;
class QgsLabelingResults;

/**
* \ingroup core
Expand Down Expand Up @@ -67,7 +68,7 @@ class CORE_EXPORT QgsLayoutExporter
*/
QgsLayoutExporter( QgsLayout *layout );

virtual ~QgsLayoutExporter() = default;
virtual ~QgsLayoutExporter();

/**
* Returns the layout linked to this exporter.
Expand Down Expand Up @@ -592,6 +593,29 @@ class CORE_EXPORT QgsLayoutExporter
*/
QString errorFile() const { return mErrorFileName; }

/**
* Returns the labeling results for all map items included in the export. Map keys are the item UUIDs (see QgsLayoutItem::uuid()).
*
* Ownership of the results remains with the layout exporter.
*
* \since QGIS 3.20
*/
QMap< QString, QgsLabelingResults * > labelingResults();

#ifndef SIP_RUN

/**
* Takes the labeling results for all map items included in the export. Map keys are the item UUIDs (see QgsLayoutItem::uuid()).
*
* Ownership of the results is transferred to the caller.
*
* \note Not available in Python bindings
*
* \since QGIS 3.20
*/
QMap< QString, QgsLabelingResults * > takeLabelingResults();
#endif

/**
* Georeferences a \a file (image of PDF) exported from the layout.
*
Expand Down Expand Up @@ -657,6 +681,9 @@ class CORE_EXPORT QgsLayoutExporter

QPointer< QgsLayout > mLayout;

void captureLabelingResults();
QMap< QString, QgsLabelingResults * > mLabelingResults;

mutable QString mErrorFileName;

QImage createImage( const ImageExportSettings &settings, int page, QRectF &bounds, bool &skipPage ) const;
Expand Down
65 changes: 64 additions & 1 deletion tests/src/python/test_qgslayoutexporter.py
Expand Up @@ -41,7 +41,12 @@
QgsPrintLayout,
QgsSingleSymbolRenderer,
QgsRenderContext,
QgsReport)
QgsReport,
QgsPalLayerSettings,
QgsFeature,
QgsGeometry,
QgsPointXY,
QgsVectorLayerSimpleLabeling)
from qgis.PyQt.QtCore import QSize, QSizeF, QDir, QRectF, Qt, QDateTime, QDate, QTime, QTimeZone
from qgis.PyQt.QtGui import QImage, QPainter
from qgis.PyQt.QtPrintSupport import QPrinter
Expand Down Expand Up @@ -1154,6 +1159,64 @@ def testContainsAdvancedEffects(self):
label.setVisibility(False)
self.assertFalse(QgsLayoutExporter.containsAdvancedEffects(l))

def testLabelingResults(self):
"""
Test QgsLayoutExporter.labelingResults()
"""
settings = QgsPalLayerSettings()
settings.fieldName = "\"id\""
settings.isExpression = True
settings.placement = QgsPalLayerSettings.OverPoint
settings.priority = 10
settings.displayAll = True

vl = QgsVectorLayer("Point?crs=epsg:4326&field=id:integer", "vl", "memory")
f = QgsFeature()
f.setAttributes([1])
f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(-6.250851540391068, 53.335006994584944)))
self.assertTrue(vl.dataProvider().addFeature(f))
f.setAttributes([8888])
f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(-21.950014487179544, 64.150023619739216)))
self.assertTrue(vl.dataProvider().addFeature(f))
f.setAttributes([33333])
f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(-0.118667702475932, 51.5019405883275)))
self.assertTrue(vl.dataProvider().addFeature(f))
vl.updateExtents()

p = QgsProject()
p.addMapLayer(vl)

vl.setLabeling(QgsVectorLayerSimpleLabeling(settings))
vl.setLabelsEnabled(True)

l = QgsLayout(p)
l.initializeDefaults()

map = QgsLayoutItemMap(l)
map.attemptSetSceneRect(QRectF(20, 20, 200, 100))
map.setFrameEnabled(False)
map.setBackgroundEnabled(False)
map.setCrs(vl.crs())
map.zoomToExtent(vl.extent())
map.setLayers([vl])
l.addLayoutItem(map)

exporter = QgsLayoutExporter(l)
self.assertEqual(exporter.labelingResults(), {})
# setup settings
settings = QgsLayoutExporter.ImageExportSettings()
settings.dpi = 80

rendered_file_path = os.path.join(self.basetestpath, 'test_exportlabelresults.png')
self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success)

results = exporter.labelingResults()
self.assertEqual(len(results), 1)

labels = results[map.uuid()].allLabels()
self.assertEqual(len(labels), 3)
self.assertCountEqual([l.labelText for l in labels], ['1', '33333', '8888'])


if __name__ == '__main__':
unittest.main()

0 comments on commit 2a57287

Please sign in to comment.