QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
geomfunction.cpp
Go to the documentation of this file.
1/*
2 * libpal - Automated Placement of Labels Library
3 *
4 * Copyright (C) 2008 Maxence Laurent, MIS-TIC, HEIG-VD
5 * University of Applied Sciences, Western Switzerland
6 * http://www.hes-so.ch
7 *
8 * Contact:
9 * maxence.laurent <at> heig-vd <dot> ch
10 * or
11 * eric.taillard <at> heig-vd <dot> ch
12 *
13 * This file is part of libpal.
14 *
15 * libpal is free software: you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation, either version 3 of the License, or
18 * (at your option) any later version.
19 *
20 * libpal is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with libpal. If not, see <http://www.gnu.org/licenses/>.
27 *
28 */
29
30#include "geomfunction.h"
31
32#include <vector>
33
34#include "feature.h"
35#include "pal.h"
36#include "qgis.h"
38#include "qgsmessagelog.h"
39#include "util.h"
40
41using namespace pal;
42
43void heapsort( std::vector< int > &sid, int *id, const std::vector< double > &x, std::size_t N )
44{
45 std::size_t n = N;
46 std::size_t i = n / 2;
47 std::size_t parent;
48 std::size_t child;
49 int tx;
50 for ( ;; )
51 {
52 if ( i > 0 )
53 {
54 i--;
55 tx = sid[i];
56 }
57 else
58 {
59 n--;
60 if ( n == 0 ) return;
61 tx = sid[n];
62 sid[n] = sid[0];
63 }
64 parent = i;
65 child = i * 2 + 1;
66 while ( child < n )
67 {
68 if ( child + 1 < n && x[id[sid[child + 1]]] > x[id[sid[child]]] )
69 {
70 child++;
71 }
72 if ( x[id[sid[child]]] > x[id[tx]] )
73 {
74 sid[parent] = sid[child];
75 parent = child;
76 child = parent * 2 + 1;
77 }
78 else
79 {
80 break;
81 }
82 }
83 sid[parent] = tx;
84 }
85}
86
87
88void heapsort2( int *x, double *heap, std::size_t N )
89{
90 std::size_t n = N;
91 std::size_t i = n / 2;
92 std::size_t parent;
93 std::size_t child;
94 double t;
95 int tx;
96 for ( ;; )
97 {
98 if ( i > 0 )
99 {
100 i--;
101 t = heap[i];
102 tx = x[i];
103 }
104 else
105 {
106 n--;
107 if ( n == 0 ) return;
108 t = heap[n];
109 tx = x[n];
110 heap[n] = heap[0];
111 x[n] = x[0];
112 }
113 parent = i;
114 child = i * 2 + 1;
115 while ( child < n )
116 {
117 if ( child + 1 < n && heap[child + 1] > heap[child] )
118 {
119 child++;
120 }
121 if ( heap[child] > t )
122 {
123 heap[parent] = heap[child];
124 x[parent] = x[child];
125 parent = child;
126 child = parent * 2 + 1;
127 }
128 else
129 {
130 break;
131 }
132 }
133 heap[parent] = t;
134 x[parent] = tx;
135 }
136}
137
138bool GeomFunction::isSegIntersects( double x1, double y1, double x2, double y2, // 1st segment
139 double x3, double y3, double x4, double y4 ) // 2nd segment
140{
141 return ( cross_product( x1, y1, x2, y2, x3, y3 ) * cross_product( x1, y1, x2, y2, x4, y4 ) < 0
142 && cross_product( x3, y3, x4, y4, x1, y1 ) * cross_product( x3, y3, x4, y4, x2, y2 ) < 0 );
143}
144
145bool GeomFunction::computeLineIntersection( double x1, double y1, double x2, double y2, // 1st line (segment)
146 double x3, double y3, double x4, double y4, // 2nd line segment
147 double *x, double *y )
148{
149
150 double a1, a2, b1, b2, c1, c2;
151 double denom;
152
153 a1 = y2 - y1;
154 b1 = x1 - x2;
155 c1 = x2 * y1 - x1 * y2;
156
157 a2 = y4 - y3;
158 b2 = x3 - x4;
159 c2 = x4 * y3 - x3 * y4;
160
161 denom = a1 * b2 - a2 * b1;
162 if ( qgsDoubleNear( denom, 0.0 ) )
163 {
164 return false;
165 }
166 else
167 {
168 *x = ( b1 * c2 - b2 * c1 ) / denom;
169 *y = ( a2 * c1 - a1 * c2 ) / denom;
170 }
171
172 return true;
173}
174
175std::vector< int > GeomFunction::convexHullId( std::vector< int > &id, const std::vector< double > &x, const std::vector< double > &y )
176{
177 std::vector< int > convexHull( x.size() );
178 for ( std::size_t i = 0; i < x.size(); i++ )
179 {
180 convexHull[i] = static_cast< int >( i );
181 }
182
183 if ( x.size() <= 3 )
184 return convexHull;
185
186 std::vector< int > stack( x.size() );
187 std::vector< double > tan( x.size() );
188
189 // find the lowest y value
190 heapsort( convexHull, id.data(), y, y.size() );
191
192 // find the lowest x value from the lowest y
193 std::size_t ref = 1;
194 while ( ref < x.size() && qgsDoubleNear( y[id[convexHull[ref]]], y[id[convexHull[0]]] ) )
195 ref++;
196
197 heapsort( convexHull, id.data(), x, ref );
198
199 // the first point is now for sure in the hull as well as the ref one
200 for ( std::size_t i = ref; i < x.size(); i++ )
201 {
202 if ( qgsDoubleNear( y[id[convexHull[i]]], y[id[convexHull[0]]] ) )
203 tan[i] = FLT_MAX;
204 else
205 tan[i] = ( x[id[convexHull[0]]] - x[id[convexHull[i]]] ) / ( y[id[convexHull[i]]] - y[id[convexHull[0]]] );
206 }
207
208 if ( ref < x.size() )
209 heapsort2( convexHull.data() + ref, tan.data() + ref, x.size() - ref );
210
211 // the second point is in too
212 stack[0] = convexHull[0];
213 if ( ref == 1 )
214 {
215 stack[1] = convexHull[1];
216 ref++;
217 }
218 else
219 stack[1] = convexHull[ref - 1];
220
221 std::size_t top = 1;
222 std::size_t second = 0;
223
224 for ( std::size_t i = ref; i < x.size(); i++ )
225 {
226 double result = cross_product( x[id[stack[second]]], y[id[stack[second]]],
227 x[id[stack[top]]], y[id[stack[top]]], x[id[convexHull[i]]], y[id[convexHull[i]]] );
228 // Coolineaire !! garder le plus éloigné
229 if ( qgsDoubleNear( result, 0.0 ) )
230 {
231 if ( QgsGeometryUtilsBase::sqrDistance2D( x[id[stack[second]]], y[id[stack[second]]], x[id[convexHull[i]]], y[id[convexHull[i]]] )
232 > QgsGeometryUtilsBase::sqrDistance2D( x[id[stack[second]]], y[id[stack[second]]], x[id[stack[top]]], y[id[stack[top]]] ) )
233 {
234 stack[top] = convexHull[i];
235 }
236 }
237 else if ( result > 0 ) //convex
238 {
239 second++;
240 top++;
241 stack[top] = convexHull[i];
242 }
243 else
244 {
245 while ( result < 0 && top > 1 )
246 {
247 second--;
248 top--;
249 result = cross_product( x[id[stack[second]]],
250 y[id[stack[second]]], x[id[stack[top]]],
251 y[id[stack[top]]], x[id[convexHull[i]]], y[id[convexHull[i]]] );
252 }
253 second++;
254 top++;
255 stack[top] = convexHull[i];
256 }
257 }
258
259 for ( std::size_t i = 0; i <= top; i++ )
260 {
261 convexHull[i] = stack[i];
262 }
263
264 convexHull.resize( top + 1 );
265 return convexHull;
266}
267
268bool GeomFunction::reorderPolygon( std::vector<double> &x, std::vector<double> &y )
269{
270 std::vector< int > pts( x.size() );
271 for ( std::size_t i = 0; i < x.size(); i++ )
272 pts[i] = static_cast< int >( i );
273
274 std::vector< int > convexHull = convexHullId( pts, x, y );
275
276 int inc = 0;
277 if ( pts[convexHull[0]] < pts[convexHull[1]] && pts[convexHull[1]] < pts[convexHull[2]] )
278 inc = 1;
279 else if ( pts[convexHull[0]] > pts[convexHull[1]] && pts[convexHull[1]] > pts[convexHull[2]] )
280 inc = -1;
281 else if ( pts[convexHull[0]] > pts[convexHull[1]] && pts[convexHull[1]] < pts[convexHull[2]] && pts[convexHull[2]] < pts[convexHull[0]] )
282 inc = 1;
283 else if ( pts[convexHull[0]] > pts[convexHull[1]] && pts[convexHull[1]] < pts[convexHull[2]] && pts[convexHull[2]] > pts[convexHull[0]] )
284 inc = -1;
285 else if ( pts[convexHull[0]] < pts[convexHull[1]] && pts[convexHull[1]] > pts[convexHull[2]] && pts[convexHull[2]] > pts[convexHull[0]] )
286 inc = -1;
287 else if ( pts[convexHull[0]] < pts[convexHull[1]] && pts[convexHull[1]] > pts[convexHull[2]] && pts[convexHull[2]] < pts[convexHull[0]] )
288 inc = 1;
289 else
290 {
291 // wrong cHull
292 return false;
293 }
294
295 if ( inc == -1 ) // re-order points
296 {
297 for ( std::size_t i = 0, j = x.size() - 1; i <= j; i++, j-- )
298 {
299 std::swap( x[i], x[j] );
300 std::swap( y[i], y[j] );
301 }
302 }
303 return true;
304}
305
306bool GeomFunction::containsCandidate( const GEOSPreparedGeometry *geom, double x, double y, double width, double height, double alpha )
307{
308 if ( !geom )
309 return false;
310
311 try
312 {
313 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
314 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 5, 2 );
315
316 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, x, y );
317 if ( !qgsDoubleNear( alpha, 0.0 ) )
318 {
319 const double beta = alpha + M_PI_2;
320 const double dx1 = std::cos( alpha ) * width;
321 const double dy1 = std::sin( alpha ) * width;
322 const double dx2 = std::cos( beta ) * height;
323 const double dy2 = std::sin( beta ) * height;
324 GEOSCoordSeq_setXY_r( geosctxt, coord, 1, x + dx1, y + dy1 );
325 GEOSCoordSeq_setXY_r( geosctxt, coord, 2, x + dx1 + dx2, y + dy1 + dy2 );
326 GEOSCoordSeq_setXY_r( geosctxt, coord, 3, x + dx2, y + dy2 );
327 }
328 else
329 {
330 GEOSCoordSeq_setXY_r( geosctxt, coord, 1, x + width, y );
331 GEOSCoordSeq_setXY_r( geosctxt, coord, 2, x + width, y + height );
332 GEOSCoordSeq_setXY_r( geosctxt, coord, 3, x, y + height );
333 }
334 //close ring
335 GEOSCoordSeq_setXY_r( geosctxt, coord, 4, x, y );
336
337 geos::unique_ptr bboxGeos( GEOSGeom_createLinearRing_r( geosctxt, coord ) );
338 const bool result = ( GEOSPreparedContainsProperly_r( geosctxt, geom, bboxGeos.get() ) == 1 );
339 return result;
340 }
341 catch ( QgsGeosException &e )
342 {
343 qWarning( "GEOS exception: %s", e.what() );
345 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
346 return false;
348 }
349 return false;
350}
351
352void GeomFunction::findLineCircleIntersection( double cx, double cy, double radius,
353 double x1, double y1, double x2, double y2,
354 double &xRes, double &yRes )
355{
356 double multiplier = 1;
357 if ( radius < 10 )
358 {
359 // these calculations get unstable for small coordinates differences, e.g. as a result of map labeling in a geographic
360 // CRS
361 multiplier = 10000;
362 x1 *= multiplier;
363 y1 *= multiplier;
364 x2 *= multiplier;
365 y2 *= multiplier;
366 cx *= multiplier;
367 cy *= multiplier;
368 radius *= multiplier;
369 }
370
371 const double dx = x2 - x1;
372 const double dy = y2 - y1;
373
374 const double A = dx * dx + dy * dy;
375 const double B = 2 * ( dx * ( x1 - cx ) + dy * ( y1 - cy ) );
376 const double C = ( x1 - cx ) * ( x1 - cx ) + ( y1 - cy ) * ( y1 - cy ) - radius * radius;
377
378 const double det = B * B - 4 * A * C;
379 if ( A <= 0.000000000001 || det < 0 )
380 // Should never happen, No real solutions.
381 return;
382
383 if ( qgsDoubleNear( det, 0.0 ) )
384 {
385 // Could potentially happen.... One solution.
386 const double t = -B / ( 2 * A );
387 xRes = x1 + t * dx;
388 yRes = y1 + t * dy;
389 }
390 else
391 {
392 // Two solutions.
393 // Always use the 1st one
394 // We only really have one solution here, as we know the line segment will start in the circle and end outside
395 const double t = ( -B + std::sqrt( det ) ) / ( 2 * A );
396 xRes = x1 + t * dx;
397 yRes = y1 + t * dy;
398 }
399
400 if ( multiplier != 1 )
401 {
402 xRes /= multiplier;
403 yRes /= multiplier;
404 }
405}
static double sqrDistance2D(double x1, double y1, double x2, double y2)
Returns the squared 2D distance between (x1, y1) and (x2, y2).
static GEOSContextHandle_t get()
Returns a thread local instance of a GEOS context, safe for use in the current thread.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
static bool reorderPolygon(std::vector< double > &x, std::vector< double > &y)
Reorder points to have cross prod ((x,y)[i], (x,y)[i+1), point) > 0 when point is outside.
static std::vector< int > convexHullId(std::vector< int > &id, const std::vector< double > &x, const std::vector< double > &y)
Compute the convex hull in O(n·log(n)).
static bool computeLineIntersection(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double *x, double *y)
Compute the point where two lines intersect.
static bool isSegIntersects(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
Returns true if the two segments intersect.
static double cross_product(double x1, double y1, double x2, double y2, double x3, double y3)
static bool containsCandidate(const GEOSPreparedGeometry *geom, double x, double y, double width, double height, double alpha)
Returns true if a GEOS prepared geometry totally contains a label candidate.
static void findLineCircleIntersection(double cx, double cy, double radius, double x1, double y1, double x2, double y2, double &xRes, double &yRes)
void heapsort(std::vector< int > &sid, int *id, const std::vector< double > &x, std::size_t N)
void heapsort2(int *x, double *heap, std::size_t N)
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:114
#define Q_NOWARN_UNREACHABLE_PUSH
Definition qgis.h:7171
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
#define Q_NOWARN_UNREACHABLE_POP
Definition qgis.h:7172