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 ¢er, 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