QGIS API Documentation 3.99.0-Master (09f76ad7019)
Loading...
Searching...
No Matches
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 <memory>
19
20#include "qgs3dmapsettings.h"
21#include "qgs3dsymbolregistry.h"
22#include "qgs3dutils.h"
23#include "qgsapplication.h"
26#include "qgsvectorlayer.h"
27#include "qgsxmlutils.h"
28
29#include <QString>
30
31using namespace Qt::StringLiterals;
32
37
39{
40 QDomElement rulesElem = elem.firstChildElement( u"rules"_s );
41
43 if ( !root )
44 return nullptr;
45
47 r->readXml( elem, context );
48 return r;
49}
50
51
52// ---------
53
54
56
57QgsRuleBased3DRenderer::Rule::Rule( QgsAbstract3DSymbol *symbol, const QString &filterExp, const QString &description, bool elseRule )
58 : mSymbol( symbol )
59 , mFilterExp( filterExp )
60 , mDescription( description )
61 , mElseRule( elseRule )
62
63{
64 initFilter();
65}
66
68{
69 qDeleteAll( mChildren );
70 // do NOT delete parent
71}
72
74{
75 if ( mSymbol.get() == symbol )
76 return;
77
78 mSymbol.reset( symbol );
79}
80
82{
83 RuleList l;
84 for ( Rule *c : mChildren )
85 {
86 l += c;
87 l += c->descendants();
88 }
89 return l;
90}
91
92void QgsRuleBased3DRenderer::Rule::initFilter()
93{
94 if ( mElseRule || mFilterExp.compare( "ELSE"_L1, Qt::CaseInsensitive ) == 0 )
95 {
96 mElseRule = true;
97 mFilter.reset( nullptr );
98 }
99 else if ( !mFilterExp.isEmpty() )
100 {
101 mFilter = std::make_unique<QgsExpression>( mFilterExp );
102 }
103 else
104 {
105 mFilter.reset( nullptr );
106 }
107}
108
109void QgsRuleBased3DRenderer::Rule::updateElseRules()
110{
111 mElseRules.clear();
112 for ( Rule *rule : std::as_const( mChildren ) )
113 {
114 if ( rule->isElse() )
115 mElseRules << rule;
116 }
117}
118
119
121{
122 mChildren.append( rule );
123 rule->mParent = this;
124 updateElseRules();
125}
126
128{
129 mChildren.insert( i, rule );
130 rule->mParent = this;
131 updateElseRules();
132}
133
135{
136 delete mChildren.at( i );
137 mChildren.removeAt( i );
138 updateElseRules();
139}
140
142{
143 // we could use a hash / map for search if this will be slow...
144
145 if ( key == mRuleKey )
146 return this;
147
148 for ( Rule *rule : std::as_const( mChildren ) )
149 {
150 const Rule *r = rule->findRuleByKey( key );
151 if ( r )
152 return r;
153 }
154 return nullptr;
155}
156
158{
159 if ( key == mRuleKey )
160 return this;
161
162 for ( Rule *rule : std::as_const( mChildren ) )
163 {
164 Rule *r = rule->findRuleByKey( key );
165 if ( r )
166 return r;
167 }
168 return nullptr;
169}
170
172{
173 QgsAbstract3DSymbol *symbol = mSymbol.get() ? mSymbol->clone() : nullptr;
174 Rule *newrule = new Rule( symbol, mFilterExp, mDescription );
175 newrule->setActive( mIsActive );
176 // clone children
177 for ( Rule *rule : std::as_const( mChildren ) )
178 newrule->appendChild( rule->clone() );
179 return newrule;
180}
181
183{
184 QgsAbstract3DSymbol *symbol = nullptr;
185 QDomElement elemSymbol = ruleElem.firstChildElement( u"symbol"_s );
186 if ( !elemSymbol.isNull() )
187 {
188 QString symbolType = elemSymbol.attribute( u"type"_s );
190 if ( symbol )
191 symbol->readXml( elemSymbol, context );
192 }
193
194 QString filterExp = ruleElem.attribute( u"filter"_s );
195 QString description = ruleElem.attribute( u"description"_s );
196 QString ruleKey = ruleElem.attribute( u"key"_s );
197 Rule *rule = new Rule( symbol, filterExp, description );
198
199 if ( !ruleKey.isEmpty() )
200 rule->mRuleKey = ruleKey;
201
202 rule->setActive( ruleElem.attribute( u"active"_s, u"1"_s ).toInt() );
203
204 QDomElement childRuleElem = ruleElem.firstChildElement( u"rule"_s );
205 while ( !childRuleElem.isNull() )
206 {
207 Rule *childRule = create( childRuleElem, context );
208 if ( childRule )
209 {
210 rule->appendChild( childRule );
211 }
212 else
213 {
214 //QgsDebugError( u"failed to init a child rule!"_s );
215 }
216 childRuleElem = childRuleElem.nextSiblingElement( u"rule"_s );
217 }
218
219 return rule;
220}
221
222QDomElement QgsRuleBased3DRenderer::Rule::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
223{
224 QDomElement ruleElem = doc.createElement( u"rule"_s );
225
226 if ( mSymbol )
227 {
228 QDomElement elemSymbol = doc.createElement( u"symbol"_s );
229 elemSymbol.setAttribute( u"type"_s, mSymbol->type() );
230 mSymbol->writeXml( elemSymbol, context );
231 ruleElem.appendChild( elemSymbol );
232 }
233
234 if ( !mFilterExp.isEmpty() )
235 ruleElem.setAttribute( u"filter"_s, mFilterExp );
236 if ( !mDescription.isEmpty() )
237 ruleElem.setAttribute( u"description"_s, mDescription );
238 if ( !mIsActive )
239 ruleElem.setAttribute( u"active"_s, 0 );
240 ruleElem.setAttribute( u"key"_s, mRuleKey );
241
242 for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )
243 {
244 Rule *rule = *it;
245 ruleElem.appendChild( rule->save( doc, context ) );
246 }
247 return ruleElem;
248}
249
250
252{
253 if ( mSymbol )
254 {
255 // add handler!
256 Q_ASSERT( !handlers.value( this ) );
257 QgsFeature3DHandler *handler = QgsApplication::symbol3DRegistry()->createHandlerForSymbol( layer, mSymbol.get() );
258 if ( handler )
259 handlers[this] = handler;
260 }
261
262 // call recursively
263 for ( Rule *rule : std::as_const( mChildren ) )
264 {
265 rule->createHandlers( layer, handlers );
266 }
267}
268
269
270void QgsRuleBased3DRenderer::Rule::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent, QgsRuleBased3DRenderer::RuleToHandlerMap &handlers ) const
271{
272 if ( mSymbol )
273 {
274 QgsFeature3DHandler *handler = handlers[this];
275 if ( !handler->prepare( context, attributeNames, chunkExtent ) )
276 {
277 handlers.remove( this );
278 delete handler;
279 }
280 }
281
282 if ( mFilter )
283 {
284 attributeNames.unite( mFilter->referencedColumns() );
285 mFilter->prepare( &context.expressionContext() );
286 }
287
288 // call recursively
289 for ( Rule *rule : std::as_const( mChildren ) )
290 {
291 rule->prepare( context, attributeNames, chunkExtent, handlers );
292 }
293}
294
296{
297 if ( !isFilterOK( feature, context ) )
298 return Filtered;
299
300 bool registered = false;
301
302 // do we have active handler for the rule?
303 if ( handlers.contains( this ) && mIsActive )
304 {
305 handlers[this]->processFeature( feature, context );
306 registered = true;
307 }
308
309 bool matchedAChild = false;
310
311 // call recursively
312 for ( Rule *rule : std::as_const( mChildren ) )
313 {
314 // Don't process else rules yet
315 if ( !rule->isElse() )
316 {
317 const RegisterResult res = rule->registerFeature( feature, context, handlers );
318 // consider inactive items as "matched" so the else rule will ignore them
319 matchedAChild |= ( res == Registered || res == Inactive );
320 registered |= matchedAChild;
321 }
322 }
323
324 // If none of the rules passed then we jump into the else rules and process them.
325 if ( !matchedAChild )
326 {
327 for ( Rule *rule : std::as_const( mElseRules ) )
328 {
329 const RegisterResult res = rule->registerFeature( feature, context, handlers );
330 matchedAChild |= ( res == Registered || res == Inactive );
331 registered |= res != Filtered;
332 }
333 }
334
335 if ( !mIsActive || ( matchedAChild && !registered ) )
336 return Inactive;
337 else if ( registered )
338 return Registered;
339 else
340 return Filtered;
341}
342
343
344bool QgsRuleBased3DRenderer::Rule::isFilterOK( const QgsFeature &f, Qgs3DRenderContext &context ) const
345{
346 if ( !mFilter || mElseRule )
347 return true;
348
349 context.expressionContext().setFeature( f );
350 QVariant res = mFilter->evaluate( &context.expressionContext() );
351 return res.toInt() != 0;
352}
353
354
356
357
362
364{
365 delete mRootRule;
366}
367
369{
370 Rule *rootRule = mRootRule->clone();
371
372 // normally with clone() the individual rules get new keys (UUID), but here we want to keep
373 // the tree of rules intact, so that other components that may use the rule keys work nicely (e.g. map themes)
374 rootRule->setRuleKey( mRootRule->ruleKey() );
375 RuleList origDescendants = mRootRule->descendants();
376 RuleList clonedDescendants = rootRule->descendants();
377 Q_ASSERT( origDescendants.count() == clonedDescendants.count() );
378 for ( int i = 0; i < origDescendants.count(); ++i )
379 clonedDescendants[i]->setRuleKey( origDescendants[i]->ruleKey() );
380
383 return r;
384}
385
387{
388 QgsVectorLayer *vl = layer();
389
390 if ( !vl )
391 return nullptr;
392
393 // we start with a maximal z range because we can't know this upfront. There's too many
394 // factors to consider eg vertex z data, terrain heights, data defined offsets and extrusion heights,...
395 // This range will be refined after populating the nodes to the actual z range of the generated chunks nodes.
396 // Assuming the vertical height is in meter, then it's extremely unlikely that a real vertical
397 // height will exceed this amount!
398 constexpr double MINIMUM_VECTOR_Z_ESTIMATE = -100000;
399 constexpr double MAXIMUM_VECTOR_Z_ESTIMATE = 100000;
400
401 return new QgsRuleBasedChunkedEntity( map, vl, MINIMUM_VECTOR_Z_ESTIMATE, MAXIMUM_VECTOR_Z_ESTIMATE, tilingSettings(), mRootRule );
402}
403
404void QgsRuleBased3DRenderer::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
405{
406 QDomDocument doc = elem.ownerDocument();
407
408 writeXmlBaseProperties( elem, context );
409
410 QDomElement rulesElem = mRootRule->save( doc, context );
411 rulesElem.setTagName( u"rules"_s ); // instead of just "rule"
412 elem.appendChild( rulesElem );
413}
414
415void QgsRuleBased3DRenderer::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
416{
417 readXmlBaseProperties( elem, context );
418
419 // root rule is read before class constructed
420}
Definition of the world.
Rendering context for preparation of 3D entities.
QgsExpressionContext & expressionContext()
Gets the expression context.
Qgs3DRendererAbstractMetadata(const QString &type)
Constructor of the base class.
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 participate in 3D views.
Abstract base class for 3D symbols that are used by VectorLayer3DRenderer objects.
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.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
A container for the context for various read/write operations on objects.
QgsAbstract3DRenderer * createRenderer(QDomElement &elem, const QgsReadWriteContext &context) override
Creates an instance of a 3D renderer based on a DOM element with renderer configuration.
A child rule for a QgsRuleBased3DRenderer.
void prepare(const Qgs3DRenderContext &context, QSet< QString > &attributeNames, const QgsBox3D &chunkExtent, RuleToHandlerMap &handlers) const
call prepare() on handlers and populate attributeNames
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 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.
@ Registered
Something was registered.
@ Filtered
The rule does not apply.
RegisterResult registerFeature(const QgsFeature &feature, Qgs3DRenderContext &context, const 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
QgsAbstract3DSymbol * symbol() const
Returns the labeling settings.
void appendChild(QgsRuleBased3DRenderer::Rule *rule)
add child rule, take ownership, sets this as parent
QString description() const
A human readable description for this rule.
Rule-based 3D renderer.
QgsRuleBased3DRenderer * clone() const override
Returns a cloned instance.
QHash< const QgsRuleBased3DRenderer::Rule *, QgsFeature3DHandler * > RuleToHandlerMap
Qt3DCore::QEntity * createEntity(Qgs3DMapSettings *map) const override
Returns a 3D entity that will be used to show renderer's data in 3D scene.
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
Represents a vector layer which manages a vector based dataset.
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