1 /***************************************************************************
2     qgsrubberband.cpp - Rubberband widget for drawing multilines and polygons
3      --------------------------------------
4     Date                 : 07-Jan-2006
5     Copyright            : (C) 2006 by Tom Elwertowski
6     Email                : telwertowski at users dot sourceforge dot net
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 "qgsrubberband.h"
17 #include "qgsgeometry.h"
18 #include "qgslogger.h"
19 #include "qgsmapcanvas.h"
20 #include "qgsvectorlayer.h"
21 #include "qgsproject.h"
22 #include "qgsrectangle.h"
23 #include "qgssymbol.h"
24 #include "qgsrendercontext.h"
25 #include "qgslinesymbol.h"
26 #include "qgsfillsymbol.h"
27 
28 #include <QPainter>
29 
QgsRubberBand(QgsMapCanvas * mapCanvas,QgsWkbTypes::GeometryType geometryType)30 QgsRubberBand::QgsRubberBand( QgsMapCanvas *mapCanvas, QgsWkbTypes::GeometryType geometryType )
31   : QObject( nullptr )
32   , QgsMapCanvasItem( mapCanvas )
33   , mGeometryType( geometryType )
34 {
35   reset( geometryType );
36   QColor color( Qt::lightGray );
37   color.setAlpha( 63 );
38   setColor( color );
39   setWidth( 1 );
40   setLineStyle( Qt::SolidLine );
41   setBrushStyle( Qt::SolidPattern );
42   setSecondaryStrokeColor( QColor() );
43 }
44 
QgsRubberBand()45 QgsRubberBand::QgsRubberBand()
46   : QObject( nullptr )
47   , QgsMapCanvasItem( nullptr )
48 {
49 }
50 
51 QgsRubberBand::~QgsRubberBand() = default;
52 
setColor(const QColor & color)53 void QgsRubberBand::setColor( const QColor &color )
54 {
55   setStrokeColor( color );
56   setFillColor( color );
57 }
58 
setFillColor(const QColor & color)59 void QgsRubberBand::setFillColor( const QColor &color )
60 {
61   if ( mBrush.color() == color )
62     return;
63 
64   mBrush.setColor( color );
65 }
66 
setStrokeColor(const QColor & color)67 void QgsRubberBand::setStrokeColor( const QColor &color )
68 {
69   mPen.setColor( color );
70 }
71 
setSecondaryStrokeColor(const QColor & color)72 void QgsRubberBand::setSecondaryStrokeColor( const QColor &color )
73 {
74   mSecondaryPen.setColor( color );
75 }
76 
setWidth(int width)77 void QgsRubberBand::setWidth( int width )
78 {
79   mPen.setWidth( width );
80 }
81 
setIcon(IconType icon)82 void QgsRubberBand::setIcon( IconType icon )
83 {
84   mIconType = icon;
85 }
86 
setSvgIcon(const QString & path,QPoint drawOffset)87 void QgsRubberBand::setSvgIcon( const QString &path, QPoint drawOffset )
88 {
89   setIcon( ICON_SVG );
90   mSvgRenderer = std::make_unique<QSvgRenderer>( path );
91   mSvgOffset = drawOffset;
92 }
93 
setIconSize(int iconSize)94 void QgsRubberBand::setIconSize( int iconSize )
95 {
96   mIconSize = iconSize;
97 }
98 
setLineStyle(Qt::PenStyle penStyle)99 void QgsRubberBand::setLineStyle( Qt::PenStyle penStyle )
100 {
101   mPen.setStyle( penStyle );
102 }
103 
setBrushStyle(Qt::BrushStyle brushStyle)104 void QgsRubberBand::setBrushStyle( Qt::BrushStyle brushStyle )
105 {
106   mBrush.setStyle( brushStyle );
107 }
108 
reset(QgsWkbTypes::GeometryType geometryType)109 void QgsRubberBand::reset( QgsWkbTypes::GeometryType geometryType )
110 {
111   mPoints.clear();
112   mGeometryType = geometryType;
113   updateRect();
114   update();
115 }
116 
addPoint(const QgsPointXY & p,bool doUpdate,int geometryIndex,int ringIndex)117 void QgsRubberBand::addPoint( const QgsPointXY &p, bool doUpdate /* = true */, int geometryIndex, int ringIndex )
118 {
119   if ( geometryIndex < 0 )
120   {
121     geometryIndex = mPoints.size() - 1;
122   }
123 
124   if ( geometryIndex < 0 || geometryIndex > mPoints.size() )
125   {
126     return;
127   }
128 
129   if ( geometryIndex == mPoints.size() )
130   {
131     // since we're adding a geometry, ringIndex must be 0 or negative for last ring
132     if ( ringIndex > 0 )
133       return;
134     mPoints.append( QgsPolygonXY() );
135   }
136 
137   // negative ringIndex means last ring
138   if ( ringIndex < 0 )
139   {
140     if ( mPoints.at( geometryIndex ).isEmpty() )
141       ringIndex = 0;
142     else
143       ringIndex = mPoints.at( geometryIndex ).size() - 1;
144   }
145 
146   if ( ringIndex > mPoints.at( geometryIndex ).size() )
147     return;
148 
149   if ( ringIndex == mPoints.at( geometryIndex ).size() )
150   {
151     mPoints[geometryIndex].append( QgsPolylineXY() );
152     if ( mGeometryType != QgsWkbTypes::PointGeometry )
153       mPoints[geometryIndex][ringIndex].append( p );
154   }
155 
156   if ( mPoints.at( geometryIndex ).at( ringIndex ).size() == 2 &&
157        mPoints.at( geometryIndex ).at( ringIndex ).at( 0 ) == mPoints.at( geometryIndex ).at( ringIndex ).at( 1 ) )
158   {
159     mPoints[geometryIndex][ringIndex].last() = p;
160   }
161   else
162   {
163     mPoints[geometryIndex][ringIndex].append( p );
164   }
165 
166 
167   if ( doUpdate )
168   {
169     setVisible( true );
170     updateRect();
171     update();
172   }
173 }
174 
closePoints(bool doUpdate,int geometryIndex,int ringIndex)175 void QgsRubberBand::closePoints( bool doUpdate, int geometryIndex, int ringIndex )
176 {
177   if ( geometryIndex < 0 || ringIndex < 0 ||
178        mPoints.size() <= geometryIndex ||
179        mPoints.at( geometryIndex ).size() <= ringIndex ||
180        mPoints.at( geometryIndex ).at( ringIndex ).isEmpty() )
181   {
182     return;
183   }
184 
185   if ( mPoints.at( geometryIndex ).at( ringIndex ).constFirst() != mPoints.at( geometryIndex ).at( ringIndex ).constLast() )
186   {
187     mPoints[geometryIndex][ringIndex].append( mPoints.at( geometryIndex ).at( ringIndex ).constFirst() );
188   }
189 
190   if ( doUpdate )
191   {
192     setVisible( true );
193     updateRect();
194     update();
195   }
196 }
197 
198 
removePoint(int index,bool doUpdate,int geometryIndex,int ringIndex)199 void QgsRubberBand::removePoint( int index, bool doUpdate/* = true*/, int geometryIndex/* = 0*/, int ringIndex/* = 0*/ )
200 {
201 
202   if ( geometryIndex < 0 || ringIndex < 0 ||
203        mPoints.size() <= geometryIndex ||
204        mPoints.at( geometryIndex ).size() <= ringIndex ||
205        mPoints.at( geometryIndex ).at( ringIndex ).size() <= index ||
206        mPoints.at( geometryIndex ).at( ringIndex ).size() < -index ||
207        mPoints.at( geometryIndex ).at( ringIndex ).isEmpty() )
208   {
209     return;
210   }
211 
212   // negative index removes from end, e.g., -1 removes last one
213   if ( index < 0 )
214   {
215     index = mPoints.at( geometryIndex ).at( ringIndex ).size() + index;
216   }
217   mPoints[geometryIndex][ringIndex].removeAt( index );
218 
219   if ( doUpdate )
220   {
221     updateRect();
222     update();
223   }
224 }
225 
removeLastPoint(int geometryIndex,bool doUpdate,int ringIndex)226 void QgsRubberBand::removeLastPoint( int geometryIndex, bool doUpdate/* = true*/, int ringIndex/* = 0*/ )
227 {
228   removePoint( -1, doUpdate, geometryIndex, ringIndex );
229 }
230 
movePoint(const QgsPointXY & p,int geometryIndex,int ringIndex)231 void QgsRubberBand::movePoint( const QgsPointXY &p, int geometryIndex, int ringIndex )
232 {
233   if ( geometryIndex < 0 || ringIndex < 0 ||
234        mPoints.size() <= geometryIndex ||
235        mPoints.at( geometryIndex ).size() <= ringIndex ||
236        mPoints.at( geometryIndex ).at( ringIndex ).isEmpty() )
237   {
238     return;
239   }
240 
241   mPoints[geometryIndex][ringIndex].last() = p;
242 
243   updateRect();
244   update();
245 }
246 
movePoint(int index,const QgsPointXY & p,int geometryIndex,int ringIndex)247 void QgsRubberBand::movePoint( int index, const QgsPointXY &p, int geometryIndex, int ringIndex )
248 {
249   if ( geometryIndex < 0 || ringIndex < 0 || index < 0 ||
250        mPoints.size() <= geometryIndex ||
251        mPoints.at( geometryIndex ).size() <= ringIndex ||
252        mPoints.at( geometryIndex ).at( ringIndex ).size() <= index )
253   {
254     return;
255   }
256 
257   mPoints[geometryIndex][ringIndex][index] = p;
258 
259   updateRect();
260   update();
261 }
262 
setToGeometry(const QgsGeometry & geom,QgsVectorLayer * layer)263 void QgsRubberBand::setToGeometry( const QgsGeometry &geom, QgsVectorLayer *layer )
264 {
265   if ( geom.isNull() )
266   {
267     reset( mGeometryType );
268     return;
269   }
270 
271   reset( geom.type() );
272   addGeometry( geom, layer );
273 }
274 
setToGeometry(const QgsGeometry & geom,const QgsCoordinateReferenceSystem & crs)275 void QgsRubberBand::setToGeometry( const QgsGeometry &geom, const QgsCoordinateReferenceSystem &crs )
276 {
277   if ( geom.isNull() )
278   {
279     reset( mGeometryType );
280     return;
281   }
282 
283   reset( geom.type() );
284   addGeometry( geom, crs );
285 }
286 
addGeometry(const QgsGeometry & geometry,QgsMapLayer * layer,bool doUpdate)287 void QgsRubberBand::addGeometry( const QgsGeometry &geometry, QgsMapLayer *layer, bool doUpdate )
288 {
289   QgsGeometry geom = geometry;
290   if ( layer )
291   {
292     QgsCoordinateTransform ct = mMapCanvas->mapSettings().layerTransform( layer );
293     try
294     {
295       geom.transform( ct );
296     }
297     catch ( QgsCsException & )
298     {
299       return;
300     }
301   }
302 
303   addGeometry( geom, QgsCoordinateReferenceSystem(), doUpdate );
304 }
305 
addGeometry(const QgsGeometry & geometry,const QgsCoordinateReferenceSystem & crs,bool doUpdate)306 void QgsRubberBand::addGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &crs, bool doUpdate )
307 {
308   if ( geometry.isEmpty() )
309   {
310     return;
311   }
312 
313   //maprender object of canvas
314   const QgsMapSettings &ms = mMapCanvas->mapSettings();
315 
316   int idx = mPoints.size();
317 
318   QgsGeometry geom = geometry;
319   if ( crs.isValid() )
320   {
321     QgsCoordinateTransform ct( crs, ms.destinationCrs(), QgsProject::instance() );
322     try
323     {
324       geom.transform( ct );
325     }
326     catch ( QgsCsException & )
327     {
328       QgsDebugMsg( QStringLiteral( "Could not transform rubber band geometry to map CRS" ) );
329       return;
330     }
331   }
332 
333   QgsWkbTypes::Type geomType = geom.wkbType();
334   if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::PointGeometry && !QgsWkbTypes::isMultiType( geomType ) )
335   {
336     QgsPointXY pt = geom.asPoint();
337     addPoint( pt, false, idx );
338   }
339   else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::PointGeometry && QgsWkbTypes::isMultiType( geomType ) )
340   {
341     const QgsMultiPointXY mpt = geom.asMultiPoint();
342     for ( const QgsPointXY &pt : mpt )
343     {
344       addPoint( pt, false, idx );
345       idx++;
346     }
347   }
348   else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::LineGeometry && !QgsWkbTypes::isMultiType( geomType ) )
349   {
350     const QgsPolylineXY line = geom.asPolyline();
351     for ( const QgsPointXY &pt : line )
352     {
353       addPoint( pt, false, idx );
354     }
355   }
356   else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::LineGeometry && QgsWkbTypes::isMultiType( geomType ) )
357   {
358     const QgsMultiPolylineXY mline = geom.asMultiPolyline();
359     for ( const QgsPolylineXY &line : mline )
360     {
361       if ( line.isEmpty() )
362       {
363         continue;
364       }
365       for ( const QgsPointXY &pt : line )
366       {
367         addPoint( pt, false, idx );
368       }
369       idx++;
370     }
371   }
372   else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::PolygonGeometry && !QgsWkbTypes::isMultiType( geomType ) )
373   {
374     const QgsPolygonXY poly = geom.asPolygon();
375     int ringIdx = 0;
376     for ( const QgsPolylineXY &ring : poly )
377     {
378       for ( const QgsPointXY &pt : ring )
379       {
380         addPoint( pt, false, idx, ringIdx );
381       }
382       ringIdx++;
383     }
384   }
385   else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::PolygonGeometry && QgsWkbTypes::isMultiType( geomType ) )
386   {
387     const QgsMultiPolygonXY multipoly = geom.asMultiPolygon();
388     for ( const QgsPolygonXY &poly : multipoly )
389     {
390       if ( poly.isEmpty() )
391         continue;
392 
393       int ringIdx = 0;
394       for ( const QgsPolylineXY &ring : poly )
395       {
396         for ( const QgsPointXY &pt : ring )
397         {
398           addPoint( pt, false, idx, ringIdx );
399         }
400         ringIdx++;
401       }
402       idx++;
403     }
404   }
405   else
406   {
407     return;
408   }
409 
410   setVisible( true );
411   if ( doUpdate )
412   {
413     updateRect();
414     update();
415   }
416 }
417 
setToCanvasRectangle(QRect rect)418 void QgsRubberBand::setToCanvasRectangle( QRect rect )
419 {
420   if ( !mMapCanvas )
421   {
422     return;
423   }
424 
425   const QgsMapToPixel *transform = mMapCanvas->getCoordinateTransform();
426   QgsPointXY ll = transform->toMapCoordinates( rect.left(), rect.bottom() );
427   QgsPointXY lr = transform->toMapCoordinates( rect.right(), rect.bottom() );
428   QgsPointXY ul = transform->toMapCoordinates( rect.left(), rect.top() );
429   QgsPointXY ur = transform->toMapCoordinates( rect.right(), rect.top() );
430 
431   reset( QgsWkbTypes::PolygonGeometry );
432   addPoint( ll, false );
433   addPoint( lr, false );
434   addPoint( ur, false );
435   addPoint( ul, true );
436 }
437 
copyPointsFrom(const QgsRubberBand * other)438 void QgsRubberBand::copyPointsFrom( const QgsRubberBand *other )
439 {
440   reset( other->mGeometryType );
441   mPoints = other->mPoints;
442   updateRect();
443   update();
444 }
445 
paint(QPainter * p)446 void QgsRubberBand::paint( QPainter *p )
447 {
448   if ( mPoints.isEmpty() )
449     return;
450 
451   QVector< QVector<QPolygonF> > shapes;
452   shapes.reserve( mPoints.size() );
453   for ( const QgsPolygonXY &poly : std::as_const( mPoints ) )
454   {
455     QVector<QPolygonF> rings;
456     rings.reserve( poly.size() );
457     for ( const QgsPolylineXY &line : poly )
458     {
459       QVector<QPointF> pts;
460       pts.reserve( line.size() );
461       for ( const QgsPointXY &pt : line )
462       {
463         const QPointF cur = toCanvasCoordinates( QgsPointXY( pt.x() + mTranslationOffsetX, pt.y() + mTranslationOffsetY ) ) - pos();
464         if ( pts.isEmpty() || std::abs( pts.last().x() - cur.x() ) > 1 ||  std::abs( pts.last().y() - cur.y() ) > 1 )
465           pts.append( cur );
466       }
467       rings.append( pts );
468     }
469     shapes.append( rings );
470   }
471 
472   if ( QgsLineSymbol *lineSymbol = dynamic_cast< QgsLineSymbol * >( mSymbol.get() ) )
473   {
474     QgsRenderContext context( QgsRenderContext::fromQPainter( p ) );
475     context.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
476 
477     lineSymbol->startRender( context );
478     for ( const QVector<QPolygonF> &shape : std::as_const( shapes ) )
479     {
480       for ( const QPolygonF &ring : shape )
481       {
482         lineSymbol->renderPolyline( ring, nullptr, context );
483       }
484     }
485     lineSymbol->stopRender( context );
486   }
487   else if ( QgsFillSymbol *fillSymbol = dynamic_cast< QgsFillSymbol * >( mSymbol.get() ) )
488   {
489     QgsRenderContext context( QgsRenderContext::fromQPainter( p ) );
490     context.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
491 
492     fillSymbol->startRender( context );
493     for ( const QVector<QPolygonF> &shape : std::as_const( shapes ) )
494     {
495       for ( const QPolygonF &ring : shape )
496       {
497         fillSymbol->renderPolygon( ring, nullptr, nullptr, context );
498       }
499     }
500     fillSymbol->stopRender( context );
501   }
502   else
503   {
504     int iterations = mSecondaryPen.color().isValid() ? 2 : 1;
505     for ( int i = 0; i < iterations; ++i )
506     {
507       if ( i == 0 && iterations > 1 )
508       {
509         // first iteration with multi-pen painting, so use secondary pen
510         mSecondaryPen.setWidth( mPen.width() + 2 );
511         p->setBrush( Qt::NoBrush );
512         p->setPen( mSecondaryPen );
513       }
514       else
515       {
516         // "top" layer, use primary pen/brush
517         p->setBrush( mBrush );
518         p->setPen( mPen );
519       }
520 
521       for ( const QVector<QPolygonF> &shape : std::as_const( shapes ) )
522       {
523         drawShape( p, shape );
524       }
525     }
526   }
527 }
528 
drawShape(QPainter * p,const QVector<QPolygonF> & rings)529 void QgsRubberBand::drawShape( QPainter *p, const QVector<QPolygonF> &rings )
530 {
531   if ( rings.size() == 1 )
532   {
533     drawShape( p, rings.at( 0 ) );
534   }
535   else
536   {
537     QPainterPath path;
538     for ( const QPolygonF &poly : rings )
539     {
540       path.addPolygon( poly );
541     }
542     p->drawPath( path );
543   }
544 }
545 
drawShape(QPainter * p,const QVector<QPointF> & pts)546 void QgsRubberBand::drawShape( QPainter *p, const QVector<QPointF> &pts )
547 {
548   switch ( mGeometryType )
549   {
550     case QgsWkbTypes::PolygonGeometry:
551     {
552       p->drawPolygon( pts );
553     }
554     break;
555 
556     case QgsWkbTypes::PointGeometry:
557     {
558       const auto constPts = pts;
559       for ( QPointF pt : constPts )
560       {
561         double x = pt.x();
562         double y = pt.y();
563 
564         qreal s = ( mIconSize - 1 ) / 2.0;
565 
566         switch ( mIconType )
567         {
568           case ICON_NONE:
569             break;
570 
571           case ICON_CROSS:
572             p->drawLine( QLineF( x - s, y, x + s, y ) );
573             p->drawLine( QLineF( x, y - s, x, y + s ) );
574             break;
575 
576           case ICON_X:
577             p->drawLine( QLineF( x - s, y - s, x + s, y + s ) );
578             p->drawLine( QLineF( x - s, y + s, x + s, y - s ) );
579             break;
580 
581           case ICON_BOX:
582             p->drawLine( QLineF( x - s, y - s, x + s, y - s ) );
583             p->drawLine( QLineF( x + s, y - s, x + s, y + s ) );
584             p->drawLine( QLineF( x + s, y + s, x - s, y + s ) );
585             p->drawLine( QLineF( x - s, y + s, x - s, y - s ) );
586             break;
587 
588           case ICON_FULL_BOX:
589             p->drawRect( static_cast< int>( x - s ), static_cast< int >( y - s ), mIconSize, mIconSize );
590             break;
591 
592           case ICON_CIRCLE:
593             p->drawEllipse( static_cast< int >( x - s ), static_cast< int >( y - s ), mIconSize, mIconSize );
594             break;
595 
596           case ICON_DIAMOND:
597           case ICON_FULL_DIAMOND:
598           {
599             QPointF pts[] =
600             {
601               QPointF( x, y - s ),
602               QPointF( x + s, y ),
603               QPointF( x, y + s ),
604               QPointF( x - s, y )
605             };
606             if ( mIconType == ICON_FULL_DIAMOND )
607               p->drawPolygon( pts, 4 );
608             else
609               p->drawPolyline( pts, 4 );
610             break;
611           }
612 
613           case ICON_SVG:
614           {
615             QRectF viewBox = mSvgRenderer->viewBoxF();
616             QRectF r( mSvgOffset.x(), mSvgOffset.y(), viewBox.width(), viewBox.height() );
617             QgsScopedQPainterState painterState( p );
618             p->translate( pt );
619             mSvgRenderer->render( p, r );
620             break;
621           }
622         }
623       }
624     }
625     break;
626 
627     case QgsWkbTypes::LineGeometry:
628     default:
629     {
630       p->drawPolyline( pts );
631     }
632     break;
633   }
634 }
635 
updateRect()636 void QgsRubberBand::updateRect()
637 {
638   if ( mPoints.isEmpty() )
639   {
640     setRect( QgsRectangle() );
641     setVisible( false );
642     return;
643   }
644 
645   const QgsMapToPixel &m2p = *( mMapCanvas->getCoordinateTransform() );
646 
647 #if 0 // unused?
648   double iconSize = ( mIconSize + 1 ) / 2.;
649   if ( mSvgRenderer )
650   {
651     QRectF viewBox = mSvgRenderer->viewBoxF();
652     iconSize = std::max( std::fabs( mSvgOffset.x() ) + .5 * viewBox.width(), std::fabs( mSvgOffset.y() ) + .5 * viewBox.height() );
653   }
654 #endif
655 
656   qreal w = ( ( mIconSize - 1 ) / 2 + mPen.width() ); // in canvas units
657 
658   QgsRectangle r;  // in canvas units
659   for ( const QgsPolygonXY &poly : std::as_const( mPoints ) )
660   {
661     for ( const QgsPointXY &point : poly.at( 0 ) )
662     {
663       QgsPointXY p( point.x() + mTranslationOffsetX, point.y() + mTranslationOffsetY );
664       p = m2p.transform( p );
665       // no need to normalize the rectangle -- we know it is already normal
666       QgsRectangle rect( p.x() - w, p.y() - w, p.x() + w, p.y() + w, false );
667       r.combineExtentWith( rect );
668     }
669   }
670 
671   // This is an hack to pass QgsMapCanvasItem::setRect what it
672   // expects (encoding of position and size of the item)
673   qreal res = m2p.mapUnitsPerPixel();
674   QgsPointXY topLeft = m2p.toMapCoordinates( r.xMinimum(), r.yMinimum() );
675   QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + r.width()*res, topLeft.y() - r.height()*res );
676 
677   setRect( rect );
678 }
679 
symbol() const680 QgsSymbol *QgsRubberBand::symbol() const
681 {
682   return mSymbol.get();
683 }
684 
setSymbol(QgsSymbol * symbol)685 void QgsRubberBand::setSymbol( QgsSymbol *symbol )
686 {
687   mSymbol.reset( symbol );
688 }
689 
updatePosition()690 void QgsRubberBand::updatePosition()
691 {
692   // re-compute rectangle
693   // See https://github.com/qgis/QGIS/issues/20566
694   // NOTE: could be optimized by saving map-extent
695   //       of rubberband and simply re-projecting
696   //       that to device-rectangle on "updatePosition"
697   updateRect();
698 }
699 
setTranslationOffset(double dx,double dy)700 void QgsRubberBand::setTranslationOffset( double dx, double dy )
701 {
702   mTranslationOffsetX = dx;
703   mTranslationOffsetY = dy;
704   updateRect();
705 }
706 
size() const707 int QgsRubberBand::size() const
708 {
709   return mPoints.size();
710 }
711 
partSize(int geometryIndex) const712 int QgsRubberBand::partSize( int geometryIndex ) const
713 {
714   if ( geometryIndex < 0 ||
715        geometryIndex >= mPoints.size() ||
716        mPoints.at( geometryIndex ).isEmpty() )
717     return 0;
718   return mPoints.at( geometryIndex ).at( 0 ).size();
719 }
720 
numberOfVertices() const721 int QgsRubberBand::numberOfVertices() const
722 {
723   int count = 0;
724   for ( const QgsPolygonXY &poly : std::as_const( mPoints ) )
725   {
726     for ( const QgsPolylineXY &ring : poly )
727     {
728       count += ring.size();
729     }
730   }
731   return count;
732 }
733 
getPoint(int i,int j,int ringIndex) const734 const QgsPointXY *QgsRubberBand::getPoint( int i, int j, int ringIndex ) const
735 {
736   if ( i < 0 || ringIndex < 0 || j < 0 ||
737        mPoints.size() <= i ||
738        mPoints.at( i ).size() <= ringIndex ||
739        mPoints.at( i ).at( ringIndex ).size() <= j )
740     return nullptr;
741   else
742     return &mPoints[i][ringIndex][j];
743 }
744 
asGeometry() const745 QgsGeometry QgsRubberBand::asGeometry() const
746 {
747   QgsGeometry geom;
748 
749   switch ( mGeometryType )
750   {
751     case QgsWkbTypes::PolygonGeometry:
752     {
753       geom = QgsGeometry::fromMultiPolygonXY( mPoints );
754       break;
755     }
756 
757     case QgsWkbTypes::PointGeometry:
758     {
759       QgsMultiPointXY multiPoint;
760 
761       for ( const QgsPolygonXY &poly : std::as_const( mPoints ) )
762       {
763         if ( poly.isEmpty() )
764           continue;
765         multiPoint.append( poly.at( 0 ) );
766       }
767       geom = QgsGeometry::fromMultiPointXY( multiPoint );
768       break;
769     }
770 
771     case QgsWkbTypes::LineGeometry:
772     default:
773     {
774       if ( !mPoints.isEmpty() )
775       {
776         if ( mPoints.size() > 1 )
777         {
778           QgsMultiPolylineXY multiPolyline;
779           for ( const QgsPolygonXY &poly : std::as_const( mPoints ) )
780           {
781             if ( poly.isEmpty() )
782               continue;
783             multiPolyline.append( poly.at( 0 ) );
784           }
785           geom = QgsGeometry::fromMultiPolylineXY( multiPolyline );
786         }
787         else
788         {
789           if ( !mPoints.at( 0 ).isEmpty() )
790             geom = QgsGeometry::fromPolylineXY( mPoints.at( 0 ).at( 0 ) );
791           else
792             geom = QgsGeometry::fromPolylineXY( QgsPolylineXY() );
793         }
794       }
795       break;
796     }
797   }
798   return geom;
799 }
800