QGIS API Documentation 3.99.0-Master (26c88405ac0)
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 "labelposition.h"
31
32#include <cmath>
33
34#include "costcalculator.h"
35#include "feature.h"
36#include "layer.h"
38#include "qgsgeos.h"
40#include "qgsmessagelog.h"
41
42using namespace pal;
43
44LabelPosition::LabelPosition( int id, double x1, double y1, double w, double h, double alpha, double cost, FeaturePart *feature, LabelDirectionToLine directionToLine, Qgis::LabelQuadrantPosition 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 , upsideDown( false )
54 , mQuadrant( quadrant )
55 , mDirectionToLine( directionToLine )
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 {
98 if ( feature->onlyShowUprightLabels() )
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 mDirectionToLine = other.mDirectionToLine;
162 mQuadrant = other.mQuadrant;
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 ( QgsGeosException &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 ( QgsGeosException &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 const double minDuplicateSeparation = std::max( getFeaturePart()->feature()->thinningSettings().noRepeatDistance(),
241 const bool noDuplicateDistanceApplies = minDuplicateSeparation > 0 && getFeaturePart()->feature()->labelText() == lp->getFeaturePart()->feature()->labelText();
242
243 // if either this label doesn't cause collisions, or the other one doesn't, then we don't conflict!
244 const bool allowOverlapAtNoCost = this->feature->feature()->overlapHandling() == Qgis::LabelOverlapHandling::AllowOverlapAtNoCost ||
246
247 // early exit shortcut
248 if ( !noDuplicateDistanceApplies && allowOverlapAtNoCost )
249 return false;
250
251 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
252
253 const bool bothLabelsAreSinglePart = !nextPart() && !lp->nextPart();
254 if ( bothLabelsAreSinglePart )
255 {
256 if ( qgsDoubleNear( alpha, 0 ) && qgsDoubleNear( lp->alpha, 0 ) )
257 {
258 // simple case -- both candidates are oriented to axis, so shortcut with easy calculation
259 if ( mOuterBoundsGeos )
260 {
261 if ( outerBoundingBoxIntersects( lp ) )
262 return true;
263 }
264 else if ( boundingBoxIntersects( lp ) )
265 {
266 return true;
267 }
268
269 if ( noDuplicateDistanceApplies )
270 {
271 const double minSeparationSquared = minDuplicateSeparation * minDuplicateSeparation;
272 for ( std::size_t i = 0; i < static_cast< std::size_t >( nbPoints ); ++i )
273 {
274 const double lx1 = x[i];
275 const double ly1 = y[i];
276 for ( std::size_t j = 0; j < static_cast< std::size_t>( lp->nbPoints ); ++j )
277 {
278 const double lx2 = lp->x[j];
279 const double ly2 = lp->y[j];
280 if ( ( lx2 - lx1 ) * ( lx2 - lx1 ) + ( ly2 - ly1 ) * ( ly2 - ly1 ) < minSeparationSquared )
281 return true;
282 }
283 }
284 }
285
286 return false;
287 }
288 else
289 {
290 // this method considers the label's outer bounds
291
292 if ( noDuplicateDistanceApplies )
293 {
294 try
295 {
296#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
297 if ( GEOSPreparedDistanceWithin_r( geosctxt, lp->preparedOuterBoundsGeom() ? lp->preparedOuterBoundsGeom() : lp->preparedGeom(),
298 mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos, minDuplicateSeparation ) )
299 {
300 return true;
301 }
302#else
303 QgsMessageLog::logMessage( QStringLiteral( "label margin distance requires GEOS 3.10+" ) );
304#endif
305 }
306 catch ( QgsGeosException &e )
307 {
308 QgsDebugError( QStringLiteral( "GEOS exception: %1" ).arg( e.what() ) );
309 }
310 }
311 else
312 {
313
314 if ( !mOuterBoundsGeos && !mGeos )
316
317 try
318 {
319 const bool result = ( GEOSPreparedIntersects_r( geosctxt, lp->preparedOuterBoundsGeom() ? lp->preparedOuterBoundsGeom() : lp->preparedGeom(),
320 mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 );
321 return result;
322 }
323 catch ( QgsGeosException &e )
324 {
325 qWarning( "GEOS exception: %s", e.what() );
326 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
327 return false;
328 }
329 }
330 return false;
331
332 }
333 }
334
335 return isInConflictMultiPart( lp );
336}
337
338bool LabelPosition::isInConflictMultiPart( const LabelPosition *lp ) const
339{
340 if ( !mMultipartGeos )
341 createMultiPartGeosGeom();
342
343 if ( !lp->mMultipartGeos )
344 lp->createMultiPartGeosGeom();
345
346 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
347
348 const bool noRepeatDistanceApplies = ( getFeaturePart()->feature()->thinningSettings().noRepeatDistance() > 0
351
352 // test for duplicate labels too close together
353 if ( noRepeatDistanceApplies )
354 {
355 const double minSeparation = std::max( getFeaturePart()->feature()->thinningSettings().noRepeatDistance(),
357
358 try
359 {
360#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
361 if ( GEOSPreparedDistanceWithin_r( geosctxt, preparedMultiPartGeom(), lp->multiPartGeom(), minSeparation ) )
362 {
363 return true;
364 }
365#else
366 QgsMessageLog::logMessage( QStringLiteral( "label margin distance requires GEOS 3.10+" ) );
367#endif
368 }
369 catch ( QgsGeosException &e )
370 {
371 QgsDebugError( QStringLiteral( "GEOS exception: %1" ).arg( e.what() ) );
372 }
373 }
374 else
375 {
376 try
377 {
378 const bool result = ( GEOSPreparedIntersects_r( geosctxt, preparedMultiPartGeom(), lp->mMultipartGeos ) == 1 );
379 return result;
380 }
381 catch ( QgsGeosException &e )
382 {
383 qWarning( "GEOS exception: %s", e.what() );
384 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
385 return false;
386 }
387 }
388
389 return false;
390}
391
392void LabelPosition::createOuterBoundsGeom()
393{
394 const QRectF outerBounds = getFeaturePart()->feature()->outerBounds();
395 if ( outerBounds.isNull() )
396 return;
397
398 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
399
400 const double beta = this->alpha + M_PI_2;
401
402 const double dx1 = std::cos( this->alpha ) * outerBounds.width();
403 const double dy1 = std::sin( this->alpha ) * outerBounds.width();
404
405 const double dx2 = std::cos( beta ) * outerBounds.height();
406 const double dy2 = std::sin( beta ) * outerBounds.height();
407
408 mOuterBoundsX.resize( 5 );
409 mOuterBoundsY.resize( 5 );
410
411 const double x1 = x[0] + outerBounds.left();
412 const double y1 = y[0] + outerBounds.top();
413
414 mOuterBoundsX[0] = x1;
415 mOuterBoundsY[0] = y1;
416
417 mOuterBoundsX[1] = x1 + dx1;
418 mOuterBoundsY[1] = y1 + dy1;
419
420 mOuterBoundsX[2] = x1 + dx1 + dx2;
421 mOuterBoundsY[2] = y1 + dy1 + dy2;
422
423 mOuterBoundsX[3] = x1 + dx2;
424 mOuterBoundsY[3] = y1 + dy2;
425
426 mOuterBoundsX[4] = mOuterBoundsX[0];
427 mOuterBoundsY[4] = mOuterBoundsY[0];
428
429 GEOSCoordSequence *coord = nullptr;
430#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
431 // use optimised method if we don't have to force close an open ring
432 coord = GEOSCoordSeq_copyFromArrays_r( geosctxt, mOuterBoundsX.data(), mOuterBoundsY.data(), nullptr, nullptr, 5 );
433#else
434 coord = GEOSCoordSeq_create_r( geosctxt, 5, 2 );
435 for ( int i = 0; i < nbPoints; ++i )
436 {
437 GEOSCoordSeq_setXY_r( geosctxt, coord, i, mOuterBoundsX[i], mOuterBoundsY[i] );
438 }
439#endif
440
441 mOuterBoundsGeos.reset( GEOSGeom_createPolygon_r( geosctxt, GEOSGeom_createLinearRing_r( geosctxt, coord ), nullptr, 0 ) );
442
443 mPreparedOuterBoundsGeos = GEOSPrepare_r( QgsGeosContext::get(), mOuterBoundsGeos.get() );
444
445 auto xminmax = std::minmax_element( mOuterBoundsX.begin(), mOuterBoundsX.end() );
446 mOuterBoundsXMin = *xminmax.first;
447 mOuterBoundsXMax = *xminmax.second;
448 auto yminmax = std::minmax_element( mOuterBoundsY.begin(), mOuterBoundsY.end() );
449 mOuterBoundsYMin = *yminmax.first;
450 mOuterBoundsYMax = *yminmax.second;
451}
452
453int LabelPosition::partCount() const
454{
455 if ( mNextPart )
456 return mNextPart->partCount() + 1;
457 else
458 return 1;
459}
460
462{
463 return id;
464}
465
466double LabelPosition::getX( int i ) const
467{
468 return ( i >= 0 && i < 4 ? x[i] : -1 );
469}
470
471double LabelPosition::getY( int i ) const
472{
473 return ( i >= 0 && i < 4 ? y[i] : -1 );
474}
475
477{
478 return alpha;
479}
480
482{
483 if ( mCost >= 1 )
484 {
485 mCost -= int ( mCost ); // label cost up to 1
486 }
487}
488
490{
491 return feature;
492}
493
494void LabelPosition::getBoundingBox( double amin[2], double amax[2] ) const
495{
496 // this method considers the label's outer bounds
497 if ( mNextPart )
498 {
499 mNextPart->getBoundingBox( amin, amax );
500 }
501 else
502 {
503 amin[0] = std::numeric_limits<double>::max();
504 amax[0] = std::numeric_limits<double>::lowest();
505 amin[1] = std::numeric_limits<double>::max();
506 amax[1] = std::numeric_limits<double>::lowest();
507 }
508 for ( int c = 0; c < 4; c++ )
509 {
510 if ( mOuterBoundsGeos )
511 {
512 if ( mOuterBoundsX[c] < amin[0] )
513 amin[0] = mOuterBoundsX[c];
514 if ( mOuterBoundsX[c] > amax[0] )
515 amax[0] = mOuterBoundsX[c];
516 if ( mOuterBoundsY[c] < amin[1] )
517 amin[1] = mOuterBoundsY[c];
518 if ( mOuterBoundsY[c] > amax[1] )
519 amax[1] = mOuterBoundsY[c];
520 }
521 else
522 {
523 if ( x[c] < amin[0] )
524 amin[0] = x[c];
525 if ( x[c] > amax[0] )
526 amax[0] = x[c];
527 if ( y[c] < amin[1] )
528 amin[1] = y[c];
529 if ( y[c] > amax[1] )
530 amax[1] = y[c];
531 }
532 }
533}
534
536{
537 double amin[2];
538 double amax[2];
539 getBoundingBox( amin, amax );
540 return QgsRectangle( amin[0], amin[1], amax[0], amax[1] );
541}
542
544{
545 if ( !mBoundsForConflictIndex.isNull() )
546 return mBoundsForConflictIndex;
547
549 mBoundsForConflictIndex = bounds;
550
551 const double labelMarginDistance = feature->feature()->thinningSettings().labelMarginDistance();
552 if ( labelMarginDistance > 0 )
553 {
554 mBoundsForConflictIndex.grow( labelMarginDistance );
555 }
556
557 const double noRepeatDistance = feature->feature()->thinningSettings().noRepeatDistance();
558 if ( noRepeatDistance > 0 )
559 {
560 const QgsRectangle modifiedBounds = bounds.buffered( noRepeatDistance );
561 mBoundsForConflictIndex.combineExtentWith( modifiedBounds );
562 }
563
564 const QList< QgsAbstractLabelingEngineRule * > rules = pal->rules();
565 for ( QgsAbstractLabelingEngineRule *rule : rules )
566 {
567 const QgsRectangle modifiedBounds = rule->modifyCandidateConflictSearchBoundingBox( bounds );
568 mBoundsForConflictIndex.combineExtentWith( modifiedBounds );
569 }
570 return mBoundsForConflictIndex;
571}
572
574{
575 if ( other->mOuterBoundsGeos )
576 {
577 const double x1 = ( mOuterBoundsXMin > other->mOuterBoundsXMin ? mOuterBoundsXMin : other->mOuterBoundsXMin );
578 const double x2 = ( mOuterBoundsXMax < other->mOuterBoundsXMax ? mOuterBoundsXMax : other->mOuterBoundsXMax );
579 if ( x1 > x2 )
580 return false;
581 const double y1 = ( mOuterBoundsYMin > other->mOuterBoundsYMin ? mOuterBoundsYMin : other->mOuterBoundsYMin );
582 const double y2 = ( mOuterBoundsYMax < other->mOuterBoundsYMax ? mOuterBoundsYMax : other->mOuterBoundsYMax );
583 return y1 <= y2;
584 }
585 else
586 {
587 const double x1 = ( mOuterBoundsXMin > other->xmin ? mOuterBoundsXMin : other->xmin );
588 const double x2 = ( mOuterBoundsXMax < other->xmax ? mOuterBoundsXMax : other->xmax );
589 if ( x1 > x2 )
590 return false;
591 const double y1 = ( mOuterBoundsYMin > other->ymin ? mOuterBoundsYMin : other->ymin );
592 const double y2 = ( mOuterBoundsYMax < other->ymax ? mOuterBoundsYMax : other->ymax );
593 return y1 <= y2;
594 }
595}
596
598{
599 mHasObstacleConflict = conflicts;
600 if ( mNextPart )
601 mNextPart->setConflictsWithObstacle( conflicts );
602}
603
605{
606 mHasHardConflict = conflicts;
607 if ( mNextPart )
608 mNextPart->setHasHardObstacleConflict( conflicts );
609}
610
615
620
622{
623 if ( !mMultipartGeos )
624 createMultiPartGeosGeom();
625
626 return mMultipartGeos;
627}
628
629void LabelPosition::createMultiPartGeosGeom() const
630{
631 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
632
633 std::vector< const GEOSGeometry * > geometries;
634 const LabelPosition *tmp1 = this;
635 while ( tmp1 )
636 {
637 const GEOSGeometry *partGeos = tmp1->geos();
638 if ( !GEOSisEmpty_r( geosctxt, partGeos ) )
639 geometries.emplace_back( partGeos );
640 tmp1 = tmp1->nextPart();
641 }
642
643 const std::size_t partCount = geometries.size();
644 GEOSGeometry **geomarr = new GEOSGeometry*[ partCount ];
645 for ( std::size_t i = 0; i < partCount; ++i )
646 {
647 geomarr[i ] = GEOSGeom_clone_r( geosctxt, geometries[i] );
648 }
649
650 mMultipartGeos = GEOSGeom_createCollection_r( geosctxt, GEOS_MULTIPOLYGON, geomarr, partCount );
651 delete [] geomarr;
652}
653
654const GEOSPreparedGeometry *LabelPosition::preparedMultiPartGeom() const
655{
656 if ( !mMultipartGeos )
657 createMultiPartGeosGeom();
658
659 if ( !mMultipartPreparedGeos )
660 {
661 mMultipartPreparedGeos = GEOSPrepare_r( QgsGeosContext::get(), mMultipartGeos );
662 }
663 return mMultipartPreparedGeos;
664}
665
666const GEOSPreparedGeometry *LabelPosition::preparedOuterBoundsGeom() const
667{
668 return mPreparedOuterBoundsGeos;
669}
670
671double LabelPosition::getDistanceToPoint( double xp, double yp, bool useOuterBounds ) const
672{
673 // this method may consider the label's outer bounds!
674
675 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
676
677 //first check if inside, if so then distance is -1
678 bool contains = false;
679 if ( alpha == 0 )
680 {
681 // easy case -- horizontal label
682 if ( useOuterBounds && mOuterBoundsGeos )
683 {
684 contains = mOuterBoundsX[0] <= xp && mOuterBoundsX[1] >= xp && mOuterBoundsY[0] <= yp && mOuterBoundsY[2] >= yp;
685 }
686 else
687 {
688 contains = x[0] <= xp && x[1] >= xp && y[0] <= yp && y[2] >= yp;
689 }
690 }
691 else
692 {
693 if ( useOuterBounds && mPreparedOuterBoundsGeos )
694 {
695 try
696 {
697 geos::unique_ptr point( GEOSGeom_createPointFromXY_r( geosctxt, xp, yp ) );
698 contains = ( GEOSPreparedContainsProperly_r( geosctxt, mPreparedOuterBoundsGeos, point.get() ) == 1 );
699 }
700 catch ( QgsGeosException & )
701 {
702 contains = false;
703 }
704 }
705 else
706 {
707 contains = containsPoint( xp, yp );
708 }
709 }
710
711 double distance = -1;
712 if ( !contains )
713 {
714 if ( alpha == 0 )
715 {
716 if ( useOuterBounds && mOuterBoundsGeos )
717 {
718 const double dx = std::max( std::max( mOuterBoundsX[0] - xp, 0.0 ), xp - mOuterBoundsX[1] );
719 const double dy = std::max( std::max( mOuterBoundsY[0] - yp, 0.0 ), yp - mOuterBoundsY[2] );
720 distance = std::sqrt( dx * dx + dy * dy );
721 }
722 else
723 {
724 const double dx = std::max( std::max( x[0] - xp, 0.0 ), xp - x[1] );
725 const double dy = std::max( std::max( y[0] - yp, 0.0 ), yp - y[2] );
726 distance = std::sqrt( dx * dx + dy * dy );
727 }
728 }
729 else
730 {
731 if ( useOuterBounds && mPreparedOuterBoundsGeos )
732 {
733 try
734 {
735 geos::unique_ptr geosPt( GEOSGeom_createPointFromXY_r( geosctxt, xp, yp ) );
736
737 const geos::coord_sequence_unique_ptr nearestCoord( GEOSPreparedNearestPoints_r( geosctxt, mPreparedOuterBoundsGeos, geosPt.get() ) );
738 double nx;
739 double ny;
740 unsigned int nPoints = 0;
741 GEOSCoordSeq_getSize_r( geosctxt, nearestCoord.get(), &nPoints );
742 if ( nPoints == 0 )
743 {
744 distance = -1;
745 }
746 else
747 {
748 ( void )GEOSCoordSeq_getXY_r( geosctxt, nearestCoord.get(), 0, &nx, &ny );
749 distance = QgsGeometryUtilsBase::sqrDistance2D( xp, yp, nx, ny );
750 }
751 }
752 catch ( QgsGeosException &e )
753 {
754 qWarning( "GEOS exception: %s", e.what() );
755 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
756 distance = -1;
757 }
758 }
759 else
760 {
761 distance = std::sqrt( minDistanceToPoint( xp, yp ) );
762 }
763 }
764 }
765
766 if ( mNextPart && distance > 0 )
767 return std::min( distance, mNextPart->getDistanceToPoint( xp, yp, useOuterBounds ) );
768
769 return distance;
770}
771
773{
774 // this method considers the label's outer bounds
775 if ( !mOuterBoundsGeos && !mGeos )
777
778 if ( !line->mGeos )
779 line->createGeosGeom();
780
781 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
782 try
783 {
784 if ( GEOSPreparedIntersects_r( geosctxt, line->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 )
785 {
786 return true;
787 }
788 else if ( mNextPart )
789 {
790 return mNextPart->crossesLine( line );
791 }
792 }
793 catch ( QgsGeosException &e )
794 {
795 qWarning( "GEOS exception: %s", e.what() );
796 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
797 return false;
798 }
799
800 return false;
801}
802
804{
805 // this method considers the label's outer bounds
806 if ( !mOuterBoundsGeos && !mGeos )
808
809 if ( !polygon->mGeos )
810 polygon->createGeosGeom();
811
812 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
813 try
814 {
815 if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1
816 && GEOSPreparedContains_r( geosctxt, polygon->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) != 1 )
817 {
818 return true;
819 }
820 else if ( mNextPart )
821 {
822 return mNextPart->crossesBoundary( polygon );
823 }
824 }
825 catch ( QgsGeosException &e )
826 {
827 qWarning( "GEOS exception: %s", e.what() );
828 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
829 return false;
830 }
831
832 return false;
833}
834
836{
837 //effectively take the average polygon intersection cost for all label parts
838 const double totalCost = polygonIntersectionCostForParts( polygon );
839 const int n = partCount();
840 return std::ceil( totalCost / n );
841}
842
844{
845 // this method considers the label's outer bounds
846 if ( !mOuterBoundsGeos && !mGeos )
848
849 if ( !polygon->mGeos )
850 polygon->createGeosGeom();
851
852 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
853 try
854 {
855 if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 )
856 {
857 return true;
858 }
859 }
860 catch ( QgsGeosException &e )
861 {
862 qWarning( "GEOS exception: %s", e.what() );
863 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
864 }
865
866 if ( mNextPart )
867 {
868 return mNextPart->intersectsWithPolygon( polygon );
869 }
870 else
871 {
872 return false;
873 }
874}
875
876double LabelPosition::polygonIntersectionCostForParts( PointSet *polygon ) const
877{
878 // this method considers the label's outer bounds
879 if ( !mOuterBoundsGeos && !mGeos )
881
882 if ( !polygon->mGeos )
883 polygon->createGeosGeom();
884
885 GEOSContextHandle_t geosctxt = QgsGeosContext::get();
886 double cost = 0;
887 try
888 {
889 if ( GEOSPreparedIntersects_r( geosctxt, polygon->preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() : mGeos ) == 1 )
890 {
891 //at least a partial intersection
892 cost += 1;
893
894 double px, py;
895
896 // check each corner
897 for ( int i = 0; i < 4; ++i )
898 {
899 px = x[i];
900 py = y[i];
901
902 for ( int a = 0; a < 2; ++a ) // and each middle of segment
903 {
904 if ( polygon->containsPoint( px, py ) )
905 cost++;
906 px = ( x[i] + x[( i + 1 ) % 4] ) / 2.0;
907 py = ( y[i] + y[( i + 1 ) % 4] ) / 2.0;
908 }
909 }
910
911 px = ( x[0] + x[2] ) / 2.0;
912 py = ( y[0] + y[2] ) / 2.0;
913
914 //check the label center. if covered by polygon, cost of 4
915 if ( polygon->containsPoint( px, py ) )
916 cost += 4;
917 }
918 }
919 catch ( QgsGeosException &e )
920 {
921 qWarning( "GEOS exception: %s", e.what() );
922 QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
923 }
924
925 //maintain scaling from 0 -> 12
926 cost = 12.0 * cost / 13.0;
927
928 if ( mNextPart )
929 {
930 cost += mNextPart->polygonIntersectionCostForParts( polygon );
931 }
932
933 return cost;
934}
935
937{
938 double angleDiff = 0.0;
939 double angleLast = 0.0;
940 LabelPosition *tmp = this;
941 while ( tmp )
942 {
943 if ( tmp != this ) // not first?
944 {
945 double diff = std::fabs( tmp->getAlpha() - angleLast );
946 if ( diff > 2 * M_PI )
947 diff -= 2 * M_PI;
948 diff = std::min( diff, 2 * M_PI - diff ); // difference 350 deg is actually just 10 deg...
949 angleDiff += diff;
950 }
951
952 angleLast = tmp->getAlpha();
953 tmp = tmp->nextPart();
954 }
955 return angleDiff;
956}
A rtree spatial index for use in the pal labeling engine.
Definition palrtree.h:37
void insert(T *data, const QgsRectangle &bounds)
Inserts new data into the spatial index, with the specified bounds.
Definition palrtree.h:60
void remove(T *data, const QgsRectangle &bounds)
Removes existing data from the spatial index, with the specified bounds.
Definition palrtree.h:83
LabelQuadrantPosition
Label quadrant positions.
Definition qgis.h:1255
@ AllowOverlapAtNoCost
Labels may freely overlap other labels, at no cost.
Definition qgis.h:1170
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 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 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.
QgsLabelFeature * feature()
Returns the parent feature.
Definition feature.h:89
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.
Qgis::LabelQuadrantPosition quadrant() const
Returns the quadrant associated with this label position.
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.
Main Pal labeling class.
Definition pal.h:84
The underlying raw pal geometry class.
Definition pointset.h:78
friend class LabelPosition
Definition pointset.h:80
double ymax
Definition pointset.h:262
double ymin
Definition pointset.h:261
void createGeosGeom() const
Definition pointset.cpp:102
std::vector< double > y
Definition pointset.h:232
bool boundingBoxIntersects(const PointSet *other) const
Returns true if the bounding box of this pointset intersects the bounding box of another pointset.
Definition pointset.cpp:967
std::vector< double > x
Definition pointset.h:231
const GEOSPreparedGeometry * preparedGeom() const
Definition pointset.cpp:157
GEOSGeometry * mGeos
Definition pointset.h:235
double xmin
Definition pointset.h:259
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:863
friend class FeaturePart
Definition pointset.h:79
double xmax
Definition pointset.h:260
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
Definition pointset.cpp:273
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:114
std::unique_ptr< GEOSCoordSequence, GeosDeleter > coord_sequence_unique_ptr
Scoped GEOS coordinate sequence pointer.
Definition qgsgeos.h:129
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
#define QgsDebugError(str)
Definition qgslogger.h:57
struct GEOSGeom_t GEOSGeometry
Definition util.h:42