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