Skip to content

Commit b95296a

Browse files
author
Hugo Mercier
committed
Add support for Atlas export to SVG and multipage PDF
1 parent f076c2e commit b95296a

11 files changed

+308
-126
lines changed

src/app/composer/qgscomposer.cpp

Lines changed: 212 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -565,19 +565,24 @@ void QgsComposer::on_mActionExportAsPDF_triggered()
565565
showWMSPrintingWarning();
566566
}
567567

568-
if ( mComposition->atlasMap() == 0 )
569-
{
570-
// if no Atlas map is defined
568+
bool hasAnAtlas = mComposition->atlasMap() != 0;
569+
bool atlasOnASingleFile = hasAnAtlas && mComposition->atlasMap()->atlasSingleFile();
570+
QgsComposerMap* atlasMap = mComposition->atlasMap();
571+
572+
QString outputFileName;
573+
QString outputDir;
571574

575+
if ( !hasAnAtlas || atlasOnASingleFile )
576+
{
572577
QSettings myQSettings; // where we keep last used filter in persistent state
573578
QString lastUsedFile = myQSettings.value( "/UI/lastSaveAsPdfFile", "qgis.pdf" ).toString();
574579
QFileInfo file( lastUsedFile );
575580

576-
QString outputFileName = QFileDialog::getSaveFileName(
577-
this,
578-
tr( "Choose a file name to save the map as" ),
579-
file.path(),
580-
tr( "PDF Format" ) + " (*.pdf *.PDF)" );
581+
outputFileName = QFileDialog::getSaveFileName(
582+
this,
583+
tr( "Choose a file name to save the map as" ),
584+
file.path(),
585+
tr( "PDF Format" ) + " (*.pdf *.PDF)" );
581586
if ( outputFileName.isEmpty() )
582587
{
583588
return;
@@ -589,17 +594,10 @@ void QgsComposer::on_mActionExportAsPDF_triggered()
589594
}
590595

591596
myQSettings.setValue( "/UI/lastSaveAsPdfFile", outputFileName );
592-
593-
QApplication::setOverrideCursor( Qt::BusyCursor );
594-
mView->setPaintingEnabled( false );
595-
mComposition->exportAsPDF( outputFileName );
596-
mView->setPaintingEnabled( true );
597-
QApplication::restoreOverrideCursor();
598597
}
599-
// else, it is an atlas
598+
// else, we need to choose a directory
600599
else
601600
{
602-
QgsComposerMap* atlasMap = mComposition->atlasMap();
603601
if ( atlasMap->atlasFilenamePattern().size() == 0 )
604602
{
605603
int res = QMessageBox::warning( 0, tr( "Empty filename pattern" ),
@@ -615,16 +613,16 @@ void QgsComposer::on_mActionExportAsPDF_triggered()
615613

616614
QSettings myQSettings;
617615
QString lastUsedDir = myQSettings.value( "/UI/lastSaveAtlasAsPdfDir", "." ).toString();
618-
QString dir = QFileDialog::getExistingDirectory(this,
619-
tr("Directory where to save PDF files"),
620-
lastUsedDir,
621-
QFileDialog::ShowDirsOnly);
622-
if ( dir.isEmpty() )
616+
outputDir = QFileDialog::getExistingDirectory(this,
617+
tr("Directory where to save PDF files"),
618+
lastUsedDir,
619+
QFileDialog::ShowDirsOnly);
620+
if ( outputDir.isEmpty() )
623621
{
624622
return;
625623
}
626624
// test directory (if it exists and is writeable)
627-
if ( !QDir(dir).exists() || !QFileInfo(dir).isWritable() )
625+
if ( !QDir(outputDir).exists() || !QFileInfo(outputDir).isWritable() )
628626
{
629627
QMessageBox::warning( 0, tr( "Unable to write into the directory" ),
630628
tr( "The given output directory is not writeable. Cancelling." ),
@@ -633,29 +631,44 @@ void QgsComposer::on_mActionExportAsPDF_triggered()
633631
return;
634632
}
635633

636-
myQSettings.setValue( "/UI/lastSaveAtlasAsPdfDir", dir );
634+
myQSettings.setValue( "/UI/lastSaveAtlasAsPdfDir", outputDir );
635+
}
637636

638-
QgsAtlasRendering atlasRender( mComposition );
637+
size_t featureI = 0;
638+
QgsAtlasRendering atlasRender( mComposition );
639+
QPrinter printer;
640+
if ( hasAnAtlas )
641+
{
639642
atlasRender.begin( atlasMap->atlasFilenamePattern() );
643+
if ( atlasOnASingleFile )
644+
{
645+
mComposition->beginPrintAsPDF( printer, outputFileName );
646+
}
647+
}
648+
QPainter painter( &printer );
640649

641-
QProgressDialog progress( tr("Rendering maps..."), tr("Abort"), 0, atlasRender.numFeatures(), this );
642-
QApplication::setOverrideCursor( Qt::BusyCursor );
643-
mView->setPaintingEnabled( false );
650+
QProgressDialog progress( tr("Rendering maps..."), tr("Abort"), 0, atlasRender.numFeatures(), this );
651+
QApplication::setOverrideCursor( Qt::BusyCursor );
652+
mView->setPaintingEnabled( false );
644653

645-
for ( size_t i = 0; i < atlasRender.numFeatures(); ++i )
654+
do
655+
{
656+
if ( hasAnAtlas )
646657
{
647-
progress.setValue( i );
648-
// process input events in order to allow cancelling
658+
if ( 0 == atlasRender.numFeatures() )
659+
break;
660+
661+
progress.setValue( featureI );
662+
// process input events in order to allow aborting
649663
QCoreApplication::processEvents();
650-
651664
if ( progress.wasCanceled() )
652665
{
653666
atlasRender.end();
654667
break;
655668
}
656669
try
657670
{
658-
atlasRender.prepareForFeature( i );
671+
atlasRender.prepareForFeature( featureI );
659672
}
660673
catch ( std::runtime_error& e )
661674
{
@@ -665,13 +678,36 @@ void QgsComposer::on_mActionExportAsPDF_triggered()
665678
QMessageBox::Ok);
666679
break;
667680
}
668-
mComposition->exportAsPDF( QDir(dir).filePath(atlasRender.currentFilename()) + ".pdf" );
681+
if ( !atlasOnASingleFile )
682+
{
683+
outputFileName = QDir(outputDir).filePath( atlasRender.currentFilename() ) + ".pdf";
684+
}
669685
}
670-
atlasRender.end();
671686

672-
mView->setPaintingEnabled( true );
673-
QApplication::restoreOverrideCursor();
687+
if ( !atlasOnASingleFile )
688+
{
689+
mComposition->exportAsPDF( outputFileName );
690+
}
691+
else
692+
{
693+
if ( featureI > 0 )
694+
{
695+
printer.newPage();
696+
}
697+
mComposition->doPrint( printer, painter );
698+
}
699+
700+
featureI++;
701+
} while ( hasAnAtlas && featureI < atlasRender.numFeatures() );
702+
703+
if ( hasAnAtlas )
704+
{
705+
atlasRender.end();
674706
}
707+
painter.end();
708+
709+
mView->setPaintingEnabled( true );
710+
QApplication::restoreOverrideCursor();
675711
}
676712

677713
void QgsComposer::on_mActionPrint_triggered()
@@ -827,21 +863,31 @@ void QgsComposer::on_mActionExportAsImage_triggered()
827863

828864
QSettings myQSettings;
829865
QString lastUsedDir = myQSettings.value( "/UI/lastSaveAtlasAsImagesDir", "." ).toString();
866+
QString lastUsedFormat = myQSettings.value( "/UI/lastSaveAtlasAsImagesFormat", "jpg" ).toString();
830867

831868
QFileDialog dlg( this, tr("Directory where to save image files") );
832869
dlg.setFileMode( QFileDialog::Directory );
833870
dlg.setOption( QFileDialog::ShowDirsOnly, true );
834-
// dlg.setOption( QFileDialog::DontUseNativeDialog, true );
835871

872+
//
873+
// Build an augmented FialeDialog with a combo box to select the output format
836874
QComboBox *box = new QComboBox();
837875
QHBoxLayout* hlayout = new QHBoxLayout();
838876
QWidget* widget = new QWidget();
839877

840878
QList<QByteArray> formats = QImageWriter::supportedImageFormats();
879+
int selectedFormat = 0;
841880
for ( int i = 0; i < formats.size(); ++i )
842881
{
843-
box->addItem( QString( formats.at(i) ));
882+
QString format = QString( formats.at(i) );
883+
if ( format == lastUsedFormat )
884+
{
885+
selectedFormat = i;
886+
}
887+
box->addItem( format );
844888
}
889+
box->setCurrentIndex( selectedFormat );
890+
845891
hlayout->setMargin( 0 );
846892
hlayout->addWidget( new QLabel( tr("Image format: ")) );
847893
hlayout->addWidget( box );
@@ -969,56 +1015,149 @@ void QgsComposer::on_mActionExportAsSVG_triggered()
9691015
m->exec();
9701016
}
9711017

972-
QString lastUsedFile = settings.value( "/UI/lastSaveAsSvgFile", "qgis.svg" ).toString();
973-
QFileInfo file( lastUsedFile );
1018+
bool hasAnAtlas = mComposition->atlasMap() != 0;
9741019

975-
QString outputFileName = QFileDialog::getSaveFileName(
976-
this,
977-
tr( "Choose a file name to save the map as" ),
978-
file.path(),
979-
tr( "SVG Format" ) + " (*.svg *.SVG)" );
980-
if ( outputFileName.isEmpty() )
981-
return;
1020+
QString outputFileName;
1021+
QString outputDir;
9821022

983-
if ( !outputFileName.endsWith( ".svg", Qt::CaseInsensitive ) )
1023+
if ( !hasAnAtlas )
9841024
{
985-
outputFileName += ".svg";
1025+
QString lastUsedFile = settings.value( "/UI/lastSaveAsSvgFile", "qgis.svg" ).toString();
1026+
QFileInfo file( lastUsedFile );
1027+
1028+
outputFileName = QFileDialog::getSaveFileName(
1029+
this,
1030+
tr( "Choose a file name to save the map as" ),
1031+
file.path(),
1032+
tr( "SVG Format" ) + " (*.svg *.SVG)" );
1033+
if ( outputFileName.isEmpty() )
1034+
return;
1035+
1036+
if ( !outputFileName.endsWith( ".svg", Qt::CaseInsensitive ) )
1037+
{
1038+
outputFileName += ".svg";
1039+
}
1040+
1041+
settings.setValue( "/UI/lastSaveAsSvgFile", outputFileName );
9861042
}
1043+
else
1044+
{
1045+
// If we have an Atlas
1046+
QgsComposerMap* atlasMap = mComposition->atlasMap();
1047+
if ( atlasMap->atlasFilenamePattern().size() == 0 )
1048+
{
1049+
int res = QMessageBox::warning( 0, tr( "Empty filename pattern" ),
1050+
tr( "The filename pattern is empty. A default one will be used." ),
1051+
QMessageBox::Ok | QMessageBox::Cancel,
1052+
QMessageBox::Ok );
1053+
if ( res == QMessageBox::Cancel )
1054+
{
1055+
return;
1056+
}
1057+
atlasMap->setAtlasFilenamePattern( "'output_'||$id||'_'||$page" );
1058+
}
9871059

988-
settings.setValue( "/UI/lastSaveAsSvgFile", outputFileName );
1060+
QSettings myQSettings;
1061+
QString lastUsedDir = myQSettings.value( "/UI/lastSaveAtlasAsSvgDir", "." ).toString();
9891062

990-
mView->setPaintingEnabled( false );
991-
for ( int i = 0; i < mComposition->numPages(); ++i )
992-
{
993-
QSvgGenerator generator;
994-
#if QT_VERSION >= 0x040500
995-
generator.setTitle( QgsProject::instance()->title() );
996-
#endif
997-
if ( i == 0 )
1063+
outputDir = QFileDialog::getExistingDirectory(this,
1064+
tr("Directory where to save SVG files"),
1065+
lastUsedDir,
1066+
QFileDialog::ShowDirsOnly);
1067+
if ( outputDir.isEmpty() )
9981068
{
999-
generator.setFileName( outputFileName );
1069+
return;
10001070
}
1001-
else
1071+
// test directory (if it exists and is writeable)
1072+
if ( !QDir(outputDir).exists() || !QFileInfo(outputDir).isWritable() )
10021073
{
1003-
QFileInfo fi( outputFileName );
1004-
generator.setFileName( fi.absolutePath() + "/" + fi.baseName() + "_" + QString::number( i + 1 ) + "." + fi.suffix() );
1074+
QMessageBox::warning( 0, tr( "Unable to write into the directory" ),
1075+
tr( "The given output directory is not writeable. Cancelling." ),
1076+
QMessageBox::Ok,
1077+
QMessageBox::Ok );
1078+
return;
10051079
}
1080+
1081+
myQSettings.setValue( "/UI/lastSaveAtlasAsSvgDir", outputDir );
1082+
}
1083+
1084+
mView->setPaintingEnabled( false );
10061085

1007-
//width in pixel
1008-
int width = ( int )( mComposition->paperWidth() * mComposition->printResolution() / 25.4 );
1009-
//height in pixel
1010-
int height = ( int )( mComposition->paperHeight() * mComposition->printResolution() / 25.4 );
1011-
generator.setSize( QSize( width, height ) );
1086+
QgsAtlasRendering atlasRender( mComposition );
1087+
size_t featureI = 0;
1088+
if ( hasAnAtlas )
1089+
{
1090+
atlasRender.begin( mComposition->atlasMap()->atlasFilenamePattern() );
1091+
}
1092+
QProgressDialog progress( tr("Rendering maps..."), tr("Abort"), 0, atlasRender.numFeatures(), this );
1093+
1094+
do
1095+
{
1096+
if ( hasAnAtlas )
1097+
{
1098+
if ( atlasRender.numFeatures() == 0 )
1099+
break;
1100+
1101+
progress.setValue( featureI );
1102+
// process input events in order to allow aborting
1103+
QCoreApplication::processEvents();
1104+
if ( progress.wasCanceled() )
1105+
{
1106+
atlasRender.end();
1107+
break;
1108+
}
1109+
try
1110+
{
1111+
atlasRender.prepareForFeature( featureI );
1112+
}
1113+
catch ( std::runtime_error& e )
1114+
{
1115+
QMessageBox::warning( this, tr( "Atlas processing error" ),
1116+
e.what(),
1117+
QMessageBox::Ok,
1118+
QMessageBox::Ok);
1119+
break;
1120+
}
1121+
outputFileName = QDir(outputDir).filePath( atlasRender.currentFilename() ) + ".svg";
1122+
}
1123+
1124+
for ( int i = 0; i < mComposition->numPages(); ++i )
1125+
{
1126+
QSvgGenerator generator;
10121127
#if QT_VERSION >= 0x040500
1013-
generator.setViewBox( QRect( 0, 0, width, height ) );
1128+
generator.setTitle( QgsProject::instance()->title() );
10141129
#endif
1015-
generator.setResolution( mComposition->printResolution() ); //because the rendering is done in mm, convert the dpi
1130+
if ( i == 0 )
1131+
{
1132+
generator.setFileName( outputFileName );
1133+
}
1134+
else
1135+
{
1136+
QFileInfo fi( outputFileName );
1137+
generator.setFileName( fi.absolutePath() + "/" + fi.baseName() + "_" + QString::number( i + 1 ) + "." + fi.suffix() );
1138+
}
1139+
1140+
//width in pixel
1141+
int width = ( int )( mComposition->paperWidth() * mComposition->printResolution() / 25.4 );
1142+
//height in pixel
1143+
int height = ( int )( mComposition->paperHeight() * mComposition->printResolution() / 25.4 );
1144+
generator.setSize( QSize( width, height ) );
1145+
#if QT_VERSION >= 0x040500
1146+
generator.setViewBox( QRect( 0, 0, width, height ) );
1147+
#endif
1148+
generator.setResolution( mComposition->printResolution() ); //because the rendering is done in mm, convert the dpi
1149+
1150+
QPainter p( &generator );
1151+
1152+
mComposition->renderPage( &p, i );
1153+
p.end();
1154+
}
1155+
featureI++;
1156+
} while ( hasAnAtlas && featureI < atlasRender.numFeatures() );
10161157

1017-
QPainter p( &generator );
1158+
if ( hasAnAtlas )
1159+
atlasRender.end();
10181160

1019-
mComposition->renderPage( &p, i );
1020-
p.end();
1021-
}
10221161
mView->setPaintingEnabled( true );
10231162
}
10241163

0 commit comments

Comments
 (0)