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