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