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