/*************************************************************************** qgsrubberband.cpp - Rubberband widget for drawing multilines and polygons -------------------------------------- Date : 07-Jan-2006 Copyright : (C) 2006 by Tom Elwertowski Email : telwertowski at users dot sourceforge dot net *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "qgsrubberband.h" #include "qgsfeature.h" #include "qgsgeometry.h" #include "qgslogger.h" #include "qgsmapcanvas.h" #include "qgsvectorlayer.h" #include "qgsproject.h" #include QgsRubberBand::QgsRubberBand( QgsMapCanvas *mapCanvas, QgsWkbTypes::GeometryType geometryType ) : QgsMapCanvasItem( mapCanvas ) , mGeometryType( geometryType ) { reset( geometryType ); QColor color( Qt::lightGray ); color.setAlpha( 63 ); setColor( color ); setWidth( 1 ); setLineStyle( Qt::SolidLine ); setBrushStyle( Qt::SolidPattern ); setSecondaryStrokeColor( QColor() ); } QgsRubberBand::QgsRubberBand() : QgsMapCanvasItem( nullptr ) { } void QgsRubberBand::setColor( const QColor &color ) { setStrokeColor( color ); setFillColor( color ); } void QgsRubberBand::setFillColor( const QColor &color ) { mBrush.setColor( color ); } void QgsRubberBand::setStrokeColor( const QColor &color ) { mPen.setColor( color ); } void QgsRubberBand::setSecondaryStrokeColor( const QColor &color ) { mSecondaryPen.setColor( color ); } void QgsRubberBand::setWidth( int width ) { mPen.setWidth( width ); } void QgsRubberBand::setIcon( IconType icon ) { mIconType = icon; } void QgsRubberBand::setIconSize( int iconSize ) { mIconSize = iconSize; } void QgsRubberBand::setLineStyle( Qt::PenStyle penStyle ) { mPen.setStyle( penStyle ); } void QgsRubberBand::setBrushStyle( Qt::BrushStyle brushStyle ) { mBrush.setStyle( brushStyle ); } void QgsRubberBand::reset( QgsWkbTypes::GeometryType geometryType ) { mPoints.clear(); mGeometryType = geometryType; updateRect(); update(); } void QgsRubberBand::addPoint( const QgsPointXY &p, bool doUpdate /* = true */, int geometryIndex ) { if ( geometryIndex < 0 ) { geometryIndex = mPoints.size() - 1; } if ( geometryIndex < 0 || geometryIndex > mPoints.size() ) { return; } if ( geometryIndex == mPoints.size() ) { mPoints.push_back( QList() << p ); } if ( mPoints.at( geometryIndex ).size() == 2 && mPoints.at( geometryIndex ).at( 0 ) == mPoints.at( geometryIndex ).at( 1 ) ) { mPoints[geometryIndex].last() = p; } else { mPoints[geometryIndex] << p; } if ( doUpdate ) { setVisible( true ); updateRect(); update(); } } void QgsRubberBand::closePoints( bool doUpdate, int geometryIndex ) { if ( geometryIndex < 0 || geometryIndex >= mPoints.size() ) { return; } if ( mPoints.at( geometryIndex ).at( 0 ) != mPoints.at( geometryIndex ).at( mPoints.at( geometryIndex ).size() - 1 ) ) { mPoints[geometryIndex] << mPoints.at( geometryIndex ).at( 0 ); } if ( doUpdate ) { setVisible( true ); updateRect(); update(); } } void QgsRubberBand::removePoint( int index, bool doUpdate/* = true*/, int geometryIndex/* = 0*/ ) { if ( mPoints.size() < geometryIndex + 1 ) { return; } if ( !mPoints[geometryIndex].isEmpty() ) { // negative index removes from end, e.g., -1 removes last one if ( index < 0 ) { index = mPoints.at( geometryIndex ).size() + index; } mPoints[geometryIndex].removeAt( index ); } if ( doUpdate ) { updateRect(); update(); } } void QgsRubberBand::removeLastPoint( int geometryIndex, bool doUpdate/* = true*/ ) { removePoint( -1, doUpdate, geometryIndex ); } void QgsRubberBand::movePoint( const QgsPointXY &p, int geometryIndex ) { if ( mPoints.size() < geometryIndex + 1 ) { return; } if ( mPoints.at( geometryIndex ).empty() ) { return; } mPoints[geometryIndex].last() = p; updateRect(); update(); } void QgsRubberBand::movePoint( int index, const QgsPointXY &p, int geometryIndex ) { if ( mPoints.size() < geometryIndex + 1 ) { return; } if ( mPoints.at( geometryIndex ).size() < index ) { return; } mPoints[geometryIndex][index] = p; updateRect(); update(); } void QgsRubberBand::setToGeometry( const QgsGeometry &geom, QgsVectorLayer *layer ) { if ( geom.isNull() ) { reset( mGeometryType ); return; } reset( geom.type() ); addGeometry( geom, layer ); } void QgsRubberBand::addGeometry( const QgsGeometry &geometry, QgsVectorLayer *layer ) { QgsGeometry geom = geometry; if ( layer ) { QgsCoordinateTransform ct = mMapCanvas->mapSettings().layerTransform( layer ); geom.transform( ct ); } addGeometry( geom ); } void QgsRubberBand::addGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &crs ) { if ( geometry.isEmpty() ) { return; } //maprender object of canvas const QgsMapSettings &ms = mMapCanvas->mapSettings(); int idx = mPoints.size(); QgsGeometry geom = geometry; if ( crs.isValid() ) { QgsCoordinateTransform ct( crs, ms.destinationCrs(), QgsProject::instance() ); geom.transform( ct ); } QgsWkbTypes::Type geomType = geom.wkbType(); if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::PointGeometry && !QgsWkbTypes::isMultiType( geomType ) ) { QgsPointXY pt = geom.asPoint(); addPoint( pt, false, idx ); removeLastPoint( idx, false ); } else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::PointGeometry && QgsWkbTypes::isMultiType( geomType ) ) { const QgsMultiPointXY mpt = geom.asMultiPoint(); for ( const QgsPointXY &pt : mpt ) { addPoint( pt, false, idx ); removeLastPoint( idx, false ); idx++; } } else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::LineGeometry && !QgsWkbTypes::isMultiType( geomType ) ) { const QgsPolylineXY line = geom.asPolyline(); for ( const QgsPointXY &pt : line ) { addPoint( pt, false, idx ); } } else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::LineGeometry && QgsWkbTypes::isMultiType( geomType ) ) { const QgsMultiPolylineXY mline = geom.asMultiPolyline(); for ( const QgsPolylineXY &line : mline ) { if ( line.isEmpty() ) { continue; } for ( const QgsPointXY &pt : line ) { addPoint( pt, false, idx ); } idx++; } } else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::PolygonGeometry && !QgsWkbTypes::isMultiType( geomType ) ) { const QgsPolygonXY poly = geom.asPolygon(); const QgsPolylineXY line = poly.at( 0 ); for ( const QgsPointXY &pt : line ) { addPoint( pt, false, idx ); } } else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::PolygonGeometry && QgsWkbTypes::isMultiType( geomType ) ) { const QgsMultiPolygonXY multipoly = geom.asMultiPolygon(); for ( const QgsPolygonXY &poly : multipoly ) { if ( poly.empty() ) continue; const QgsPolylineXY line = poly.at( 0 ); for ( const QgsPointXY &pt : line ) { addPoint( pt, false, idx ); } idx++; } } else { return; } setVisible( true ); updateRect(); update(); } void QgsRubberBand::setToCanvasRectangle( QRect rect ) { if ( !mMapCanvas ) { return; } const QgsMapToPixel *transform = mMapCanvas->getCoordinateTransform(); QgsPointXY ll = transform->toMapCoordinates( rect.left(), rect.bottom() ); QgsPointXY lr = transform->toMapCoordinates( rect.right(), rect.bottom() ); QgsPointXY ul = transform->toMapCoordinates( rect.left(), rect.top() ); QgsPointXY ur = transform->toMapCoordinates( rect.right(), rect.top() ); reset( QgsWkbTypes::PolygonGeometry ); addPoint( ll, false ); addPoint( lr, false ); addPoint( ur, false ); addPoint( ul, true ); } void QgsRubberBand::paint( QPainter *p ) { if ( mPoints.isEmpty() ) return; QVector< QVector > shapes; for ( const QList &line : qgis::as_const( mPoints ) ) { QVector pts; for ( const QgsPointXY &pt : line ) { const QPointF cur = toCanvasCoordinates( QgsPointXY( pt.x() + mTranslationOffsetX, pt.y() + mTranslationOffsetY ) ) - pos(); if ( pts.empty() || std::abs( pts.back().x() - cur.x() ) > 1 || std::abs( pts.back().y() - cur.y() ) > 1 ) pts.append( cur ); } shapes << pts; } int iterations = mSecondaryPen.color().isValid() ? 2 : 1; for ( int i = 0; i < iterations; ++i ) { if ( i == 0 && iterations > 1 ) { // first iteration with multi-pen painting, so use secondary pen mSecondaryPen.setWidth( mPen.width() + 2 ); p->setBrush( Qt::NoBrush ); p->setPen( mSecondaryPen ); } else { // "top" layer, use primary pen/brush p->setBrush( mBrush ); p->setPen( mPen ); } for ( const QVector &shape : qgis::as_const( shapes ) ) { drawShape( p, shape ); } } } void QgsRubberBand::drawShape( QPainter *p, const QVector &pts ) { switch ( mGeometryType ) { case QgsWkbTypes::PolygonGeometry: { p->drawPolygon( pts ); } break; case QgsWkbTypes::PointGeometry: { Q_FOREACH ( QPointF pt, pts ) { double x = pt.x(); double y = pt.y(); qreal s = ( mIconSize - 1 ) / 2.0; switch ( mIconType ) { case ICON_NONE: break; case ICON_CROSS: p->drawLine( QLineF( x - s, y, x + s, y ) ); p->drawLine( QLineF( x, y - s, x, y + s ) ); break; case ICON_X: p->drawLine( QLineF( x - s, y - s, x + s, y + s ) ); p->drawLine( QLineF( x - s, y + s, x + s, y - s ) ); break; case ICON_BOX: p->drawLine( QLineF( x - s, y - s, x + s, y - s ) ); p->drawLine( QLineF( x + s, y - s, x + s, y + s ) ); p->drawLine( QLineF( x + s, y + s, x - s, y + s ) ); p->drawLine( QLineF( x - s, y + s, x - s, y - s ) ); break; case ICON_FULL_BOX: p->drawRect( x - s, y - s, mIconSize, mIconSize ); break; case ICON_CIRCLE: p->drawEllipse( x - s, y - s, mIconSize, mIconSize ); break; case ICON_DIAMOND: case ICON_FULL_DIAMOND: { QPointF pts[] = { QPointF( x, y - s ), QPointF( x + s, y ), QPointF( x, y + s ), QPointF( x - s, y ) }; if ( mIconType == ICON_FULL_DIAMOND ) p->drawPolygon( pts, 4 ); else p->drawPolyline( pts, 4 ); } } } } break; case QgsWkbTypes::LineGeometry: default: { p->drawPolyline( pts ); } break; } } void QgsRubberBand::updateRect() { if ( mPoints.empty() ) { setRect( QgsRectangle() ); setVisible( false ); return; } const QgsMapToPixel &m2p = *( mMapCanvas->getCoordinateTransform() ); qreal w = ( ( mIconSize - 1 ) / 2 + mPen.width() ); // in canvas units QgsRectangle r; // in canvas units for ( int i = 0; i < mPoints.size(); ++i ) { QList::const_iterator it = mPoints.at( i ).constBegin(), itE = mPoints.at( i ).constEnd(); for ( ; it != itE; ++it ) { QgsPointXY p( it->x() + mTranslationOffsetX, it->y() + mTranslationOffsetY ); p = m2p.transform( p ); QgsRectangle rect( p.x() - w, p.y() - w, p.x() + w, p.y() + w ); if ( r.isEmpty() ) { // Get rectangle of the first point r = rect; } else { r.combineExtentWith( rect ); } } } // This is an hack to pass QgsMapCanvasItem::setRect what it // expects (encoding of position and size of the item) qreal res = m2p.mapUnitsPerPixel(); QgsPointXY topLeft = m2p.toMapPoint( r.xMinimum(), r.yMinimum() ); QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + r.width()*res, topLeft.y() - r.height()*res ); setRect( rect ); } void QgsRubberBand::updatePosition() { // re-compute rectangle // See https://issues.qgis.org/issues/12392 // NOTE: could be optimized by saving map-extent // of rubberband and simply re-projecting // that to device-rectangle on "updatePosition" updateRect(); } void QgsRubberBand::setTranslationOffset( double dx, double dy ) { mTranslationOffsetX = dx; mTranslationOffsetY = dy; updateRect(); } int QgsRubberBand::size() const { return mPoints.size(); } int QgsRubberBand::partSize( int geometryIndex ) const { if ( geometryIndex < 0 || geometryIndex >= mPoints.size() ) return 0; return mPoints[geometryIndex].size(); } int QgsRubberBand::numberOfVertices() const { int count = 0; QList >::const_iterator it = mPoints.constBegin(); for ( ; it != mPoints.constEnd(); ++it ) { QList::const_iterator iter = it->constBegin(); for ( ; iter != it->constEnd(); ++iter ) { ++count; } } return count; } const QgsPointXY *QgsRubberBand::getPoint( int i, int j ) const { if ( i < mPoints.size() && j < mPoints[i].size() ) return &mPoints[i][j]; else return nullptr; } QgsGeometry QgsRubberBand::asGeometry() const { QgsGeometry geom; switch ( mGeometryType ) { case QgsWkbTypes::PolygonGeometry: { QgsPolygonXY polygon; QList< QList >::const_iterator it = mPoints.constBegin(); for ( ; it != mPoints.constEnd(); ++it ) { polygon.append( getPolyline( *it ) ); } geom = QgsGeometry::fromPolygonXY( polygon ); break; } case QgsWkbTypes::PointGeometry: { QgsMultiPointXY multiPoint; QList< QList >::const_iterator it = mPoints.constBegin(); for ( ; it != mPoints.constEnd(); ++it ) { multiPoint += getPolyline( *it ); } geom = QgsGeometry::fromMultiPointXY( multiPoint ); break; } case QgsWkbTypes::LineGeometry: default: { if ( !mPoints.isEmpty() ) { if ( mPoints.size() > 1 ) { QgsMultiPolylineXY multiPolyline; QList< QList >::const_iterator it = mPoints.constBegin(); for ( ; it != mPoints.constEnd(); ++it ) { multiPolyline.append( getPolyline( *it ) ); } geom = QgsGeometry::fromMultiPolylineXY( multiPolyline ); } else { geom = QgsGeometry::fromPolylineXY( getPolyline( mPoints.at( 0 ) ) ); } } break; } } return geom; } QgsPolylineXY QgsRubberBand::getPolyline( const QList &points ) { QgsPolylineXY polyline; QList::const_iterator iter = points.constBegin(); for ( ; iter != points.constEnd(); ++iter ) { polyline.append( *iter ); } return polyline; }