[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
This commit is contained in:
Nyall Dawson 2016-05-09 12:36:08 +10:00
parent c3d6c796f4
commit 794ab065dc
6 changed files with 97 additions and 0 deletions

View File

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

View File

@ -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

View File

@ -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.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB