1 /***************************************************************************
2   qgsinternalgeometryengine.h - QgsInternalGeometryEngine
3 
4  ---------------------
5  begin                : 13.1.2016
6  copyright            : (C) 2016 by Matthias Kuhn
7  email                : matthias@opengis.ch
8  ***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 #ifndef QGSINTERNALGEOMETRYENGINE_H
17 #define QGSINTERNALGEOMETRYENGINE_H
18 
19 #define SIP_NO_FILE
20 
21 #include <functional>
22 
23 #include "qgspointxy.h"
24 
25 class QgsGeometry;
26 class QgsAbstractGeometry;
27 class QgsLineString;
28 class QgsLineSegment2D;
29 class QgsFeedback;
30 
31 /**
32  * \ingroup core
33  * \brief This class offers geometry processing methods.
34  *
35  * The methods are available via QgsGeometry::[geometryfunction]
36  * and therefore this does not need to be accessed directly.
37  *
38  * \note not available in Python bindings
39  */
40 
41 class QgsInternalGeometryEngine
42 {
43   public:
44 
45     /**
46      * The caller is responsible that the geometry is available and unchanged
47      * for the whole lifetime of this object.
48      * \param geometry
49      */
50     explicit QgsInternalGeometryEngine( const QgsGeometry &geometry );
51 
52     /**
53      * Returns an error string referring to the last error encountered.
54      *
55      * \since QGIS 3.16
56      */
57     QString lastError() const;
58 
59     /**
60      * Returns TRUE if the geometry is a polygon that is almost an axis-parallel rectangle.
61      *
62      * The \a maximumDeviation argument specifes the maximum angle (in degrees) that the polygon edges
63      * are allowed to deviate from axis parallel lines.
64      *
65      * By default the check will permit polygons with more than 4 edges, so long as the overall shape of
66      * the polygon is an axis-parallel rectangle (i.e. it is tolerant to rectangles with additional vertices
67      * added along the rectangle sides). If \a simpleRectanglesOnly is set to TRUE then the method will
68      * only return TRUE if the geometry is a simple rectangle consisting of 4 edges.
69      *
70      * \since QGIS 3.20
71      */
72     bool isAxisParallelRectangle( double maximumDeviation, bool simpleRectanglesOnly = false ) const;
73 
74     /**
75      * Will extrude a line or (segmentized) curve by a given offset and return a polygon
76      * representation of it.
77      *
78      * \param x offset in x direction
79      * \param y offset in y direction
80      * \returns an extruded polygon
81      */
82     QgsGeometry extrude( double x, double y ) const;
83 
84     /**
85      * Calculates the approximate pole of inaccessibility for a surface, which is the
86      * most distant internal point from the boundary of the surface. This function
87      * uses the 'polylabel' algorithm (Vladimir Agafonkin, 2016), which is an iterative
88      * approach guaranteed to find the true pole of inaccessibility within a specified
89      * tolerance. More precise tolerances require more iterations and will take longer
90      * to calculate.
91      * Optionally, the distance to the polygon boundary from the pole can be stored.
92      */
93     QgsGeometry poleOfInaccessibility( double precision, double *distanceFromBoundary = nullptr ) const;
94 
95     /**
96      * Attempts to orthogonalize a line or polygon geometry by shifting vertices to make the geometries
97      * angles either right angles or flat lines. This is an iterative algorithm which will loop until
98      * either the vertices are within a specified tolerance of right angles or a set number of maximum
99      * iterations is reached. The angle threshold parameter specifies how close to a right angle or
100      * straight line an angle must be before it is attempted to be straightened.
101      * \since QGIS 3.0
102      */
103     QgsGeometry orthogonalize( double tolerance = 1.0E-8, int maxIterations = 1000, double angleThreshold = 15.0 ) const;
104 
105     /**
106      * Densifies the geometry by adding the specified number of extra nodes within each
107      * segment of the geometry.
108      * If the geometry has z or m values present then these will be linearly interpolated
109      * at the added nodes.
110      * Curved geometry types are automatically segmentized by this routine.
111      * \since QGIS 3.0
112      */
113     QgsGeometry densifyByCount( int extraNodesPerSegment ) const;
114 
115     /**
116      * Densifies the geometry by adding regularly placed extra nodes inside each segment
117      * so that the maximum distance between any two nodes does not exceed the
118      * specified \a distance.
119      * E.g. specifying a distance 3 would cause the segment [0 0] -> [10 0]
120      * to be converted to [0 0] -> [2.5 0] -> [5 0] -> [7.5 0] -> [10 0], since
121      * 3 extra nodes are required on the segment and spacing these at 2.5 increments
122      * allows them to be evenly spaced over the segment.
123      * If the geometry has z or m values present then these will be linearly interpolated
124      * at the added nodes.
125      * Curved geometry types are automatically segmentized by this routine.
126      * \since QGIS 3.0
127      */
128     QgsGeometry densifyByDistance( double distance ) const;
129 
130     /**
131      * Calculates a variable width buffer for a (multi)curve geometry.
132      *
133      * The width of the buffer at each node in the input linestrings is calculated by
134      * calling the specified \a widthFunction, which must return an array of the buffer widths
135      * for every node in the line.
136      *
137      * The \a segments argument specifies the number of segments to approximate quarter-circle
138      * curves in the buffer.
139      *
140      * Non (multi)curve input geometries will return a null output geometry.
141      *
142      * \since QGIS 3.2
143      */
144     QgsGeometry variableWidthBuffer( int segments, const std::function< std::unique_ptr< double[] >( const QgsLineString *line ) > &widthFunction ) const;
145 
146     /**
147      * Calculates a tapered width buffer for a (multi)curve geometry.
148      *
149      * The buffer begins at a width of \a startWidth at the start of each curve, and
150      * ends at a width of \a endWidth. Note that unlike QgsGeometry::buffer() methods, \a startWidth
151      * and \a endWidth are the diameter of the buffer at these points, not the radius.
152      *
153      * The \a segments argument specifies the number of segments to approximate quarter-circle
154      * curves in the buffer.
155      *
156      * Non (multi)curve input geometries will return a null output geometry.
157      *
158      * \since QGIS 3.2
159      */
160     QgsGeometry taperedBuffer( double startWidth, double endWidth, int segments ) const;
161 
162     /**
163      * Calculates a variable width buffer using the m-values from a (multi)line geometry.
164      *
165      * The \a segments argument specifies the number of segments to approximate quarter-circle
166      * curves in the buffer.
167      *
168      * Non (multi)line input geometries will return a null output geometry.
169      *
170      * \since QGIS 3.2
171      */
172     QgsGeometry variableWidthBufferByM( int segments ) const;
173 
174     /**
175      * Returns a list of \a count random points generated inside a \a polygon geometry
176      * (if \a acceptPoint is specified, and restrictive, the number of points returned may
177      * be less than \a count).
178      *
179      * Optionally, a specific random \a seed can be used when generating points. If \a seed
180      * is 0, then a completely random sequence of points will be generated.
181      *
182      * The \a acceptPoint function is used to filter result candidates. If the function returns
183      * FALSE, then the point will not be accepted and another candidate generated.
184      *
185      * The optional \a feedback argument can be used to provide cancellation support during
186      * the point generation.
187      *
188      * When \a acceptPoint is specified, \a maxTriesPerPoint
189      * defines how many attempts to perform before giving up generating
190      * a point.
191      *
192      * \since QGIS 3.10
193      */
194     static QVector< QgsPointXY > randomPointsInPolygon( const QgsGeometry &polygon, int count,
195         const std::function< bool( const QgsPointXY & ) > &acceptPoint, unsigned long seed = 0, QgsFeedback *feedback = nullptr, int maxTriesPerPoint = 0 );
196 
197     /**
198      * Attempts to convert a non-curved geometry into a curved geometry type (e.g.
199      * LineString to CompoundCurve, Polygon to CurvePolygon).
200      *
201      * The \a distanceTolerance specifies the maximum deviation allowed between the original location
202      * of vertices and where they would fall on the candidate curved geometry.
203      *
204      * This method only consider a segments as suitable for replacing with an arc if the points are all
205      * regularly spaced on the candidate arc. The \a pointSpacingAngleTolerance parameter specifies the maximum
206      * angular deviation (in radians) allowed when testing for regular point spacing.
207      *
208      * \note The API is considered EXPERIMENTAL and can be changed without a notice
209      *
210      * \since QGIS 3.14
211      */
212     QgsGeometry convertToCurves( double distanceTolerance, double angleTolerance ) const;
213 
214     /**
215      * Returns the oriented minimum bounding box for the geometry, which is the smallest (by area)
216      * rotated rectangle which fully encompasses the geometry.
217      * The area, angle of the long axis (clockwise in degrees from North),
218      * width and height of the rotated bounding box will also be returned.
219      *
220      * If an error was encountered while creating the result, more information can be retrieved
221      * by calling lastError().
222      *
223      * \since QGIS 3.16
224      */
225     QgsGeometry orientedMinimumBoundingBox( double &area SIP_OUT, double &angle SIP_OUT, double &width SIP_OUT, double &height SIP_OUT ) const;
226 
227   private:
228     const QgsAbstractGeometry *mGeometry = nullptr;
229 
230     mutable QString mLastError;
231 };
232 
233 /**
234  * \brief A 2D ray which extends from an origin point to an infinite distance in a given direction.
235  * \ingroup core
236  * \note not available in Python bindings
237  * \since QGIS 3.2
238  */
239 class CORE_EXPORT QgsRay2D
240 {
241   public:
242 
243     /**
244      * Constructor for a ray starting at the given \a origin and extending an infinite distance
245      * in the specified \a direction.
246      */
QgsRay2D(const QgsPointXY & origin,QgsVector direction)247     QgsRay2D( const QgsPointXY &origin, QgsVector direction )
248       : origin( origin )
249       , direction( direction )
250     {}
251 
252     /**
253      * Finds the closest intersection point of the ray and a line \a segment.
254      *
255      * If found, the intersection point will be stored in \a intersectPoint.
256      *
257      * Returns TRUE if the ray intersects the line segment.
258      */
259     bool intersects( const QgsLineSegment2D &segment, QgsPointXY &intersectPoint ) const;
260 
261   private:
262 
263     QgsPointXY origin;
264     QgsVector direction;
265 };
266 
267 ///@cond PRIVATE
268 
269 // adapted for QGIS geometry classes from original work at https://github.com/trylock/visibility by trylock
270 
271 /**
272  * \brief Compares two line segments based on their distance from a given point.
273  *
274  * Assumes: (1) the line segments are intersected by some ray from the origin
275  *          (2) the line segments do not intersect except at their endpoints
276  *          (3) no line segment is collinear with the origin
277  * \ingroup core
278  * \since QGIS 3.2
279  */
280 class CORE_EXPORT QgsLineSegmentDistanceComparer
281 {
282   public:
283 
284     /**
285      * Constructor for QgsLineSegmentDistanceComparer, comparing points
286      * to the specified \a origin point.
287      */
QgsLineSegmentDistanceComparer(const QgsPointXY & origin)288     explicit QgsLineSegmentDistanceComparer( const QgsPointXY &origin )
289       : mOrigin( origin )
290     {}
291 
292     /**
293      * Checks whether the line segment \a ab is closer to the origin than the
294      * line segment \a cd.
295      * \param ab line segment: left hand side of the comparison operator
296      * \param cd line segment: right hand side of the comparison operator
297      * \returns TRUE if ab < cd (ab is closer than cd) to origin
298      */
299     bool operator()( QgsLineSegment2D ab, QgsLineSegment2D cd ) const;
300 
301   private:
302 
303     QgsPointXY mOrigin;
304 
305 };
306 
307 
308 // adapted for QGIS geometry classes from original work at https://github.com/trylock/visibility by trylock
309 
310 /**
311  * \brief Compares angles from an origin to points clockwise, starting at the positive y-axis.
312  *
313  * \ingroup core
314  * \since QGIS 3.2
315  */
316 class CORE_EXPORT QgsClockwiseAngleComparer
317 {
318   public:
QgsClockwiseAngleComparer(const QgsPointXY & origin)319     explicit QgsClockwiseAngleComparer( const QgsPointXY &origin )
320       : mVertex( origin )
321     {}
322 
323     bool operator()( const QgsPointXY &a, const QgsPointXY &b ) const;
324 
325   private:
326 
327     QgsPointXY mVertex;
328 
329 };
330 
331 ///@endcond PRIVATE
332 
333 #endif // QGSINTERNALGEOMETRYENGINE_H
334