Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsmergedfeaturerenderer.h
3 : : ---------------------
4 : : begin : December 2020
5 : : copyright : (C) 2020 by Nyall Dawson
6 : : email : nyall dot dawson 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 "qgsmergedfeaturerenderer.h"
17 : :
18 : : #include "qgssymbol.h"
19 : : #include "qgssymbollayerutils.h"
20 : :
21 : : #include "qgslogger.h"
22 : : #include "qgsfeature.h"
23 : : #include "qgsvectorlayer.h"
24 : : #include "qgssymbollayer.h"
25 : : #include "qgsogcutils.h"
26 : : #include "qgspainteffect.h"
27 : : #include "qgspainteffectregistry.h"
28 : : #include "qgsstyleentityvisitor.h"
29 : :
30 : : #include <QDomDocument>
31 : : #include <QDomElement>
32 : :
33 : 0 : QgsMergedFeatureRenderer::QgsMergedFeatureRenderer( QgsFeatureRenderer *subRenderer )
34 : 0 : : QgsMergedFeatureRenderer( QStringLiteral( "mergedFeatureRenderer" ), subRenderer )
35 : : {
36 : :
37 : 0 : }
38 : :
39 : 0 : QgsMergedFeatureRenderer::QgsMergedFeatureRenderer( const QString &type, QgsFeatureRenderer *subRenderer )
40 : 0 : : QgsFeatureRenderer( type )
41 : 0 : {
42 : 0 : if ( subRenderer )
43 : : {
44 : 0 : mSubRenderer.reset( subRenderer );
45 : 0 : }
46 : 0 : }
47 : :
48 : 0 : void QgsMergedFeatureRenderer::setEmbeddedRenderer( QgsFeatureRenderer *subRenderer )
49 : : {
50 : 0 : mSubRenderer.reset( subRenderer );
51 : 0 : }
52 : :
53 : 0 : const QgsFeatureRenderer *QgsMergedFeatureRenderer::embeddedRenderer() const
54 : : {
55 : 0 : return mSubRenderer.get();
56 : : }
57 : :
58 : 0 : void QgsMergedFeatureRenderer::setLegendSymbolItem( const QString &key, QgsSymbol *symbol )
59 : : {
60 : 0 : if ( !mSubRenderer )
61 : 0 : return;
62 : :
63 : 0 : mSubRenderer->setLegendSymbolItem( key, symbol );
64 : 0 : }
65 : :
66 : 0 : bool QgsMergedFeatureRenderer::legendSymbolItemsCheckable() const
67 : : {
68 : 0 : if ( !mSubRenderer )
69 : 0 : return false;
70 : :
71 : 0 : return mSubRenderer->legendSymbolItemsCheckable();
72 : 0 : }
73 : :
74 : 0 : bool QgsMergedFeatureRenderer::legendSymbolItemChecked( const QString &key )
75 : : {
76 : 0 : if ( !mSubRenderer )
77 : 0 : return false;
78 : :
79 : 0 : return mSubRenderer->legendSymbolItemChecked( key );
80 : 0 : }
81 : :
82 : 0 : void QgsMergedFeatureRenderer::checkLegendSymbolItem( const QString &key, bool state )
83 : : {
84 : 0 : if ( !mSubRenderer )
85 : 0 : return;
86 : :
87 : 0 : mSubRenderer->checkLegendSymbolItem( key, state );
88 : 0 : }
89 : :
90 : 0 : bool QgsMergedFeatureRenderer::accept( QgsStyleEntityVisitorInterface *visitor ) const
91 : : {
92 : 0 : if ( !mSubRenderer )
93 : 0 : return true;
94 : :
95 : 0 : return mSubRenderer->accept( visitor );
96 : 0 : }
97 : :
98 : 0 : void QgsMergedFeatureRenderer::startRender( QgsRenderContext &context, const QgsFields &fields )
99 : : {
100 : 0 : QgsFeatureRenderer::startRender( context, fields );
101 : :
102 : 0 : if ( !mSubRenderer )
103 : : {
104 : 0 : return;
105 : : }
106 : :
107 : : // first call start render on the sub renderer
108 : 0 : mSubRenderer->startRender( context, fields );
109 : :
110 : 0 : mFeaturesCategories.clear();
111 : 0 : mSymbolCategories.clear();
112 : 0 : mFeatureDecorations.clear();
113 : 0 : mFields = fields;
114 : :
115 : : // We compute coordinates of the extent which will serve as exterior ring
116 : : // for the final polygon
117 : : // It must be computed in the destination CRS if reprojection is enabled.
118 : :
119 : 0 : if ( !context.painter() )
120 : : {
121 : 0 : return;
122 : : }
123 : :
124 : : // copy the rendering context
125 : 0 : mContext = context;
126 : :
127 : : // If reprojection is enabled, we must reproject during renderFeature
128 : : // and act as if there is no reprojection
129 : : // If we don't do that, there is no need to have a simple rectangular extent
130 : : // that covers the whole screen
131 : : // (a rectangle in the destCRS cannot be expressed as valid coordinates in the sourceCRS in general)
132 : 0 : if ( context.coordinateTransform().isValid() )
133 : : {
134 : : // disable projection
135 : 0 : mContext.setCoordinateTransform( QgsCoordinateTransform() );
136 : : // recompute extent so that polygon clipping is correct
137 : 0 : mContext.setExtent( context.mapExtent() );
138 : : // do we have to recompute the MapToPixel ?
139 : 0 : }
140 : :
141 : 0 : switch ( mOperation )
142 : : {
143 : : case InvertOnly:
144 : : case MergeAndInvert:
145 : : {
146 : : // convert viewport to dest CRS
147 : : // add some space to hide borders and tend to infinity
148 : 0 : const double buffer = std::max( context.mapExtent().width(), context.mapExtent().height() ) * 0.1;
149 : 0 : const QRectF outer = context.mapExtent().buffered( buffer ).toRectF();
150 : 0 : QgsPolylineXY exteriorRing;
151 : 0 : exteriorRing.reserve( 5 );
152 : 0 : exteriorRing << outer.topLeft();
153 : 0 : exteriorRing << outer.topRight();
154 : 0 : exteriorRing << outer.bottomRight();
155 : 0 : exteriorRing << outer.bottomLeft();
156 : 0 : exteriorRing << outer.topLeft();
157 : :
158 : 0 : mExtentPolygon.clear();
159 : 0 : mExtentPolygon.append( exteriorRing );
160 : : break;
161 : 0 : }
162 : :
163 : : case Merge:
164 : 0 : break;
165 : : }
166 : 0 : }
167 : :
168 : 0 : bool QgsMergedFeatureRenderer::renderFeature( const QgsFeature &feature, QgsRenderContext &context, int layer, bool selected, bool drawVertexMarker )
169 : : {
170 : 0 : if ( !context.painter() || !mSubRenderer )
171 : : {
172 : 0 : return false;
173 : : }
174 : :
175 : : // store this feature as a feature to render with decoration if needed
176 : 0 : if ( selected || drawVertexMarker )
177 : : {
178 : 0 : mFeatureDecorations.append( FeatureDecoration( feature, selected, drawVertexMarker, layer ) );
179 : 0 : }
180 : :
181 : : // Features are grouped by category of symbols (returned by symbol(s)ForFeature)
182 : : // This way, users can have multiple inverted polygon fills for a layer,
183 : : // for instance, with rule based renderer and different symbols
184 : : // that have transparency.
185 : : //
186 : : // In order to assign a unique category to a set of symbols
187 : : // during each rendering session (between startRender() and stopRender()),
188 : : // we build an unique id as a QByteArray that is the concatenation
189 : : // of each symbol's memory address.
190 : : // The only assumption made here is that symbol(s)ForFeature will
191 : : // always return the same address for the same symbol(s) shared amongst
192 : : // different features.
193 : : // This QByteArray can then be used as a key for a QMap where the list of
194 : : // features for this category is stored
195 : 0 : QByteArray catId;
196 : 0 : if ( capabilities() & MoreSymbolsPerFeature )
197 : : {
198 : 0 : const QgsSymbolList syms( mSubRenderer->symbolsForFeature( feature, context ) );
199 : 0 : for ( QgsSymbol *sym : syms )
200 : : {
201 : : // append the memory address
202 : 0 : catId.append( reinterpret_cast<const char *>( &sym ), sizeof( sym ) );
203 : : }
204 : 0 : }
205 : : else
206 : : {
207 : 0 : if ( QgsSymbol *sym = mSubRenderer->symbolForFeature( feature, context ) )
208 : : {
209 : 0 : catId.append( reinterpret_cast<const char *>( &sym ), sizeof( sym ) );
210 : 0 : }
211 : : }
212 : :
213 : 0 : if ( catId.isEmpty() )
214 : : {
215 : 0 : return false;
216 : : }
217 : :
218 : 0 : if ( ! mSymbolCategories.contains( catId ) )
219 : : {
220 : 0 : CombinedFeature cFeat;
221 : : // store the first feature
222 : 0 : cFeat.feature = feature;
223 : 0 : mSymbolCategories.insert( catId, mSymbolCategories.count() );
224 : 0 : mFeaturesCategories.append( cFeat );
225 : 0 : }
226 : :
227 : : // update the geometry
228 : 0 : CombinedFeature &cFeat = mFeaturesCategories[ mSymbolCategories[catId] ];
229 : 0 : if ( !feature.hasGeometry() )
230 : : {
231 : 0 : return false;
232 : : }
233 : 0 : QgsGeometry geom = feature.geometry();
234 : :
235 : 0 : QgsCoordinateTransform xform = context.coordinateTransform();
236 : 0 : if ( xform.isValid() )
237 : : {
238 : 0 : geom.transform( xform );
239 : 0 : }
240 : :
241 : 0 : switch ( mOperation )
242 : : {
243 : : case QgsMergedFeatureRenderer::MergeAndInvert:
244 : : // fix the polygon if it is not valid
245 : 0 : if ( ! geom.isGeosValid() )
246 : : {
247 : 0 : geom = geom.buffer( 0, 0 );
248 : 0 : }
249 : 0 : break;
250 : :
251 : : case QgsMergedFeatureRenderer::InvertOnly:
252 : : case QgsMergedFeatureRenderer::Merge: // maybe we should also fix for this? not sure if the fixing step was only required for the differencing operation...
253 : 0 : break;
254 : : }
255 : :
256 : 0 : if ( geom.isNull() )
257 : 0 : return false; // do not let invalid geometries sneak in!
258 : :
259 : : // add the geometry to the list of geometries for this feature
260 : 0 : cFeat.geometries.append( geom );
261 : :
262 : 0 : return true;
263 : 0 : }
264 : :
265 : 0 : void QgsMergedFeatureRenderer::stopRender( QgsRenderContext &context )
266 : : {
267 : 0 : QgsFeatureRenderer::stopRender( context );
268 : 0 : if ( context.renderingStopped() )
269 : : {
270 : 0 : if ( mSubRenderer )
271 : 0 : mSubRenderer->stopRender( mContext );
272 : 0 : return;
273 : : }
274 : :
275 : 0 : if ( !mSubRenderer )
276 : : {
277 : 0 : return;
278 : : }
279 : 0 : if ( !context.painter() )
280 : : {
281 : 0 : return;
282 : : }
283 : :
284 : 0 : QgsMultiPolygonXY finalMulti; //avoid expensive allocation for list for every feature
285 : 0 : QgsPolygonXY newPoly;
286 : :
287 : 0 : for ( const CombinedFeature &cit : std::as_const( mFeaturesCategories ) )
288 : : {
289 : 0 : finalMulti.resize( 0 ); //preserve capacity - don't use clear!
290 : 0 : QgsFeature feat = cit.feature; // just a copy, so that we do not accumulate geometries again
291 : :
292 : 0 : switch ( mOperation )
293 : : {
294 : : case QgsMergedFeatureRenderer::Merge:
295 : : {
296 : 0 : QgsGeometry unioned( QgsGeometry::unaryUnion( cit.geometries ) );
297 : 0 : if ( unioned.type() == QgsWkbTypes::LineGeometry )
298 : 0 : unioned = unioned.mergeLines();
299 : 0 : feat.setGeometry( unioned );
300 : : break;
301 : 0 : }
302 : :
303 : : case QgsMergedFeatureRenderer::MergeAndInvert:
304 : : {
305 : : // compute the unary union on the polygons
306 : 0 : const QgsGeometry unioned( QgsGeometry::unaryUnion( cit.geometries ) );
307 : : // compute the difference with the extent
308 : 0 : const QgsGeometry rect = QgsGeometry::fromPolygonXY( mExtentPolygon );
309 : 0 : const QgsGeometry final = rect.difference( unioned );
310 : 0 : feat.setGeometry( final );
311 : : break;
312 : 0 : }
313 : :
314 : : case QgsMergedFeatureRenderer::InvertOnly:
315 : : {
316 : : // No preprocessing involved.
317 : : // We build here a "reversed" geometry of all the polygons
318 : : //
319 : : // The final geometry is a multipolygon F, with :
320 : : // * the first polygon of F having the current extent as its exterior ring
321 : : // * each polygon's exterior ring is added as interior ring of the first polygon of F
322 : : // * each polygon's interior ring is added as new polygons in F
323 : : //
324 : : // No validity check is done, on purpose, it will be very slow and painting
325 : : // operations do not need geometries to be valid
326 : :
327 : 0 : finalMulti.append( mExtentPolygon );
328 : 0 : for ( const QgsGeometry &geom : std::as_const( cit.geometries ) )
329 : : {
330 : 0 : QgsMultiPolygonXY multi;
331 : 0 : QgsWkbTypes::Type type = QgsWkbTypes::flatType( geom.constGet()->wkbType() );
332 : :
333 : 0 : if ( ( type == QgsWkbTypes::Polygon ) || ( type == QgsWkbTypes::CurvePolygon ) )
334 : : {
335 : 0 : multi.append( geom.asPolygon() );
336 : 0 : }
337 : 0 : else if ( ( type == QgsWkbTypes::MultiPolygon ) || ( type == QgsWkbTypes::MultiSurface ) )
338 : : {
339 : 0 : multi = geom.asMultiPolygon();
340 : 0 : }
341 : :
342 : 0 : for ( int i = 0; i < multi.size(); i++ )
343 : : {
344 : 0 : const QgsPolylineXY &exterior = multi[i][0];
345 : : // add the exterior ring as interior ring to the first polygon
346 : : // make sure it satisfies at least very basic requirements of GEOS
347 : : // (otherwise the creation of GEOS geometry will fail)
348 : 0 : if ( exterior.count() < 4 || exterior[0] != exterior[exterior.count() - 1] )
349 : 0 : continue;
350 : 0 : finalMulti[0].append( exterior );
351 : :
352 : : // add interior rings as new polygons
353 : 0 : for ( int j = 1; j < multi[i].size(); j++ )
354 : : {
355 : 0 : newPoly.resize( 0 ); //preserve capacity - don't use clear!
356 : 0 : newPoly.append( multi[i][j] );
357 : 0 : finalMulti.append( newPoly );
358 : 0 : }
359 : 0 : }
360 : 0 : }
361 : 0 : feat.setGeometry( QgsGeometry::fromMultiPolygonXY( finalMulti ) );
362 : 0 : break;
363 : : }
364 : : }
365 : :
366 : 0 : if ( feat.hasGeometry() )
367 : : {
368 : 0 : mContext.expressionContext().setFeature( feat );
369 : 0 : mSubRenderer->renderFeature( feat, mContext );
370 : 0 : }
371 : 0 : }
372 : :
373 : : // when no features are visible, we still have to draw the exterior rectangle
374 : : // warning: when sub renderers have more than one possible symbols,
375 : : // there is no way to choose a correct one, because there is no attribute here
376 : : // in that case, nothing will be rendered
377 : 0 : switch ( mOperation )
378 : : {
379 : : case QgsMergedFeatureRenderer::Merge:
380 : 0 : break;
381 : : case QgsMergedFeatureRenderer::InvertOnly:
382 : : case QgsMergedFeatureRenderer::MergeAndInvert:
383 : 0 : if ( mFeaturesCategories.isEmpty() )
384 : : {
385 : : // empty feature with default attributes
386 : 0 : QgsFeature feat( mFields );
387 : 0 : feat.setGeometry( QgsGeometry::fromPolygonXY( mExtentPolygon ) );
388 : 0 : mSubRenderer->renderFeature( feat, mContext );
389 : 0 : }
390 : 0 : break;
391 : : }
392 : :
393 : : // draw feature decorations
394 : 0 : for ( FeatureDecoration deco : std::as_const( mFeatureDecorations ) )
395 : : {
396 : 0 : mSubRenderer->renderFeature( deco.feature, mContext, deco.layer, deco.selected, deco.drawMarkers );
397 : 0 : }
398 : :
399 : 0 : mSubRenderer->stopRender( mContext );
400 : 0 : }
401 : :
402 : 0 : QString QgsMergedFeatureRenderer::dump() const
403 : : {
404 : 0 : if ( !mSubRenderer )
405 : : {
406 : 0 : return QStringLiteral( "MERGED FEATURES: NULL" );
407 : : }
408 : 0 : return "MERGED FEATURES [" + mSubRenderer->dump() + ']';
409 : 0 : }
410 : :
411 : 0 : QgsMergedFeatureRenderer *QgsMergedFeatureRenderer::clone() const
412 : : {
413 : 0 : QgsMergedFeatureRenderer *newRenderer = nullptr;
414 : 0 : if ( !mSubRenderer )
415 : : {
416 : 0 : newRenderer = new QgsMergedFeatureRenderer( nullptr );
417 : 0 : }
418 : : else
419 : : {
420 : 0 : newRenderer = new QgsMergedFeatureRenderer( mSubRenderer->clone() );
421 : : }
422 : 0 : copyRendererData( newRenderer );
423 : 0 : return newRenderer;
424 : 0 : }
425 : :
426 : 0 : QgsFeatureRenderer *QgsMergedFeatureRenderer::create( QDomElement &element, const QgsReadWriteContext &context )
427 : : {
428 : 0 : QgsMergedFeatureRenderer *r = new QgsMergedFeatureRenderer( nullptr );
429 : : //look for an embedded renderer <renderer-v2>
430 : 0 : QDomElement embeddedRendererElem = element.firstChildElement( QStringLiteral( "renderer-v2" ) );
431 : 0 : if ( !embeddedRendererElem.isNull() )
432 : : {
433 : 0 : QgsFeatureRenderer *renderer = QgsFeatureRenderer::load( embeddedRendererElem, context );
434 : 0 : r->setEmbeddedRenderer( renderer );
435 : 0 : }
436 : 0 : return r;
437 : 0 : }
438 : :
439 : 0 : QDomElement QgsMergedFeatureRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
440 : : {
441 : : // clazy:skip
442 : :
443 : 0 : QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
444 : 0 : rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "mergedFeatureRenderer" ) );
445 : 0 : rendererElem.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
446 : :
447 : 0 : if ( mSubRenderer )
448 : : {
449 : 0 : QDomElement embeddedRendererElem = mSubRenderer->save( doc, context );
450 : 0 : rendererElem.appendChild( embeddedRendererElem );
451 : 0 : }
452 : :
453 : 0 : if ( mPaintEffect && !QgsPaintEffectRegistry::isDefaultStack( mPaintEffect ) )
454 : 0 : mPaintEffect->saveProperties( doc, rendererElem );
455 : :
456 : 0 : if ( !mOrderBy.isEmpty() )
457 : : {
458 : 0 : QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
459 : 0 : mOrderBy.save( orderBy );
460 : 0 : rendererElem.appendChild( orderBy );
461 : 0 : }
462 : 0 : rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
463 : :
464 : 0 : return rendererElem;
465 : 0 : }
466 : :
467 : 0 : QgsSymbol *QgsMergedFeatureRenderer::symbolForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
468 : : {
469 : 0 : if ( !mSubRenderer )
470 : : {
471 : 0 : return nullptr;
472 : : }
473 : 0 : return mSubRenderer->symbolForFeature( feature, context );
474 : 0 : }
475 : :
476 : 0 : QgsSymbol *QgsMergedFeatureRenderer::originalSymbolForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
477 : : {
478 : 0 : if ( !mSubRenderer )
479 : 0 : return nullptr;
480 : 0 : return mSubRenderer->originalSymbolForFeature( feature, context );
481 : 0 : }
482 : :
483 : 0 : QgsSymbolList QgsMergedFeatureRenderer::symbolsForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
484 : : {
485 : 0 : if ( !mSubRenderer )
486 : : {
487 : 0 : return QgsSymbolList();
488 : : }
489 : 0 : return mSubRenderer->symbolsForFeature( feature, context );
490 : 0 : }
491 : :
492 : 0 : QgsSymbolList QgsMergedFeatureRenderer::originalSymbolsForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
493 : : {
494 : 0 : if ( !mSubRenderer )
495 : 0 : return QgsSymbolList();
496 : 0 : return mSubRenderer->originalSymbolsForFeature( feature, context );
497 : 0 : }
498 : :
499 : 0 : QSet<QString> QgsMergedFeatureRenderer::legendKeysForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
500 : : {
501 : 0 : if ( !mSubRenderer )
502 : 0 : return QSet<QString>();
503 : 0 : return mSubRenderer->legendKeysForFeature( feature, context );
504 : 0 : }
505 : :
506 : 0 : QgsSymbolList QgsMergedFeatureRenderer::symbols( QgsRenderContext &context ) const
507 : : {
508 : 0 : if ( !mSubRenderer )
509 : : {
510 : 0 : return QgsSymbolList();
511 : : }
512 : 0 : return mSubRenderer->symbols( context );
513 : 0 : }
514 : :
515 : 0 : QgsFeatureRenderer::Capabilities QgsMergedFeatureRenderer::capabilities()
516 : : {
517 : 0 : if ( !mSubRenderer )
518 : : {
519 : 0 : return Capabilities();
520 : : }
521 : 0 : return mSubRenderer->capabilities();
522 : 0 : }
523 : :
524 : 0 : QSet<QString> QgsMergedFeatureRenderer::usedAttributes( const QgsRenderContext &context ) const
525 : : {
526 : 0 : if ( !mSubRenderer )
527 : : {
528 : 0 : return QSet<QString>();
529 : : }
530 : 0 : return mSubRenderer->usedAttributes( context );
531 : 0 : }
532 : :
533 : 0 : bool QgsMergedFeatureRenderer::filterNeedsGeometry() const
534 : : {
535 : 0 : return mSubRenderer ? mSubRenderer->filterNeedsGeometry() : false;
536 : : }
537 : :
538 : 0 : QgsLegendSymbolList QgsMergedFeatureRenderer::legendSymbolItems() const
539 : : {
540 : 0 : if ( !mSubRenderer )
541 : : {
542 : 0 : return QgsLegendSymbolList();
543 : : }
544 : 0 : return mSubRenderer->legendSymbolItems();
545 : 0 : }
546 : :
547 : 0 : bool QgsMergedFeatureRenderer::willRenderFeature( const QgsFeature &feature, QgsRenderContext &context ) const
548 : : {
549 : 0 : if ( !mSubRenderer )
550 : : {
551 : 0 : return false;
552 : : }
553 : 0 : return mSubRenderer->willRenderFeature( feature, context );
554 : 0 : }
555 : :
556 : 0 : QgsMergedFeatureRenderer *QgsMergedFeatureRenderer::convertFromRenderer( const QgsFeatureRenderer *renderer )
557 : : {
558 : 0 : if ( renderer->type() == QLatin1String( "mergedFeatureRenderer" ) )
559 : : {
560 : 0 : return dynamic_cast<QgsMergedFeatureRenderer *>( renderer->clone() );
561 : : }
562 : :
563 : 0 : if ( renderer->type() == QLatin1String( "singleSymbol" ) ||
564 : 0 : renderer->type() == QLatin1String( "categorizedSymbol" ) ||
565 : 0 : renderer->type() == QLatin1String( "graduatedSymbol" ) ||
566 : 0 : renderer->type() == QLatin1String( "RuleRenderer" ) )
567 : : {
568 : 0 : return new QgsMergedFeatureRenderer( renderer->clone() );
569 : : }
570 : 0 : else if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
571 : : {
572 : 0 : return new QgsMergedFeatureRenderer( renderer->embeddedRenderer() ? renderer->embeddedRenderer()->clone() : nullptr );
573 : : }
574 : 0 : return nullptr;
575 : 0 : }
576 : :
|