Skip to content

Commit

Permalink
Make map id optional in map_credits layout function
Browse files Browse the repository at this point in the history
If not specified, the credits for ALL layers from ALL maps
will be collected
  • Loading branch information
nyalldawson committed Apr 13, 2023
1 parent e5c2f56 commit f7a7a24
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 21 deletions.
8 changes: 6 additions & 2 deletions resources/function_help/json/map_credits
Expand Up @@ -2,10 +2,11 @@
"name": "map_credits",
"type": "function",
"groups": ["Layout"],
"description": "Returns a list of credit (usage rights) strings for the layers shown in a layout map item.",
"description": "Returns a list of credit (usage rights) strings for the layers shown in a layout, or specific layout map item.",
"arguments": [{
"arg": "id",
"description": "map item ID"
"optional": true,
"description": "Map item ID. If not specified, the layers from all maps in the layout will be used."
}, {
"arg": "include_layer_names",
"description": "Set to true to include layer names before their credit strings",
Expand All @@ -18,6 +19,9 @@
"default": "': '"
}],
"examples": [{
"expression": "array_to_string( map_credits() )",
"returns": "comma separated list of layer credits for all layers shown in all map items in the layout, e.g 'CC-BY-NC, CC-BY-SA'"
}, {
"expression": "array_to_string( map_credits( 'Main Map' ) )",
"returns": "comma separated list of layer credits for layers shown in the 'Main Map' layout item, e.g 'CC-BY-NC, CC-BY-SA'"
}, {
Expand Down
57 changes: 38 additions & 19 deletions src/core/expression/qgsexpressioncontextutils.cpp
Expand Up @@ -127,7 +127,7 @@ class GetLayoutMapLayerCredits : public QgsScopedExpressionFunction
public:
GetLayoutMapLayerCredits( const QgsLayout *c )
: QgsScopedExpressionFunction( QStringLiteral( "map_credits" ),
QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "id" ) )
QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "id" ), true )
<< QgsExpressionFunction::Parameter( QStringLiteral( "include_layer_names" ), true, false )
<< QgsExpressionFunction::Parameter( QStringLiteral( "layer_name_separator" ), true, QStringLiteral( ": " ) ), QStringLiteral( "Layout" ) )
, mLayout( c )
Expand All @@ -139,38 +139,57 @@ class GetLayoutMapLayerCredits : public QgsScopedExpressionFunction
return QVariant();

const QString id = values.value( 0 ).toString();
const bool includeLayerNames = values.value( 1 ).toBool();
const QString layerNameSeparator = values.value( 2 ).toString();

if ( QgsLayoutItemMap *map = qobject_cast< QgsLayoutItemMap * >( mLayout->itemById( id ) ) )
QList< QgsLayoutItemMap * > maps;
mLayout->layoutItems( maps );

// collect all the layers in matching maps first
QList< const QgsMapLayer * > layers;
bool foundMap = false;
for ( QgsLayoutItemMap *map : std::as_const( maps ) )
{
if ( !id.isEmpty() && map->id() != id )
continue;

foundMap = true;

const QgsExpressionContext c = map->createExpressionContext();
const QVariantList mapLayers = c.variable( QStringLiteral( "map_layers" ) ).toList();

const bool includeLayerNames = values.value( 1 ).toBool();
const QString layerNameSeparator = values.value( 2 ).toString();

QVariantList res;
for ( const QVariant &value : mapLayers )
{
if ( const QgsMapLayer *layer = qobject_cast< const QgsMapLayer * >( value.value< QObject * >() ) )
{
const QStringList credits = !layer->metadata().rights().isEmpty() ? layer->metadata().rights() : QStringList() << layer->attribution();
for ( const QString &credit : credits )
{
if ( credit.trimmed().isEmpty() )
continue;

const QString creditString = includeLayerNames ? layer->name() + layerNameSeparator + credit
: credit;

if ( !res.contains( creditString ) )
res << creditString;
}
if ( !layers.contains( layer ) )
layers << layer;
}
}
}
if ( !foundMap )
return QVariant();

return res;
QVariantList res;
res.reserve( layers.size() );
for ( const QgsMapLayer *layer : std::as_const( layers ) )
{
const QStringList credits = !layer->metadata().rights().isEmpty() ? layer->metadata().rights() : QStringList() << layer->attribution();
for ( const QString &credit : credits )
{
if ( credit.trimmed().isEmpty() )
continue;

const QString creditString = includeLayerNames ? layer->name() + layerNameSeparator + credit
: credit;

if ( !res.contains( creditString ) )
res << creditString;
}
}
return QVariant();

return res;
}

QgsScopedExpressionFunction *clone() const override
Expand Down
11 changes: 11 additions & 0 deletions tests/src/core/testqgslayoutitem.cpp
Expand Up @@ -1454,13 +1454,19 @@ void TestQgsLayoutItem::mapCreditsFunction()
QVariant r = e.evaluate( &c );
QVERIFY( !r.isValid() );

e = QgsExpression( QStringLiteral( "array_to_string( map_credits() )" ) );
r = e.evaluate( &c );
QVERIFY( !r.isValid() );

QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
map->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
map->attemptSetSceneRect( QRectF( 30, 60, 200, 100 ) );
map->setExtent( extent );
l.addLayoutItem( map );
map->setId( QStringLiteral( "Map_id" ) );

e = QgsExpression( QStringLiteral( "array_to_string( map_credits( 'Map_id' ) )" ) );

c = l.createExpressionContext();
e.prepare( &c );
r = e.evaluate( &c );
Expand Down Expand Up @@ -1506,6 +1512,11 @@ void TestQgsLayoutItem::mapCreditsFunction()
QgsExpression e4( QStringLiteral( "array_to_string( map_credits( 'Map_2', include_layer_names:=true ) )" ) );
e4.prepare( &c );
QCOMPARE( e4.evaluate( &c ).toString(), QStringLiteral( "A: CC BY SA" ) );

// credits from all maps
QgsExpression e5( QStringLiteral( "array_to_string( map_credits(include_layer_names:=true ) )" ) );
e5.prepare( &c );
QCOMPARE( e5.evaluate( &c ).toString(), QStringLiteral( "A: CC BY SA,B: CC NC,C: CC BY SA" ) );
}

void TestQgsLayoutItem::rotation()
Expand Down

0 comments on commit f7a7a24

Please sign in to comment.