QGIS API Documentation  3.27.0-Master (e113457133)
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 
28 #include "qgsapplication.h"
29 #include "qgs3dsymbolregistry.h"
30 
32  : Qgs3DRendererAbstractMetadata( QStringLiteral( "rulebased" ) )
33 {
34 }
35 
37 {
38  QDomElement rulesElem = elem.firstChildElement( QStringLiteral( "rules" ) );
39 
41  if ( !root )
42  return nullptr;
43 
45  r->readXml( elem, context );
46  return r;
47 }
48 
49 
50 // ---------
51 
52 
54 
55 QgsRuleBased3DRenderer::Rule::Rule( QgsAbstract3DSymbol *symbol, const QString &filterExp, const QString &description, bool elseRule )
56  : mSymbol( symbol )
57  , mFilterExp( filterExp )
58  , mDescription( description )
59  , mElseRule( elseRule )
60 
61 {
62  initFilter();
63 }
64 
66 {
67  qDeleteAll( mChildren );
68  // do NOT delete parent
69 }
70 
72 {
73  if ( mSymbol.get() == symbol )
74  return;
75 
76  mSymbol.reset( symbol );
77 }
78 
80 {
81  RuleList l;
82  for ( Rule *c : mChildren )
83  {
84  l += c;
85  l += c->descendants();
86  }
87  return l;
88 }
89 
90 void QgsRuleBased3DRenderer::Rule::initFilter()
91 {
92  if ( mElseRule || mFilterExp.compare( QLatin1String( "ELSE" ), Qt::CaseInsensitive ) == 0 )
93  {
94  mElseRule = true;
95  mFilter.reset( nullptr );
96  }
97  else if ( !mFilterExp.isEmpty() )
98  {
99  mFilter.reset( new QgsExpression( mFilterExp ) );
100  }
101  else
102  {
103  mFilter.reset( nullptr );
104  }
105 }
106 
107 void QgsRuleBased3DRenderer::Rule::updateElseRules()
108 {
109  mElseRules.clear();
110  for ( Rule *rule : std::as_const( mChildren ) )
111  {
112  if ( rule->isElse() )
113  mElseRules << rule;
114  }
115 }
116 
117 
119 {
120  mChildren.append( rule );
121  rule->mParent = this;
122  updateElseRules();
123 }
124 
126 {
127  mChildren.insert( i, rule );
128  rule->mParent = this;
129  updateElseRules();
130 }
131 
133 {
134  delete mChildren.at( i );
135  mChildren.removeAt( i );
136  updateElseRules();
137 }
138 
140 {
141  // we could use a hash / map for search if this will be slow...
142 
143  if ( key == mRuleKey )
144  return this;
145 
146  for ( Rule *rule : std::as_const( mChildren ) )
147  {
148  const Rule *r = rule->findRuleByKey( key );
149  if ( r )
150  return r;
151  }
152  return nullptr;
153 }
154 
156 {
157  if ( key == mRuleKey )
158  return this;
159 
160  for ( Rule *rule : std::as_const( mChildren ) )
161  {
162  Rule *r = rule->findRuleByKey( key );
163  if ( r )
164  return r;
165  }
166  return nullptr;
167 }
168 
170 {
171  QgsAbstract3DSymbol *symbol = mSymbol.get() ? mSymbol->clone() : nullptr;
172  Rule *newrule = new Rule( symbol, mFilterExp, mDescription );
173  newrule->setActive( mIsActive );
174  // clone children
175  for ( Rule *rule : std::as_const( mChildren ) )
176  newrule->appendChild( rule->clone() );
177  return newrule;
178 }
179 
181 {
182  QgsAbstract3DSymbol *symbol = nullptr;
183  QDomElement elemSymbol = ruleElem.firstChildElement( QStringLiteral( "symbol" ) );
184  if ( !elemSymbol.isNull() )
185  {
186  QString symbolType = elemSymbol.attribute( QStringLiteral( "type" ) );
187  symbol = QgsApplication::symbol3DRegistry()->createSymbol( symbolType );
188  if ( symbol )
189  symbol->readXml( elemSymbol, context );
190  }
191 
192  QString filterExp = ruleElem.attribute( QStringLiteral( "filter" ) );
193  QString description = ruleElem.attribute( QStringLiteral( "description" ) );
194  QString ruleKey = ruleElem.attribute( QStringLiteral( "key" ) );
195  Rule *rule = new Rule( symbol, filterExp, description );
196 
197  if ( !ruleKey.isEmpty() )
198  rule->mRuleKey = ruleKey;
199 
200  rule->setActive( ruleElem.attribute( QStringLiteral( "active" ), QStringLiteral( "1" ) ).toInt() );
201 
202  QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
203  while ( !childRuleElem.isNull() )
204  {
205  Rule *childRule = create( childRuleElem, context );
206  if ( childRule )
207  {
208  rule->appendChild( childRule );
209  }
210  else
211  {
212  //QgsDebugMsg( QStringLiteral( "failed to init a child rule!" ) );
213  }
214  childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
215  }
216 
217  return rule;
218 }
219 
220 QDomElement QgsRuleBased3DRenderer::Rule::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
221 {
222  QDomElement ruleElem = doc.createElement( QStringLiteral( "rule" ) );
223 
224  if ( mSymbol )
225  {
226  QDomElement elemSymbol = doc.createElement( QStringLiteral( "symbol" ) );
227  elemSymbol.setAttribute( QStringLiteral( "type" ), mSymbol->type() );
228  mSymbol->writeXml( elemSymbol, context );
229  ruleElem.appendChild( elemSymbol );
230  }
231 
232  if ( !mFilterExp.isEmpty() )
233  ruleElem.setAttribute( QStringLiteral( "filter" ), mFilterExp );
234  if ( !mDescription.isEmpty() )
235  ruleElem.setAttribute( QStringLiteral( "description" ), mDescription );
236  if ( !mIsActive )
237  ruleElem.setAttribute( QStringLiteral( "active" ), 0 );
238  ruleElem.setAttribute( QStringLiteral( "key" ), mRuleKey );
239 
240  for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )
241  {
242  Rule *rule = *it;
243  ruleElem.appendChild( rule->save( doc, context ) );
244  }
245  return ruleElem;
246 }
247 
248 
250 {
251  if ( mSymbol )
252  {
253  // add handler!
254  Q_ASSERT( !handlers.value( this ) );
255  QgsFeature3DHandler *handler = QgsApplication::symbol3DRegistry()->createHandlerForSymbol( layer, mSymbol.get() );
256  if ( handler )
257  handlers[this] = handler;
258  }
259 
260  // call recursively
261  for ( Rule *rule : std::as_const( mChildren ) )
262  {
263  rule->createHandlers( layer, handlers );
264  }
265 }
266 
267 
268 void QgsRuleBased3DRenderer::Rule::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, QgsRuleBased3DRenderer::RuleToHandlerMap &handlers ) const
269 {
270  if ( mSymbol )
271  {
272  QgsFeature3DHandler *handler = handlers[this];
273  if ( !handler->prepare( context, attributeNames ) )
274  {
275  handlers.remove( this );
276  delete handler;
277  }
278  }
279 
280  if ( mFilter )
281  {
282  attributeNames.unite( mFilter->referencedColumns() );
283  mFilter->prepare( &context.expressionContext() );
284  }
285 
286  // call recursively
287  for ( Rule *rule : std::as_const( mChildren ) )
288  {
289  rule->prepare( context, attributeNames, handlers );
290  }
291 }
292 
294 {
295  if ( !isFilterOK( feature, context ) )
296  return Filtered;
297 
298  bool registered = false;
299 
300  // do we have active handler for the rule?
301  if ( handlers.contains( this ) && mIsActive )
302  {
303  handlers[this]->processFeature( feature, context );
304  registered = true;
305  }
306 
307  bool matchedAChild = false;
308 
309  // call recursively
310  for ( Rule *rule : std::as_const( mChildren ) )
311  {
312  // Don't process else rules yet
313  if ( !rule->isElse() )
314  {
315  const RegisterResult res = rule->registerFeature( feature, context, handlers );
316  // consider inactive items as "matched" so the else rule will ignore them
317  matchedAChild |= ( res == Registered || res == Inactive );
318  registered |= matchedAChild;
319  }
320  }
321 
322  // If none of the rules passed then we jump into the else rules and process them.
323  if ( !matchedAChild )
324  {
325  for ( Rule *rule : std::as_const( mElseRules ) )
326  {
327  const RegisterResult res = rule->registerFeature( feature, context, handlers );
328  matchedAChild |= ( res == Registered || res == Inactive );
329  registered |= res != Filtered;
330  }
331  }
332 
333  if ( !mIsActive || ( matchedAChild && !registered ) )
334  return Inactive;
335  else if ( registered )
336  return Registered;
337  else
338  return Filtered;
339 }
340 
341 
342 bool QgsRuleBased3DRenderer::Rule::isFilterOK( QgsFeature &f, Qgs3DRenderContext &context ) const
343 {
344  if ( ! mFilter || mElseRule )
345  return true;
346 
347  context.expressionContext().setFeature( f );
348  QVariant res = mFilter->evaluate( &context.expressionContext() );
349  return res.toInt() != 0;
350 }
351 
352 
354 
355 
357  : mRootRule( root )
358 {
359 }
360 
362 {
363  delete mRootRule;
364 }
365 
367 {
368  Rule *rootRule = mRootRule->clone();
369 
370  // normally with clone() the individual rules get new keys (UUID), but here we want to keep
371  // the tree of rules intact, so that other components that may use the rule keys work nicely (e.g. map themes)
372  rootRule->setRuleKey( mRootRule->ruleKey() );
373  RuleList origDescendants = mRootRule->descendants();
374  RuleList clonedDescendants = rootRule->descendants();
375  Q_ASSERT( origDescendants.count() == clonedDescendants.count() );
376  for ( int i = 0; i < origDescendants.count(); ++i )
377  clonedDescendants[i]->setRuleKey( origDescendants[i]->ruleKey() );
378 
380  copyBaseProperties( r );
381  return r;
382 }
383 
384 Qt3DCore::QEntity *QgsRuleBased3DRenderer::createEntity( const Qgs3DMapSettings &map ) const
385 {
386  QgsVectorLayer *vl = layer();
387 
388  if ( !vl )
389  return nullptr;
390 
391  // we start with a maximal z range (based on a number similar to the radius of the Earth),
392  // because we can't know this upfront. There's too many
393  // factors to consider eg vertex z data, terrain heights, data defined offsets and extrusion heights,...
394  // This range will be refined after populating the nodes to the actual z range of the generated chunks nodes.
395  // Assuming the vertical height is in meter, then it's extremely unlikely that a real vertical
396  // height will exceed this amount!
397  constexpr double MINIMUM_VECTOR_Z_ESTIMATE = -5000000;
398  constexpr double MAXIMUM_VECTOR_Z_ESTIMATE = 5000000;
399 
400  return new QgsRuleBasedChunkedEntity( vl, MINIMUM_VECTOR_Z_ESTIMATE, MAXIMUM_VECTOR_Z_ESTIMATE, tilingSettings(), mRootRule, map );
401 }
402 
403 void QgsRuleBased3DRenderer::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
404 {
405  QDomDocument doc = elem.ownerDocument();
406 
407  writeXmlBaseProperties( elem, context );
408 
409  QDomElement rulesElem = mRootRule->save( doc, context );
410  rulesElem.setTagName( QStringLiteral( "rules" ) ); // instead of just "rule"
411  elem.appendChild( rulesElem );
412 }
413 
414 void QgsRuleBased3DRenderer::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
415 {
416  readXmlBaseProperties( elem, context );
417 
418  // root rule is read before class constructed
419 }
Base metadata class for 3D renderers.
QgsAbstract3DSymbol * createSymbol(const QString &type) const
Creates a new instance of a symbol of the specified type.
QgsFeature3DHandler * createHandlerForSymbol(QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol)
Creates a feature handler for a symbol, for the specified vector layer.
Base class for all renderers that may to participate in 3D view.
virtual void readXml(const QDomElement &elem, const QgsReadWriteContext &context)=0
Reads symbol configuration from the given DOM element.
virtual QgsAbstract3DSymbol * clone() const =0
Returns a new instance of the symbol with the same settings.
QgsVectorLayer3DTilingSettings tilingSettings() const
Returns tiling settings of the renderer.
void writeXmlBaseProperties(QDomElement &elem, const QgsReadWriteContext &context) const
Writes common properties of this object to DOM element.
void readXmlBaseProperties(const QDomElement &elem, const QgsReadWriteContext &context)
Reads common properties of this object from DOM element.
void copyBaseProperties(QgsAbstractVectorLayer3DRenderer *r) const
Copies common properties of this object to another object.
QgsVectorLayer * layer() const
Returns vector layer associated with the renderer.
static Qgs3DSymbolRegistry * symbol3DRegistry()
Returns registry of available 3D symbols.
Class for parsing and evaluation of expressions (formerly called "search strings").
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
The class is used as a container of context for various read/write operations on other objects.
QgsAbstract3DRenderer * createRenderer(QDomElement &elem, const QgsReadWriteContext &context) override
Creates an instance of a 3D renderer based on a DOM element with renderer configuration.
void insertChild(int i, QgsRuleBased3DRenderer::Rule *rule)
add child rule, take ownership, sets this as parent
const QgsRuleBased3DRenderer::Rule * findRuleByKey(const QString &key) const
Try to find a rule given its unique key.
void prepare(const Qgs3DRenderContext &context, QSet< QString > &attributeNames, RuleToHandlerMap &handlers) const
call prepare() on handlers and populate attributeNames
void setSymbol(QgsAbstract3DSymbol *symbol)
Sets new symbol (or nullptr). Deletes old symbol if any.
Rule(QgsAbstract3DSymbol *symbol, const QString &filterExp=QString(), const QString &description=QString(), bool elseRule=false)
takes ownership of symbol, symbol may be nullptr
void setActive(bool state)
Sets if this rule is active.
QString ruleKey() const
Unique rule identifier (for identification of rule within labeling, used as provider ID)
RegisterResult
The result of registering a rule.
RegisterResult registerFeature(QgsFeature &feature, Qgs3DRenderContext &context, RuleToHandlerMap &handlers) const
register individual features
QgsRuleBased3DRenderer::Rule * clone() const
clone this rule, return new instance
QgsRuleBased3DRenderer::RuleList descendants() const
Returns all children, grand-children, grand-grand-children, grand-gra...
void createHandlers(QgsVectorLayer *layer, RuleToHandlerMap &handlers) const
add handlers
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) const
store labeling info to XML element
static QgsRuleBased3DRenderer::Rule * create(const QDomElement &ruleElem, const QgsReadWriteContext &context)
Create a rule from an XML definition.
void removeChildAt(int i)
delete child rule
void setRuleKey(const QString &key)
Override the assigned rule key (should be used just internally by rule-based renderer)
void appendChild(QgsRuleBased3DRenderer::Rule *rule)
add child rule, take ownership, sets this as parent
QgsRuleBased3DRenderer * clone() const override
Returns a cloned instance.
QgsRuleBased3DRenderer::Rule * rootRule()
Returns pointer to the root rule.
QHash< const QgsRuleBased3DRenderer::Rule *, QgsFeature3DHandler * > RuleToHandlerMap
QgsRuleBased3DRenderer(QgsRuleBased3DRenderer::Rule *root)
Construct renderer with the given root rule (takes ownership)
void readXml(const QDomElement &elem, const QgsReadWriteContext &context) override
Reads renderer's properties from given XML element.
void writeXml(QDomElement &elem, const QgsReadWriteContext &context) const override
Writes renderer's properties to given XML element.
QList< QgsRuleBased3DRenderer::Rule * > RuleList
Qt3DCore::QEntity * createEntity(const Qgs3DMapSettings &map) const override
Returns a 3D entity that will be used to show renderer's data in 3D scene.
Represents a vector layer which manages a vector based data sets.
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