Skip to content

Commit 794ab06

Browse files
committedMay 9, 2016
[FEATURE] Embed atlas feature into composer HTML source as GeoJSON
This change makes the current atlas feature (and additionally all attributes of related child features) available to the source of a composer HTML item, allowing the item to dynamically adjust its rendered HTML in response to the feature's properties. An example use case is dynamically populating a HTML table with all the attributes of related child features for the atlas feature. To use this, the HTML source must implement a "setFeature(feature)" JavaScript function. This function is called whenever the atlas feature changes, and is passed the atlas feature (+related attributes) as a GeoJSON Feature. Sponsored by Kanton of Zug, Switzerland
1 parent c3d6c79 commit 794ab06

File tree

6 files changed

+97
-0
lines changed

6 files changed

+97
-0
lines changed
 

‎src/core/composer/qgscomposerhtml.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "qgsvectorlayer.h"
2626
#include "qgsproject.h"
2727
#include "qgsdistancearea.h"
28+
#include "qgsjsonutils.h"
2829

2930
#include "qgswebpage.h"
3031
#include "qgswebframe.h"
@@ -210,6 +211,14 @@ void QgsComposerHtml::loadHtml( const bool useCache, const QgsExpressionContext
210211
qApp->processEvents();
211212
}
212213

214+
//inject JSON feature
215+
if ( !mAtlasFeatureJSON.isEmpty() )
216+
{
217+
mWebPage->mainFrame()->evaluateJavaScript( QString( "if ( typeof setFeature === \"function\" ) { setFeature(%1); }" ).arg( mAtlasFeatureJSON ) );
218+
//needs an extra process events here to give javascript a chance to execute
219+
qApp->processEvents();
220+
}
221+
213222
recalculateFrameSizes();
214223
//trigger a repaint
215224
emit contentsChanged();
@@ -544,6 +553,11 @@ void QgsComposerHtml::setExpressionContext( const QgsFeature &feature, QgsVector
544553
mDistanceArea->setEllipsoidalMode( mComposition->mapSettings().hasCrsTransformEnabled() );
545554
}
546555
mDistanceArea->setEllipsoid( QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE ) );
556+
557+
// create JSON representation of feature
558+
QgsJSONExporter exporter( layer );
559+
exporter.setIncludeRelated( true );
560+
mAtlasFeatureJSON = exporter.exportFeature( feature );
547561
}
548562

549563
void QgsComposerHtml::refreshExpressionContext()

‎src/core/composer/qgscomposerhtml.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,9 @@ class CORE_EXPORT QgsComposerHtml: public QgsComposerMultiFrame
248248
QString mUserStylesheet;
249249
bool mEnableUserStylesheet;
250250

251+
//! JSON string representation of current atlas feature
252+
QString mAtlasFeatureJSON;
253+
251254
QgsNetworkContentFetcher* mFetcher;
252255

253256
double htmlUnitsToMM(); //calculate scale factor

‎tests/src/core/testqgscomposerhtml.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
#include "qgscomposition.h"
2222
#include "qgsmultirenderchecker.h"
2323
#include "qgsfontutils.h"
24+
#include "qgsvectorlayer.h"
25+
#include "qgsmaplayerregistry.h"
26+
#include "qgsvectordataprovider.h"
27+
#include "qgsproject.h"
2428
#include <QObject>
2529
#include <QtTest/QtTest>
2630

@@ -43,6 +47,8 @@ class TestQgsComposerHtml : public QObject
4347
void table(); //test if rendering a HTML url works
4448
void tableMultiFrame(); //tests multiframe capabilities of composer html
4549
void htmlMultiFrameSmartBreak(); //tests smart page breaks in html multi frame
50+
void javascriptSetFeature(); //test that JavaScript setFeature() function is correctly called
51+
4652
private:
4753
QgsComposition *mComposition;
4854
QgsMapSettings *mMapSettings;
@@ -243,6 +249,80 @@ void TestQgsComposerHtml::htmlMultiFrameSmartBreak()
243249
QVERIFY( result );
244250
}
245251

252+
void TestQgsComposerHtml::javascriptSetFeature()
253+
{
254+
//test that JavaScript setFeature() function is correctly called
255+
256+
// first need to setup some layers with a relation
257+
258+
//parent layer
259+
QgsVectorLayer* parentLayer = new QgsVectorLayer( "Point?field=fldtxt:string&field=fldint:integer&field=foreignkey:integer", "parent", "memory" );
260+
QgsVectorDataProvider* pr = parentLayer->dataProvider();
261+
QgsFeature pf1;
262+
pf1.setFields( parentLayer->fields() );
263+
pf1.setAttributes( QgsAttributes() << "test1" << 67 << 123 );
264+
QgsFeature pf2;
265+
pf2.setFields( parentLayer->fields() );
266+
pf2.setAttributes( QgsAttributes() << "test2" << 68 << 124 );
267+
QVERIFY( pr->addFeatures( QgsFeatureList() << pf1 << pf2 ) );
268+
269+
// child layer
270+
QgsVectorLayer* childLayer = new QgsVectorLayer( "Point?field=x:string&field=y:integer&field=z:integer", "referencedlayer", "memory" );
271+
pr = childLayer->dataProvider();
272+
QgsFeature f1;
273+
f1.setFields( childLayer->fields() );
274+
f1.setAttributes( QgsAttributes() << "foo" << 123 << 321 );
275+
QgsFeature f2;
276+
f2.setFields( childLayer->fields() );
277+
f2.setAttributes( QgsAttributes() << "bar" << 123 << 654 );
278+
QgsFeature f3;
279+
f3.setFields( childLayer->fields() );
280+
f3.setAttributes( QgsAttributes() << "foobar" << 124 << 554 );
281+
QVERIFY( pr->addFeatures( QgsFeatureList() << f1 << f2 << f3 ) );
282+
283+
QgsMapLayerRegistry::instance()->addMapLayers( QList<QgsMapLayer *>() << childLayer << parentLayer );
284+
285+
//atlas
286+
mComposition->atlasComposition().setCoverageLayer( parentLayer );
287+
mComposition->atlasComposition().setEnabled( true );
288+
289+
QgsRelation rel;
290+
rel.setRelationId( "rel1" );
291+
rel.setRelationName( "relation one" );
292+
rel.setReferencingLayer( childLayer->id() );
293+
rel.setReferencedLayer( parentLayer->id() );
294+
rel.addFieldPair( "y", "foreignkey" );
295+
QgsProject::instance()->relationManager()->addRelation( rel );
296+
297+
QgsComposerHtml* htmlItem = new QgsComposerHtml( mComposition, false );
298+
QgsComposerFrame* htmlFrame = new QgsComposerFrame( mComposition, htmlItem, 0, 0, 100, 200 );
299+
htmlFrame->setFrameEnabled( true );
300+
htmlItem->addFrame( htmlFrame );
301+
htmlItem->setContentMode( QgsComposerHtml::ManualHtml );
302+
htmlItem->setEvaluateExpressions( true );
303+
// hopefully arial bold 40px is big enough to avoid cross-platform rendering issues
304+
htmlItem->setHtml( QString( "<body style=\"margin: 10px; font-family: Arial; font-weight: bold; font-size: 40px;\">"
305+
"<div id=\"dest\"></div><script>setFeature=function(feature){"
306+
"document.getElementById('dest').innerHTML = feature.properties.foreignkey + ',' +"
307+
" feature.properties['relation one'][0].z + ',' + feature.properties['relation one'][1].z;}"
308+
"</script></body>" ) );
309+
310+
mComposition->setAtlasMode( QgsComposition::ExportAtlas );
311+
QVERIFY( mComposition->atlasComposition().beginRender() );
312+
QVERIFY( mComposition->atlasComposition().prepareForFeature( 0 ) );
313+
314+
htmlItem->loadHtml();
315+
316+
QgsCompositionChecker checker( "composerhtml_setfeature", mComposition );
317+
checker.setControlPathPrefix( "composer_html" );
318+
bool result = checker.testComposition( mReport );
319+
mComposition->removeMultiFrame( htmlItem );
320+
delete htmlItem;
321+
QVERIFY( result );
322+
323+
QgsMapLayerRegistry::instance()->removeMapLayers( QList<QgsMapLayer *>() << childLayer << parentLayer );
324+
}
325+
246326

247327
QTEST_MAIN( TestQgsComposerHtml )
248328
#include "testqgscomposerhtml.moc"

Error rendering embedded code

Invalid image source.

Error rendering embedded code

Invalid image source.

0 commit comments

Comments
 (0)
Please sign in to comment.