mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
[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:
parent
c3d6c796f4
commit
794ab065dc
@ -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.
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 |
Loading…
x
Reference in New Issue
Block a user