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