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