1 /***************************************************************************
2                           qgsrectangle.h  -  description
3                              -------------------
4     begin                : Sat Jun 22 2002
5     copyright            : (C) 2002 by Gary E.Sherman
6     email                : sherman at mrcc.com
7 ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #ifndef QGSRECTANGLE_H
19 #define QGSRECTANGLE_H
20 
21 #include "qgis_core.h"
22 #include "qgis.h"
23 #include <iosfwd>
24 #include <QDomDocument>
25 #include <QRectF>
26 
27 class QString;
28 class QRectF;
29 class QgsBox3d;
30 #include "qgspointxy.h"
31 
32 
33 /**
34  * \ingroup core
35  * \brief A rectangle specified with double values.
36  *
37  * QgsRectangle is used to store a rectangle when double values are required.
38  * Examples are storing a layer extent or the current view extent of a map
39  * \see QgsBox3d
40  */
41 class CORE_EXPORT QgsRectangle
42 {
43   public:
44 
45     //! Constructor for a null rectangle
46     QgsRectangle() = default; // optimised constructor for null rectangle - no need to call normalize here
47 
48     /**
49      * Constructs a QgsRectangle from a set of x and y minimum and maximum coordinates.
50      *
51      * The rectangle will be normalized after creation. Since QGIS 3.20, if \a normalize is FALSE then
52      * the normalization step will not be applied automatically.
53      */
54     explicit QgsRectangle( double xMin, double yMin = 0, double xMax = 0, double yMax = 0, bool normalize = true ) SIP_HOLDGIL
mXmin(xMin)55   : mXmin( xMin )
56     , mYmin( yMin )
57     , mXmax( xMax )
58     , mYmax( yMax )
59     {
60       if ( normalize )
61         QgsRectangle::normalize();
62     }
63 
64     /**
65      * Construct a rectangle from two points.
66      *
67      * The rectangle is normalized after construction. Since QGIS 3.20, if \a normalize is FALSE then
68      * the normalization step will not be applied automatically.
69      */
70     QgsRectangle( const QgsPointXY &p1, const QgsPointXY &p2, bool normalize = true ) SIP_HOLDGIL
71     {
72       set( p1, p2, normalize );
73     }
74 
75     /**
76      * Construct a rectangle from a QRectF.
77      *
78      * The rectangle is NOT normalized after construction.
79      */
QgsRectangle(const QRectF & qRectF)80     QgsRectangle( const QRectF &qRectF ) SIP_HOLDGIL
81     {
82       mXmin = qRectF.topLeft().x();
83       mYmin = qRectF.topLeft().y();
84       mXmax = qRectF.bottomRight().x();
85       mYmax = qRectF.bottomRight().y();
86     }
87 
88     //! Copy constructor
QgsRectangle(const QgsRectangle & other)89     QgsRectangle( const QgsRectangle &other ) SIP_HOLDGIL
90     {
91       mXmin = other.xMinimum();
92       mYmin = other.yMinimum();
93       mXmax = other.xMaximum();
94       mYmax = other.yMaximum();
95     }
96 
97     // IMPORTANT - while QgsRectangle is inherited by QgsReferencedRectangle, we do NOT want a virtual destructor here
98     // because this class MUST be lightweight and we don't want the cost of the vtable here.
99     // see https://github.com/qgis/QGIS/pull/4720#issuecomment-308652392
100     ~QgsRectangle() = default;
101 
102     /**
103     * Creates a new rectangle from a \a wkt string.
104     * The WKT must contain only 5 vertices, representing a rectangle aligned with X and Y axes.
105     * \since QGIS 3.0
106     */
107     static QgsRectangle fromWkt( const QString &wkt );
108 
109     /**
110      * Creates a new rectangle, given the specified \a center point
111      * and \a width and \a height.
112      * \since QGIS 3.0
113      */
114     static QgsRectangle fromCenterAndSize( const QgsPointXY &center, double width, double height );
115 
116     /**
117      * Sets the rectangle from two QgsPoints.
118      *
119      * The rectangle is normalised after construction. Since QGIS 3.20, if \a normalize is FALSE then
120      * the normalization step will not be applied automatically.
121      */
122     void set( const QgsPointXY &p1, const QgsPointXY &p2, bool normalize = true )
123     {
124       mXmin = p1.x();
125       mXmax = p2.x();
126       mYmin = p1.y();
127       mYmax = p2.y();
128       if ( normalize )
129         QgsRectangle::normalize();
130     }
131 
132     /**
133      * Sets the rectangle from four points.
134      *
135      * The rectangle is normalised after construction. Since QGIS 3.20, if \a normalize is FALSE then
136      * the normalization step will not be applied automatically.
137      */
138     void set( double xMin, double yMin, double xMax, double yMax, bool normalize = true )
139     {
140       mXmin = xMin;
141       mYmin = yMin;
142       mXmax = xMax;
143       mYmax = yMax;
144       if ( normalize )
145         QgsRectangle::normalize();
146     }
147 
148     /**
149      * Set the minimum x value.
150      */
setXMinimum(double x)151     void setXMinimum( double x ) SIP_HOLDGIL { mXmin = x; }
152 
153     /**
154      * Set the maximum x value.
155      */
setXMaximum(double x)156     void setXMaximum( double x ) SIP_HOLDGIL { mXmax = x; }
157 
158     /**
159      * Set the minimum y value.
160      */
setYMinimum(double y)161     void setYMinimum( double y ) SIP_HOLDGIL { mYmin = y; }
162 
163     /**
164      * Set the maximum y value.
165      */
setYMaximum(double y)166     void setYMaximum( double y ) SIP_HOLDGIL { mYmax = y; }
167 
168     /**
169      * Set a rectangle so that min corner is at max
170      * and max corner is at min. It is NOT normalized.
171      */
setMinimal()172     void setMinimal() SIP_HOLDGIL
173     {
174       mXmin = std::numeric_limits<double>::max();
175       mYmin = std::numeric_limits<double>::max();
176       mXmax = -std::numeric_limits<double>::max();
177       mYmax = -std::numeric_limits<double>::max();
178     }
179 
180     /**
181      * Returns the x maximum value (right side of rectangle).
182      */
xMaximum()183     double xMaximum() const SIP_HOLDGIL { return mXmax; }
184 
185     /**
186      * Returns the x minimum value (left side of rectangle).
187      */
xMinimum()188     double xMinimum() const SIP_HOLDGIL { return mXmin; }
189 
190     /**
191      * Returns the y maximum value (top side of rectangle).
192      */
yMaximum()193     double yMaximum() const SIP_HOLDGIL { return mYmax; }
194 
195     /**
196      * Returns the y minimum value (bottom side of rectangle).
197      */
yMinimum()198     double yMinimum() const SIP_HOLDGIL { return mYmin; }
199 
200     /**
201      * Normalize the rectangle so it has non-negative width/height.
202      */
normalize()203     void normalize()
204     {
205       if ( isNull() )
206         return;
207 
208       if ( mXmin > mXmax )
209       {
210         std::swap( mXmin, mXmax );
211       }
212       if ( mYmin > mYmax )
213       {
214         std::swap( mYmin, mYmax );
215       }
216     }
217 
218     /**
219      * Returns the width of the rectangle.
220      * \see height()
221      * \see area()
222      */
width()223     double width() const SIP_HOLDGIL { return mXmax - mXmin; }
224 
225     /**
226      * Returns the height of the rectangle.
227      * \see width()
228      * \see area()
229      */
height()230     double height() const SIP_HOLDGIL { return mYmax - mYmin; }
231 
232     /**
233      * Returns the area of the rectangle.
234      * \see width()
235      * \see height()
236      * \see perimeter()
237      * \since QGIS 3.0
238      */
area()239     double area() const SIP_HOLDGIL { return ( mXmax - mXmin ) * ( mYmax - mYmin ); }
240 
241     /**
242      * Returns the perimeter of the rectangle.
243      * \see area()
244      * \since QGIS 3.0
245      */
perimeter()246     double perimeter() const SIP_HOLDGIL { return 2 * ( mXmax - mXmin ) + 2 * ( mYmax - mYmin ); }
247 
248     /**
249      * Returns the center point of the rectangle.
250      */
center()251     QgsPointXY center() const SIP_HOLDGIL { return QgsPointXY( mXmax * 0.5 + mXmin * 0.5, mYmin * 0.5 + mYmax * 0.5 ); }
252 
253     /**
254      * Scale the rectangle around its center point.
255      */
256     void scale( double scaleFactor, const QgsPointXY *c = nullptr )
257     {
258       // scale from the center
259       double centerX, centerY;
260       if ( c )
261       {
262         centerX = c->x();
263         centerY = c->y();
264       }
265       else
266       {
267         centerX = mXmin + width() / 2;
268         centerY = mYmin + height() / 2;
269       }
270       scale( scaleFactor, centerX, centerY );
271     }
272 
273     /**
274      * Scale the rectangle around its center point.
275      */
scale(double scaleFactor,double centerX,double centerY)276     void scale( double scaleFactor, double centerX, double centerY )
277     {
278       const double newWidth = width() * scaleFactor;
279       const double newHeight = height() * scaleFactor;
280       mXmin = centerX - newWidth / 2.0;
281       mXmax = centerX + newWidth / 2.0;
282       mYmin = centerY - newHeight / 2.0;
283       mYmax = centerY + newHeight / 2.0;
284     }
285 
286     /**
287      * Scale the rectangle around its \a center point.
288      * \since QGIS 3.4
289      */
290     QgsRectangle scaled( double scaleFactor, const QgsPointXY *center = nullptr ) const;
291 
292     /**
293      * Grows the rectangle in place by the specified amount.
294      * \see buffered()
295      */
grow(double delta)296     void grow( double delta )
297     {
298       mXmin -= delta;
299       mXmax += delta;
300       mYmin -= delta;
301       mYmax += delta;
302     }
303 
304     /**
305      * Updates the rectangle to include the specified point.
306      */
include(const QgsPointXY & p)307     void include( const QgsPointXY &p )
308     {
309       if ( p.x() < xMinimum() )
310         setXMinimum( p.x() );
311       if ( p.x() > xMaximum() )
312         setXMaximum( p.x() );
313       if ( p.y() < yMinimum() )
314         setYMinimum( p.y() );
315       if ( p.y() > yMaximum() )
316         setYMaximum( p.y() );
317     }
318 
319     /**
320      * Gets rectangle enlarged by buffer.
321      * \note In earlier QGIS releases this method was named buffer().
322      * \see grow()
323      * \since QGIS 3.0
324     */
buffered(double width)325     QgsRectangle buffered( double width ) const
326     {
327       return QgsRectangle( mXmin - width, mYmin - width, mXmax + width, mYmax + width );
328     }
329 
330     /**
331      * Returns the intersection with the given rectangle.
332      */
intersect(const QgsRectangle & rect)333     QgsRectangle intersect( const QgsRectangle &rect ) const
334     {
335       QgsRectangle intersection = QgsRectangle();
336       if ( intersects( rect ) )
337       {
338         intersection.setXMinimum( mXmin > rect.xMinimum() ? mXmin : rect.xMinimum() );
339         intersection.setXMaximum( mXmax < rect.xMaximum() ? mXmax : rect.xMaximum() );
340         intersection.setYMinimum( mYmin > rect.yMinimum() ? mYmin : rect.yMinimum() );
341         intersection.setYMaximum( mYmax < rect.yMaximum() ? mYmax : rect.yMaximum() );
342       }
343       return intersection;
344     }
345 
346     /**
347      * Returns TRUE when rectangle intersects with other rectangle.
348      */
intersects(const QgsRectangle & rect)349     bool intersects( const QgsRectangle &rect ) const SIP_HOLDGIL
350     {
351       const double x1 = ( mXmin > rect.mXmin ? mXmin : rect.mXmin );
352       const double x2 = ( mXmax < rect.mXmax ? mXmax : rect.mXmax );
353       if ( x1 > x2 )
354         return false;
355       const double y1 = ( mYmin > rect.mYmin ? mYmin : rect.mYmin );
356       const double y2 = ( mYmax < rect.mYmax ? mYmax : rect.mYmax );
357       return y1 <= y2;
358     }
359 
360     /**
361      * Returns TRUE when rectangle contains other rectangle.
362      */
contains(const QgsRectangle & rect)363     bool contains( const QgsRectangle &rect ) const SIP_HOLDGIL
364     {
365       return ( rect.mXmin >= mXmin && rect.mXmax <= mXmax && rect.mYmin >= mYmin && rect.mYmax <= mYmax );
366     }
367 
368     /**
369      * Returns TRUE when rectangle contains a point.
370      */
contains(const QgsPointXY & p)371     bool contains( const QgsPointXY &p ) const SIP_HOLDGIL
372     {
373       return mXmin <= p.x() && p.x() <= mXmax &&
374              mYmin <= p.y() && p.y() <= mYmax;
375     }
376 
377     /**
378      * Returns TRUE when rectangle contains the point at (\a x, \a y).
379      *
380      * \since QGIS 3.20
381      */
contains(double x,double y)382     bool contains( double x, double y ) const SIP_HOLDGIL
383     {
384       return mXmin <= x && x <= mXmax &&
385              mYmin <= y && y <= mYmax;
386     }
387 
388     /**
389      * Expands the rectangle so that it covers both the original rectangle and the given rectangle.
390      */
combineExtentWith(const QgsRectangle & rect)391     void combineExtentWith( const QgsRectangle &rect )
392     {
393       if ( isNull() )
394         *this = rect;
395       else if ( !rect.isNull() )
396       {
397         mXmin = std::min( mXmin, rect.xMinimum() );
398         mXmax = std::max( mXmax, rect.xMaximum() );
399         mYmin = std::min( mYmin, rect.yMinimum() );
400         mYmax = std::max( mYmax, rect.yMaximum() );
401       }
402     }
403 
404     /**
405      * Expands the rectangle so that it covers both the original rectangle and the given point.
406      */
combineExtentWith(double x,double y)407     void combineExtentWith( double x, double y )
408     {
409       if ( isNull() )
410         *this = QgsRectangle( x, y, x, y );
411       else
412       {
413         mXmin = ( ( mXmin < x ) ? mXmin : x );
414         mXmax = ( ( mXmax > x ) ? mXmax : x );
415 
416         mYmin = ( ( mYmin < y ) ? mYmin : y );
417         mYmax = ( ( mYmax > y ) ? mYmax : y );
418       }
419     }
420 
421     /**
422      * Expands the rectangle so that it covers both the original rectangle and the given point.
423      * \since QGIS 3.2
424      */
combineExtentWith(const QgsPointXY & point)425     void combineExtentWith( const QgsPointXY &point )
426     {
427       combineExtentWith( point.x(), point.y() );
428     }
429 
430     /**
431      * Returns the distance from \a point to the nearest point on the boundary of the rectangle.
432      * \since QGIS 3.14
433      */
distance(const QgsPointXY & point)434     double distance( const QgsPointXY &point ) const
435     {
436       const double dx = std::max( std::max( mXmin - point.x(), 0.0 ), point.x() - mXmax );
437       const double dy = std::max( std::max( mYmin - point.y(), 0.0 ), point.y() - mYmax );
438       return std::sqrt( dx * dx + dy * dy );
439     }
440 
441     /**
442      * Returns a rectangle offset from this one in the direction of the reversed vector.
443      * \since QGIS 3.0
444      */
445     QgsRectangle operator-( QgsVector v ) const;
446 
447     /**
448      * Returns a rectangle offset from this one in the direction of the vector.
449      * \since QGIS 3.0
450      */
451     QgsRectangle operator+( QgsVector v ) const;
452 
453     /**
454      * Moves this rectangle in the direction of the reversed vector.
455      * \since QGIS 3.0
456      */
457     QgsRectangle &operator-=( QgsVector v );
458 
459     /**
460      * Moves this rectangle in the direction of the vector.
461      * \since QGIS 3.0
462      */
463     QgsRectangle &operator+=( QgsVector v );
464 
465     /**
466      * Returns TRUE if the rectangle is empty.
467      * An empty rectangle may still be non-null if it contains valid information (e.g. bounding box of a point).
468      */
isEmpty()469     bool isEmpty() const
470     {
471       return mXmax < mXmin || mYmax < mYmin || qgsDoubleNear( mXmax, mXmin ) || qgsDoubleNear( mYmax, mYmin );
472     }
473 
474     /**
475      * Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
476      * A null rectangle is also an empty rectangle.
477      * \since QGIS 2.4
478      */
isNull()479     bool isNull() const
480     {
481       // rectangle created QgsRectangle() or with rect.setMinimal() ?
482       return ( qgsDoubleNear( mXmin, 0.0 ) && qgsDoubleNear( mXmax, 0.0 ) && qgsDoubleNear( mYmin, 0.0 ) && qgsDoubleNear( mYmax, 0.0 ) ) ||
483              ( qgsDoubleNear( mXmin, std::numeric_limits<double>::max() ) && qgsDoubleNear( mYmin, std::numeric_limits<double>::max() ) &&
484                qgsDoubleNear( mXmax, -std::numeric_limits<double>::max() ) && qgsDoubleNear( mYmax, -std::numeric_limits<double>::max() ) );
485     }
486 
487     /**
488      * Returns a string representation of the rectangle in WKT format.
489      */
490     QString asWktCoordinates() const;
491 
492     /**
493      * Returns a string representation of the rectangle as a WKT Polygon.
494      */
495     QString asWktPolygon() const;
496 
497     /**
498      * Returns a QRectF with same coordinates as the rectangle.
499      */
toRectF()500     QRectF toRectF() const
501     {
502       return QRectF( static_cast< qreal >( mXmin ), static_cast< qreal >( mYmin ), static_cast< qreal >( mXmax - mXmin ), static_cast< qreal >( mYmax - mYmin ) );
503     }
504 
505     /**
506      * Returns a string representation of form xmin,ymin : xmax,ymax
507      * Coordinates will be truncated to the specified precision.
508      * If the specified precision is less than 0, a suitable minimum precision is used.
509      */
510     QString toString( int precision = 16 ) const;
511 
512     /**
513      * Returns the rectangle as a polygon.
514      */
515     QString asPolygon() const;
516 
517     /**
518      * Comparison operator
519      * \returns TRUE if rectangles are equal
520      */
521     bool operator==( const QgsRectangle &r1 ) const
522     {
523       return qgsDoubleNear( r1.xMaximum(), xMaximum() ) &&
524              qgsDoubleNear( r1.xMinimum(), xMinimum() ) &&
525              qgsDoubleNear( r1.yMaximum(), yMaximum() ) &&
526              qgsDoubleNear( r1.yMinimum(), yMinimum() );
527     }
528 
529     /**
530      * Comparison operator
531      * \returns FALSE if rectangles are equal
532      */
533     bool operator!=( const QgsRectangle &r1 ) const
534     {
535       return ( ! operator==( r1 ) );
536     }
537 
538     /**
539      * Assignment operator
540      * \param r1 QgsRectangle to assign from
541      */
542     QgsRectangle &operator=( const QgsRectangle &r1 )
543     {
544       if ( &r1 != this )
545       {
546         mXmax = r1.xMaximum();
547         mXmin = r1.xMinimum();
548         mYmax = r1.yMaximum();
549         mYmin = r1.yMinimum();
550       }
551 
552       return *this;
553     }
554 
555     /**
556      * Returns TRUE if the rectangle has finite boundaries. Will
557      * return FALSE if any of the rectangle boundaries are NaN or Inf.
558      */
isFinite()559     bool isFinite() const
560     {
561       if ( std::isinf( mXmin ) || std::isinf( mYmin ) || std::isinf( mXmax ) || std::isinf( mYmax ) )
562       {
563         return false;
564       }
565       if ( std::isnan( mXmin ) || std::isnan( mYmin ) || std::isnan( mXmax ) || std::isnan( mYmax ) )
566       {
567         return false;
568       }
569       return true;
570     }
571 
572     /**
573      * Swap x/y coordinates in the rectangle.
574      */
invert()575     void invert()
576     {
577       std::swap( mXmin, mYmin );
578       std::swap( mXmax, mYmax );
579     }
580 
581     /**
582      * Converts the rectangle to a 3D box, with the specified
583      * \a zMin and \a zMax z values.
584      * \since QGIS 3.0
585      */
586     QgsBox3d toBox3d( double zMin, double zMax ) const;
587 
588     //! Allows direct construction of QVariants from rectangles.
QVariant()589     operator QVariant() const
590     {
591       return QVariant::fromValue( *this );
592     }
593 
594     /**
595      * Returns a copy of this rectangle that is snapped to a grid with
596      * the specified \a spacing between the grid lines.
597      *
598      * \since QGIS 3.4
599      */
600     QgsRectangle snappedToGrid( double spacing ) const;
601 
602 #ifdef SIP_RUN
603     SIP_PYOBJECT __repr__();
604     % MethodCode
605     QString str = QStringLiteral( "<QgsRectangle: %1>" ).arg( sipCpp->asWktCoordinates() );
606     sipRes = PyUnicode_FromString( str.toUtf8().constData() );
607     % End
608 #endif
609 
610   private:
611 
612     double mXmin = 0.0;
613     double mYmin = 0.0;
614     double mXmax = 0.0;
615     double mYmax = 0.0;
616 
617 };
618 
619 Q_DECLARE_METATYPE( QgsRectangle )
620 
621 #ifndef SIP_RUN
622 
623 /**
624  * Writes the list rectangle to stream out. QGIS version compatibility is not guaranteed.
625  */
626 CORE_EXPORT QDataStream &operator<<( QDataStream &out, const QgsRectangle &rectangle );
627 
628 /**
629  * Reads a rectangle from stream in into rectangle. QGIS version compatibility is not guaranteed.
630  */
631 CORE_EXPORT QDataStream &operator>>( QDataStream &in, QgsRectangle &rectangle );
632 
633 inline std::ostream &operator << ( std::ostream &os, const QgsRectangle &r )
634 {
635   return os << r.toString().toLocal8Bit().data();
636 }
637 
638 #endif
639 
640 #endif // QGSRECTANGLE_H
641