QGIS API Documentation 3.43.0-Master (c4a2e9c6d2f)
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 "qgsgeos.h"
37#include "qgsmessagelog.h"
38#include <cmath>
39
40using namespace pal;
41
42LabelPosition::LabelPosition( int id, double x1, double y1, double w, double h, double alpha, double cost, FeaturePart *feature, LabelDirectionToLine directionToLine, Qgis::LabelQuadrantPosition quadrant )
43 : id( id )
44 , feature( feature )
45 , probFeat( 0 )
46 , nbOverlap( 0 )
47 , alpha( alpha )
48 , w( w )
49 , h( h )
50 , partId( -1 )
51 , upsideDown( false )
52 , mQuadrant( quadrant )
53 , mDirectionToLine( directionToLine )
54 , mCost( cost )
55 , mHasObstacleConflict( false )
56 , mUpsideDownCharCount( 0 )
57{
58 type = GEOS_POLYGON;
59 nbPoints = 4;
60 x.resize( nbPoints );
61 y.resize( nbPoints );
62
63 // alpha take his value bw 0 and 2*pi rad
64 while ( this->alpha > 2 * M_PI )
65 this->alpha -= 2 * M_PI;
66
67 while ( this->alpha < 0 )
68 this->alpha += 2 * M_PI;
69
70 const double beta = this->alpha + M_PI_2;
71
72 double dx1, dx2, dy1, dy2;
73
74 dx1 = std::cos( this->alpha ) * w;
75 dy1 = std::sin( this->alpha ) * w;
76
77 dx2 = std::cos( beta ) * h;
78 dy2 = std::sin( beta ) * h;
79
80 x[0] = x1;
81 y[0] = y1;
82
83 x[1] = x1 + dx1;
84 y[1] = y1 + dy1;
85
86 x[2] = x1 + dx1 + dx2;
87 y[2] = y1 + dy1 + dy2;
88
89 x[3] = x1 + dx2;
90 y[3] = y1 + dy2;
91
92 // upside down ? (curved labels are always correct)
93 if ( !feature->layer()->isCurved() &&
94 this->alpha > M_PI_2 && this->alpha <= 3 * M_PI_2 )
95 {
97 {
98 // Turn label upsidedown by inverting boundary points
99 double tx, ty;
100
101 tx = x[0];
102 ty = y[0];
103
104 x[0] = x[2];
105 y[0] = y[2];
106
107 x[2] = tx;
108 y[2] = ty;
109
110 tx = x[1];
111 ty = y[1];
112
113 x[1] = x[3];
114 y[1] = y[3];
115
116 x[3] = tx;
117 y[3] = ty;
118
119 if ( this->alpha < M_PI )
120 this->alpha += M_PI;
121 else
122 this->alpha -= M_PI;
123
124 // labels with text shown upside down are not classified as upsideDown,
125 // only those whose boundary points have been inverted
126 upsideDown = true;
127 }
128 }
129
130 for ( int i = 0; i < nbPoints; ++i )
131 {
132 xmin = std::min( xmin, x[i] );
133 xmax = std::max( xmax, x[i] );
134 ymin = std::min( ymin, y[i] );
135 ymax = std::max( ymax, y[i] );
136 }
137
138 createOuterBoundsGeom();
139}
140
142 : PointSet( other )
143{
144 id = other.id;
145 mCost = other.mCost;
146 feature = other.feature;
147 probFeat = other.probFeat;
148 nbOverlap = other.nbOverlap;
149
150 alpha = other.alpha;
151 w = other.w;
152 h = other.h;
153
154 if ( other.mNextPart )
155 mNextPart = std::make_unique< LabelPosition >( *other.mNextPart );
156
157 partId = other.partId;
158 upsideDown = other.upsideDown;
159 mDirectionToLine = other.mDirectionToLine;
160 mQuadrant = other.mQuadrant;
161 mHasObstacleConflict = other.mHasObstacleConflict;
162 mUpsideDownCharCount = other.mUpsideDownCharCount;
163
164 createOuterBoundsGeom();
165}
166
168{
169 if ( mPreparedOuterBoundsGeos )
170 {
171 GEOSPreparedGeom_destroy_r( QgsGeosContext::get(), mPreparedOuterBoundsGeos );
172 mPreparedOuterBoundsGeos = nullptr;
173 }
174}
175
176bool LabelPosition::intersects( const GEOSPreparedGeometry *geometry )
177{
178 // this method considers the label's outer bounds
179 if ( !mOuterBoundsGeos && !mGeos )
181
182 try
183 {
184 if ( GEOSPreparedIntersects_r( QgsGeosContext::get(), geometry, mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 )
185 {
186 return true;
187 }
188 else if ( mNextPart )
189 {
190 return mNextPart->intersects( geometry );
191 }
192 }
193 catch ( GEOSException &e )
194 {
195 qWarning( "GEOS exception: %s", e.what() );
196 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
197 return false;
198 }
199
200 return false;
201}
202
203bool LabelPosition::within( const GEOSPreparedGeometry *geometry )
204{
205 // this method considers the label's outer bounds
206 if ( !mOuterBoundsGeos && !mGeos )
208
209 try
210 {
211 if ( GEOSPreparedContains_r( QgsGeosContext::get(), geometry, mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) != 1 )
212 {
213 return false;
214 }
215 else if ( mNextPart )
216 {
217 return mNextPart->within( geometry );
218 }
219 }
220 catch ( GEOSException &e )
221 {
222 qWarning( "GEOS exception: %s", e.what() );
223 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
224 return false;
225 }
226
227 return true;
228}
229
231{
232 // this method considers the label's outer bounds
233
234 if ( this->probFeat == lp->probFeat ) // bugfix #1
235 return false; // always overlapping itself !
236
237 const double minDuplicateSeparation = std::max( getFeaturePart()->feature()->thinningSettings().noRepeatDistance(),
239 const bool noDuplicateDistanceApplies = minDuplicateSeparation > 0 && getFeaturePart()->feature()->labelText() == lp->getFeaturePart()->feature()->labelText();
240
241 // if either this label doesn't cause collisions, or the other one doesn't, then we don't conflict!
242 const bool allowOverlapAtNoCost = this->feature->feature()->overlapHandling() == Qgis::LabelOverlapHandling::AllowOverlapAtNoCost ||
244
245 // early exit shortcut
246 if ( !noDuplicateDistanceApplies && allowOverlapAtNoCost )
247 return false;
248
249 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
250
251 const bool bothLabelsAreSinglePart = !nextPart() && !lp->nextPart();
252 if ( bothLabelsAreSinglePart )
253 {
254 if ( qgsDoubleNear( alpha, 0 ) && qgsDoubleNear( lp->alpha, 0 ) )
255 {
256 // simple case -- both candidates are oriented to axis, so shortcut with easy calculation
257 if ( mOuterBoundsGeos )
258 {
259 if ( outerBoundingBoxIntersects( lp ) )
260 return true;
261 }
262 else if ( boundingBoxIntersects( lp ) )
263 {
264 return true;
265 }
266
267 if ( noDuplicateDistanceApplies )
268 {
269 const double minSeparationSquared = minDuplicateSeparation * minDuplicateSeparation;
270 for ( std::size_t i = 0; i < static_cast< std::size_t >( nbPoints ); ++i )
271 {
272 const double lx1 = x[i];
273 const double ly1 = y[i];
274 for ( std::size_t j = 0; j < static_cast< std::size_t>( lp->nbPoints ); ++j )
275 {
276 const double lx2 = lp->x[j];
277 const double ly2 = lp->y[j];
278 if ( ( lx2 - lx1 ) * ( lx2 - lx1 ) + ( ly2 - ly1 ) * ( ly2 - ly1 ) < minSeparationSquared )
279 return true;
280 }
281 }
282 }
283
284 return false;
285 }
286 else
287 {
288 // this method considers the label's outer bounds
289
290 if ( noDuplicateDistanceApplies )
291 {
292 try
293 {
294#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
295 if ( GEOSPreparedDistanceWithin_r( geosctxt, lp->preparedOuterBoundsGeom() ? lp->preparedOuterBoundsGeom() : lp->preparedGeom(),
296 mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos, minDuplicateSeparation ) )
297 {
298 return true;
299 }
300#else
301 QgsMessageLog::logMessage( QStringLiteral( "label margin distance requires GEOS 3.10+" ) );
302#endif
303 }
304 catch ( GEOSException &e )
305 {
306 QgsDebugError( QStringLiteral( "GEOS exception: %1" ).arg( e.what() ) );
307 }
308 }
309 else
310 {
311
312 if ( !mOuterBoundsGeos && !mGeos )
314
315 try
316 {
317 const bool result = ( GEOSPreparedIntersects_r( geosctxt, lp->preparedOuterBoundsGeom() ? lp->preparedOuterBoundsGeom() : lp->preparedGeom(),
318 mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 );
319 return result;
320 }
321 catch ( GEOSException &e )
322 {
323 qWarning( "GEOS exception: %s", e.what() );
324 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
325 return false;
326 }
327 }
328 return false;
329
330 }
331 }
332
333 return isInConflictMultiPart( lp );
334}
335
336bool LabelPosition::isInConflictMultiPart( const LabelPosition *lp ) const
337{
338 if ( !mMultipartGeos )
339 createMultiPartGeosGeom();
340
341 if ( !lp->mMultipartGeos )
342 lp->createMultiPartGeosGeom();
343
344 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
345
346 const bool noRepeatDistanceApplies = ( getFeaturePart()->feature()->thinningSettings().noRepeatDistance() > 0
349
350 // test for duplicate labels too close together
351 if ( noRepeatDistanceApplies )
352 {
353 const double minSeparation = std::max( getFeaturePart()->feature()->thinningSettings().noRepeatDistance(),
355
356 try
357 {
358#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
359 if ( GEOSPreparedDistanceWithin_r( geosctxt, preparedMultiPartGeom(), lp->multiPartGeom(), minSeparation ) )
360 {
361 return true;
362 }
363#else
364 QgsMessageLog::logMessage( QStringLiteral( "label margin distance requires GEOS 3.10+" ) );
365#endif
366 }
367 catch ( GEOSException &e )
368 {
369 QgsDebugError( QStringLiteral( "GEOS exception: %1" ).arg( e.what() ) );
370 }
371 }
372 else
373 {
374 try
375 {
376 const bool result = ( GEOSPreparedIntersects_r( geosctxt, preparedMultiPartGeom(), lp->mMultipartGeos ) == 1 );
377 return result;
378 }
379 catch ( GEOSException &e )
380 {
381 qWarning( "GEOS exception: %s", e.what() );
382 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
383 return false;
384 }
385 }
386
387 return false;
388}
389
390void LabelPosition::createOuterBoundsGeom()
391{
392 const QRectF outerBounds = getFeaturePart()->feature()->outerBounds();
393 if ( outerBounds.isNull() )
394 return;
395
396 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
397
398 const double beta = this->alpha + M_PI_2;
399
400 const double dx1 = std::cos( this->alpha ) * outerBounds.width();
401 const double dy1 = std::sin( this->alpha ) * outerBounds.width();
402
403 const double dx2 = std::cos( beta ) * outerBounds.height();
404 const double dy2 = std::sin( beta ) * outerBounds.height();
405
406 mOuterBoundsX.resize( 5 );
407 mOuterBoundsY.resize( 5 );
408
409 const double x1 = x[0] + outerBounds.left();
410 const double y1 = y[0] + outerBounds.top();
411
412 mOuterBoundsX[0] = x1;
413 mOuterBoundsY[0] = y1;
414
415 mOuterBoundsX[1] = x1 + dx1;
416 mOuterBoundsY[1] = y1 + dy1;
417
418 mOuterBoundsX[2] = x1 + dx1 + dx2;
419 mOuterBoundsY[2] = y1 + dy1 + dy2;
420
421 mOuterBoundsX[3] = x1 + dx2;
422 mOuterBoundsY[3] = y1 + dy2;
423
424 mOuterBoundsX[4] = mOuterBoundsX[0];
425 mOuterBoundsY[4] = mOuterBoundsY[0];
426
427 GEOSCoordSequence *coord = nullptr;
428#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
429 // use optimised method if we don't have to force close an open ring
430 coord = GEOSCoordSeq_copyFromArrays_r( geosctxt, mOuterBoundsX.data(), mOuterBoundsY.data(), nullptr, nullptr, 5 );
431#else
432 coord = GEOSCoordSeq_create_r( geosctxt, 5, 2 );
433 for ( int i = 0; i < nbPoints; ++i )
434 {
435 GEOSCoordSeq_setXY_r( geosctxt, coord, i, mOuterBoundsX[i], mOuterBoundsY[i] );
436 }
437#endif
438
439 mOuterBoundsGeos.reset( GEOSGeom_createPolygon_r( geosctxt, GEOSGeom_createLinearRing_r( geosctxt, coord ), nullptr, 0 ) );
440
441 mPreparedOuterBoundsGeos = GEOSPrepare_r( QgsGeosContext::get(), mOuterBoundsGeos.get() );
442
443 auto xminmax = std::minmax_element( mOuterBoundsX.begin(), mOuterBoundsX.end() );
444 mOuterBoundsXMin = *xminmax.first;
445 mOuterBoundsXMax = *xminmax.second;
446 auto yminmax = std::minmax_element( mOuterBoundsY.begin(), mOuterBoundsY.end() );
447 mOuterBoundsYMin = *yminmax.first;
448 mOuterBoundsYMax = *yminmax.second;
449}
450
451int LabelPosition::partCount() const
452{
453 if ( mNextPart )
454 return mNextPart->partCount() + 1;
455 else
456 return 1;
457}
458
460{
461 return id;
462}
463
464double LabelPosition::getX( int i ) const
465{
466 return ( i >= 0 && i < 4 ? x[i] : -1 );
467}
468
469double LabelPosition::getY( int i ) const
470{
471 return ( i >= 0 && i < 4 ? y[i] : -1 );
472}
473
475{
476 return alpha;
477}
478
480{
481 if ( mCost >= 1 )
482 {
483 mCost -= int ( mCost ); // label cost up to 1
484 }
485}
486
488{
489 return feature;
490}
491
492void LabelPosition::getBoundingBox( double amin[2], double amax[2] ) const
493{
494 // this method considers the label's outer bounds
495 if ( mNextPart )
496 {
497 mNextPart->getBoundingBox( amin, amax );
498 }
499 else
500 {
501 amin[0] = std::numeric_limits<double>::max();
502 amax[0] = std::numeric_limits<double>::lowest();
503 amin[1] = std::numeric_limits<double>::max();
504 amax[1] = std::numeric_limits<double>::lowest();
505 }
506 for ( int c = 0; c < 4; c++ )
507 {
508 if ( mOuterBoundsGeos )
509 {
510 if ( mOuterBoundsX[c] < amin[0] )
511 amin[0] = mOuterBoundsX[c];
512 if ( mOuterBoundsX[c] > amax[0] )
513 amax[0] = mOuterBoundsX[c];
514 if ( mOuterBoundsY[c] < amin[1] )
515 amin[1] = mOuterBoundsY[c];
516 if ( mOuterBoundsY[c] > amax[1] )
517 amax[1] = mOuterBoundsY[c];
518 }
519 else
520 {
521 if ( x[c] < amin[0] )
522 amin[0] = x[c];
523 if ( x[c] > amax[0] )
524 amax[0] = x[c];
525 if ( y[c] < amin[1] )
526 amin[1] = y[c];
527 if ( y[c] > amax[1] )
528 amax[1] = y[c];
529 }
530 }
531}
532
534{
535 double amin[2];
536 double amax[2];
537 getBoundingBox( amin, amax );
538 return QgsRectangle( amin[0], amin[1], amax[0], amax[1] );
539}
540
542{
543 if ( !mBoundsForConflictIndex.isNull() )
544 return mBoundsForConflictIndex;
545
547 mBoundsForConflictIndex = bounds;
548
549 const double labelMarginDistance = feature->feature()->thinningSettings().labelMarginDistance();
550 if ( labelMarginDistance > 0 )
551 {
552 mBoundsForConflictIndex.grow( labelMarginDistance );
553 }
554
555 const double noRepeatDistance = feature->feature()->thinningSettings().noRepeatDistance();
556 if ( noRepeatDistance > 0 )
557 {
558 const QgsRectangle modifiedBounds = bounds.buffered( noRepeatDistance );
559 mBoundsForConflictIndex.combineExtentWith( modifiedBounds );
560 }
561
562 const QList< QgsAbstractLabelingEngineRule * > rules = pal->rules();
563 for ( QgsAbstractLabelingEngineRule *rule : rules )
564 {
565 const QgsRectangle modifiedBounds = rule->modifyCandidateConflictSearchBoundingBox( bounds );
566 mBoundsForConflictIndex.combineExtentWith( modifiedBounds );
567 }
568 return mBoundsForConflictIndex;
569}
570
572{
573 if ( other->mOuterBoundsGeos )
574 {
575 const double x1 = ( mOuterBoundsXMin > other->mOuterBoundsXMin ? mOuterBoundsXMin : other->mOuterBoundsXMin );
576 const double x2 = ( mOuterBoundsXMax < other->mOuterBoundsXMax ? mOuterBoundsXMax : other->mOuterBoundsXMax );
577 if ( x1 > x2 )
578 return false;
579 const double y1 = ( mOuterBoundsYMin > other->mOuterBoundsYMin ? mOuterBoundsYMin : other->mOuterBoundsYMin );
580 const double y2 = ( mOuterBoundsYMax < other->mOuterBoundsYMax ? mOuterBoundsYMax : other->mOuterBoundsYMax );
581 return y1 <= y2;
582 }
583 else
584 {
585 const double x1 = ( mOuterBoundsXMin > other->xmin ? mOuterBoundsXMin : other->xmin );
586 const double x2 = ( mOuterBoundsXMax < other->xmax ? mOuterBoundsXMax : other->xmax );
587 if ( x1 > x2 )
588 return false;
589 const double y1 = ( mOuterBoundsYMin > other->ymin ? mOuterBoundsYMin : other->ymin );
590 const double y2 = ( mOuterBoundsYMax < other->ymax ? mOuterBoundsYMax : other->ymax );
591 return y1 <= y2;
592 }
593}
594
596{
597 mHasObstacleConflict = conflicts;
598 if ( mNextPart )
599 mNextPart->setConflictsWithObstacle( conflicts );
600}
601
603{
604 mHasHardConflict = conflicts;
605 if ( mNextPart )
606 mNextPart->setHasHardObstacleConflict( conflicts );
607}
608
613
618
620{
621 if ( !mMultipartGeos )
622 createMultiPartGeosGeom();
623
624 return mMultipartGeos;
625}
626
627void LabelPosition::createMultiPartGeosGeom() const
628{
629 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
630
631 std::vector< const GEOSGeometry * > geometries;
632 const LabelPosition *tmp1 = this;
633 while ( tmp1 )
634 {
635 const GEOSGeometry *partGeos = tmp1->geos();
636 if ( !GEOSisEmpty_r( geosctxt, partGeos ) )
637 geometries.emplace_back( partGeos );
638 tmp1 = tmp1->nextPart();
639 }
640
641 const std::size_t partCount = geometries.size();
642 GEOSGeometry **geomarr = new GEOSGeometry*[ partCount ];
643 for ( std::size_t i = 0; i < partCount; ++i )
644 {
645 geomarr[i ] = GEOSGeom_clone_r( geosctxt, geometries[i] );
646 }
647
648 mMultipartGeos = GEOSGeom_createCollection_r( geosctxt, GEOS_MULTIPOLYGON, geomarr, partCount );
649 delete [] geomarr;
650}
651
652const GEOSPreparedGeometry *LabelPosition::preparedMultiPartGeom() const
653{
654 if ( !mMultipartGeos )
655 createMultiPartGeosGeom();
656
657 if ( !mMultipartPreparedGeos )
658 {
659 mMultipartPreparedGeos = GEOSPrepare_r( QgsGeosContext::get(), mMultipartGeos );
660 }
661 return mMultipartPreparedGeos;
662}
663
664const GEOSPreparedGeometry *LabelPosition::preparedOuterBoundsGeom() const
665{
666 return mPreparedOuterBoundsGeos;
667}
668
669double LabelPosition::getDistanceToPoint( double xp, double yp, bool useOuterBounds ) const
670{
671 // this method may consider the label's outer bounds!
672
673 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
674
675 //first check if inside, if so then distance is -1
676 bool contains = false;
677 if ( alpha == 0 )
678 {
679 // easy case -- horizontal label
680 if ( useOuterBounds && mOuterBoundsGeos )
681 {
682 contains = mOuterBoundsX[0] <= xp && mOuterBoundsX[1] >= xp && mOuterBoundsY[0] <= yp && mOuterBoundsY[2] >= yp;
683 }
684 else
685 {
686 contains = x[0] <= xp && x[1] >= xp && y[0] <= yp && y[2] >= yp;
687 }
688 }
689 else
690 {
691 if ( useOuterBounds && mPreparedOuterBoundsGeos )
692 {
693 try
694 {
695 geos::unique_ptr point( GEOSGeom_createPointFromXY_r( geosctxt, xp, yp ) );
696 contains = ( GEOSPreparedContainsProperly_r( geosctxt, mPreparedOuterBoundsGeos, point.get() ) == 1 );
697 }
698 catch ( GEOSException & )
699 {
700 contains = false;
701 }
702 }
703 else
704 {
705 contains = containsPoint( xp, yp );
706 }
707 }
708
709 double distance = -1;
710 if ( !contains )
711 {
712 if ( alpha == 0 )
713 {
714 if ( useOuterBounds && mOuterBoundsGeos )
715 {
716 const double dx = std::max( std::max( mOuterBoundsX[0] - xp, 0.0 ), xp - mOuterBoundsX[1] );
717 const double dy = std::max( std::max( mOuterBoundsY[0] - yp, 0.0 ), yp - mOuterBoundsY[2] );
718 distance = std::sqrt( dx * dx + dy * dy );
719 }
720 else
721 {
722 const double dx = std::max( std::max( x[0] - xp, 0.0 ), xp - x[1] );
723 const double dy = std::max( std::max( y[0] - yp, 0.0 ), yp - y[2] );
724 distance = std::sqrt( dx * dx + dy * dy );
725 }
726 }
727 else
728 {
729 if ( useOuterBounds && mPreparedOuterBoundsGeos )
730 {
731 try
732 {
733 geos::unique_ptr geosPt( GEOSGeom_createPointFromXY_r( geosctxt, xp, yp ) );
734
735 const geos::coord_sequence_unique_ptr nearestCoord( GEOSPreparedNearestPoints_r( geosctxt, mPreparedOuterBoundsGeos, geosPt.get() ) );
736 double nx;
737 double ny;
738 unsigned int nPoints = 0;
739 GEOSCoordSeq_getSize_r( geosctxt, nearestCoord.get(), &nPoints );
740 if ( nPoints == 0 )
741 {
742 distance = -1;
743 }
744 else
745 {
746 ( void )GEOSCoordSeq_getXY_r( geosctxt, nearestCoord.get(), 0, &nx, &ny );
747 distance = QgsGeometryUtilsBase::sqrDistance2D( xp, yp, nx, ny );
748 }
749 }
750 catch ( GEOSException &e )
751 {
752 qWarning( "GEOS exception: %s", e.what() );
753 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
754 distance = -1;
755 }
756 }
757 else
758 {
759 distance = std::sqrt( minDistanceToPoint( xp, yp ) );
760 }
761 }
762 }
763
764 if ( mNextPart && distance > 0 )
765 return std::min( distance, mNextPart->getDistanceToPoint( xp, yp, useOuterBounds ) );
766
767 return distance;
768}
769
771{
772 // this method considers the label's outer bounds
773 if ( !mOuterBoundsGeos && !mGeos )
775
776 if ( !line->mGeos )
777 line->createGeosGeom();
778
779 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
780 try
781 {
782 if ( GEOSPreparedIntersects_r( geosctxt, line->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 )
783 {
784 return true;
785 }
786 else if ( mNextPart )
787 {
788 return mNextPart->crossesLine( line );
789 }
790 }
791 catch ( GEOSException &e )
792 {
793 qWarning( "GEOS exception: %s", e.what() );
794 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
795 return false;
796 }
797
798 return false;
799}
800
802{
803 // this method considers the label's outer bounds
804 if ( !mOuterBoundsGeos && !mGeos )
806
807 if ( !polygon->mGeos )
808 polygon->createGeosGeom();
809
810 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
811 try
812 {
813 if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1
814 && GEOSPreparedContains_r( geosctxt, polygon->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) != 1 )
815 {
816 return true;
817 }
818 else if ( mNextPart )
819 {
820 return mNextPart->crossesBoundary( polygon );
821 }
822 }
823 catch ( GEOSException &e )
824 {
825 qWarning( "GEOS exception: %s", e.what() );
826 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
827 return false;
828 }
829
830 return false;
831}
832
834{
835 //effectively take the average polygon intersection cost for all label parts
836 const double totalCost = polygonIntersectionCostForParts( polygon );
837 const int n = partCount();
838 return std::ceil( totalCost / n );
839}
840
842{
843 // this method considers the label's outer bounds
844 if ( !mOuterBoundsGeos && !mGeos )
846
847 if ( !polygon->mGeos )
848 polygon->createGeosGeom();
849
850 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
851 try
852 {
853 if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 )
854 {
855 return true;
856 }
857 }
858 catch ( GEOSException &e )
859 {
860 qWarning( "GEOS exception: %s", e.what() );
861 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
862 }
863
864 if ( mNextPart )
865 {
866 return mNextPart->intersectsWithPolygon( polygon );
867 }
868 else
869 {
870 return false;
871 }
872}
873
874double LabelPosition::polygonIntersectionCostForParts( PointSet *polygon ) const
875{
876 // this method considers the label's outer bounds
877 if ( !mOuterBoundsGeos && !mGeos )
879
880 if ( !polygon->mGeos )
881 polygon->createGeosGeom();
882
883 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
884 double cost = 0;
885 try
886 {
887 if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 )
888 {
889 //at least a partial intersection
890 cost += 1;
891
892 double px, py;
893
894 // check each corner
895 for ( int i = 0; i < 4; ++i )
896 {
897 px = x[i];
898 py = y[i];
899
900 for ( int a = 0; a < 2; ++a ) // and each middle of segment
901 {
902 if ( polygon->containsPoint( px, py ) )
903 cost++;
904 px = ( x[i] + x[( i + 1 ) % 4] ) / 2.0;
905 py = ( y[i] + y[( i + 1 ) % 4] ) / 2.0;
906 }
907 }
908
909 px = ( x[0] + x[2] ) / 2.0;
910 py = ( y[0] + y[2] ) / 2.0;
911
912 //check the label center. if covered by polygon, cost of 4
913 if ( polygon->containsPoint( px, py ) )
914 cost += 4;
915 }
916 }
917 catch ( GEOSException &e )
918 {
919 qWarning( "GEOS exception: %s", e.what() );
920 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
921 }
922
923 //maintain scaling from 0 -> 12
924 cost = 12.0 * cost / 13.0;
925
926 if ( mNextPart )
927 {
928 cost += mNextPart->polygonIntersectionCostForParts( polygon );
929 }
930
931 return cost;
932}
933
935{
936 double angleDiff = 0.0;
937 double angleLast = 0.0;
938 LabelPosition *tmp = this;
939 while ( tmp )
940 {
941 if ( tmp != this ) // not first?
942 {
943 double diff = std::fabs( tmp->getAlpha() - angleLast );
944 if ( diff > 2 * M_PI )
945 diff -= 2 * M_PI;
946 diff = std::min( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
947 angleDiff += diff;
948 }
949
950 angleLast = tmp->getAlpha();
951 tmp = tmp->nextPart();
952 }
953 return angleDiff;
954}
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:82
LabelQuadrantPosition
Label quadrant positions.
Definition qgis.h:1219
@ 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.
double labelMarginDistance() const
Returns the minimum distance (in label units) between labels for this feature and other labels.
double noRepeatDistance() const
Returns the minimum distance (in label units) between labels for this feature and other labels with t...
const QgsLabelFeatureThinningSettings & thinningSettings() const
Returns the thinning settings for this label.
Qgis::LabelOverlapHandling overlapHandling() const
Returns the technique to use for handling overlapping labels for the feature.
QString labelText() const
Text of the label.
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, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
A rectangle specified with double values.
void grow(double delta)
Grows the rectangle in place by the specified amount.
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
QgsRectangle buffered(double width) const
Gets rectangle enlarged by buffer.
Represents a part of a label feature.
Definition feature.h:58
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:87
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 ...
QgsRectangle outerBoundingBox() const
Returns bounding box.
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).
double angleDifferential()
Returns the angle differential of all LabelPosition parts.
double alpha
Rotation in radians.
LabelPosition(int id, double x1, double y1, double w, double h, double alpha, double cost, FeaturePart *feature, LabelDirectionToLine directionToLine=LabelDirectionToLine::SameDirection, Qgis::LabelQuadrantPosition quadrant=Qgis::LabelQuadrantPosition::Over)
create a new LabelPosition
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.
void removeFromIndex(PalRtree< LabelPosition > &index, Pal *pal)
Removes the label position from the specified index.
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.
LabelDirectionToLine
Label directions in relation to line or polygon ring directions.
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.
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.
void insertIntoIndex(PalRtree< LabelPosition > &index, Pal *pal)
Inserts the label position into the specified index.
bool isCurved() const
Returns true if the layer has curved labels.
Definition layer.h:172
Main Pal labeling class.
Definition pal.h:83
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:6287
#define QgsDebugError(str)
Definition qgslogger.h:40
struct GEOSGeom_t GEOSGeometry
Definition util.h:41