Skip to content

Commit

Permalink
[geopdf] Expose option to set the initial visibility state of layers
Browse files Browse the repository at this point in the history
in the created geopdf file from layouts

Reworks the layer structure section of the GeoPDF export options
dialog to add a new column allowing users to set the initial visibility
state of layers included in the PDF. This fixes a usability issue
with large generated GeoPDFs which can cause readers to grind to halt
when opening complex GeoPDF files with many layers.

Additionally, fixes an issue where users cannot set the logical
GeoPDF group for non-vector layers

Fixes #36536
  • Loading branch information
nyalldawson committed Jun 8, 2020
1 parent 44046d7 commit e2ec260
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 115 deletions.
4 changes: 0 additions & 4 deletions src/app/layout/qgslayoutdesignerdialog.cpp
Expand Up @@ -4382,7 +4382,6 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
dialog.setGeometriesSimplified( simplify );
dialog.setExportGeoPdf( geoPdf );
dialog.setUseOgcBestPracticeFormat( useOgcBestPracticeFormat );
dialog.setExportGeoPdfFeatures( exportGeoPdfFeatures );
dialog.setExportThemes( exportThemes );

if ( dialog.exec() != QDialog::Accepted )
Expand All @@ -4396,7 +4395,6 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
QgsRenderContext::TextRenderFormat textRenderFormat = dialog.textRenderFormat();
geoPdf = dialog.exportGeoPdf();
useOgcBestPracticeFormat = dialog.useOgcBestPracticeFormat();
exportGeoPdfFeatures = dialog.exportGeoPdfFeatures();
exportThemes = dialog.exportThemes();

if ( mLayout )
Expand All @@ -4410,7 +4408,6 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
mLayout->setCustomProperty( QStringLiteral( "pdfSimplify" ), simplify ? 1 : 0 );
mLayout->setCustomProperty( QStringLiteral( "pdfCreateGeoPdf" ), geoPdf ? 1 : 0 );
mLayout->setCustomProperty( QStringLiteral( "pdfOgcBestPracticeFormat" ), useOgcBestPracticeFormat ? 1 : 0 );
mLayout->setCustomProperty( QStringLiteral( "pdfExportGeoPdfFeatures" ), exportGeoPdfFeatures ? 1 : 0 );
mLayout->setCustomProperty( QStringLiteral( "pdfExportThemes" ), exportThemes.join( QStringLiteral( "~~~" ) ) );
}

Expand All @@ -4422,7 +4419,6 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
settings.writeGeoPdf = geoPdf;
settings.useOgcBestPracticeFormatGeoreferencing = useOgcBestPracticeFormat;
settings.useIso32000ExtensionFormatGeoreferencing = !useOgcBestPracticeFormat;
settings.includeGeoPdfFeatures = exportGeoPdfFeatures;
settings.exportThemes = exportThemes;
settings.predefinedMapScales = predefinedScales();

Expand Down
1 change: 1 addition & 0 deletions src/core/layout/qgslayoutexporter.cpp
Expand Up @@ -653,6 +653,7 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToPdf( const QString &f
}

details.customLayerTreeGroups = geoPdfExporter->customLayerTreeGroups();
details.initialLayerVisibility = geoPdfExporter->initialLayerVisibility();
details.includeFeatures = settings.includeGeoPdfFeatures;
details.useOgcBestPracticeFormatGeoreferencing = settings.useOgcBestPracticeFormatGeoreferencing;
details.useIso32000ExtensionFormatGeoreferencing = settings.useIso32000ExtensionFormatGeoreferencing;
Expand Down
17 changes: 11 additions & 6 deletions src/core/layout/qgslayoutgeopdfexporter.cpp
Expand Up @@ -115,17 +115,22 @@ QgsLayoutGeoPdfExporter::QgsLayoutGeoPdfExporter( QgsLayout *layout )
const QMap< QString, QgsMapLayer * > layers = mLayout->project()->mapLayers( true );
for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
{
if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( it.value() ) )
if ( QgsMapLayer *ml = it.value() )
{
const QVariant v = vl->customProperty( QStringLiteral( "geopdf/includeFeatures" ) );
if ( !v.isValid() || v.toBool() )
const QVariant visibility = ml->customProperty( QStringLiteral( "geopdf/initiallyVisible" ), true );
mInitialLayerVisibility.insert( ml->id(), !visibility.isValid() ? true : visibility.toBool() );
if ( ml->type() == QgsMapLayerType::VectorLayer )
{
exportableLayerIds << vl->id();
const QVariant v = ml->customProperty( QStringLiteral( "geopdf/includeFeatures" ) );
if ( !v.isValid() || v.toBool() )
{
exportableLayerIds << ml->id();
}
}

const QString groupName = vl->customProperty( QStringLiteral( "geopdf/groupName" ) ).toString();
const QString groupName = ml->customProperty( QStringLiteral( "geopdf/groupName" ) ).toString();
if ( !groupName.isEmpty() )
mCustomLayerTreeGroups.insert( vl->id(), groupName );
mCustomLayerTreeGroups.insert( ml->id(), groupName );
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/core/layout/qgslayoutgeopdfexporter.h
Expand Up @@ -60,13 +60,22 @@ class CORE_EXPORT QgsLayoutGeoPdfExporter : public QgsAbstractGeoPdfExporter
*/
QMap< QString, QString > customLayerTreeGroups() const { return mCustomLayerTreeGroups; }

/**
* Optional map of map layer ID to initial visibility state. If a layer ID is not present in this,
* it will default to being initially visible when opening the PDF.
*
* \since QGIS 3.14
*/
QMap< QString, bool > initialLayerVisibility() const { return mInitialLayerVisibility; }

private:

VectorComponentDetail componentDetailForLayerId( const QString &layerId ) override;

QgsLayout *mLayout = nullptr;
QHash< QgsLayoutItemMap *, QgsGeoPdfRenderedFeatureHandler * > mMapHandlers;

QMap< QString, bool > mInitialLayerVisibility;
QMap< QString, QString > mCustomLayerTreeGroups;

friend class TestQgsLayoutGeoPdfExport;
Expand Down
6 changes: 3 additions & 3 deletions src/core/qgsabstractgeopdfexporter.cpp
Expand Up @@ -274,7 +274,7 @@ QString QgsAbstractGeoPdfExporter::createCompositionXml( const QList<ComponentLa
QDomElement layer = doc.createElement( QStringLiteral( "Layer" ) );
layer.setAttribute( QStringLiteral( "id" ), component.group.isEmpty() ? component.mapLayerId : QStringLiteral( "%1_%2" ).arg( component.group, component.mapLayerId ) );
layer.setAttribute( QStringLiteral( "name" ), details.layerIdToPdfLayerTreeNameMap.contains( component.mapLayerId ) ? details.layerIdToPdfLayerTreeNameMap.value( component.mapLayerId ) : component.name );
layer.setAttribute( QStringLiteral( "initiallyVisible" ), QStringLiteral( "true" ) );
layer.setAttribute( QStringLiteral( "initiallyVisible" ), details.initialLayerVisibility.value( component.mapLayerId, true ) ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );

if ( !component.group.isEmpty() )
{
Expand Down Expand Up @@ -302,7 +302,7 @@ QString QgsAbstractGeoPdfExporter::createCompositionXml( const QList<ComponentLa
createdLayerIds[ component.group ].insert( component.mapLayerId );
}
}
// some PDF components may not be linked to vector components - e.g. layers with labels but no features
// some PDF components may not be linked to vector components - e.g. layers with labels but no features (or raster layers)
for ( const ComponentLayerDetail &component : components )
{
if ( component.mapLayerId.isEmpty() || createdLayerIds.value( component.group ).contains( component.mapLayerId ) )
Expand All @@ -314,7 +314,7 @@ QString QgsAbstractGeoPdfExporter::createCompositionXml( const QList<ComponentLa
QDomElement layer = doc.createElement( QStringLiteral( "Layer" ) );
layer.setAttribute( QStringLiteral( "id" ), component.group.isEmpty() ? component.mapLayerId : QStringLiteral( "%1_%2" ).arg( component.group, component.mapLayerId ) );
layer.setAttribute( QStringLiteral( "name" ), details.layerIdToPdfLayerTreeNameMap.contains( component.mapLayerId ) ? details.layerIdToPdfLayerTreeNameMap.value( component.mapLayerId ) : component.name );
layer.setAttribute( QStringLiteral( "initiallyVisible" ), QStringLiteral( "true" ) );
layer.setAttribute( QStringLiteral( "initiallyVisible" ), details.initialLayerVisibility.value( component.mapLayerId, true ) ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );

if ( !component.group.isEmpty() )
{
Expand Down
9 changes: 8 additions & 1 deletion src/core/qgsabstractgeopdfexporter.h
Expand Up @@ -252,14 +252,21 @@ class CORE_EXPORT QgsAbstractGeoPdfExporter
*/
QMap< QString, QString > customLayerTreeGroups;


/**
* Optional map of map layer ID to custom layer tree name to show in the created PDF file.
*
* \since QGIS 3.14
*/
QMap< QString, QString > layerIdToPdfLayerTreeNameMap;

/**
* Optional map of map layer ID to initial visibility state. If a layer ID is not present in this,
* it will default to being initially visible when opening the PDF.
*
* \since QGIS 3.14
*/
QMap< QString, bool > initialLayerVisibility;

};

/**
Expand Down
127 changes: 89 additions & 38 deletions src/gui/layout/qgsgeopdflayertreemodel.cpp
Expand Up @@ -31,39 +31,47 @@ QgsGeoPdfLayerTreeModel::QgsGeoPdfLayerTreeModel( QgsLayerTree *rootNode, QObjec
int QgsGeoPdfLayerTreeModel::columnCount( const QModelIndex &parent ) const
{
Q_UNUSED( parent )
return 2;
return 4;
}

Qt::ItemFlags QgsGeoPdfLayerTreeModel::flags( const QModelIndex &idx ) const
{
if ( idx.column() == LayerColumn )
if ( idx.column() == IncludeVectorAttributes )
{
if ( vectorLayer( idx ) )
return QgsLayerTreeModel::flags( idx ) | Qt::ItemIsUserCheckable;
else
return QgsLayerTreeModel::flags( idx );
}

if ( idx.column() == InitiallyVisible )
{
return QgsLayerTreeModel::flags( idx ) | Qt::ItemIsUserCheckable;
}

QgsVectorLayer *vl = vectorLayer( idx );
if ( !vl )
if ( !mapLayer( idx ) )
{
return Qt::NoItemFlags;
}
else
{
const QModelIndex layerIndex = sibling( idx.row(), LayerColumn, idx );
if ( data( layerIndex, Qt::CheckStateRole ) == Qt::Checked )
{
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
return Qt::NoItemFlags;
}

QgsVectorLayer *QgsGeoPdfLayerTreeModel::vectorLayer( const QModelIndex &idx ) const
QgsMapLayer *QgsGeoPdfLayerTreeModel::mapLayer( const QModelIndex &idx ) const
{
QgsLayerTreeNode *node = index2node( index( idx.row(), LayerColumn, idx.parent() ) );
if ( !node || !QgsLayerTree::isLayer( node ) )
return nullptr;

return qobject_cast<QgsVectorLayer *>( QgsLayerTree::toLayer( node )->layer() );
return QgsLayerTree::toLayer( node )->layer();
}

QgsVectorLayer *QgsGeoPdfLayerTreeModel::vectorLayer( const QModelIndex &idx ) const
{
return qobject_cast<QgsVectorLayer *>( mapLayer( idx ) );
}

QVariant QgsGeoPdfLayerTreeModel::headerData( int section, Qt::Orientation orientation, int role ) const
Expand All @@ -74,10 +82,14 @@ QVariant QgsGeoPdfLayerTreeModel::headerData( int section, Qt::Orientation orien
{
switch ( section )
{
case 0:
case LayerColumn:
return tr( "Layer" );
case 1:
case GroupColumn:
return tr( "PDF Group" );
case InitiallyVisible:
return tr( "Initially Visible" );
case IncludeVectorAttributes:
return tr( "Include Attributes" );
default:
return QVariant();
}
Expand All @@ -91,44 +103,71 @@ QVariant QgsGeoPdfLayerTreeModel::data( const QModelIndex &idx, int role ) const
switch ( idx.column() )
{
case LayerColumn:
if ( role == Qt::CheckStateRole )
return QVariant();

return QgsLayerTreeModel::data( idx, role );

case GroupColumn:
{
switch ( role )
{
case Qt::DisplayRole:
case Qt::EditRole:
{
if ( QgsMapLayer *ml = mapLayer( idx ) )
{
return ml->customProperty( QStringLiteral( "geopdf/groupName" ) ).toString();
}
break;
}
}

return QVariant();
}

case InitiallyVisible:
{
if ( role == Qt::CheckStateRole )
{
QgsLayerTreeNode *node = index2node( index( idx.row(), LayerColumn, idx.parent() ) );
QgsVectorLayer *vl = vectorLayer( idx );
if ( vl )
if ( QgsMapLayer *ml = mapLayer( idx ) )
{
const QVariant v = vl->customProperty( QStringLiteral( "geopdf/includeFeatures" ) );
const QVariant v = ml->customProperty( QStringLiteral( "geopdf/initiallyVisible" ) );
if ( v.isValid() )
{
return v.toBool() ? Qt::Checked : Qt::Unchecked;
}
else
{
// otherwise, we default to the layer's visibility
return node->itemVisibilityChecked() ? Qt::Checked : Qt::Unchecked;
// otherwise, we default to showing by default
return Qt::Checked;
}
}
return QVariant();
}
return QgsLayerTreeModel::data( idx, role );
}
case GroupColumn:

case IncludeVectorAttributes:
{
switch ( role )
if ( role == Qt::CheckStateRole )
{
case Qt::DisplayRole:
case Qt::EditRole:
QgsLayerTreeNode *node = index2node( index( idx.row(), LayerColumn, idx.parent() ) );
if ( QgsVectorLayer *vl = vectorLayer( idx ) )
{
if ( QgsVectorLayer *vl = vectorLayer( idx ) )
const QVariant v = vl->customProperty( QStringLiteral( "geopdf/includeFeatures" ) );
if ( v.isValid() )
{
return vl->customProperty( QStringLiteral( "geopdf/groupName" ) ).toString();
return v.toBool() ? Qt::Checked : Qt::Unchecked;
}
else
{
// otherwise, we default to the layer's visibility
return node->itemVisibilityChecked() ? Qt::Checked : Qt::Unchecked;
}
break;
}
return QVariant();
}

return QVariant();
}
}

Expand All @@ -139,7 +178,7 @@ bool QgsGeoPdfLayerTreeModel::setData( const QModelIndex &index, const QVariant
{
switch ( index.column() )
{
case LayerColumn:
case IncludeVectorAttributes:
{
if ( role == Qt::CheckStateRole )
{
Expand All @@ -157,9 +196,23 @@ bool QgsGeoPdfLayerTreeModel::setData( const QModelIndex &index, const QVariant
{
if ( role == Qt::EditRole )
{
if ( QgsVectorLayer *vl = vectorLayer( index ) )
if ( QgsMapLayer *ml = mapLayer( index ) )
{
vl->setCustomProperty( QStringLiteral( "geopdf/groupName" ), value.toString() );
ml->setCustomProperty( QStringLiteral( "geopdf/groupName" ), value.toString() );
emit dataChanged( index, index );
return true;
}
}
break;
}

case InitiallyVisible:
{
if ( role == Qt::CheckStateRole )
{
if ( QgsMapLayer *ml = mapLayer( index ) )
{
ml->setCustomProperty( QStringLiteral( "geopdf/initiallyVisible" ), value.toInt() == Qt::Checked );
emit dataChanged( index, index );
return true;
}
Expand All @@ -170,16 +223,17 @@ bool QgsGeoPdfLayerTreeModel::setData( const QModelIndex &index, const QVariant
return false;
}

void QgsGeoPdfLayerTreeModel::checkAll( bool checked, const QModelIndex &parent )
void QgsGeoPdfLayerTreeModel::checkAll( bool checked, const QModelIndex &parent, int column )
{
for ( int row = 0; row < rowCount( parent ); ++row )
{
const QModelIndex childIndex = index( row, LayerColumn, parent );
const QModelIndex childIndex = index( row, column, parent );
setData( childIndex, checked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole );
checkAll( checked, childIndex );
}
}


///@cond PRIVATE
QgsGeoPdfLayerFilteredTreeModel::QgsGeoPdfLayerFilteredTreeModel( QgsGeoPdfLayerTreeModel *sourceModel, QObject *parent )
: QSortFilterProxyModel( parent )
Expand All @@ -192,13 +246,10 @@ bool QgsGeoPdfLayerFilteredTreeModel::filterAcceptsRow( int source_row, const QM
{
if ( QgsLayerTreeNode *node = mLayerTreeModel->index2node( sourceModel()->index( source_row, 0, source_parent ) ) )
{
// filter out non-vector layers
if ( QgsLayerTree::isLayer( node ) && QgsLayerTree::toLayer( node ) && QgsLayerTree::toLayer( node )->layer() && QgsLayerTree::toLayer( node )->layer()->type() != QgsMapLayerType::VectorLayer )
// filter out non-spatial layers
if ( QgsLayerTree::isLayer( node ) && QgsLayerTree::toLayer( node ) && QgsLayerTree::toLayer( node )->layer() && !QgsLayerTree::toLayer( node )->layer()->isSpatial() )
return false;

// also filter out non-spatial vector layers
if ( !qobject_cast< QgsVectorLayer * >( QgsLayerTree::toLayer( node )->layer() )->isSpatial() )
return false;
}
return true;
}
Expand Down

0 comments on commit e2ec260

Please sign in to comment.