QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgsrulebased3drenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrulebased3drenderer.cpp
3  --------------------------------------
4  Date : January 2019
5  Copyright : (C) 2019 by Martin Dobias
6  Email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgsrulebased3drenderer.h"
17 
18 #include "qgsvectorlayer.h"
19 #include "qgsxmlutils.h"
20 
21 #include "qgs3dmapsettings.h"
22 #include "qgs3dutils.h"
23 #include "qgsline3dsymbol.h"
24 #include "qgspoint3dsymbol.h"
25 #include "qgspolygon3dsymbol.h"
26 #include "qgsline3dsymbol_p.h"
27 #include "qgspoint3dsymbol_p.h"
28 #include "qgspolygon3dsymbol_p.h"
29 
31  : Qgs3DRendererAbstractMetadata( QStringLiteral( "rulebased" ) )
32 {
33 }
34 
36 {
37  QDomElement rulesElem = elem.firstChildElement( QStringLiteral( "rules" ) );
38 
40  if ( !root )
41  return nullptr;
42 
44  r->readXml( elem, context );
45  return r;
46 }
47 
48 
49 // ---------
50 
51 
53 
54 QgsRuleBased3DRenderer::Rule::Rule( QgsAbstract3DSymbol *symbol, const QString &filterExp, const QString &description, bool elseRule )
55  : mSymbol( symbol )
56  , mFilterExp( filterExp )
57  , mDescription( description )
58  , mElseRule( elseRule )
59 
60 {
61  initFilter();
62 }
63 
65 {
66  qDeleteAll( mChildren );
67  // do NOT delete parent
68 }
69 
71 {
72  if ( mSymbol.get() == symbol )
73  return;
74 
75  mSymbol.reset( symbol );
76 }
77 
79 {
80  RuleList l;
81  for ( Rule *c : mChildren )
82  {
83  l += c;
84  l += c->descendants();
85  }
86  return l;
87 }
88 
89 void QgsRuleBased3DRenderer::Rule::initFilter()
90 {
91  if ( mElseRule || mFilterExp.compare( QLatin1String( "ELSE" ), Qt::CaseInsensitive ) == 0 )
92  {
93  mElseRule = true;
94  mFilter.reset( nullptr );
95  }
96  else if ( !mFilterExp.isEmpty() )
97  {
98  mFilter.reset( new QgsExpression( mFilterExp ) );
99  }
100  else
101  {
102  mFilter.reset( nullptr );
103  }
104 }
105 
106 void QgsRuleBased3DRenderer::Rule::updateElseRules()
107 {
108  mElseRules.clear();
109  for ( Rule *rule : qgis::as_const( mChildren ) )
110  {
111  if ( rule->isElse() )
112  mElseRules << rule;
113  }
114 }
115 
116 
118 {
119  mChildren.append( rule );
120  rule->mParent = this;
121  updateElseRules();
122 }
123 
125 {
126  mChildren.insert( i, rule );
127  rule->mParent = this;
128  updateElseRules();
129 }
130 
132 {
133  delete mChildren.at( i );
134  mChildren.removeAt( i );
135  updateElseRules();
136 }
137 
139 {
140  // we could use a hash / map for search if this will be slow...
141 
142  if ( key == mRuleKey )
143  return this;
144 
145  for ( Rule *rule : qgis::as_const( mChildren ) )
146  {
147  const Rule *r = rule->findRuleByKey( key );
148  if ( r )
149  return r;
150  }
151  return nullptr;
152 }
153 
155 {
156  if ( key == mRuleKey )
157  return this;
158 
159  for ( Rule *rule : qgis::as_const( mChildren ) )
160  {
161  Rule *r = rule->findRuleByKey( key );
162  if ( r )
163  return r;
164  }
165  return nullptr;
166 }
167 
169 {
170  QgsAbstract3DSymbol *symbol = mSymbol.get() ? mSymbol->clone() : nullptr;
171  Rule *newrule = new Rule( symbol, mFilterExp, mDescription );
172  newrule->setActive( mIsActive );
173  // clone children
174  for ( Rule *rule : qgis::as_const( mChildren ) )
175  newrule->appendChild( rule->clone() );
176  return newrule;
177 }
178 
180 {
181  QgsAbstract3DSymbol *symbol = nullptr;
182  QDomElement elemSymbol = ruleElem.firstChildElement( QStringLiteral( "symbol" ) );
183  if ( !elemSymbol.isNull() )
184  {
185  QString symbolType = elemSymbol.attribute( QStringLiteral( "type" ) );
186  if ( symbolType == QLatin1String( "polygon" ) )
187  symbol = new QgsPolygon3DSymbol;
188  else if ( symbolType == QLatin1String( "point" ) )
189  symbol = new QgsPoint3DSymbol;
190  else if ( symbolType == QLatin1String( "line" ) )
191  symbol = new QgsLine3DSymbol;
192 
193  if ( symbol )
194  symbol->readXml( elemSymbol, context );
195  }
196 
197  QString filterExp = ruleElem.attribute( QStringLiteral( "filter" ) );
198  QString description = ruleElem.attribute( QStringLiteral( "description" ) );
199  QString ruleKey = ruleElem.attribute( QStringLiteral( "key" ) );
200  Rule *rule = new Rule( symbol, filterExp, description );
201 
202  if ( !ruleKey.isEmpty() )
203  rule->mRuleKey = ruleKey;
204 
205  rule->setActive( ruleElem.attribute( QStringLiteral( "active" ), QStringLiteral( "1" ) ).toInt() );
206 
207  QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
208  while ( !childRuleElem.isNull() )
209  {
210  Rule *childRule = create( childRuleElem, context );
211  if ( childRule )
212  {
213  rule->appendChild( childRule );
214  }
215  else
216  {
217  //QgsDebugMsg( QStringLiteral( "failed to init a child rule!" ) );
218  }
219  childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
220  }
221 
222  return rule;
223 }
224 
225 QDomElement QgsRuleBased3DRenderer::Rule::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
226 {
227  QDomElement ruleElem = doc.createElement( QStringLiteral( "rule" ) );
228 
229  if ( mSymbol )
230  {
231  QDomElement elemSymbol = doc.createElement( QStringLiteral( "symbol" ) );
232  elemSymbol.setAttribute( QStringLiteral( "type" ), mSymbol->type() );
233  mSymbol->writeXml( elemSymbol, context );
234  ruleElem.appendChild( elemSymbol );
235  }
236 
237  if ( !mFilterExp.isEmpty() )
238  ruleElem.setAttribute( QStringLiteral( "filter" ), mFilterExp );
239  if ( !mDescription.isEmpty() )
240  ruleElem.setAttribute( QStringLiteral( "description" ), mDescription );
241  if ( !mIsActive )
242  ruleElem.setAttribute( QStringLiteral( "active" ), 0 );
243  ruleElem.setAttribute( QStringLiteral( "key" ), mRuleKey );
244 
245  for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )
246  {
247  Rule *rule = *it;
248  ruleElem.appendChild( rule->save( doc, context ) );
249  }
250  return ruleElem;
251 }
252 
253 
255 {
256  if ( mSymbol )
257  {
258  // add handler!
259  Q_ASSERT( !handlers.value( this ) );
260  QgsFeature3DHandler *handler = nullptr;
261  if ( mSymbol->type() == QLatin1String( "polygon" ) )
262  {
263  handler = Qgs3DSymbolImpl::handlerForPolygon3DSymbol( layer, *static_cast<QgsPolygon3DSymbol *>( mSymbol.get() ) );
264  }
265  else if ( mSymbol->type() == QLatin1String( "point" ) )
266  {
267  handler = Qgs3DSymbolImpl::handlerForPoint3DSymbol( layer, *static_cast<QgsPoint3DSymbol *>( mSymbol.get() ) );
268  }
269  else if ( mSymbol->type() == QLatin1String( "line" ) )
270  {
271  handler = Qgs3DSymbolImpl::handlerForLine3DSymbol( layer, *static_cast<QgsLine3DSymbol *>( mSymbol.get() ) );
272  }
273 
274  if ( handler )
275  handlers[this] = handler;
276  }
277 
278  // call recursively
279  for ( Rule *rule : qgis::as_const( mChildren ) )
280  {
281  rule->createHandlers( layer, handlers );
282  }
283 }
284 
285 
286 void QgsRuleBased3DRenderer::Rule::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, QgsRuleBased3DRenderer::RuleToHandlerMap &handlers ) const
287 {
288  if ( mSymbol )
289  {
290  QgsFeature3DHandler *handler = handlers[this];
291  if ( !handler->prepare( context, attributeNames ) )
292  {
293  handlers.remove( this );
294  delete handler;
295  }
296  }
297 
298  if ( mFilter )
299  {
300  attributeNames.unite( mFilter->referencedColumns() );
301  mFilter->prepare( &context.expressionContext() );
302  }
303 
304  // call recursively
305  for ( Rule *rule : qgis::as_const( mChildren ) )
306  {
307  rule->prepare( context, attributeNames, handlers );
308  }
309 }
310 
312 {
313  if ( !isFilterOK( feature, context ) )
314  return Filtered;
315 
316  bool registered = false;
317 
318  // do we have active handler for the rule?
319  if ( handlers.contains( this ) && mIsActive )
320  {
321  handlers[this]->processFeature( feature, context );
322  registered = true;
323  }
324 
325  bool willRegisterSomething = false;
326 
327  // call recursively
328  for ( Rule *rule : qgis::as_const( mChildren ) )
329  {
330  // Don't process else rules yet
331  if ( !rule->isElse() )
332  {
333  RegisterResult res = rule->registerFeature( feature, context, handlers );
334  // consider inactive items as "registered" so the else rule will ignore them
335  willRegisterSomething |= ( res == Registered || res == Inactive );
336  registered |= willRegisterSomething;
337  }
338  }
339 
340  // If none of the rules passed then we jump into the else rules and process them.
341  if ( !willRegisterSomething )
342  {
343  for ( Rule *rule : qgis::as_const( mElseRules ) )
344  {
345  registered |= rule->registerFeature( feature, context, handlers ) != Filtered;
346  }
347  }
348 
349  if ( !mIsActive )
350  return Inactive;
351  else if ( registered )
352  return Registered;
353  else
354  return Filtered;
355 }
356 
357 
358 bool QgsRuleBased3DRenderer::Rule::isFilterOK( QgsFeature &f, Qgs3DRenderContext &context ) const
359 {
360  if ( ! mFilter || mElseRule )
361  return true;
362 
363  context.expressionContext().setFeature( f );
364  QVariant res = mFilter->evaluate( &context.expressionContext() );
365  return res.toInt() != 0;
366 }
367 
368 
370 
371 
373  : mRootRule( root )
374 {
375 }
376 
378 {
379  delete mRootRule;
380 }
381 
383 {
384  Rule *rootRule = mRootRule->clone();
385 
386  // normally with clone() the individual rules get new keys (UUID), but here we want to keep
387  // the tree of rules intact, so that other components that may use the rule keys work nicely (e.g. map themes)
388  rootRule->setRuleKey( mRootRule->ruleKey() );
389  RuleList origDescendants = mRootRule->descendants();
390  RuleList clonedDescendants = rootRule->descendants();
391  Q_ASSERT( origDescendants.count() == clonedDescendants.count() );
392  for ( int i = 0; i < origDescendants.count(); ++i )
393  clonedDescendants[i]->setRuleKey( origDescendants[i]->ruleKey() );
394 
395  QgsRuleBased3DRenderer *r = new QgsRuleBased3DRenderer( rootRule );
396  r->mLayerRef = mLayerRef;
397  return r;
398 }
399 
401 {
402  mLayerRef = QgsMapLayerRef( layer );
403 }
404 
406 {
407  return qobject_cast<QgsVectorLayer *>( mLayerRef.layer );
408 }
409 
410 Qt3DCore::QEntity *QgsRuleBased3DRenderer::createEntity( const Qgs3DMapSettings &map ) const
411 {
412  QgsVectorLayer *vl = layer();
413 
414  if ( !vl )
415  return nullptr;
416 
417  Qgs3DRenderContext context( map );
418 
420  exprContext.setFields( vl->fields() );
421  context.setExpressionContext( exprContext );
422 
423  RuleToHandlerMap handlers;
424  mRootRule->createHandlers( vl, handlers );
425 
426  QSet<QString> attributeNames;
427  mRootRule->prepare( context, attributeNames, handlers );
428 
429  QgsFeatureRequest req;
430  req.setDestinationCrs( map.crs(), map.transformContext() );
431  req.setSubsetOfAttributes( attributeNames, vl->fields() );
432 
433  QgsFeature f;
434  QgsFeatureIterator fi = vl->getFeatures( req );
435  while ( fi.nextFeature( f ) )
436  {
437  context.expressionContext().setFeature( f );
438  mRootRule->registerFeature( f, context, handlers );
439  }
440 
441  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
442  for ( QgsFeature3DHandler *handler : handlers.values() )
443  handler->finalize( entity, context );
444 
445  qDeleteAll( handlers );
446 
447  return entity;
448 
449 }
450 
451 void QgsRuleBased3DRenderer::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
452 {
453  QDomDocument doc = elem.ownerDocument();
454 
455  elem.setAttribute( QStringLiteral( "layer" ), mLayerRef.layerId );
456 
457  QDomElement rulesElem = mRootRule->save( doc, context );
458  rulesElem.setTagName( QStringLiteral( "rules" ) ); // instead of just "rule"
459  elem.appendChild( rulesElem );
460 }
461 
462 void QgsRuleBased3DRenderer::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
463 {
464  Q_UNUSED( context )
465  mLayerRef = QgsMapLayerRef( elem.attribute( QStringLiteral( "layer" ) ) );
466 
467  // root rule is read before class constructed
468 }
469 
471 {
472  mLayerRef.setLayer( project.mapLayer( mLayerRef.layerId ) );
473 }
void setSymbol(QgsAbstract3DSymbol *symbol)
Sets new symbol (or nullptr). Deletes old symbol if any.
Class for parsing and evaluation of expressions (formerly called "search strings").
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature&#39;s geometries.
void setLayer(QgsVectorLayer *layer)
Sets vector layer associated with the renderer.
The class is used as a container of context for various read/write operations on other objects...
Wrapper for iterator of features from vector data provider or vector layer.
_LayerRef< QgsMapLayer > QgsMapLayerRef
RegisterResult registerFeature(QgsFeature &feature, Qgs3DRenderContext &context, RuleToHandlerMap &handlers) const
register individual features
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used in the 3D scene.
Base class for all renderers that may to participate in 3D view.
QList< QgsRuleBased3DRenderer::Rule * > RuleList
const QgsRuleBased3DRenderer::Rule * findRuleByKey(const QString &key) const
Try to find a rule given its unique key.
void appendChild(QgsRuleBased3DRenderer::Rule *rule)
add child rule, take ownership, sets this as parent
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
3 3D symbol that draws polygon geometries as planar polygons, optionally extruded (with added walls)...
QgsAbstract3DSymbol * symbol() const
Returns the labeling settings.
void writeXml(QDomElement &elem, const QgsReadWriteContext &context) const override
Writes renderer&#39;s properties to given XML element.
QString ruleKey() const
Unique rule identifier (for identification of rule within labeling, used as provider ID) ...
QString description() const
A human readable description for this rule.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
3 Definition of the world
3 Abstract base class for 3D symbols that are used by VectorLayer3DRenderer objects.
QgsRuleBased3DRenderer(QgsRuleBased3DRenderer::Rule *root)
Construct renderer with the given root rule (takes ownership)
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
3 Rule-based 3D renderer.
QgsAbstract3DRenderer * createRenderer(QDomElement &elem, const QgsReadWriteContext &context) override
Creates an instance of a 3D renderer based on a DOM element with renderer configuration.
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) const
store labeling info to XML element
QString layerId
Original layer ID.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QPointer< TYPE > layer
Weak pointer to map layer.
3 3D symbol that draws point geometries as 3D objects using one of the predefined shapes...
void insertChild(int i, QgsRuleBased3DRenderer::Rule *rule)
add child rule, take ownership, sets this as parent
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void createHandlers(QgsVectorLayer *layer, RuleToHandlerMap &handlers) const
add handlers
Qt3DCore::QEntity * createEntity(const Qgs3DMapSettings &map) const override
Returns a 3D entity that will be used to show renderer&#39;s data in 3D scene.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
Reads and writes project states.
Definition: qgsproject.h:89
3 3D symbol that draws linestring geometries as planar polygons (created from lines using a buffer wi...
void setLayer(TYPE *l)
Sets the reference to point to a specified layer.
QgsRuleBased3DRenderer::RuleList descendants() const
Returns all children, grand-children, grand-grand-children, grand-gra...
void setFields(const QgsFields &fields)
Convenience function for setting a fields for the context.
QgsRuleBased3DRenderer::Rule * rootRule()
Returns pointer to the root rule.
void prepare(const Qgs3DRenderContext &context, QSet< QString > &attributeNames, RuleToHandlerMap &handlers) const
call prepare() on handlers and populate attributeNames
void resolveReferences(const QgsProject &project) override
Resolves references to other objects - second phase of loading - after readXml()
RegisterResult
The result of registering a rule.
void removeChildAt(int i)
delete child rule
QgsVectorLayer * layer() const
Returns vector layer associated with the renderer.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context) override
Reads renderer&#39;s properties from given XML element.
virtual void readXml(const QDomElement &elem, const QgsReadWriteContext &context)=0
Reads symbol configuration from the given DOM element.
QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
static QgsExpressionContext globalProjectLayerExpressionContext(QgsVectorLayer *layer)
Returns expression context for use in preparation of 3D data of a layer.
Definition: qgs3dutils.cpp:481
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsRuleBased3DRenderer * clone() const override
Returns a cloned instance.
Rule(QgsAbstract3DSymbol *symbol, const QString &filterExp=QString(), const QString &description=QString(), bool elseRule=false)
takes ownership of symbol, symbol may be nullptr
QHash< const QgsRuleBased3DRenderer::Rule *, QgsFeature3DHandler * > RuleToHandlerMap
void setActive(bool state)
Sets if this rule is active.
bool nextFeature(QgsFeature &f)
Base metadata class for 3D renderers.
Represents a vector layer which manages a vector based data sets.
void setRuleKey(const QString &key)
Override the assigned rule key (should be used just internally by rule-based renderer) ...
static QgsRuleBased3DRenderer::Rule * create(const QDomElement &ruleElem, const QgsReadWriteContext &context)
Create a rule from an XML definition.
QgsRuleBased3DRenderer::Rule * clone() const
clone this rule, return new instance