QGIS API Documentation 3.41.0-Master (3440c17df1d)
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 "qgsvectorlayer.h"
19#include "qgsxmlutils.h"
20
21#include "qgs3dmapsettings.h"
22#include "qgs3dutils.h"
25#include "qgsapplication.h"
26#include "qgs3dsymbolregistry.h"
27
32
34{
35 QDomElement rulesElem = elem.firstChildElement( QStringLiteral( "rules" ) );
36
38 if ( !root )
39 return nullptr;
40
42 r->readXml( elem, context );
43 return r;
44}
45
46
47// ---------
48
49
51
52QgsRuleBased3DRenderer::Rule::Rule( QgsAbstract3DSymbol *symbol, const QString &filterExp, const QString &description, bool elseRule )
53 : mSymbol( symbol )
54 , mFilterExp( filterExp )
55 , mDescription( description )
56 , mElseRule( elseRule )
57
58{
59 initFilter();
60}
61
63{
64 qDeleteAll( mChildren );
65 // do NOT delete parent
66}
67
69{
70 if ( mSymbol.get() == symbol )
71 return;
72
73 mSymbol.reset( symbol );
74}
75
77{
78 RuleList l;
79 for ( Rule *c : mChildren )
80 {
81 l += c;
82 l += c->descendants();
83 }
84 return l;
85}
86
87void QgsRuleBased3DRenderer::Rule::initFilter()
88{
89 if ( mElseRule || mFilterExp.compare( QLatin1String( "ELSE" ), Qt::CaseInsensitive ) == 0 )
90 {
91 mElseRule = true;
92 mFilter.reset( nullptr );
93 }
94 else if ( !mFilterExp.isEmpty() )
95 {
96 mFilter.reset( new QgsExpression( mFilterExp ) );
97 }
98 else
99 {
100 mFilter.reset( nullptr );
101 }
102}
103
104void QgsRuleBased3DRenderer::Rule::updateElseRules()
105{
106 mElseRules.clear();
107 for ( Rule *rule : std::as_const( mChildren ) )
108 {
109 if ( rule->isElse() )
110 mElseRules << rule;
111 }
112}
113
114
116{
117 mChildren.append( rule );
118 rule->mParent = this;
119 updateElseRules();
120}
121
123{
124 mChildren.insert( i, rule );
125 rule->mParent = this;
126 updateElseRules();
127}
128
130{
131 delete mChildren.at( i );
132 mChildren.removeAt( i );
133 updateElseRules();
134}
135
137{
138 // we could use a hash / map for search if this will be slow...
139
140 if ( key == mRuleKey )
141 return this;
142
143 for ( Rule *rule : std::as_const( mChildren ) )
144 {
145 const Rule *r = rule->findRuleByKey( key );
146 if ( r )
147 return r;
148 }
149 return nullptr;
150}
151
153{
154 if ( key == mRuleKey )
155 return this;
156
157 for ( Rule *rule : std::as_const( mChildren ) )
158 {
159 Rule *r = rule->findRuleByKey( key );
160 if ( r )
161 return r;
162 }
163 return nullptr;
164}
165
167{
168 QgsAbstract3DSymbol *symbol = mSymbol.get() ? mSymbol->clone() : nullptr;
169 Rule *newrule = new Rule( symbol, mFilterExp, mDescription );
170 newrule->setActive( mIsActive );
171 // clone children
172 for ( Rule *rule : std::as_const( mChildren ) )
173 newrule->appendChild( rule->clone() );
174 return newrule;
175}
176
178{
179 QgsAbstract3DSymbol *symbol = nullptr;
180 QDomElement elemSymbol = ruleElem.firstChildElement( QStringLiteral( "symbol" ) );
181 if ( !elemSymbol.isNull() )
182 {
183 QString symbolType = elemSymbol.attribute( QStringLiteral( "type" ) );
184 symbol = QgsApplication::symbol3DRegistry()->createSymbol( symbolType );
185 if ( symbol )
186 symbol->readXml( elemSymbol, context );
187 }
188
189 QString filterExp = ruleElem.attribute( QStringLiteral( "filter" ) );
190 QString description = ruleElem.attribute( QStringLiteral( "description" ) );
191 QString ruleKey = ruleElem.attribute( QStringLiteral( "key" ) );
192 Rule *rule = new Rule( symbol, filterExp, description );
193
194 if ( !ruleKey.isEmpty() )
195 rule->mRuleKey = ruleKey;
196
197 rule->setActive( ruleElem.attribute( QStringLiteral( "active" ), QStringLiteral( "1" ) ).toInt() );
198
199 QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
200 while ( !childRuleElem.isNull() )
201 {
202 Rule *childRule = create( childRuleElem, context );
203 if ( childRule )
204 {
205 rule->appendChild( childRule );
206 }
207 else
208 {
209 //QgsDebugError( QStringLiteral( "failed to init a child rule!" ) );
210 }
211 childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
212 }
213
214 return rule;
215}
216
217QDomElement QgsRuleBased3DRenderer::Rule::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
218{
219 QDomElement ruleElem = doc.createElement( QStringLiteral( "rule" ) );
220
221 if ( mSymbol )
222 {
223 QDomElement elemSymbol = doc.createElement( QStringLiteral( "symbol" ) );
224 elemSymbol.setAttribute( QStringLiteral( "type" ), mSymbol->type() );
225 mSymbol->writeXml( elemSymbol, context );
226 ruleElem.appendChild( elemSymbol );
227 }
228
229 if ( !mFilterExp.isEmpty() )
230 ruleElem.setAttribute( QStringLiteral( "filter" ), mFilterExp );
231 if ( !mDescription.isEmpty() )
232 ruleElem.setAttribute( QStringLiteral( "description" ), mDescription );
233 if ( !mIsActive )
234 ruleElem.setAttribute( QStringLiteral( "active" ), 0 );
235 ruleElem.setAttribute( QStringLiteral( "key" ), mRuleKey );
236
237 for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )
238 {
239 Rule *rule = *it;
240 ruleElem.appendChild( rule->save( doc, context ) );
241 }
242 return ruleElem;
243}
244
245
247{
248 if ( mSymbol )
249 {
250 // add handler!
251 Q_ASSERT( !handlers.value( this ) );
252 QgsFeature3DHandler *handler = QgsApplication::symbol3DRegistry()->createHandlerForSymbol( layer, mSymbol.get() );
253 if ( handler )
254 handlers[this] = handler;
255 }
256
257 // call recursively
258 for ( Rule *rule : std::as_const( mChildren ) )
259 {
260 rule->createHandlers( layer, handlers );
261 }
262}
263
264
265void QgsRuleBased3DRenderer::Rule::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsVector3D &chunkOrigin, QgsRuleBased3DRenderer::RuleToHandlerMap &handlers ) const
266{
267 if ( mSymbol )
268 {
269 QgsFeature3DHandler *handler = handlers[this];
270 if ( !handler->prepare( context, attributeNames, chunkOrigin ) )
271 {
272 handlers.remove( this );
273 delete handler;
274 }
275 }
276
277 if ( mFilter )
278 {
279 attributeNames.unite( mFilter->referencedColumns() );
280 mFilter->prepare( &context.expressionContext() );
281 }
282
283 // call recursively
284 for ( Rule *rule : std::as_const( mChildren ) )
285 {
286 rule->prepare( context, attributeNames, chunkOrigin, handlers );
287 }
288}
289
291{
292 if ( !isFilterOK( feature, context ) )
293 return Filtered;
294
295 bool registered = false;
296
297 // do we have active handler for the rule?
298 if ( handlers.contains( this ) && mIsActive )
299 {
300 handlers[this]->processFeature( feature, context );
301 registered = true;
302 }
303
304 bool matchedAChild = false;
305
306 // call recursively
307 for ( Rule *rule : std::as_const( mChildren ) )
308 {
309 // Don't process else rules yet
310 if ( !rule->isElse() )
311 {
312 const RegisterResult res = rule->registerFeature( feature, context, handlers );
313 // consider inactive items as "matched" so the else rule will ignore them
314 matchedAChild |= ( res == Registered || res == Inactive );
315 registered |= matchedAChild;
316 }
317 }
318
319 // If none of the rules passed then we jump into the else rules and process them.
320 if ( !matchedAChild )
321 {
322 for ( Rule *rule : std::as_const( mElseRules ) )
323 {
324 const RegisterResult res = rule->registerFeature( feature, context, handlers );
325 matchedAChild |= ( res == Registered || res == Inactive );
326 registered |= res != Filtered;
327 }
328 }
329
330 if ( !mIsActive || ( matchedAChild && !registered ) )
331 return Inactive;
332 else if ( registered )
333 return Registered;
334 else
335 return Filtered;
336}
337
338
339bool QgsRuleBased3DRenderer::Rule::isFilterOK( QgsFeature &f, Qgs3DRenderContext &context ) const
340{
341 if ( ! mFilter || mElseRule )
342 return true;
343
344 context.expressionContext().setFeature( f );
345 QVariant res = mFilter->evaluate( &context.expressionContext() );
346 return res.toInt() != 0;
347}
348
349
351
352
357
359{
360 delete mRootRule;
361}
362
364{
365 Rule *rootRule = mRootRule->clone();
366
367 // normally with clone() the individual rules get new keys (UUID), but here we want to keep
368 // the tree of rules intact, so that other components that may use the rule keys work nicely (e.g. map themes)
369 rootRule->setRuleKey( mRootRule->ruleKey() );
370 RuleList origDescendants = mRootRule->descendants();
371 RuleList clonedDescendants = rootRule->descendants();
372 Q_ASSERT( origDescendants.count() == clonedDescendants.count() );
373 for ( int i = 0; i < origDescendants.count(); ++i )
374 clonedDescendants[i]->setRuleKey( origDescendants[i]->ruleKey() );
375
378 return r;
379}
380
382{
383 QgsVectorLayer *vl = layer();
384
385 if ( !vl )
386 return nullptr;
387
388 // we start with a maximal z range because we can't know this upfront. There's too many
389 // factors to consider eg vertex z data, terrain heights, data defined offsets and extrusion heights,...
390 // This range will be refined after populating the nodes to the actual z range of the generated chunks nodes.
391 // Assuming the vertical height is in meter, then it's extremely unlikely that a real vertical
392 // height will exceed this amount!
393 constexpr double MINIMUM_VECTOR_Z_ESTIMATE = -100000;
394 constexpr double MAXIMUM_VECTOR_Z_ESTIMATE = 100000;
395
396 return new QgsRuleBasedChunkedEntity( map, vl, MINIMUM_VECTOR_Z_ESTIMATE, MAXIMUM_VECTOR_Z_ESTIMATE, tilingSettings(), mRootRule );
397}
398
399void QgsRuleBased3DRenderer::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
400{
401 QDomDocument doc = elem.ownerDocument();
402
403 writeXmlBaseProperties( elem, context );
404
405 QDomElement rulesElem = mRootRule->save( doc, context );
406 rulesElem.setTagName( QStringLiteral( "rules" ) ); // instead of just "rule"
407 elem.appendChild( rulesElem );
408}
409
410void QgsRuleBased3DRenderer::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
411{
412 readXmlBaseProperties( elem, context );
413
414 // root rule is read before class constructed
415}
QgsExpressionContext & expressionContext()
Gets the expression context.
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.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
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:58
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 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)
void prepare(const Qgs3DRenderContext &context, QSet< QString > &attributeNames, const QgsVector3D &chunkOrigin, RuleToHandlerMap &handlers) const
call prepare() on handlers and populate attributeNames
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
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
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition qgsvector3d.h:31
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