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