QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
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
55QgsRuleBased3DRenderer::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
90void 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
107void 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 //QgsDebugError( QStringLiteral( "failed to init a child rule!" ) );
213 }
214 childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
215 }
216
217 return rule;
218}
219
220QDomElement 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
268void 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
342bool 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
381 return r;
382}
383
384Qt3DCore::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 because we can't know this upfront. There's too many
392 // factors to consider eg vertex z data, terrain heights, data defined offsets and extrusion heights,...
393 // This range will be refined after populating the nodes to the actual z range of the generated chunks nodes.
394 // Assuming the vertical height is in meter, then it's extremely unlikely that a real vertical
395 // height will exceed this amount!
396 constexpr double MINIMUM_VECTOR_Z_ESTIMATE = -100000;
397 constexpr double MAXIMUM_VECTOR_Z_ESTIMATE = 100000;
398
399 return new QgsRuleBasedChunkedEntity( vl, MINIMUM_VECTOR_Z_ESTIMATE, MAXIMUM_VECTOR_Z_ESTIMATE, tilingSettings(), mRootRule, map );
400}
401
402void QgsRuleBased3DRenderer::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
403{
404 QDomDocument doc = elem.ownerDocument();
405
406 writeXmlBaseProperties( elem, context );
407
408 QDomElement rulesElem = mRootRule->save( doc, context );
409 rulesElem.setTagName( QStringLiteral( "rules" ) ); // instead of just "rule"
410 elem.appendChild( rulesElem );
411}
412
413void QgsRuleBased3DRenderer::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
414{
415 readXmlBaseProperties( elem, context );
416
417 // root rule is read before class constructed
418}
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... you get it.
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.
QHash< const QgsRuleBased3DRenderer::Rule *, QgsFeature3DHandler * > RuleToHandlerMap
QgsRuleBased3DRenderer(QgsRuleBased3DRenderer::Rule *root)
Construct renderer with the given root rule (takes ownership)
QgsRuleBased3DRenderer::Rule * rootRule()
Returns pointer to the root rule.
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