QGIS API Documentation 3.99.0-Master (26c88405ac0)
Loading...
Searching...
No Matches
pointset.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 "pointset.h"
31
32#include "geomfunction.h"
33#include "qgsgeometryutils.h"
35#include "qgsgeos.h"
36#include "qgsmessagelog.h"
37#include "util.h"
38
39#include <qglobal.h>
40
41using namespace pal;
42
44{
45 nbPoints = 0;
46 type = -1;
47}
48
49PointSet::PointSet( int nbPoints, double *x, double *y )
51 , type( GEOS_POLYGON )
52{
53 this->x.resize( nbPoints );
54 this->y.resize( nbPoints );
55 int i;
56
57 for ( i = 0; i < nbPoints; i++ )
58 {
59 this->x[i] = x[i];
60 this->y[i] = y[i];
61 }
62
63}
64
65PointSet::PointSet( double aX, double aY )
66 : type( GEOS_POINT )
67 , xmin( aX )
68 , xmax( aY )
69 , ymin( aX )
70 , ymax( aY )
71{
72 nbPoints = 1;
73 x.resize( 1 );
74 y.resize( 1 );
75 x[0] = aX;
76 y[0] = aY;
77}
78
80 : xmin( ps.xmin )
81 , xmax( ps.xmax )
82 , ymin( ps.ymin )
83 , ymax( ps.ymax )
84{
85 nbPoints = ps.nbPoints;
86 x = ps.x;
87 y = ps.y;
88
90
91 type = ps.type;
92
93 holeOf = ps.holeOf;
94
95 if ( ps.mGeos )
96 {
97 mGeos = GEOSGeom_clone_r( QgsGeosContext::get(), ps.mGeos );
98 mOwnsGeom = true;
99 }
100}
101
103{
104 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
105
106 bool needClose = false;
107 if ( type == GEOS_POLYGON && ( !qgsDoubleNear( x[0], x[ nbPoints - 1] ) || !qgsDoubleNear( y[0], y[ nbPoints - 1 ] ) ) )
108 {
109 needClose = true;
110 }
111
112 GEOSCoordSequence *coord = nullptr;
113#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
114 if ( !needClose )
115 {
116 // use optimised method if we don't have to force close an open ring
117 coord = GEOSCoordSeq_copyFromArrays_r( geosctxt, x.data(), y.data(), nullptr, nullptr, nbPoints );
118 }
119#else
120 if ( false )
121 {
122 }
123#endif
124 else
125 {
126 coord = GEOSCoordSeq_create_r( geosctxt, nbPoints + ( needClose ? 1 : 0 ), 2 );
127 for ( int i = 0; i < nbPoints; ++i )
128 {
129 GEOSCoordSeq_setXY_r( geosctxt, coord, i, x[i], y[i] );
130 }
131
132 //close ring if needed
133 if ( needClose )
134 {
135 GEOSCoordSeq_setXY_r( geosctxt, coord, nbPoints, x[0], y[0] );
136 }
137 }
138
139 switch ( type )
140 {
141 case GEOS_POLYGON:
142 mGeos = GEOSGeom_createPolygon_r( geosctxt, GEOSGeom_createLinearRing_r( geosctxt, coord ), nullptr, 0 );
143 break;
144
145 case GEOS_LINESTRING:
146 mGeos = GEOSGeom_createLineString_r( geosctxt, coord );
147 break;
148
149 case GEOS_POINT:
150 mGeos = GEOSGeom_createPoint_r( geosctxt, coord );
151 break;
152 }
153
154 mOwnsGeom = true;
155}
156
157const GEOSPreparedGeometry *PointSet::preparedGeom() const
158{
159 if ( !mGeos )
161
162 if ( !mPreparedGeom )
163 {
164 mPreparedGeom = GEOSPrepare_r( QgsGeosContext::get(), mGeos );
165 }
166 return mPreparedGeom;
167}
168
170{
171 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
172 if ( mOwnsGeom ) // delete old geometry if we own it
173 GEOSGeom_destroy_r( geosctxt, mGeos );
174 mOwnsGeom = false;
175 mGeos = nullptr;
176
177 if ( mPreparedGeom )
178 {
179 GEOSPreparedGeom_destroy_r( geosctxt, mPreparedGeom );
180 mPreparedGeom = nullptr;
181 }
182
183 if ( mGeosPreparedBoundary )
184 {
185 GEOSPreparedGeom_destroy_r( geosctxt, mGeosPreparedBoundary );
186 mGeosPreparedBoundary = nullptr;
187 }
188
189 if ( mMultipartPreparedGeos )
190 {
191 GEOSPreparedGeom_destroy_r( geosctxt, mMultipartPreparedGeos );
192 mMultipartPreparedGeos = nullptr;
193 }
194 if ( mMultipartGeos )
195 {
196 GEOSGeom_destroy_r( geosctxt, mMultipartGeos );
197 mMultipartGeos = nullptr;
198 }
199
200 mLength = -1;
201 mArea = -1;
202}
203
205{
206 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
207
208 if ( mGeos && mOwnsGeom )
209 {
210 GEOSGeom_destroy_r( geosctxt, mGeos );
211 mGeos = nullptr;
212 }
213 GEOSPreparedGeom_destroy_r( geosctxt, mPreparedGeom );
214
215 if ( mGeosPreparedBoundary )
216 {
217 GEOSPreparedGeom_destroy_r( geosctxt, mGeosPreparedBoundary );
218 mGeosPreparedBoundary = nullptr;
219 }
220
221 if ( mMultipartPreparedGeos )
222 {
223 GEOSPreparedGeom_destroy_r( geosctxt, mMultipartPreparedGeos );
224 mMultipartPreparedGeos = nullptr;
225 }
226 if ( mMultipartGeos )
227 {
228 GEOSGeom_destroy_r( geosctxt, mMultipartGeos );
229 mMultipartGeos = nullptr;
230 }
231
232 deleteCoords();
233}
234
236{
237 x.clear();
238 y.clear();
239}
240
241std::unique_ptr<PointSet> PointSet::extractShape( int nbPtSh, int imin, int imax, int fps, int fpe, double fptx, double fpty )
242{
243 int i, j;
244
245 auto newShape = std::make_unique< PointSet >();
246 newShape->type = GEOS_POLYGON;
247 newShape->nbPoints = nbPtSh;
248 newShape->x.resize( newShape->nbPoints );
249 newShape->y.resize( newShape->nbPoints );
250
251 // new shape # 1 from imin to imax
252 for ( j = 0, i = imin; i != ( imax + 1 ) % nbPoints; i = ( i + 1 ) % nbPoints, j++ )
253 {
254 newShape->x[j] = x[i];
255 newShape->y[j] = y[i];
256 }
257 // is the cutting point a new one ?
258 if ( fps != fpe )
259 {
260 // yes => so add it
261 newShape->x[j] = fptx;
262 newShape->y[j] = fpty;
263 }
264
265 return newShape;
266}
267
268std::unique_ptr<PointSet> PointSet::clone() const
269{
270 return std::unique_ptr< PointSet>( new PointSet( *this ) );
271}
272
273bool PointSet::containsPoint( double x, double y ) const
274{
275 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
276 try
277 {
278 geos::unique_ptr point( GEOSGeom_createPointFromXY_r( geosctxt, x, y ) );
279 const bool result = ( GEOSPreparedContainsProperly_r( geosctxt, preparedGeom(), point.get() ) == 1 );
280
281 return result;
282 }
283 catch ( QgsGeosException &e )
284 {
285 qWarning( "GEOS exception: %s", e.what() );
286 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
287 return false;
288 }
289
290}
291
292bool PointSet::containsLabelCandidate( double x, double y, double width, double height, double alpha ) const
293{
294 return GeomFunction::containsCandidate( preparedGeom(), x, y, width, height, alpha );
295}
296
297QLinkedList<PointSet *> PointSet::splitPolygons( PointSet *inputShape, double labelWidth, double labelHeight )
298{
299 int j;
300
301 double bestArea = 0;
302
303 double b;
304
305 int holeS = -1; // hole start and end points
306 int holeE = -1;
307
308 int retainedPt = -1;
309
310 const double labelArea = labelWidth * labelHeight;
311
312 QLinkedList<PointSet *> inputShapes;
313 inputShapes.push_back( inputShape );
314 QLinkedList<PointSet *> outputShapes;
315
316 while ( !inputShapes.isEmpty() )
317 {
318 PointSet *shape = inputShapes.takeFirst();
319
320 const std::vector< double > &x = shape->x;
321 const std::vector< double > &y = shape->y;
322 const int nbp = shape->nbPoints;
323 std::vector< int > pts( nbp );
324 for ( int i = 0; i < nbp; i++ )
325 {
326 pts[i] = i;
327 }
328
329 // compute convex hull
330 shape->convexHull = GeomFunction::convexHullId( pts, x, y );
331
332 bestArea = 0;
333 retainedPt = -1;
334
335 // lookup for a hole
336 for ( std::size_t ihs = 0; ihs < shape->convexHull.size(); ihs++ )
337 {
338 // ihs->ihn => cHull'seg
339 const std::size_t ihn = ( ihs + 1 ) % shape->convexHull.size();
340
341 const int ips = shape->convexHull[ihs];
342 const int ipn = ( ips + 1 ) % nbp;
343 if ( ipn != shape->convexHull[ihn] ) // next point on shape is not the next point on cHull => there is a hole here !
344 {
345 double bestcp = 0;
346 int pt = -1;
347 // lookup for the deepest point in the hole
348 for ( int i = ips; i != shape->convexHull[ihn]; i = ( i + 1 ) % nbp )
349 {
350 const double cp = std::fabs( GeomFunction::cross_product( x[shape->convexHull[ihs]], y[shape->convexHull[ihs]],
351 x[shape->convexHull[ihn]], y[shape->convexHull[ihn]],
352 x[i], y[i] ) );
353 if ( cp - bestcp > EPSILON )
354 {
355 bestcp = cp;
356 pt = i;
357 }
358 }
359
360 if ( pt != -1 )
361 {
362 // compute the ihs->ihn->pt triangle's area
363 const double base = QgsGeometryUtilsBase::distance2D( x[shape->convexHull[ihs]], y[shape->convexHull[ihs]],
364 x[shape->convexHull[ihn]], y[shape->convexHull[ihn]] );
365
366 b = QgsGeometryUtilsBase::distance2D( x[shape->convexHull[ihs]], y[shape->convexHull[ihs]],
367 x[pt], y[pt] );
368
369 const double c = QgsGeometryUtilsBase::distance2D( x[shape->convexHull[ihn]], y[shape->convexHull[ihn]],
370 x[pt], y[pt] );
371
372 const double s = ( base + b + c ) / 2; // s = half perimeter
373 double area = s * ( s - base ) * ( s - b ) * ( s - c );
374 if ( area < 0 )
375 area = -area;
376
377 // retain the biggest area
378 if ( area - bestArea > EPSILON )
379 {
380 bestArea = area;
381 retainedPt = pt;
382 holeS = ihs;
383 holeE = ihn;
384 }
385 }
386 }
387 }
388
389 // we have a hole, its area, and the deppest point in hole
390 // we're going to find the second point to cup the shape
391 // holeS = hole starting point
392 // holeE = hole ending point
393 // retainedPt = deppest point in hole
394 // bestArea = area of triangle HoleS->holeE->retainedPoint
395 bestArea = std::sqrt( bestArea );
396 double cx, cy, dx, dy, ex, ey, fx, fy, seg_length, ptx = 0, pty = 0, fptx = 0, fpty = 0;
397 int ps = -1, pe = -1, fps = -1, fpe = -1;
398 if ( retainedPt >= 0 && bestArea > labelArea ) // there is a hole so we'll cut the shape in two new shape (only if hole area is bigger than twice labelArea)
399 {
400 double c = std::numeric_limits<double>::max();
401
402 // iterate on all shape points except points which are in the hole
403 bool isValid;
404 int k, l;
405 for ( int i = ( shape->convexHull[holeE] + 1 ) % nbp; i != ( shape->convexHull[holeS] - 1 + nbp ) % nbp; i = j )
406 {
407 j = ( i + 1 ) % nbp; // i->j is shape segment not in hole
408
409 // compute distance between retainedPoint and segment
410 // whether perpendicular distance (if retaindPoint is fronting segment i->j)
411 // or distance between retainedPt and i or j (choose the nearest)
412 seg_length = QgsGeometryUtilsBase::distance2D( x[i], y[i], x[j], y[j] );
413 cx = ( x[i] + x[j] ) / 2.0;
414 cy = ( y[i] + y[j] ) / 2.0;
415 dx = cy - y[i];
416 dy = cx - x[i];
417
418 ex = cx - dx;
419 ey = cy + dy;
420 fx = cx + dx;
421 fy = cy - dy;
422
423 if ( seg_length < EPSILON || std::fabs( ( b = GeomFunction::cross_product( ex, ey, fx, fy, x[retainedPt], y[retainedPt] ) / ( seg_length ) ) ) > ( seg_length / 2 ) ) // retainedPt is not fronting i->j
424 {
425 if ( ( ex = QgsGeometryUtilsBase::sqrDistance2D( x[i], y[i], x[retainedPt], y[retainedPt] ) ) < ( ey = QgsGeometryUtilsBase::sqrDistance2D( x[j], y[j], x[retainedPt], y[retainedPt] ) ) )
426 {
427 b = ex;
428 ps = i;
429 pe = i;
430 }
431 else
432 {
433 b = ey;
434 ps = j;
435 pe = j;
436 }
437 }
438 else // point fronting i->j => compute pependicular distance => create a new point
439 {
440 b = GeomFunction::cross_product( x[i], y[i], x[j], y[j], x[retainedPt], y[retainedPt] ) / seg_length;
441 b *= b;
442 ps = i;
443 pe = j;
444
445 if ( !GeomFunction::computeLineIntersection( x[i], y[i], x[j], y[j], x[retainedPt], y[retainedPt], x[retainedPt] - dx, y[retainedPt] + dy, &ptx, &pty ) )
446 {
447 //error - it should intersect the line
448 }
449 }
450
451 isValid = true;
452 double pointX, pointY;
453 if ( ps == pe )
454 {
455 pointX = x[pe];
456 pointY = y[pe];
457 }
458 else
459 {
460 pointX = ptx;
461 pointY = pty;
462 }
463
464 for ( k = shape->convexHull[holeS]; k != shape->convexHull[holeE]; k = ( k + 1 ) % nbp )
465 {
466 l = ( k + 1 ) % nbp;
467 if ( GeomFunction::isSegIntersects( x[retainedPt], y[retainedPt], pointX, pointY, x[k], y[k], x[l], y[l] ) )
468 {
469 isValid = false;
470 break;
471 }
472 }
473
474
475 if ( isValid && b < c )
476 {
477 c = b;
478 fps = ps;
479 fpe = pe;
480 fptx = ptx;
481 fpty = pty;
482 }
483 } // for point which are not in hole
484
485 // we will cut the shapeu in two new shapes, one from [retainedPoint] to [newPoint] and one form [newPoint] to [retainedPoint]
486 const int imin = retainedPt;
487 int imax = ( ( ( fps < retainedPt && fpe < retainedPt ) || ( fps > retainedPt && fpe > retainedPt ) ) ? std::min( fps, fpe ) : std::max( fps, fpe ) );
488
489 int nbPtSh1, nbPtSh2; // how many points in new shapes ?
490 if ( imax > imin )
491 nbPtSh1 = imax - imin + 1 + ( fpe != fps );
492 else
493 nbPtSh1 = imax + nbp - imin + 1 + ( fpe != fps );
494
495 if ( ( imax == fps ? fpe : fps ) < imin )
496 nbPtSh2 = imin - ( imax == fps ? fpe : fps ) + 1 + ( fpe != fps );
497 else
498 nbPtSh2 = imin + nbp - ( imax == fps ? fpe : fps ) + 1 + ( fpe != fps );
499
500 if ( retainedPt == -1 || fps == -1 || fpe == -1 )
501 {
502 if ( shape->parent )
503 delete shape;
504 }
505 // check for useless splitting
506 else if ( imax == imin || nbPtSh1 <= 2 || nbPtSh2 <= 2 || nbPtSh1 == nbp || nbPtSh2 == nbp )
507 {
508 outputShapes.append( shape );
509 }
510 else
511 {
512
513 PointSet *newShape = shape->extractShape( nbPtSh1, imin, imax, fps, fpe, fptx, fpty ).release();
514
515 if ( shape->parent )
516 newShape->parent = shape->parent;
517 else
518 newShape->parent = shape;
519
520 inputShapes.append( newShape );
521
522 if ( imax == fps )
523 imax = fpe;
524 else
525 imax = fps;
526
527 newShape = shape->extractShape( nbPtSh2, imax, imin, fps, fpe, fptx, fpty ).release();
528
529 if ( shape->parent )
530 newShape->parent = shape->parent;
531 else
532 newShape->parent = shape;
533
534 inputShapes.append( newShape );
535
536 if ( shape->parent )
537 delete shape;
538 }
539 }
540 else
541 {
542 outputShapes.append( shape );
543 }
544 }
545 return outputShapes;
546}
547
548void PointSet::offsetCurveByDistance( double distance )
549{
550 if ( !mGeos )
552
553 if ( !mGeos || type != GEOS_LINESTRING )
554 return;
555
556 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
557 geos::unique_ptr newGeos = nullptr;
558 try
559 {
560 newGeos.reset( GEOSOffsetCurve_r( geosctxt, mGeos, distance, 0, GEOSBUF_JOIN_MITRE, 2 ) );
561 if ( !newGeos )
562 return;
563
564 // happens sometime, if the offset curve self-intersects
565 if ( GEOSGeomTypeId_r( geosctxt, newGeos.get() ) == GEOS_MULTILINESTRING )
566 {
567 // we keep the longest part
568 const int nParts = GEOSGetNumGeometries_r( geosctxt, newGeos.get() );
569 double maximumLength = -1;
570 const GEOSGeometry *longestPart = nullptr;
571 for ( int i = 0; i < nParts; ++i )
572 {
573 const GEOSGeometry *part = GEOSGetGeometryN_r( geosctxt, newGeos.get(), i );
574 double partLength = -1;
575 if ( GEOSLength_r( geosctxt, part, &partLength ) == 1 )
576 {
577 if ( partLength > maximumLength )
578 {
579 maximumLength = partLength;
580 longestPart = part;
581 }
582 }
583 }
584
585 if ( !longestPart )
586 {
587 // something is really wrong!
588 return;
589 }
590
591 geos::unique_ptr longestPartClone( GEOSGeom_clone_r( geosctxt, longestPart ) );
592 newGeos = std::move( longestPartClone );
593 }
594
595#if GEOS_VERSION_MAJOR==3 && GEOS_VERSION_MINOR<11
596 if ( distance < 0 )
597 {
598 // geos < 3.11 reverses the direction of offset curves with negative distances -- we don't want that!
599 geos::unique_ptr reversed( GEOSReverse_r( geosctxt, newGeos.get() ) );
600 if ( !reversed )
601 return;
602
603 newGeos = std::move( reversed );
604 }
605#endif
606
607 const int newNbPoints = GEOSGeomGetNumPoints_r( geosctxt, newGeos.get() );
608 const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, newGeos.get() );
609 std::vector< double > newX;
610 std::vector< double > newY;
611 newX.resize( newNbPoints );
612 newY.resize( newNbPoints );
613 for ( int i = 0; i < newNbPoints; i++ )
614 {
615 GEOSCoordSeq_getX_r( geosctxt, coordSeq, i, &newX[i] );
616 GEOSCoordSeq_getY_r( geosctxt, coordSeq, i, &newY[i] );
617 }
618 nbPoints = newNbPoints;
619 x = std::move( newX );
620 y = std::move( newY );
621 }
622 catch ( QgsGeosException &e )
623 {
624 qWarning( "GEOS exception: %s", e.what() );
625 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
626 return;
627 }
628
630 mGeos = newGeos.release();
631 mOwnsGeom = true;
632}
633
634void PointSet::extendLineByDistance( double startDistance, double endDistance, double smoothDistance )
635{
636 if ( nbPoints < 2 )
637 return;
638
639 double x0 = x[0];
640 double y0 = y[0];
641 if ( startDistance > 0 )
642 {
643 // trace forward by smoothDistance
644 double x1 = x[1];
645 double y1 = y[1];
646
647 double distanceConsumed = 0;
648 double lastX = x0;
649 double lastY = y0;
650 for ( int i = 1; i < nbPoints; ++i )
651 {
652 const double thisX = x[i];
653 const double thisY = y[i];
654 const double thisSegmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, lastX, lastY );
655 distanceConsumed += thisSegmentLength;
656 if ( distanceConsumed >= smoothDistance )
657 {
658 const double c = ( distanceConsumed - smoothDistance ) / thisSegmentLength;
659 x1 = lastX + c * ( thisX - lastX );
660 y1 = lastY + c * ( thisY - lastY );
661 break;
662 }
663 lastX = thisX;
664 lastY = thisY;
665 }
666
667 const double distance = QgsGeometryUtilsBase::distance2D( x1, y1, x0, y0 );
668 const double extensionFactor = ( startDistance + distance ) / distance;
669 const QgsPointXY newStart = QgsGeometryUtils::interpolatePointOnLine( x1, y1, x0, y0, extensionFactor );
670 x0 = newStart.x();
671 y0 = newStart.y();
672 // defer actually changing the stored start until we've calculated the new end point
673 }
674
675 if ( endDistance > 0 )
676 {
677 const double xend0 = x[nbPoints - 1];
678 const double yend0 = y[nbPoints - 1];
679 double xend1 = x[nbPoints - 2];
680 double yend1 = y[nbPoints - 2];
681
682 // trace backward by smoothDistance
683 double distanceConsumed = 0;
684 double lastX = x0;
685 double lastY = y0;
686 for ( int i = nbPoints - 2; i >= 0; --i )
687 {
688 const double thisX = x[i];
689 const double thisY = y[i];
690 const double thisSegmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, lastX, lastY );
691 distanceConsumed += thisSegmentLength;
692 if ( distanceConsumed >= smoothDistance )
693 {
694 const double c = ( distanceConsumed - smoothDistance ) / thisSegmentLength;
695 xend1 = lastX + c * ( thisX - lastX );
696 yend1 = lastY + c * ( thisY - lastY );
697 break;
698 }
699 lastX = thisX;
700 lastY = thisY;
701 }
702
703 const double distance = QgsGeometryUtilsBase::distance2D( xend1, yend1, xend0, yend0 );
704 const double extensionFactor = ( endDistance + distance ) / distance;
705 const QgsPointXY newEnd = QgsGeometryUtils::interpolatePointOnLine( xend1, yend1, xend0, yend0, extensionFactor );
706 x.emplace_back( newEnd.x() );
707 y.emplace_back( newEnd.y() );
708 nbPoints++;
709 }
710
711 if ( startDistance > 0 )
712 {
713 x.insert( x.begin(), x0 );
714 y.insert( y.begin(), y0 );
715 nbPoints++;
716 }
717
719}
720
722{
723 ok = false;
724 double bbox[4]; // xmin, ymin, xmax, ymax
725
726 double alpha;
727 int alpha_d;
728
729 double alpha_seg;
730
731 double d1, d2;
732
733 double bb[16]; // {ax, ay, bx, by, cx, cy, dx, dy, ex, ey, fx, fy, gx, gy, hx, hy}}
734
735 double best_area = std::numeric_limits<double>::max();
736 double best_alpha = -1;
737 double best_bb[16];
738 double best_length = 0;
739 double best_width = 0;
740
741
742 bbox[0] = std::numeric_limits<double>::max();
743 bbox[1] = std::numeric_limits<double>::max();
744 bbox[2] = std::numeric_limits<double>::lowest();
745 bbox[3] = std::numeric_limits<double>::lowest();
746
747 for ( std::size_t i = 0; i < convexHull.size(); i++ )
748 {
749 if ( x[convexHull[i]] < bbox[0] )
750 bbox[0] = x[convexHull[i]];
751
752 if ( x[convexHull[i]] > bbox[2] )
753 bbox[2] = x[convexHull[i]];
754
755 if ( y[convexHull[i]] < bbox[1] )
756 bbox[1] = y[convexHull[i]];
757
758 if ( y[convexHull[i]] > bbox[3] )
759 bbox[3] = y[convexHull[i]];
760 }
761
763
764 const double dref = bbox[2] - bbox[0];
765 if ( qgsDoubleNear( dref, 0 ) )
766 {
767 ok = false;
768 return finalBb;
769 }
770
771 for ( alpha_d = 0; alpha_d < 90; alpha_d++ )
772 {
773 alpha = alpha_d * M_PI / 180.0;
774 d1 = std::cos( alpha ) * dref;
775 d2 = std::sin( alpha ) * dref;
776
777 bb[0] = bbox[0];
778 bb[1] = bbox[3]; // ax, ay
779
780 bb[4] = bbox[0];
781 bb[5] = bbox[1]; // cx, cy
782
783 bb[8] = bbox[2];
784 bb[9] = bbox[1]; // ex, ey
785
786 bb[12] = bbox[2];
787 bb[13] = bbox[3]; // gx, gy
788
789
790 bb[2] = bb[0] + d1;
791 bb[3] = bb[1] + d2; // bx, by
792 bb[6] = bb[4] - d2;
793 bb[7] = bb[5] + d1; // dx, dy
794 bb[10] = bb[8] - d1;
795 bb[11] = bb[9] - d2; // fx, fy
796 bb[14] = bb[12] + d2;
797 bb[15] = bb[13] - d1; // hx, hy
798
799 // adjust all points
800 for ( int i = 0; i < 16; i += 4 )
801 {
802
803 alpha_seg = ( ( i / 4 > 0 ? ( i / 4 ) - 1 : 3 ) ) * M_PI_2 + alpha;
804
805 double best_cp = std::numeric_limits<double>::max();
806
807 for ( std::size_t j = 0; j < convexHull.size(); j++ )
808 {
809 const double cp = GeomFunction::cross_product( bb[i + 2], bb[i + 3], bb[i], bb[i + 1], x[convexHull[j]], y[convexHull[j]] );
810 if ( cp < best_cp )
811 {
812 best_cp = cp;
813 }
814 }
815
816 const double distNearestPoint = best_cp / dref;
817
818 d1 = std::cos( alpha_seg ) * distNearestPoint;
819 d2 = std::sin( alpha_seg ) * distNearestPoint;
820
821 bb[i] += d1; // x
822 bb[i + 1] += d2; // y
823 bb[i + 2] += d1; // x
824 bb[i + 3] += d2; // y
825 }
826
827 // compute and compare AREA
828 const double width = GeomFunction::cross_product( bb[6], bb[7], bb[4], bb[5], bb[12], bb[13] ) / dref;
829 const double length = GeomFunction::cross_product( bb[2], bb[3], bb[0], bb[1], bb[8], bb[9] ) / dref;
830
831 double area = width * length;
832
833 if ( area < 0 )
834 area *= -1;
835
836
837 if ( best_area - area > EPSILON )
838 {
839 best_area = area;
840 best_length = length;
841 best_width = width;
842 best_alpha = alpha;
843 memcpy( best_bb, bb, sizeof( double ) * 16 );
844 }
845 }
846
847 // best bbox is defined
848 for ( int i = 0; i < 16; i = i + 4 )
849 {
850 GeomFunction::computeLineIntersection( best_bb[i], best_bb[i + 1], best_bb[i + 2], best_bb[i + 3],
851 best_bb[( i + 4 ) % 16], best_bb[( i + 5 ) % 16], best_bb[( i + 6 ) % 16], best_bb[( i + 7 ) % 16],
852 &finalBb.x[int ( i / 4 )], &finalBb.y[int ( i / 4 )] );
853 }
854
855 finalBb.alpha = best_alpha;
856 finalBb.width = best_width;
857 finalBb.length = best_length;
858
859 ok = true;
860 return finalBb;
861}
862
863double PointSet::minDistanceToPoint( double px, double py, double *rx, double *ry ) const
864{
865 if ( !mGeos )
867
868 if ( !mGeos )
869 return 0;
870
871 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
872 try
873 {
874 geos::unique_ptr geosPt( GEOSGeom_createPointFromXY_r( geosctxt, px, py ) );
875 const int type = GEOSGeomTypeId_r( geosctxt, mGeos );
876 const GEOSGeometry *extRing = nullptr;
877 const GEOSPreparedGeometry *preparedExtRing = nullptr;
878
879 if ( type != GEOS_POLYGON )
880 {
881 extRing = mGeos;
882 preparedExtRing = preparedGeom();
883 }
884 else
885 {
886 //for polygons, we want distance to exterior ring (not an interior point)
887 extRing = GEOSGetExteriorRing_r( geosctxt, mGeos );
888 if ( ! mGeosPreparedBoundary )
889 {
890 mGeosPreparedBoundary = GEOSPrepare_r( geosctxt, extRing );
891 }
892 preparedExtRing = mGeosPreparedBoundary;
893 }
894
895 const geos::coord_sequence_unique_ptr nearestCoord( GEOSPreparedNearestPoints_r( geosctxt, preparedExtRing, geosPt.get() ) );
896 double nx;
897 double ny;
898 unsigned int nPoints = 0;
899 GEOSCoordSeq_getSize_r( geosctxt, nearestCoord.get(), &nPoints );
900 if ( nPoints == 0 )
901 return 0;
902
903 ( void )GEOSCoordSeq_getXY_r( geosctxt, nearestCoord.get(), 0, &nx, &ny );
904
905 if ( rx )
906 *rx = nx;
907 if ( ry )
908 *ry = ny;
909
910 return QgsGeometryUtilsBase::sqrDistance2D( px, py, nx, ny );
911 }
912 catch ( QgsGeosException &e )
913 {
914 qWarning( "GEOS exception: %s", e.what() );
915 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
916 return 0;
917 }
918}
919
920void PointSet::getCentroid( double &px, double &py, bool forceInside ) const
921{
922 if ( !mGeos )
924
925 if ( !mGeos )
926 return;
927
928 try
929 {
930 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
931 geos::unique_ptr centroidGeom( GEOSGetCentroid_r( geosctxt, mGeos ) );
932 if ( centroidGeom )
933 {
934 const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, centroidGeom.get() );
935 unsigned int nPoints = 0;
936 GEOSCoordSeq_getSize_r( geosctxt, coordSeq, &nPoints );
937 if ( nPoints == 0 )
938 return;
939 GEOSCoordSeq_getXY_r( geosctxt, coordSeq, 0, &px, &py );
940 }
941
942 // check if centroid inside in polygon
943 if ( forceInside && !containsPoint( px, py ) )
944 {
945 geos::unique_ptr pointGeom( GEOSPointOnSurface_r( geosctxt, mGeos ) );
946
947 if ( pointGeom )
948 {
949 const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, pointGeom.get() );
950 unsigned int nPoints = 0;
951 GEOSCoordSeq_getSize_r( geosctxt, coordSeq, &nPoints );
952 if ( nPoints == 0 )
953 return;
954
955 GEOSCoordSeq_getXY_r( geosctxt, coordSeq, 0, &px, &py );
956 }
957 }
958 }
959 catch ( QgsGeosException &e )
960 {
961 qWarning( "GEOS exception: %s", e.what() );
962 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
963 return;
964 }
965}
966
968{
969 const double x1 = ( xmin > other->xmin ? xmin : other->xmin );
970 const double x2 = ( xmax < other->xmax ? xmax : other->xmax );
971 if ( x1 > x2 )
972 return false;
973 const double y1 = ( ymin > other->ymin ? ymin : other->ymin );
974 const double y2 = ( ymax < other->ymax ? ymax : other->ymax );
975 return y1 <= y2;
976}
977
978void PointSet::getPointByDistance( double *d, double *ad, double dl, double *px, double *py ) const
979{
980 int i;
981 double dx, dy, di;
982 double distr;
983
984 i = 0;
985 if ( dl >= 0 )
986 {
987 while ( i < nbPoints && ad[i] <= dl ) i++;
988 i--;
989 }
990
991 if ( i < nbPoints - 1 )
992 {
993 if ( dl < 0 )
994 {
995 dx = x[nbPoints - 1] - x[0];
996 dy = y[nbPoints - 1] - y[0];
997 di = std::sqrt( dx * dx + dy * dy );
998 }
999 else
1000 {
1001 dx = x[i + 1] - x[i];
1002 dy = y[i + 1] - y[i];
1003 di = d[i];
1004 }
1005
1006 distr = dl - ad[i];
1007 *px = x[i] + dx * distr / di;
1008 *py = y[i] + dy * distr / di;
1009 }
1010 else // just select last point...
1011 {
1012 *px = x[i];
1013 *py = y[i];
1014 }
1015}
1016
1018{
1019 const GEOSGeometry *thisGeos = geos();
1020 if ( !thisGeos )
1021 return nullptr;
1022
1023 try
1024 {
1025 geos::unique_ptr res( GEOSInterpolate_r( QgsGeosContext::get(), thisGeos, distance ) );
1026 return res;
1027 }
1028 catch ( QgsGeosException &e )
1029 {
1030 qWarning( "GEOS exception: %s", e.what() );
1031 return nullptr;
1032 }
1033}
1034
1035double PointSet::lineLocatePoint( const GEOSGeometry *point ) const
1036{
1037 const GEOSGeometry *thisGeos = geos();
1038 if ( !thisGeos )
1039 return -1;
1040
1041 double distance = -1;
1042 try
1043 {
1044 distance = GEOSProject_r( QgsGeosContext::get(), thisGeos, point );
1045 }
1046 catch ( QgsGeosException &e )
1047 {
1048 qWarning( "GEOS exception: %s", e.what() );
1049 return -1;
1050 }
1051
1052 return distance;
1053}
1054
1056{
1057 if ( !mGeos )
1059
1060 return mGeos;
1061}
1062
1063double PointSet::length() const
1064{
1065 if ( mLength >= 0 )
1066 return mLength;
1067
1068 if ( !mGeos )
1070
1071 if ( !mGeos )
1072 return -1;
1073
1074 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
1075
1076 try
1077 {
1078 ( void )GEOSLength_r( geosctxt, mGeos, &mLength );
1079 return mLength;
1080 }
1081 catch ( QgsGeosException &e )
1082 {
1083 qWarning( "GEOS exception: %s", e.what() );
1084 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1085 return -1;
1086 }
1087}
1088
1089double PointSet::area() const
1090{
1091 if ( mArea >= 0 )
1092 return mArea;
1093
1094 if ( !mGeos )
1096
1097 if ( !mGeos )
1098 return -1;
1099
1100 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
1101
1102 try
1103 {
1104 ( void )GEOSArea_r( geosctxt, mGeos, &mArea );
1105 mArea = std::fabs( mArea );
1106 return mArea;
1107 }
1108 catch ( QgsGeosException &e )
1109 {
1110 qWarning( "GEOS exception: %s", e.what() );
1111 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1112 return -1;
1113 }
1114}
1115
1117{
1118 return qgsDoubleNear( x[0], x[nbPoints - 1] ) && qgsDoubleNear( y[0], y[nbPoints - 1] );
1119}
1120
1121QString PointSet::toWkt() const
1122{
1123 if ( !mGeos )
1125
1126 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
1127
1128 try
1129 {
1130 GEOSWKTWriter *writer = GEOSWKTWriter_create_r( geosctxt );
1131
1132 char *wkt = GEOSWKTWriter_write_r( geosctxt, writer, mGeos );
1133 const QString res( wkt );
1134
1135 GEOSFree_r( geosctxt, wkt );
1136
1137 GEOSWKTWriter_destroy_r( geosctxt, writer );
1138 writer = nullptr;
1139
1140 return res;
1141 }
1142 catch ( QgsGeosException &e )
1143 {
1144 qWarning( "GEOS exception: %s", e.what() );
1145 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
1146 return QString();
1147 }
1148}
1149
1150std::tuple< std::vector< double >, double > PointSet::edgeDistances() const
1151{
1152 std::vector< double > distances( nbPoints );
1153 double totalDistance = 0;
1154 double oldX = -1.0, oldY = -1.0;
1155 for ( int i = 0; i < nbPoints; i++ )
1156 {
1157 if ( i == 0 )
1158 distances[i] = 0;
1159 else
1160 distances[i] = std::sqrt( std::pow( oldX - x[i], 2 ) + std::pow( oldY - y[i], 2 ) );
1161
1162 oldX = x[i];
1163 oldY = y[i];
1164 totalDistance += distances[i];
1165 }
1166 return std::make_tuple( std::move( distances ), totalDistance );
1167}
static double sqrDistance2D(double x1, double y1, double x2, double y2)
Returns the squared 2D distance between (x1, y1) and (x2, y2).
static double distance2D(double x1, double y1, double x2, double y2)
Returns the 2D distance between (x1, y1) and (x2, y2).
static QgsPointXY interpolatePointOnLine(double x1, double y1, double x2, double y2, double fraction)
Interpolates the position of a point a fraction of the way along the line from (x1,...
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).
Represents a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
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.
geos::unique_ptr interpolatePoint(double distance) const
Returns a GEOS geometry representing the point interpolated on the shape by distance.
std::unique_ptr< PointSet > clone() const
Returns a copy of the point set.
Definition pointset.cpp:268
bool containsLabelCandidate(double x, double y, double width, double height, double alpha=0) const
Tests whether a possible label candidate will fit completely within the shape.
Definition pointset.cpp:292
double lineLocatePoint(const GEOSGeometry *point) const
Returns the distance along the geometry closest to the specified GEOS point.
double length() const
Returns length of line geometry.
void deleteCoords()
Definition pointset.cpp:235
void extendLineByDistance(double startDistance, double endDistance, double smoothDistance)
Extends linestrings by the specified amount at the start and end of the line, by extending the existi...
Definition pointset.cpp:634
virtual ~PointSet()
Definition pointset.cpp:204
double ymax
Definition pointset.h:262
double ymin
Definition pointset.h:261
double area() const
Returns area of polygon geometry.
bool isClosed() const
Returns true if pointset is closed.
PointSet * holeOf
Definition pointset.h:242
void createGeosGeom() const
Definition pointset.cpp:102
void getPointByDistance(double *d, double *ad, double dl, double *px, double *py) const
Gets a point a set distance along a line geometry.
Definition pointset.cpp:978
std::vector< double > y
Definition pointset.h:232
bool boundingBoxIntersects(const PointSet *other) const
Returns true if the bounding box of this pointset intersects the bounding box of another pointset.
Definition pointset.cpp:967
void getCentroid(double &px, double &py, bool forceInside=false) const
Definition pointset.cpp:920
OrientedConvexHullBoundingBox computeConvexHullOrientedBoundingBox(bool &ok) const
Computes an oriented bounding box for the shape's convex hull.
Definition pointset.cpp:721
std::vector< double > x
Definition pointset.h:231
void offsetCurveByDistance(double distance)
Offsets linestrings by the specified distance.
Definition pointset.cpp:548
std::vector< int > convexHull
Definition pointset.h:238
const GEOSPreparedGeometry * preparedGeom() const
Definition pointset.cpp:157
QString toWkt() const
Returns a WKT representation of the point set.
GEOSGeometry * mGeos
Definition pointset.h:235
double xmin
Definition pointset.h:259
const GEOSGeometry * geos() const
Returns the point set's GEOS geometry.
void invalidateGeos() const
Definition pointset.cpp:169
double minDistanceToPoint(double px, double py, double *rx=nullptr, double *ry=nullptr) const
Returns the squared minimum distance between the point set geometry and the point (px,...
Definition pointset.cpp:863
double xmax
Definition pointset.h:260
std::unique_ptr< PointSet > extractShape(int nbPtSh, int imin, int imax, int fps, int fpe, double fptx, double fpty)
Does... something completely inscrutable.
Definition pointset.cpp:241
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition pointset.cpp:273
std::tuple< std::vector< double >, double > edgeDistances() const
Returns a vector of edge distances as well as its total length.
PointSet * parent
Definition pointset.h:243
double mLength
Definition pointset.h:246
double mArea
Definition pointset.h:245
static QLinkedList< PointSet * > splitPolygons(PointSet *inputShape, double labelWidth, double labelHeight)
Split a polygon using some random logic into some other polygons.
Definition pointset.cpp:297
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:114
std::unique_ptr< GEOSCoordSequence, GeosDeleter > coord_sequence_unique_ptr
Scoped GEOS coordinate sequence pointer.
Definition qgsgeos.h:129
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
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
Represents the minimum area, oriented bounding box surrounding a convex hull.
Definition pointset.h:61
#define EPSILON
Definition util.h:82
struct GEOSGeom_t GEOSGeometry
Definition util.h:42