QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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
36
38{
39 QDomElement rulesElem = elem.firstChildElement( u"rules"_s );
40
42 if ( !root )
43 return nullptr;
44
46 r->readXml( elem, context );
47 return r;
48}
49
50
51// ---------
52
53
55
56QgsRuleBased3DRenderer::Rule::Rule( QgsAbstract3DSymbol *symbol, const QString &filterExp, const QString &description, bool elseRule )
57 : mSymbol( symbol )
58 , mFilterExp( filterExp )
59 , mDescription( description )
60 , mElseRule( elseRule )
61
62{
63 initFilter();
64}
65
67{
68 qDeleteAll( mChildren );
69 // do NOT delete parent
70}
71
73{
74 if ( mSymbol.get() == symbol )
75 return;
76
77 mSymbol.reset( symbol );
78}
79
81{
82 RuleList l;
83 for ( Rule *c : mChildren )
84 {
85 l += c;
86 l += c->descendants();
87 }
88 return l;
89}
90
91void QgsRuleBased3DRenderer::Rule::initFilter()
92{
93 if ( mElseRule || mFilterExp.compare( "ELSE"_L1, Qt::CaseInsensitive ) == 0 )
94 {
95 mElseRule = true;
96 mFilter.reset( nullptr );
97 }
98 else if ( !mFilterExp.isEmpty() )
99 {
100 mFilter = std::make_unique<QgsExpression>( mFilterExp );
101 }
102 else
103 {
104 mFilter.reset( nullptr );
105 }
106}
107
108void QgsRuleBased3DRenderer::Rule::updateElseRules()
109{
110 mElseRules.clear();
111 for ( Rule *rule : std::as_const( mChildren ) )
112 {
113 if ( rule->isElse() )
114 mElseRules << rule;
115 }
116}
117
118
120{
121 mChildren.append( rule );
122 rule->mParent = this;
123 updateElseRules();
124}
125
127{
128 mChildren.insert( i, rule );
129 rule->mParent = this;
130 updateElseRules();
131}
132
134{
135 delete mChildren.at( i );
136 mChildren.removeAt( i );
137 updateElseRules();
138}
139
141{
142 // we could use a hash / map for search if this will be slow...
143
144 if ( key == mRuleKey )
145 return this;
146
147 for ( Rule *rule : std::as_const( mChildren ) )
148 {
149 const Rule *r = rule->findRuleByKey( key );
150 if ( r )
151 return r;
152 }
153 return nullptr;
154}
155
157{
158 if ( key == mRuleKey )
159 return this;
160
161 for ( Rule *rule : std::as_const( mChildren ) )
162 {
163 Rule *r = rule->findRuleByKey( key );
164 if ( r )
165 return r;
166 }
167 return nullptr;
168}
169
171{
172 QgsAbstract3DSymbol *symbol = mSymbol.get() ? mSymbol->clone() : nullptr;
173 Rule *newrule = new Rule( symbol, mFilterExp, mDescription );
174 newrule->setActive( mIsActive );
175 // clone children
176 for ( Rule *rule : std::as_const( mChildren ) )
177 newrule->appendChild( rule->clone() );
178 return newrule;
179}
180
182{
183 QgsAbstract3DSymbol *symbol = nullptr;
184 QDomElement elemSymbol = ruleElem.firstChildElement( u"symbol"_s );
185 if ( !elemSymbol.isNull() )
186 {
187 QString symbolType = elemSymbol.attribute( u"type"_s );
189 if ( symbol )
190 symbol->readXml( elemSymbol, context );
191 }
192
193 QString filterExp = ruleElem.attribute( u"filter"_s );
194 QString description = ruleElem.attribute( u"description"_s );
195 QString ruleKey = ruleElem.attribute( u"key"_s );
196 Rule *rule = new Rule( symbol, filterExp, description );
197
198 if ( !ruleKey.isEmpty() )
199 rule->mRuleKey = ruleKey;
200
201 rule->setActive( ruleElem.attribute( u"active"_s, u"1"_s ).toInt() );
202
203 QDomElement childRuleElem = ruleElem.firstChildElement( u"rule"_s );
204 while ( !childRuleElem.isNull() )
205 {
206 Rule *childRule = create( childRuleElem, context );
207 if ( childRule )
208 {
209 rule->appendChild( childRule );
210 }
211 else
212 {
213 //QgsDebugError( u"failed to init a child rule!"_s );
214 }
215 childRuleElem = childRuleElem.nextSiblingElement( u"rule"_s );
216 }
217
218 return rule;
219}
220
221QDomElement QgsRuleBased3DRenderer::Rule::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
222{
223 QDomElement ruleElem = doc.createElement( u"rule"_s );
224
225 if ( mSymbol )
226 {
227 QDomElement elemSymbol = doc.createElement( u"symbol"_s );
228 elemSymbol.setAttribute( u"type"_s, mSymbol->type() );
229 mSymbol->writeXml( elemSymbol, context );
230 ruleElem.appendChild( elemSymbol );
231 }
232
233 if ( !mFilterExp.isEmpty() )
234 ruleElem.setAttribute( u"filter"_s, mFilterExp );
235 if ( !mDescription.isEmpty() )
236 ruleElem.setAttribute( u"description"_s, mDescription );
237 if ( !mIsActive )
238 ruleElem.setAttribute( u"active"_s, 0 );
239 ruleElem.setAttribute( u"key"_s, mRuleKey );
240
241 for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )
242 {
243 Rule *rule = *it;
244 ruleElem.appendChild( rule->save( doc, context ) );
245 }
246 return ruleElem;
247}
248
249
251{
252 if ( mSymbol )
253 {
254 // add handler!
255 Q_ASSERT( !handlers.value( this ) );
256 QgsFeature3DHandler *handler = QgsApplication::symbol3DRegistry()->createHandlerForSymbol( layer, mSymbol.get() );
257 if ( handler )
258 handlers[this] = handler;
259 }
260
261 // call recursively
262 for ( Rule *rule : std::as_const( mChildren ) )
263 {
264 rule->createHandlers( layer, handlers );
265 }
266}
267
268
269void QgsRuleBased3DRenderer::Rule::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames, const QgsBox3D &chunkExtent, QgsRuleBased3DRenderer::RuleToHandlerMap &handlers ) const
270{
271 if ( mSymbol )
272 {
273 QgsFeature3DHandler *handler = handlers[this];
274 if ( !handler->prepare( context, attributeNames, chunkExtent ) )
275 {
276 handlers.remove( this );
277 delete handler;
278 }
279 }
280
281 if ( mFilter )
282 {
283 attributeNames.unite( mFilter->referencedColumns() );
284 mFilter->prepare( &context.expressionContext() );
285 }
286
287 // call recursively
288 for ( Rule *rule : std::as_const( mChildren ) )
289 {
290 rule->prepare( context, attributeNames, chunkExtent, handlers );
291 }
292}
293
295 const QgsFeature &feature, Qgs3DRenderContext &context, const QgsRuleBased3DRenderer::RuleToHandlerMap &handlers
296) const
297{
298 if ( !isFilterOK( feature, context ) )
299 return Filtered;
300
301 bool registered = false;
302
303 // do we have active handler for the rule?
304 if ( handlers.contains( this ) && mIsActive )
305 {
306 handlers[this]->processFeature( feature, context );
307 registered = true;
308 }
309
310 bool matchedAChild = false;
311
312 // call recursively
313 for ( Rule *rule : std::as_const( mChildren ) )
314 {
315 // Don't process else rules yet
316 if ( !rule->isElse() )
317 {
318 const RegisterResult res = rule->registerFeature( feature, context, handlers );
319 // consider inactive items as "matched" so the else rule will ignore them
320 matchedAChild |= ( res == Registered || res == Inactive );
321 registered |= matchedAChild;
322 }
323 }
324
325 // If none of the rules passed then we jump into the else rules and process them.
326 if ( !matchedAChild )
327 {
328 for ( Rule *rule : std::as_const( mElseRules ) )
329 {
330 const RegisterResult res = rule->registerFeature( feature, context, handlers );
331 matchedAChild |= ( res == Registered || res == Inactive );
332 registered |= res != Filtered;
333 }
334 }
335
336 if ( !mIsActive || ( matchedAChild && !registered ) )
337 return Inactive;
338 else if ( registered )
339 return Registered;
340 else
341 return Filtered;
342}
343
344
345bool QgsRuleBased3DRenderer::Rule::isFilterOK( const QgsFeature &f, Qgs3DRenderContext &context ) const
346{
347 if ( !mFilter || mElseRule )
348 return true;
349
350 context.expressionContext().setFeature( f );
351 QVariant res = mFilter->evaluate( &context.expressionContext() );
352 return res.toInt() != 0;
353}
354
355
357
358
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