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