QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
labelposition.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 "layer.h"
31#include "costcalculator.h"
32#include "feature.h"
33#include "labelposition.h"
34#include "geomfunction.h"
35#include "qgsgeos.h"
37#include "qgsmessagelog.h"
38#include <cmath>
39#include <cfloat>
40
41using namespace pal;
42
43LabelPosition::LabelPosition( int id, double x1, double y1, double w, double h, double alpha, double cost, FeaturePart *feature, bool isReversed, Quadrant quadrant )
44 : id( id )
45 , feature( feature )
46 , probFeat( 0 )
47 , nbOverlap( 0 )
48 , alpha( alpha )
49 , w( w )
50 , h( h )
51 , partId( -1 )
52 , reversed( isReversed )
53 , upsideDown( false )
54 , quadrant( quadrant )
55 , mCost( cost )
56 , mHasObstacleConflict( false )
57 , mUpsideDownCharCount( 0 )
58{
59 type = GEOS_POLYGON;
60 nbPoints = 4;
61 x.resize( nbPoints );
62 y.resize( nbPoints );
63
64 // alpha take his value bw 0 and 2*pi rad
65 while ( this->alpha > 2 * M_PI )
66 this->alpha -= 2 * M_PI;
67
68 while ( this->alpha < 0 )
69 this->alpha += 2 * M_PI;
70
71 const double beta = this->alpha + M_PI_2;
72
73 double dx1, dx2, dy1, dy2;
74
75 dx1 = std::cos( this->alpha ) * w;
76 dy1 = std::sin( this->alpha ) * w;
77
78 dx2 = std::cos( beta ) * h;
79 dy2 = std::sin( beta ) * h;
80
81 x[0] = x1;
82 y[0] = y1;
83
84 x[1] = x1 + dx1;
85 y[1] = y1 + dy1;
86
87 x[2] = x1 + dx1 + dx2;
88 y[2] = y1 + dy1 + dy2;
89
90 x[3] = x1 + dx2;
91 y[3] = y1 + dy2;
92
93 // upside down ? (curved labels are always correct)
94 if ( !feature->layer()->isCurved() &&
95 this->alpha > M_PI_2 && this->alpha <= 3 * M_PI_2 )
96 {
98 {
99 // Turn label upsidedown by inverting boundary points
100 double tx, ty;
101
102 tx = x[0];
103 ty = y[0];
104
105 x[0] = x[2];
106 y[0] = y[2];
107
108 x[2] = tx;
109 y[2] = ty;
110
111 tx = x[1];
112 ty = y[1];
113
114 x[1] = x[3];
115 y[1] = y[3];
116
117 x[3] = tx;
118 y[3] = ty;
119
120 if ( this->alpha < M_PI )
121 this->alpha += M_PI;
122 else
123 this->alpha -= M_PI;
124
125 // labels with text shown upside down are not classified as upsideDown,
126 // only those whose boundary points have been inverted
127 upsideDown = true;
128 }
129 }
130
131 for ( int i = 0; i < nbPoints; ++i )
132 {
133 xmin = std::min( xmin, x[i] );
134 xmax = std::max( xmax, x[i] );
135 ymin = std::min( ymin, y[i] );
136 ymax = std::max( ymax, y[i] );
137 }
138
139 createOuterBoundsGeom();
140}
141
143 : PointSet( other )
144{
145 id = other.id;
146 mCost = other.mCost;
147 feature = other.feature;
148 probFeat = other.probFeat;
149 nbOverlap = other.nbOverlap;
150
151 alpha = other.alpha;
152 w = other.w;
153 h = other.h;
154
155 if ( other.mNextPart )
156 mNextPart = std::make_unique< LabelPosition >( *other.mNextPart );
157
158 partId = other.partId;
159 upsideDown = other.upsideDown;
160 reversed = other.reversed;
161 quadrant = other.quadrant;
162 mHasObstacleConflict = other.mHasObstacleConflict;
163 mUpsideDownCharCount = other.mUpsideDownCharCount;
164
165 createOuterBoundsGeom();
166}
167
169{
170 if ( mPreparedOuterBoundsGeos )
171 {
172 GEOSPreparedGeom_destroy_r( QgsGeos::getGEOSHandler(), mPreparedOuterBoundsGeos );
173 mPreparedOuterBoundsGeos = nullptr;
174 }
175}
176
177bool LabelPosition::intersects( const GEOSPreparedGeometry *geometry )
178{
179 // this method considers the label's outer bounds
180 if ( !mOuterBoundsGeos && !mGeos )
182
183 try
184 {
185 if ( GEOSPreparedIntersects_r( QgsGeos::getGEOSHandler(), geometry, mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 )
186 {
187 return true;
188 }
189 else if ( mNextPart )
190 {
191 return mNextPart->intersects( geometry );
192 }
193 }
194 catch ( GEOSException &e )
195 {
196 qWarning( "GEOS exception: %s", e.what() );
197 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
198 return false;
199 }
200
201 return false;
202}
203
204bool LabelPosition::within( const GEOSPreparedGeometry *geometry )
205{
206 // this method considers the label's outer bounds
207 if ( !mOuterBoundsGeos && !mGeos )
209
210 try
211 {
212 if ( GEOSPreparedContains_r( QgsGeos::getGEOSHandler(), geometry, mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) != 1 )
213 {
214 return false;
215 }
216 else if ( mNextPart )
217 {
218 return mNextPart->within( geometry );
219 }
220 }
221 catch ( GEOSException &e )
222 {
223 qWarning( "GEOS exception: %s", e.what() );
224 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
225 return false;
226 }
227
228 return true;
229}
230
232{
233 // this method considers the label's outer bounds
234
235 if ( this->probFeat == lp->probFeat ) // bugfix #1
236 return false; // always overlapping itself !
237
238 // if either this label doesn't cause collisions, or the other one doesn't, then we don't conflict!
241 return false;
242
243 if ( !nextPart() && !lp->nextPart() )
244 {
245 if ( qgsDoubleNear( alpha, 0 ) && qgsDoubleNear( lp->alpha, 0 ) )
246 {
247 // simple case -- both candidates are oriented to axis, so shortcut with easy calculation
248 if ( mOuterBoundsGeos )
249 {
250 return outerBoundingBoxIntersects( lp );
251 }
252 else
253 {
254 return boundingBoxIntersects( lp );
255 }
256 }
257 else
258 {
259 // this method considers the label's outer bounds
260 if ( !mOuterBoundsGeos && !mGeos )
262
263 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
264 try
265 {
266 const bool result = ( GEOSPreparedIntersects_r( geosctxt, lp->preparedOuterBoundsGeom() ? lp->preparedOuterBoundsGeom() : lp->preparedGeom(),
267 mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 );
268 return result;
269 }
270 catch ( GEOSException &e )
271 {
272 qWarning( "GEOS exception: %s", e.what() );
273 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
274 return false;
275 }
276
277 return false;
278 }
279 }
280
281 return isInConflictMultiPart( lp );
282}
283
284bool LabelPosition::isInConflictMultiPart( const LabelPosition *lp ) const
285{
286 if ( !mMultipartGeos )
287 createMultiPartGeosGeom();
288
289 if ( !lp->mMultipartGeos )
290 lp->createMultiPartGeosGeom();
291
292 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
293 try
294 {
295 const bool result = ( GEOSPreparedIntersects_r( geosctxt, preparedMultiPartGeom(), lp->mMultipartGeos ) == 1 );
296 return result;
297 }
298 catch ( GEOSException &e )
299 {
300 qWarning( "GEOS exception: %s", e.what() );
301 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
302 return false;
303 }
304
305 return false;
306}
307
308void LabelPosition::createOuterBoundsGeom()
309{
310 const QRectF outerBounds = getFeaturePart()->feature()->outerBounds();
311 if ( outerBounds.isNull() )
312 return;
313
314 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
315
316 const double beta = this->alpha + M_PI_2;
317
318 const double dx1 = std::cos( this->alpha ) * outerBounds.width();
319 const double dy1 = std::sin( this->alpha ) * outerBounds.width();
320
321 const double dx2 = std::cos( beta ) * outerBounds.height();
322 const double dy2 = std::sin( beta ) * outerBounds.height();
323
324 mOuterBoundsX.resize( 5 );
325 mOuterBoundsY.resize( 5 );
326
327 const double x1 = x[0] + outerBounds.left();
328 const double y1 = y[0] + outerBounds.top();
329
330 mOuterBoundsX[0] = x1;
331 mOuterBoundsY[0] = y1;
332
333 mOuterBoundsX[1] = x1 + dx1;
334 mOuterBoundsY[1] = y1 + dy1;
335
336 mOuterBoundsX[2] = x1 + dx1 + dx2;
337 mOuterBoundsY[2] = y1 + dy1 + dy2;
338
339 mOuterBoundsX[3] = x1 + dx2;
340 mOuterBoundsY[3] = y1 + dy2;
341
342 mOuterBoundsX[4] = mOuterBoundsX[0];
343 mOuterBoundsY[4] = mOuterBoundsY[0];
344
345 GEOSCoordSequence *coord = nullptr;
346#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
347 // use optimised method if we don't have to force close an open ring
348 coord = GEOSCoordSeq_copyFromArrays_r( geosctxt, mOuterBoundsX.data(), mOuterBoundsY.data(), nullptr, nullptr, 5 );
349#else
350 coord = GEOSCoordSeq_create_r( geosctxt, 5, 2 );
351 for ( int i = 0; i < nbPoints; ++i )
352 {
353 GEOSCoordSeq_setXY_r( geosctxt, coord, i, mOuterBoundsX[i], mOuterBoundsY[i] );
354 }
355#endif
356
357 mOuterBoundsGeos.reset( GEOSGeom_createPolygon_r( geosctxt, GEOSGeom_createLinearRing_r( geosctxt, coord ), nullptr, 0 ) );
358
359 mPreparedOuterBoundsGeos = GEOSPrepare_r( QgsGeos::getGEOSHandler(), mOuterBoundsGeos.get() );
360
361 auto xminmax = std::minmax_element( mOuterBoundsX.begin(), mOuterBoundsX.end() );
362 mOuterBoundsXMin = *xminmax.first;
363 mOuterBoundsXMax = *xminmax.second;
364 auto yminmax = std::minmax_element( mOuterBoundsY.begin(), mOuterBoundsY.end() );
365 mOuterBoundsYMin = *yminmax.first;
366 mOuterBoundsYMax = *yminmax.second;
367}
368
369int LabelPosition::partCount() const
370{
371 if ( mNextPart )
372 return mNextPart->partCount() + 1;
373 else
374 return 1;
375}
376
378{
379 return id;
380}
381
382double LabelPosition::getX( int i ) const
383{
384 return ( i >= 0 && i < 4 ? x[i] : -1 );
385}
386
387double LabelPosition::getY( int i ) const
388{
389 return ( i >= 0 && i < 4 ? y[i] : -1 );
390}
391
393{
394 return alpha;
395}
396
398{
399 if ( mCost >= 1 )
400 {
401 mCost -= int ( mCost ); // label cost up to 1
402 }
403}
404
406{
407 return feature;
408}
409
410void LabelPosition::getBoundingBox( double amin[2], double amax[2] ) const
411{
412 // this method considers the label's outer bounds
413 if ( mNextPart )
414 {
415 mNextPart->getBoundingBox( amin, amax );
416 }
417 else
418 {
419 amin[0] = std::numeric_limits<double>::max();
420 amax[0] = std::numeric_limits<double>::lowest();
421 amin[1] = std::numeric_limits<double>::max();
422 amax[1] = std::numeric_limits<double>::lowest();
423 }
424 for ( int c = 0; c < 4; c++ )
425 {
426 if ( mOuterBoundsGeos )
427 {
428 if ( mOuterBoundsX[c] < amin[0] )
429 amin[0] = mOuterBoundsX[c];
430 if ( mOuterBoundsX[c] > amax[0] )
431 amax[0] = mOuterBoundsX[c];
432 if ( mOuterBoundsY[c] < amin[1] )
433 amin[1] = mOuterBoundsY[c];
434 if ( mOuterBoundsY[c] > amax[1] )
435 amax[1] = mOuterBoundsY[c];
436 }
437 else
438 {
439 if ( x[c] < amin[0] )
440 amin[0] = x[c];
441 if ( x[c] > amax[0] )
442 amax[0] = x[c];
443 if ( y[c] < amin[1] )
444 amin[1] = y[c];
445 if ( y[c] > amax[1] )
446 amax[1] = y[c];
447 }
448 }
449}
450
452{
453 if ( other->mOuterBoundsGeos )
454 {
455 const double x1 = ( mOuterBoundsXMin > other->mOuterBoundsXMin ? mOuterBoundsXMin : other->mOuterBoundsXMin );
456 const double x2 = ( mOuterBoundsXMax < other->mOuterBoundsXMax ? mOuterBoundsXMax : other->mOuterBoundsXMax );
457 if ( x1 > x2 )
458 return false;
459 const double y1 = ( mOuterBoundsYMin > other->mOuterBoundsYMin ? mOuterBoundsYMin : other->mOuterBoundsYMin );
460 const double y2 = ( mOuterBoundsYMax < other->mOuterBoundsYMax ? mOuterBoundsYMax : other->mOuterBoundsYMax );
461 return y1 <= y2;
462 }
463 else
464 {
465 const double x1 = ( mOuterBoundsXMin > other->xmin ? mOuterBoundsXMin : other->xmin );
466 const double x2 = ( mOuterBoundsXMax < other->xmax ? mOuterBoundsXMax : other->xmax );
467 if ( x1 > x2 )
468 return false;
469 const double y1 = ( mOuterBoundsYMin > other->ymin ? mOuterBoundsYMin : other->ymin );
470 const double y2 = ( mOuterBoundsYMax < other->ymax ? mOuterBoundsYMax : other->ymax );
471 return y1 <= y2;
472 }
473}
474
476{
477 mHasObstacleConflict = conflicts;
478 if ( mNextPart )
479 mNextPart->setConflictsWithObstacle( conflicts );
480}
481
483{
484 mHasHardConflict = conflicts;
485 if ( mNextPart )
486 mNextPart->setHasHardObstacleConflict( conflicts );
487}
488
490{
491 double amin[2];
492 double amax[2];
493 getBoundingBox( amin, amax );
494 index.remove( this, QgsRectangle( amin[0], amin[1], amax[0], amax[1] ) );
495}
496
498{
499 double amin[2];
500 double amax[2];
501 getBoundingBox( amin, amax );
502 index.insert( this, QgsRectangle( amin[0], amin[1], amax[0], amax[1] ) );
503}
504
505
506void LabelPosition::createMultiPartGeosGeom() const
507{
508 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
509
510 std::vector< const GEOSGeometry * > geometries;
511 const LabelPosition *tmp1 = this;
512 while ( tmp1 )
513 {
514 const GEOSGeometry *partGeos = tmp1->geos();
515 if ( !GEOSisEmpty_r( geosctxt, partGeos ) )
516 geometries.emplace_back( partGeos );
517 tmp1 = tmp1->nextPart();
518 }
519
520 const std::size_t partCount = geometries.size();
521 GEOSGeometry **geomarr = new GEOSGeometry*[ partCount ];
522 for ( std::size_t i = 0; i < partCount; ++i )
523 {
524 geomarr[i ] = GEOSGeom_clone_r( geosctxt, geometries[i] );
525 }
526
527 mMultipartGeos = GEOSGeom_createCollection_r( geosctxt, GEOS_MULTIPOLYGON, geomarr, partCount );
528 delete [] geomarr;
529}
530
531const GEOSPreparedGeometry *LabelPosition::preparedMultiPartGeom() const
532{
533 if ( !mMultipartGeos )
534 createMultiPartGeosGeom();
535
536 if ( !mMultipartPreparedGeos )
537 {
538 mMultipartPreparedGeos = GEOSPrepare_r( QgsGeos::getGEOSHandler(), mMultipartGeos );
539 }
540 return mMultipartPreparedGeos;
541}
542
543const GEOSPreparedGeometry *LabelPosition::preparedOuterBoundsGeom() const
544{
545 return mPreparedOuterBoundsGeos;
546}
547
548double LabelPosition::getDistanceToPoint( double xp, double yp, bool useOuterBounds ) const
549{
550 // this method may consider the label's outer bounds!
551
552 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
553
554 //first check if inside, if so then distance is -1
555 bool contains = false;
556 if ( alpha == 0 )
557 {
558 // easy case -- horizontal label
559 if ( useOuterBounds && mOuterBoundsGeos )
560 {
561 contains = mOuterBoundsX[0] <= xp && mOuterBoundsX[1] >= xp && mOuterBoundsY[0] <= yp && mOuterBoundsY[2] >= yp;
562 }
563 else
564 {
565 contains = x[0] <= xp && x[1] >= xp && y[0] <= yp && y[2] >= yp;
566 }
567 }
568 else
569 {
570 if ( useOuterBounds && mPreparedOuterBoundsGeos )
571 {
572 try
573 {
574 geos::unique_ptr point( GEOSGeom_createPointFromXY_r( geosctxt, xp, yp ) );
575 contains = ( GEOSPreparedContainsProperly_r( geosctxt, mPreparedOuterBoundsGeos, point.get() ) == 1 );
576 }
577 catch ( GEOSException &e )
578 {
579 contains = false;
580 }
581 }
582 else
583 {
584 contains = containsPoint( xp, yp );
585 }
586 }
587
588 double distance = -1;
589 if ( !contains )
590 {
591 if ( alpha == 0 )
592 {
593 if ( useOuterBounds && mOuterBoundsGeos )
594 {
595 const double dx = std::max( std::max( mOuterBoundsX[0] - xp, 0.0 ), xp - mOuterBoundsX[1] );
596 const double dy = std::max( std::max( mOuterBoundsY[0] - yp, 0.0 ), yp - mOuterBoundsY[2] );
597 distance = std::sqrt( dx * dx + dy * dy );
598 }
599 else
600 {
601 const double dx = std::max( std::max( x[0] - xp, 0.0 ), xp - x[1] );
602 const double dy = std::max( std::max( y[0] - yp, 0.0 ), yp - y[2] );
603 distance = std::sqrt( dx * dx + dy * dy );
604 }
605 }
606 else
607 {
608 if ( useOuterBounds && mPreparedOuterBoundsGeos )
609 {
610 try
611 {
612 geos::unique_ptr geosPt( GEOSGeom_createPointFromXY_r( geosctxt, xp, yp ) );
613
614 const geos::coord_sequence_unique_ptr nearestCoord( GEOSPreparedNearestPoints_r( geosctxt, mPreparedOuterBoundsGeos, geosPt.get() ) );
615 double nx;
616 double ny;
617 unsigned int nPoints = 0;
618 GEOSCoordSeq_getSize_r( geosctxt, nearestCoord.get(), &nPoints );
619 if ( nPoints == 0 )
620 {
621 distance = -1;
622 }
623 else
624 {
625 ( void )GEOSCoordSeq_getXY_r( geosctxt, nearestCoord.get(), 0, &nx, &ny );
626 distance = QgsGeometryUtilsBase::sqrDistance2D( xp, yp, nx, ny );
627 }
628 }
629 catch ( GEOSException &e )
630 {
631 qWarning( "GEOS exception: %s", e.what() );
632 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
633 distance = -1;
634 }
635 }
636 else
637 {
638 distance = std::sqrt( minDistanceToPoint( xp, yp ) );
639 }
640 }
641 }
642
643 if ( mNextPart && distance > 0 )
644 return std::min( distance, mNextPart->getDistanceToPoint( xp, yp, useOuterBounds ) );
645
646 return distance;
647}
648
650{
651 // this method considers the label's outer bounds
652 if ( !mOuterBoundsGeos && !mGeos )
654
655 if ( !line->mGeos )
656 line->createGeosGeom();
657
658 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
659 try
660 {
661 if ( GEOSPreparedIntersects_r( geosctxt, line->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 )
662 {
663 return true;
664 }
665 else if ( mNextPart )
666 {
667 return mNextPart->crossesLine( line );
668 }
669 }
670 catch ( GEOSException &e )
671 {
672 qWarning( "GEOS exception: %s", e.what() );
673 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
674 return false;
675 }
676
677 return false;
678}
679
681{
682 // this method considers the label's outer bounds
683 if ( !mOuterBoundsGeos && !mGeos )
685
686 if ( !polygon->mGeos )
687 polygon->createGeosGeom();
688
689 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
690 try
691 {
692 if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1
693 && GEOSPreparedContains_r( geosctxt, polygon->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) != 1 )
694 {
695 return true;
696 }
697 else if ( mNextPart )
698 {
699 return mNextPart->crossesBoundary( polygon );
700 }
701 }
702 catch ( GEOSException &e )
703 {
704 qWarning( "GEOS exception: %s", e.what() );
705 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
706 return false;
707 }
708
709 return false;
710}
711
713{
714 //effectively take the average polygon intersection cost for all label parts
715 const double totalCost = polygonIntersectionCostForParts( polygon );
716 const int n = partCount();
717 return std::ceil( totalCost / n );
718}
719
721{
722 // this method considers the label's outer bounds
723 if ( !mOuterBoundsGeos && !mGeos )
725
726 if ( !polygon->mGeos )
727 polygon->createGeosGeom();
728
729 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
730 try
731 {
732 if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 )
733 {
734 return true;
735 }
736 }
737 catch ( GEOSException &e )
738 {
739 qWarning( "GEOS exception: %s", e.what() );
740 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
741 }
742
743 if ( mNextPart )
744 {
745 return mNextPart->intersectsWithPolygon( polygon );
746 }
747 else
748 {
749 return false;
750 }
751}
752
753double LabelPosition::polygonIntersectionCostForParts( PointSet *polygon ) const
754{
755 // this method considers the label's outer bounds
756 if ( !mOuterBoundsGeos && !mGeos )
758
759 if ( !polygon->mGeos )
760 polygon->createGeosGeom();
761
762 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
763 double cost = 0;
764 try
765 {
766 if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 )
767 {
768 //at least a partial intersection
769 cost += 1;
770
771 double px, py;
772
773 // check each corner
774 for ( int i = 0; i < 4; ++i )
775 {
776 px = x[i];
777 py = y[i];
778
779 for ( int a = 0; a < 2; ++a ) // and each middle of segment
780 {
781 if ( polygon->containsPoint( px, py ) )
782 cost++;
783 px = ( x[i] + x[( i + 1 ) % 4] ) / 2.0;
784 py = ( y[i] + y[( i + 1 ) % 4] ) / 2.0;
785 }
786 }
787
788 px = ( x[0] + x[2] ) / 2.0;
789 py = ( y[0] + y[2] ) / 2.0;
790
791 //check the label center. if covered by polygon, cost of 4
792 if ( polygon->containsPoint( px, py ) )
793 cost += 4;
794 }
795 }
796 catch ( GEOSException &e )
797 {
798 qWarning( "GEOS exception: %s", e.what() );
799 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
800 }
801
802 //maintain scaling from 0 -> 12
803 cost = 12.0 * cost / 13.0;
804
805 if ( mNextPart )
806 {
807 cost += mNextPart->polygonIntersectionCostForParts( polygon );
808 }
809
810 return cost;
811}
812
814{
815 double angleDiff = 0.0;
816 double angleLast = 0.0;
817 LabelPosition *tmp = this;
818 while ( tmp )
819 {
820 if ( tmp != this ) // not first?
821 {
822 double diff = std::fabs( tmp->getAlpha() - angleLast );
823 if ( diff > 2 * M_PI )
824 diff -= 2 * M_PI;
825 diff = std::min( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
826 angleDiff += diff;
827 }
828
829 angleLast = tmp->getAlpha();
830 tmp = tmp->nextPart();
831 }
832 return angleDiff;
833}
A rtree spatial index for use in the pal labeling engine.
Definition: palrtree.h:36
void insert(T *data, const QgsRectangle &bounds)
Inserts new data into the spatial index, with the specified bounds.
Definition: palrtree.h:59
void remove(T *data, const QgsRectangle &bounds)
Removes existing data from the spatial index, with the specified bounds.
Definition: palrtree.h:78
@ AllowOverlapAtNoCost
Labels may freely overlap other labels, at no cost.
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 getGEOSHandler()
Definition: qgsgeos.cpp:3576
Qgis::LabelOverlapHandling overlapHandling() const
Returns the technique to use for handling overlapping labels for the feature.
QRectF outerBounds() const
Returns the extreme outer bounds of the label feature, including any surrounding content like borders...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
A rectangle specified with double values.
Definition: qgsrectangle.h:42
Main class to handle feature.
Definition: feature.h:65
bool onlyShowUprightLabels() const
Returns true if feature's label must be displayed upright.
Definition: feature.cpp:2395
QgsLabelFeature * feature()
Returns the parent feature.
Definition: feature.h:94
Layer * layer()
Returns the layer that feature belongs to.
Definition: feature.cpp:163
LabelPosition is a candidate feature label position.
Definition: labelposition.h:56
bool outerBoundingBoxIntersects(const LabelPosition *other) const
Returns true if the outer bounding box of this pointset intersects the outer bounding box of another ...
bool intersectsWithPolygon(PointSet *polygon) const
Returns true if any intersection between polygon and position exists.
bool isInConflict(const LabelPosition *ls) const
Check whether or not this overlap with another labelPosition.
double getAlpha() const
Returns the angle to rotate text (in radians).
Quadrant
Position of label candidate relative to feature.
Definition: labelposition.h:66
LabelPosition::Quadrant quadrant
double angleDifferential()
Returns the angle differential of all LabelPosition parts.
double alpha
Rotation in radians.
void removeFromIndex(PalRtree< LabelPosition > &index)
Removes the label position from the specified index.
~LabelPosition() override
bool crossesLine(PointSet *line) const
Returns true if this label crosses the specified line.
int getId() const
Returns the id.
FeaturePart * feature
const GEOSPreparedGeometry * preparedOuterBoundsGeom() const
Returns the prepared outer boundary geometry.
void validateCost()
Make sure the cost is less than 1.
double cost() const
Returns the candidate label position's geographical cost.
void setConflictsWithObstacle(bool conflicts)
Sets whether the position is marked as conflicting with an obstacle feature.
bool intersects(const GEOSPreparedGeometry *geometry)
Returns true if the label position intersects a geometry.
void setHasHardObstacleConflict(bool conflicts)
Sets whether the position is marked as having a hard conflict with an obstacle feature.
bool crossesBoundary(PointSet *polygon) const
Returns true if this label crosses the boundary of the specified polygon.
double getDistanceToPoint(double xp, double yp, bool useOuterBounds) const
Gets distance from this label to a point.
FeaturePart * getFeaturePart() const
Returns the feature corresponding to this labelposition.
const GEOSPreparedGeometry * preparedMultiPartGeom() const
Returns a prepared GEOS representation of all label parts as a multipolygon.
double getX(int i=0) const
Returns the down-left x coordinate.
void getBoundingBox(double amin[2], double amax[2]) const
Returns bounding box - amin: xmin,ymin - amax: xmax,ymax.
double getY(int i=0) const
Returns the down-left y coordinate.
bool within(const GEOSPreparedGeometry *geometry)
Returns true if the label position is within a geometry.
void insertIntoIndex(PalRtree< LabelPosition > &index)
Inserts the label position into the specified index.
int polygonIntersectionCost(PointSet *polygon) const
Returns cost of position intersection with polygon (testing area of intersection and center).
LabelPosition * nextPart() const
Returns the next part of this label position (i.e.
bool isCurved() const
Returns true if the layer has curved labels.
Definition: layer.h:173
The underlying raw pal geometry class.
Definition: pointset.h:77
friend class LabelPosition
Definition: pointset.h:79
double ymax
Definition: pointset.h:261
double ymin
Definition: pointset.h:260
void createGeosGeom() const
Definition: pointset.cpp:100
std::vector< double > y
Definition: pointset.h:231
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:965
std::vector< double > x
Definition: pointset.h:230
const GEOSPreparedGeometry * preparedGeom() const
Definition: pointset.cpp:155
GEOSGeometry * mGeos
Definition: pointset.h:234
double xmin
Definition: pointset.h:258
const GEOSGeometry * geos() const
Returns the point set's GEOS geometry.
Definition: pointset.cpp:1053
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:861
double xmax
Definition: pointset.h:259
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition: pointset.cpp:271
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition: qgsgeos.h:73
std::unique_ptr< GEOSCoordSequence, GeosDeleter > coord_sequence_unique_ptr
Scoped GEOS coordinate sequence pointer.
Definition: qgsgeos.h:88
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:5207
struct GEOSGeom_t GEOSGeometry
Definition: util.h:41