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