Skip to content
Permalink
Browse files
[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
  • Loading branch information
nyalldawson committed May 9, 2016
1 parent c3d6c79 commit 794ab065dc7ada524ee9b284f90ea70c8e4c75c8
@@ -25,6 +25,7 @@
#include "qgsvectorlayer.h"
#include "qgsproject.h"
#include "qgsdistancearea.h"
#include "qgsjsonutils.h"

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

//inject JSON feature
if ( !mAtlasFeatureJSON.isEmpty() )
{
mWebPage->mainFrame()->evaluateJavaScript( QString( "if ( typeof setFeature === \"function\" ) { setFeature(%1); }" ).arg( mAtlasFeatureJSON ) );
//needs an extra process events here to give javascript a chance to execute
qApp->processEvents();
}

recalculateFrameSizes();
//trigger a repaint
emit contentsChanged();
@@ -544,6 +553,11 @@ void QgsComposerHtml::setExpressionContext( const QgsFeature &feature, QgsVector
mDistanceArea->setEllipsoidalMode( mComposition->mapSettings().hasCrsTransformEnabled() );
}
mDistanceArea->setEllipsoid( QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE ) );

// create JSON representation of feature
QgsJSONExporter exporter( layer );
exporter.setIncludeRelated( true );
mAtlasFeatureJSON = exporter.exportFeature( feature );
}

void QgsComposerHtml::refreshExpressionContext()
@@ -248,6 +248,9 @@ class CORE_EXPORT QgsComposerHtml: public QgsComposerMultiFrame
QString mUserStylesheet;
bool mEnableUserStylesheet;

//! JSON string representation of current atlas feature
QString mAtlasFeatureJSON;

QgsNetworkContentFetcher* mFetcher;

double htmlUnitsToMM(); //calculate scale factor
@@ -21,6 +21,10 @@
#include "qgscomposition.h"
#include "qgsmultirenderchecker.h"
#include "qgsfontutils.h"
#include "qgsvectorlayer.h"
#include "qgsmaplayerregistry.h"
#include "qgsvectordataprovider.h"
#include "qgsproject.h"
#include <QObject>
#include <QtTest/QtTest>

@@ -43,6 +47,8 @@ class TestQgsComposerHtml : public QObject
void table(); //test if rendering a HTML url works
void tableMultiFrame(); //tests multiframe capabilities of composer html
void htmlMultiFrameSmartBreak(); //tests smart page breaks in html multi frame
void javascriptSetFeature(); //test that JavaScript setFeature() function is correctly called

private:
QgsComposition *mComposition;
QgsMapSettings *mMapSettings;
@@ -243,6 +249,80 @@ void TestQgsComposerHtml::htmlMultiFrameSmartBreak()
QVERIFY( result );
}

void TestQgsComposerHtml::javascriptSetFeature()
{
//test that JavaScript setFeature() function is correctly called

// first need to setup some layers with a relation

//parent layer
QgsVectorLayer* parentLayer = new QgsVectorLayer( "Point?field=fldtxt:string&field=fldint:integer&field=foreignkey:integer", "parent", "memory" );
QgsVectorDataProvider* pr = parentLayer->dataProvider();
QgsFeature pf1;
pf1.setFields( parentLayer->fields() );
pf1.setAttributes( QgsAttributes() << "test1" << 67 << 123 );
QgsFeature pf2;
pf2.setFields( parentLayer->fields() );
pf2.setAttributes( QgsAttributes() << "test2" << 68 << 124 );
QVERIFY( pr->addFeatures( QgsFeatureList() << pf1 << pf2 ) );

// child layer
QgsVectorLayer* childLayer = new QgsVectorLayer( "Point?field=x:string&field=y:integer&field=z:integer", "referencedlayer", "memory" );
pr = childLayer->dataProvider();
QgsFeature f1;
f1.setFields( childLayer->fields() );
f1.setAttributes( QgsAttributes() << "foo" << 123 << 321 );
QgsFeature f2;
f2.setFields( childLayer->fields() );
f2.setAttributes( QgsAttributes() << "bar" << 123 << 654 );
QgsFeature f3;
f3.setFields( childLayer->fields() );
f3.setAttributes( QgsAttributes() << "foobar" << 124 << 554 );
QVERIFY( pr->addFeatures( QgsFeatureList() << f1 << f2 << f3 ) );

QgsMapLayerRegistry::instance()->addMapLayers( QList<QgsMapLayer *>() << childLayer << parentLayer );

//atlas
mComposition->atlasComposition().setCoverageLayer( parentLayer );
mComposition->atlasComposition().setEnabled( true );

QgsRelation rel;
rel.setRelationId( "rel1" );
rel.setRelationName( "relation one" );
rel.setReferencingLayer( childLayer->id() );
rel.setReferencedLayer( parentLayer->id() );
rel.addFieldPair( "y", "foreignkey" );
QgsProject::instance()->relationManager()->addRelation( rel );

QgsComposerHtml* htmlItem = new QgsComposerHtml( mComposition, false );
QgsComposerFrame* htmlFrame = new QgsComposerFrame( mComposition, htmlItem, 0, 0, 100, 200 );
htmlFrame->setFrameEnabled( true );
htmlItem->addFrame( htmlFrame );
htmlItem->setContentMode( QgsComposerHtml::ManualHtml );
htmlItem->setEvaluateExpressions( true );
// hopefully arial bold 40px is big enough to avoid cross-platform rendering issues
htmlItem->setHtml( QString( "<body style=\"margin: 10px; font-family: Arial; font-weight: bold; font-size: 40px;\">"
"<div id=\"dest\"></div><script>setFeature=function(feature){"
"document.getElementById('dest').innerHTML = feature.properties.foreignkey + ',' +"
" feature.properties['relation one'][0].z + ',' + feature.properties['relation one'][1].z;}"
"</script></body>" ) );

mComposition->setAtlasMode( QgsComposition::ExportAtlas );
QVERIFY( mComposition->atlasComposition().beginRender() );
QVERIFY( mComposition->atlasComposition().prepareForFeature( 0 ) );

htmlItem->loadHtml();

QgsCompositionChecker checker( "composerhtml_setfeature", mComposition );
checker.setControlPathPrefix( "composer_html" );
bool result = checker.testComposition( mReport );
mComposition->removeMultiFrame( htmlItem );
delete htmlItem;
QVERIFY( result );

QgsMapLayerRegistry::instance()->removeMapLayers( QList<QgsMapLayer *>() << childLayer << parentLayer );
}


QTEST_MAIN( TestQgsComposerHtml )
#include "testqgscomposerhtml.moc"
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit 794ab06

Please sign in to comment.