LCOV - code coverage report
Current view: top level - core/geometry - qgsquadrilateral.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 235 240 97.9 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :                          qgsquadrilateral.cpp
       3                 :            :                          -------------------
       4                 :            :     begin                : November 2018
       5                 :            :     copyright            : (C) 2018 by Loïc Bartoletti
       6                 :            :     email                : loic dot bartoletti at oslandia dot 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                 :            : #include "qgsquadrilateral.h"
      19                 :            : #include "qgsgeometryutils.h"
      20                 :            : 
      21                 :         45 : QgsQuadrilateral::QgsQuadrilateral() = default;
      22                 :            : 
      23                 :         33 : QgsQuadrilateral::QgsQuadrilateral( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 )
      24                 :            : {
      25                 :         33 :   setPoints( p1, p2, p3, p4 );
      26                 :         33 : }
      27                 :            : 
      28                 :          0 : QgsQuadrilateral::QgsQuadrilateral( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3, const QgsPointXY &p4 )
      29                 :            : {
      30                 :          0 :   setPoints( QgsPoint( p1 ), QgsPoint( p2 ), QgsPoint( p3 ), QgsPoint( p4 ) );
      31                 :          0 : }
      32                 :            : 
      33                 :         15 : QgsQuadrilateral QgsQuadrilateral::rectangleFrom3Points( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, ConstructionOption mode )
      34                 :            : {
      35                 :         15 :   QgsWkbTypes::Type pType( QgsWkbTypes::Point );
      36                 :            : 
      37                 :         15 :   double z = std::numeric_limits< double >::quiet_NaN();
      38                 :            : 
      39                 :         15 :   if ( p1.is3D() )
      40                 :          7 :     z = p1.z();
      41                 :         15 :   if ( p2.is3D() && std::isnan( z ) )
      42                 :          2 :     z = p2.z();
      43                 :         15 :   if ( p3.is3D() && std::isnan( z ) )
      44                 :          2 :     z = p3.z();
      45                 :         15 :   if ( !std::isnan( z ) )
      46                 :            :   {
      47                 :         11 :     pType = QgsWkbTypes::addZ( pType );
      48                 :         11 :   }
      49                 :            :   else
      50                 :            :   {
      51                 :            :     // This is only necessary to facilitate the calculation of the perpendicular
      52                 :            :     // point with QgsVector3D.
      53                 :          4 :     if ( mode == Projected )
      54                 :          2 :       z = 0;
      55                 :            :   }
      56                 :         15 :   QgsPoint point1( pType, p1.x(), p1.y(), std::isnan( p1.z() ) ? z : p1.z() );
      57                 :         15 :   QgsPoint point2( pType, p2.x(), p2.y(), std::isnan( p2.z() ) ? z : p2.z() );
      58                 :         15 :   QgsPoint point3( pType, p3.x(), p3.y(), std::isnan( p3.z() ) ? z : p3.z() );
      59                 :            : 
      60                 :         15 :   QgsQuadrilateral rect;
      61                 :         15 :   double inclination = 90.0;
      62                 :         15 :   double distance = 0;
      63                 :         15 :   double azimuth = point1.azimuth( point2 ) + 90.0 * QgsGeometryUtils::leftOfLine( point3.x(), point3.y(), point1.x(), point1.y(), point2.x(), point2.y() );
      64                 :         15 :   switch ( mode )
      65                 :            :   {
      66                 :            :     case Distance:
      67                 :            :     {
      68                 :          5 :       if ( point2.is3D() && point3.is3D() )
      69                 :            :       {
      70                 :          3 :         inclination = point2.inclination( point3 );
      71                 :          3 :         distance = point2.distance3D( point3 );
      72                 :          3 :       }
      73                 :            :       else
      74                 :            :       {
      75                 :          2 :         distance = point2.distance( point3 );
      76                 :            :       }
      77                 :            : 
      78                 :          5 :       rect.setPoints( point1, point2, point2.project( distance, azimuth, inclination ), point1.project( distance, azimuth, inclination ) );
      79                 :          5 :       break;
      80                 :            :     }
      81                 :            :     case Projected:
      82                 :            :     {
      83                 :         20 :       QgsVector3D v3 = QgsVector3D::perpendicularPoint( QgsVector3D( point1.x(), point1.y(), std::isnan( point1.z() ) ? z : point1.z() ),
      84                 :         10 :                        QgsVector3D( point2.x(), point2.y(), std::isnan( point2.z() ) ? z : point2.z() ),
      85                 :         10 :                        QgsVector3D( point3.x(), point3.y(), std::isnan( point3.z() ) ? z : point3.z() ) );
      86                 :         10 :       QgsPoint pV3( pType, v3.x(), v3.y(), v3.z() );
      87                 :         10 :       if ( p3.is3D() )
      88                 :            :       {
      89                 :          4 :         inclination = pV3.inclination( p3 );
      90                 :          4 :         distance = p3.distance3D( pV3 );
      91                 :          4 :       }
      92                 :            :       else
      93                 :          6 :         distance = p3.distance( pV3 );
      94                 :            : 
      95                 :            :       // Final points
      96                 :         10 :       QgsPoint fp1 = point1;
      97                 :         10 :       QgsPoint fp2 = point2;
      98                 :         10 :       QgsPoint fp3 = point2.project( distance, azimuth, inclination );
      99                 :         10 :       QgsPoint fp4 = point1.project( distance, azimuth, inclination ) ;
     100                 :            : 
     101                 :         10 :       if ( pType != QgsWkbTypes::PointZ )
     102                 :            :       {
     103                 :          2 :         fp1.dropZValue();
     104                 :          2 :         fp2.dropZValue();
     105                 :          2 :         fp3.dropZValue();
     106                 :          2 :         fp4.dropZValue();
     107                 :          2 :       }
     108                 :         10 :       rect.setPoints( fp1, fp2, fp3, fp4 );
     109                 :            :       break;
     110                 :         10 :     }
     111                 :            :   }
     112                 :            : 
     113                 :         15 :   return rect;
     114                 :            : 
     115                 :         15 : }
     116                 :            : 
     117                 :          7 : QgsQuadrilateral QgsQuadrilateral::rectangleFromExtent( const QgsPoint &p1, const QgsPoint &p2 )
     118                 :            : {
     119                 :          7 :   if ( QgsPoint( p1.x(), p1.y() ) == QgsPoint( p2.x(), p2.y() ) )
     120                 :          1 :     return QgsQuadrilateral();
     121                 :            : 
     122                 :          6 :   QgsQuadrilateral quad;
     123                 :          6 :   double z = p1.z();
     124                 :            : 
     125                 :          6 :   double xMin = 0, xMax = 0, yMin = 0, yMax = 0;
     126                 :            : 
     127                 :          6 :   if ( p1.x() < p2.x() )
     128                 :            :   {
     129                 :          4 :     xMin = p1.x();
     130                 :          4 :     xMax = p2.x();
     131                 :          4 :   }
     132                 :            :   else
     133                 :            :   {
     134                 :            : 
     135                 :          2 :     xMin = p2.x();
     136                 :          2 :     xMax = p1.x();
     137                 :            :   }
     138                 :            : 
     139                 :          6 :   if ( p1.y() < p2.y() )
     140                 :            :   {
     141                 :          5 :     yMin = p1.y();
     142                 :          5 :     yMax = p2.y();
     143                 :          5 :   }
     144                 :            :   else
     145                 :            :   {
     146                 :            : 
     147                 :          1 :     yMin = p2.y();
     148                 :          1 :     yMax = p1.y();
     149                 :            :   }
     150                 :            : 
     151                 :         12 :   quad.setPoints( QgsPoint( xMin, yMin, z ),
     152                 :          6 :                   QgsPoint( xMin, yMax, z ),
     153                 :          6 :                   QgsPoint( xMax, yMax, z ),
     154                 :          6 :                   QgsPoint( xMax, yMin, z ) );
     155                 :            : 
     156                 :          6 :   return quad;
     157                 :          7 : }
     158                 :            : 
     159                 :          7 : QgsQuadrilateral QgsQuadrilateral::squareFromDiagonal( const QgsPoint &p1, const QgsPoint &p2 )
     160                 :            : {
     161                 :            : 
     162                 :          7 :   if ( QgsPoint( p1.x(), p1.y() ) == QgsPoint( p2.x(), p2.y() ) )
     163                 :          1 :     return QgsQuadrilateral();
     164                 :            : 
     165                 :          6 :   QgsQuadrilateral quad;
     166                 :          6 :   QgsPoint point2, point3 = QgsPoint( p2.x(), p2.y() ), point4;
     167                 :            : 
     168                 :          6 :   double azimuth = p1.azimuth( point3 ) + 90.0;
     169                 :          6 :   double distance = p1.distance( point3 ) / 2.0;
     170                 :          6 :   QgsPoint midPoint = QgsGeometryUtils::midpoint( p1, point3 );
     171                 :            : 
     172                 :          6 :   point2 = midPoint.project( -distance, azimuth );
     173                 :          6 :   point4 = midPoint.project( distance, azimuth );
     174                 :            : 
     175                 :          6 :   if ( p1.is3D() )
     176                 :            :   {
     177                 :          1 :     double z = 0;
     178                 :          1 :     z = p1.z();
     179                 :          1 :     point2 = QgsPoint( point2.x(), point2.y(), z );
     180                 :          1 :     point3 = QgsPoint( point3.x(), point3.y(), z );
     181                 :          1 :     point4 = QgsPoint( point4.x(), point4.y(), z );
     182                 :          1 :   }
     183                 :            : 
     184                 :          6 :   quad.setPoints( p1, point2, point3, point4 );
     185                 :            : 
     186                 :          6 :   return quad;
     187                 :          7 : }
     188                 :            : 
     189                 :         13 : QgsQuadrilateral QgsQuadrilateral::rectangleFromCenterPoint( const QgsPoint &center, const QgsPoint &point )
     190                 :            : {
     191                 :         13 :   if ( QgsPoint( center.x(), center.y() ) == QgsPoint( point.x(), point.y() ) )
     192                 :          2 :     return QgsQuadrilateral();
     193                 :         11 :   double xOffset = std::fabs( point.x() - center.x() );
     194                 :         11 :   double yOffset = std::fabs( point.y() - center.y() );
     195                 :            : 
     196                 :         22 :   return QgsQuadrilateral( QgsPoint( center.x() - xOffset, center.y() - yOffset, center.z() ),
     197                 :         11 :                            QgsPoint( center.x() - xOffset, center.y() + yOffset, center.z() ),
     198                 :         11 :                            QgsPoint( center.x() + xOffset, center.y() + yOffset, center.z() ),
     199                 :         11 :                            QgsPoint( center.x() + xOffset, center.y() - yOffset, center.z() ) );
     200                 :         13 : }
     201                 :            : 
     202                 :          3 : QgsQuadrilateral QgsQuadrilateral::fromRectangle( const QgsRectangle &rectangle )
     203                 :            : {
     204                 :          3 :   QgsQuadrilateral quad;
     205                 :          3 :   quad.setPoints(
     206                 :          3 :     QgsPoint( rectangle.xMinimum(), rectangle.yMinimum() ),
     207                 :          3 :     QgsPoint( rectangle.xMinimum(), rectangle.yMaximum() ),
     208                 :          3 :     QgsPoint( rectangle.xMaximum(), rectangle.yMaximum() ),
     209                 :          3 :     QgsPoint( rectangle.xMaximum(), rectangle.yMinimum() )
     210                 :            :   );
     211                 :          3 :   return quad;
     212                 :          3 : }
     213                 :            : 
     214                 :            : // Convenient method for comparison
     215                 :            : // TODO: should be have a equals method for QgsPoint allowing tolerance.
     216                 :         88 : static bool equalPoint( const QgsPoint &p1, const QgsPoint &p2, double epsilon )
     217                 :            : {
     218                 :         88 :   bool equal = true;
     219                 :         88 :   equal &= qgsDoubleNear( p1.x(), p2.x(), epsilon );
     220                 :         88 :   equal &= qgsDoubleNear( p1.y(), p2.y(), epsilon );
     221                 :         88 :   if ( p1.is3D() || p2.is3D() )
     222                 :         30 :     equal &= qgsDoubleNear( p1.z(), p2.z(), epsilon ) || ( std::isnan( p1.z() ) && std::isnan( p2.z() ) );
     223                 :         88 :   if ( p1.isMeasure() || p2.isMeasure() )
     224                 :          0 :     equal &= qgsDoubleNear( p1.m(), p2.m(), epsilon ) || ( std::isnan( p1.m() ) && std::isnan( p2.m() ) );
     225                 :            : 
     226                 :         88 :   return equal;
     227                 :            : }
     228                 :            : 
     229                 :         32 : bool QgsQuadrilateral::equals( const QgsQuadrilateral &other, double epsilon ) const
     230                 :            : {
     231                 :         32 :   if ( !( isValid() || other.isValid() ) )
     232                 :            :   {
     233                 :          7 :     return true;
     234                 :            :   }
     235                 :         25 :   else if ( !isValid() || !other.isValid() )
     236                 :            :   {
     237                 :          0 :     return false;
     238                 :            :   }
     239                 :         46 :   return ( ( equalPoint( mPoint1, other.mPoint1, epsilon ) ) &&
     240                 :         21 :            ( equalPoint( mPoint2, other.mPoint2, epsilon ) ) &&
     241                 :         21 :            ( equalPoint( mPoint3, other.mPoint3, epsilon ) ) &&
     242                 :         21 :            ( equalPoint( mPoint4, other.mPoint4, epsilon ) ) );
     243                 :         32 : }
     244                 :            : 
     245                 :         29 : bool QgsQuadrilateral::operator==( const QgsQuadrilateral &other ) const
     246                 :            : {
     247                 :         29 :   return equals( other );
     248                 :            : }
     249                 :            : 
     250                 :          4 : bool QgsQuadrilateral::operator!=( const QgsQuadrilateral &other ) const
     251                 :            : {
     252                 :          4 :   return !operator==( other );
     253                 :            : }
     254                 :            : 
     255                 :            : // Returns true if segments are not self-intersected ( [2-3] / [4-1] or [1-2] /
     256                 :            : // [3-4] )
     257                 :            : //
     258                 :            : // p3    p1      p1    p3
     259                 :            : // | \  /|       | \  /|
     260                 :            : // |  \/ |       |  \/ |
     261                 :            : // |  /\ |   or  |  /\ |
     262                 :            : // | /  \|       | /  \|
     263                 :            : // p2    p4      p2    p4
     264                 :            : 
     265                 :        182 : static bool isNotAntiParallelogram( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 )
     266                 :            : {
     267                 :        182 :   QgsPoint inter;
     268                 :        182 :   bool isIntersection1234 = QgsGeometryUtils::segmentIntersection( p1, p2, p3, p4, inter, isIntersection1234 );
     269                 :        182 :   bool isIntersection2341 = QgsGeometryUtils::segmentIntersection( p2, p3, p4, p1, inter, isIntersection2341 );
     270                 :            : 
     271                 :        182 :   return !( isIntersection1234 || isIntersection2341 );
     272                 :        182 : }
     273                 :            : 
     274                 :        172 : static bool isNotCollinear( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 )
     275                 :            : {
     276                 :        172 :   bool isCollinear =
     277                 :            :     (
     278                 :        172 :       ( QgsGeometryUtils::segmentSide( p1, p2, p3 ) == 0 ) ||
     279                 :        168 :       ( QgsGeometryUtils::segmentSide( p1, p2, p4 ) == 0 ) ||
     280                 :        166 :       ( QgsGeometryUtils::segmentSide( p1, p3, p4 ) == 0 ) ||
     281                 :        164 :       ( QgsGeometryUtils::segmentSide( p2, p3, p4 ) == 0 )
     282                 :            :     );
     283                 :            : 
     284                 :            : 
     285                 :        172 :   return !isCollinear;
     286                 :            : }
     287                 :            : 
     288                 :        211 : static bool notHaveDoublePoints( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 )
     289                 :            : {
     290                 :        211 :   bool doublePoints =
     291                 :            :     (
     292                 :        211 :       ( p1 == p2 ) || ( p1 == p3 ) || ( p1 == p4 ) || ( p2 == p3 ) || ( p2 == p4 ) || ( p3 == p4 ) );
     293                 :            : 
     294                 :        211 :   return !doublePoints;
     295                 :            : }
     296                 :            : 
     297                 :        215 : static bool haveSameType( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 )
     298                 :            : {
     299                 :        215 :   bool sameType = !( ( p1.wkbType() != p2.wkbType() ) || ( p1.wkbType() != p3.wkbType() ) || ( p1.wkbType() != p4.wkbType() ) );
     300                 :        215 :   return sameType;
     301                 :            : }
     302                 :            : // Convenient method to validate inputs
     303                 :        215 : static bool validate( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 )
     304                 :            : {
     305                 :        215 :   return (
     306                 :        215 :            haveSameType( p1, p2, p3, p4 ) &&
     307                 :        211 :            notHaveDoublePoints( p1, p2, p3, p4 ) &&
     308                 :        182 :            isNotAntiParallelogram( p1, p2, p3, p4 ) &&
     309                 :        172 :            isNotCollinear( p1, p2, p3, p4 )
     310                 :            :          );
     311                 :            : }
     312                 :            : 
     313                 :        128 : bool QgsQuadrilateral::isValid() const
     314                 :            : {
     315                 :        128 :   return validate( mPoint1, mPoint2, mPoint3, mPoint4 );
     316                 :            : }
     317                 :            : 
     318                 :         24 : bool QgsQuadrilateral::setPoint( const QgsPoint &newPoint, Point index )
     319                 :            : {
     320                 :         24 :   switch ( index )
     321                 :            :   {
     322                 :            :     case Point1:
     323                 :          6 :       if ( validate( newPoint, mPoint2, mPoint3, mPoint4 ) == false )
     324                 :          5 :         return false;
     325                 :          1 :       mPoint1 = newPoint;
     326                 :          1 :       break;
     327                 :            :     case Point2:
     328                 :          6 :       if ( validate( mPoint1, newPoint, mPoint3, mPoint4 ) == false )
     329                 :          5 :         return false;
     330                 :          1 :       mPoint2 = newPoint;
     331                 :          1 :       break;
     332                 :            :     case Point3:
     333                 :          6 :       if ( validate( mPoint1, mPoint2, newPoint, mPoint4 ) == false )
     334                 :          5 :         return false;
     335                 :          1 :       mPoint3 = newPoint;
     336                 :          1 :       break;
     337                 :            :     case Point4:
     338                 :          6 :       if ( validate( mPoint1, mPoint2, mPoint3, newPoint ) == false )
     339                 :          5 :         return false;
     340                 :          1 :       mPoint4 = newPoint;
     341                 :          1 :       break;
     342                 :            :   }
     343                 :            : 
     344                 :          4 :   return true;
     345                 :         24 : }
     346                 :            : 
     347                 :         63 : bool QgsQuadrilateral::setPoints( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 )
     348                 :            : {
     349                 :         63 :   if ( validate( p1, p2, p3, p4 ) == false )
     350                 :          8 :     return false;
     351                 :            : 
     352                 :         55 :   mPoint1 = p1;
     353                 :         55 :   mPoint2 = p2;
     354                 :         55 :   mPoint3 = p3;
     355                 :         55 :   mPoint4 = p4;
     356                 :            : 
     357                 :         55 :   return true;
     358                 :         63 : }
     359                 :            : 
     360                 :         24 : QgsPointSequence QgsQuadrilateral::points() const
     361                 :            : {
     362                 :         24 :   QgsPointSequence pts;
     363                 :            : 
     364                 :         24 :   pts << mPoint1 << mPoint2 << mPoint3 << mPoint4 << mPoint1;
     365                 :            : 
     366                 :         24 :   return pts;
     367                 :         24 : }
     368                 :            : 
     369                 :          8 : QgsPolygon *QgsQuadrilateral::toPolygon( bool force2D ) const
     370                 :            : {
     371                 :          8 :   std::unique_ptr<QgsPolygon> polygon = std::make_unique< QgsPolygon >();
     372                 :          8 :   if ( !isValid() )
     373                 :            :   {
     374                 :          3 :     return polygon.release();
     375                 :            :   }
     376                 :            : 
     377                 :          5 :   polygon->setExteriorRing( toLineString( force2D ) );
     378                 :            : 
     379                 :          5 :   return polygon.release();
     380                 :          8 : }
     381                 :            : 
     382                 :         18 : QgsLineString *QgsQuadrilateral::toLineString( bool force2D ) const
     383                 :            : {
     384                 :         18 :   std::unique_ptr<QgsLineString> ext = std::make_unique< QgsLineString>();
     385                 :         18 :   if ( !isValid() )
     386                 :            :   {
     387                 :          1 :     return ext.release();
     388                 :            :   }
     389                 :            : 
     390                 :         17 :   QgsPointSequence pts;
     391                 :         17 :   pts = points();
     392                 :            : 
     393                 :         17 :   ext->setPoints( pts );
     394                 :            : 
     395                 :         17 :   if ( force2D )
     396                 :          2 :     ext->dropZValue();
     397                 :            : 
     398                 :         17 :   return ext.release();
     399                 :         18 : }
     400                 :            : 
     401                 :          6 : QString QgsQuadrilateral::toString( int pointPrecision ) const
     402                 :            : {
     403                 :          6 :   QString rep;
     404                 :          6 :   if ( !isValid() )
     405                 :          4 :     rep = QStringLiteral( "Empty" );
     406                 :            :   else
     407                 :         12 :     rep = QStringLiteral( "Quadrilateral (Point 1: %1, Point 2: %2, Point 3: %3, Point 4: %4)" )
     408                 :          4 :           .arg( mPoint1.asWkt( pointPrecision ), 0, 's' )
     409                 :          4 :           .arg( mPoint2.asWkt( pointPrecision ), 0, 's' )
     410                 :          4 :           .arg( mPoint3.asWkt( pointPrecision ), 0, 's' )
     411                 :          4 :           .arg( mPoint4.asWkt( pointPrecision ), 0, 's' );
     412                 :            : 
     413                 :          6 :   return rep;
     414                 :          6 : }
     415                 :            : 
     416                 :          2 : double QgsQuadrilateral::area() const
     417                 :            : {
     418                 :          2 :   return toPolygon()->area();
     419                 :            : }
     420                 :            : 
     421                 :          2 : double QgsQuadrilateral::perimeter() const
     422                 :            : {
     423                 :          2 :   return toPolygon()->perimeter();
     424                 :            : }

Generated by: LCOV version 1.14