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