Skip to content
Permalink
Browse files

[geopdf] Expose option to set the initial visibility state of layers

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 5, 2020
1 parent 44046d7 commit e2ec260b9603eb50b0b28e179984da56eb3d6288
@@ -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 )
@@ -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 )
@@ -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( "~~~" ) ) );
}

@@ -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();

@@ -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;
@@ -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 );
}
}

@@ -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;
@@ -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() )
{
@@ -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 ) )
@@ -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() )
{
@@ -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;

};

/**
@@ -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
@@ -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();
}
@@ -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();
}
}

@@ -139,7 +178,7 @@ bool QgsGeoPdfLayerTreeModel::setData( const QModelIndex &index, const QVariant
{
switch ( index.column() )
{
case LayerColumn:
case IncludeVectorAttributes:
{
if ( role == Qt::CheckStateRole )
{
@@ -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;
}
@@ -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 )
@@ -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;
}

0 comments on commit e2ec260

Please sign in to comment.
You can’t perform that action at this time.