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