44using namespace Qt::StringLiterals;
55 , mDirectionToLine( directionToLine )
64 while ( this->alpha > 2 * M_PI )
65 this->alpha -= 2 * M_PI;
67 while ( this->alpha < 0 )
68 this->alpha += 2 * M_PI;
70 const double beta = this->alpha + M_PI_2;
72 double dx1, dx2, dy1, dy2;
74 dx1 = std::cos( this->alpha ) *
w;
75 dy1 = std::sin( this->alpha ) *
w;
77 dx2 = std::cos( beta ) *
h;
78 dy2 = std::sin( beta ) *
h;
86 x[2] = x1 + dx1 + dx2;
87 y[2] = y1 + dy1 + dy2;
93 if ( !
feature->layer()->isCurved() && this->alpha > M_PI_2 && this->alpha <= 3 * M_PI_2 )
95 if (
feature->onlyShowUprightLabels() )
118 if ( this->alpha < M_PI )
129 for (
int i = 0; i <
nbPoints; ++i )
137 createOuterBoundsGeom();
153 if ( other.mNextPart )
154 mNextPart = std::make_unique< LabelPosition >( *other.mNextPart );
158 mDirectionToLine = other.mDirectionToLine;
159 mQuadrant = other.mQuadrant;
160 mHasObstacleConflict = other.mHasObstacleConflict;
161 mUpsideDownCharCount = other.mUpsideDownCharCount;
163 createOuterBoundsGeom();
168 if ( mPreparedOuterBoundsGeos )
171 mPreparedOuterBoundsGeos =
nullptr;
178 if ( !mOuterBoundsGeos && !
mGeos )
183 if ( GEOSPreparedIntersects_r(
QgsGeosContext::get(), geometry, mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) == 1 )
187 else if ( mNextPart )
189 return mNextPart->intersects( geometry );
192 catch ( QgsGeosException &e )
194 qWarning(
"GEOS exception: %s", e.what() );
205 if ( !mOuterBoundsGeos && !
mGeos )
210 if ( GEOSPreparedContains_r(
QgsGeosContext::get(), geometry, mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) != 1 )
214 else if ( mNextPart )
216 return mNextPart->within( geometry );
219 catch ( QgsGeosException &e )
221 qWarning(
"GEOS exception: %s", e.what() );
244 if ( !noDuplicateDistanceApplies && allowOverlapAtNoCost )
250 if ( bothLabelsAreSinglePart )
255 if ( mOuterBoundsGeos )
265 if ( noDuplicateDistanceApplies )
267 const double minSeparationSquared = minDuplicateSeparation * minDuplicateSeparation;
268 for ( std::size_t i = 0; i < static_cast< std::size_t >(
nbPoints ); ++i )
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 )
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 )
288 if ( noDuplicateDistanceApplies )
292#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 10 )
301 catch ( QgsGeosException &e )
308 if ( !mOuterBoundsGeos && !
mGeos )
317 catch ( QgsGeosException &e )
319 qWarning(
"GEOS exception: %s", e.what() );
328 return isInConflictMultiPart( lp );
331bool LabelPosition::isInConflictMultiPart(
const LabelPosition *lp )
const
333 if ( !mMultipartGeos )
334 createMultiPartGeosGeom();
336 if ( !lp->mMultipartGeos )
337 lp->createMultiPartGeosGeom();
345 if ( noRepeatDistanceApplies )
351#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 10 )
360 catch ( QgsGeosException &e )
369 const bool result = ( GEOSPreparedIntersects_r( geosctxt,
preparedMultiPartGeom(), lp->mMultipartGeos ) == 1 );
372 catch ( QgsGeosException &e )
374 qWarning(
"GEOS exception: %s", e.what() );
383void LabelPosition::createOuterBoundsGeom()
386 if ( outerBounds.isNull() )
391 const double beta = this->
alpha + M_PI_2;
393 const double dx1 = std::cos( this->
alpha ) * outerBounds.width();
394 const double dy1 = std::sin( this->
alpha ) * outerBounds.width();
396 const double dx2 = std::cos( beta ) * outerBounds.height();
397 const double dy2 = std::sin( beta ) * outerBounds.height();
399 mOuterBoundsX.resize( 5 );
400 mOuterBoundsY.resize( 5 );
402 const double x1 =
x[0] + outerBounds.left();
403 const double y1 =
y[0] + outerBounds.top();
405 mOuterBoundsX[0] = x1;
406 mOuterBoundsY[0] = y1;
408 mOuterBoundsX[1] = x1 + dx1;
409 mOuterBoundsY[1] = y1 + dy1;
411 mOuterBoundsX[2] = x1 + dx1 + dx2;
412 mOuterBoundsY[2] = y1 + dy1 + dy2;
414 mOuterBoundsX[3] = x1 + dx2;
415 mOuterBoundsY[3] = y1 + dy2;
417 mOuterBoundsX[4] = mOuterBoundsX[0];
418 mOuterBoundsY[4] = mOuterBoundsY[0];
420 GEOSCoordSequence *coord =
nullptr;
421#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 10 )
423 coord = GEOSCoordSeq_copyFromArrays_r( geosctxt, mOuterBoundsX.data(), mOuterBoundsY.data(),
nullptr,
nullptr, 5 );
425 coord = GEOSCoordSeq_create_r( geosctxt, 5, 2 );
426 for (
int i = 0; i <
nbPoints; ++i )
428 GEOSCoordSeq_setXY_r( geosctxt, coord, i, mOuterBoundsX[i], mOuterBoundsY[i] );
432 mOuterBoundsGeos.reset( GEOSGeom_createPolygon_r( geosctxt, GEOSGeom_createLinearRing_r( geosctxt, coord ),
nullptr, 0 ) );
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;
444int LabelPosition::partCount()
const
447 return mNextPart->partCount() + 1;
459 return ( i >= 0 && i < 4 ?
x[i] : -1 );
464 return ( i >= 0 && i < 4 ?
y[i] : -1 );
476 mCost -= int( mCost );
490 mNextPart->getBoundingBox( amin, amax );
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();
499 for (
int c = 0;
c < 4;
c++ )
501 if ( mOuterBoundsGeos )
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];
514 if (
x[
c] < amin[0] )
516 if (
x[
c] > amax[0] )
518 if (
y[
c] < amin[1] )
520 if (
y[
c] > amax[1] )
531 return QgsRectangle( amin[0], amin[1], amax[0], amax[1] );
536 if ( !mBoundsForConflictIndex.isNull() )
537 return mBoundsForConflictIndex;
540 mBoundsForConflictIndex = bounds;
542 const double labelMarginDistance =
feature->feature()->thinningSettings().labelMarginDistance();
543 if ( labelMarginDistance > 0 )
545 mBoundsForConflictIndex.grow( labelMarginDistance );
548 const double noRepeatDistance =
feature->feature()->thinningSettings().noRepeatDistance();
549 if ( noRepeatDistance > 0 )
555 const QList< QgsAbstractLabelingEngineRule * > rules =
pal->rules();
558 const QgsRectangle modifiedBounds = rule->modifyCandidateConflictSearchBoundingBox( bounds );
561 return mBoundsForConflictIndex;
566 if ( other->mOuterBoundsGeos )
568 const double x1 = ( mOuterBoundsXMin > other->mOuterBoundsXMin ? mOuterBoundsXMin : other->mOuterBoundsXMin );
569 const double x2 = ( mOuterBoundsXMax < other->mOuterBoundsXMax ? mOuterBoundsXMax : other->mOuterBoundsXMax );
572 const double y1 = ( mOuterBoundsYMin > other->mOuterBoundsYMin ? mOuterBoundsYMin : other->mOuterBoundsYMin );
573 const double y2 = ( mOuterBoundsYMax < other->mOuterBoundsYMax ? mOuterBoundsYMax : other->mOuterBoundsYMax );
578 const double x1 = ( mOuterBoundsXMin > other->
xmin ? mOuterBoundsXMin : other->
xmin );
579 const double x2 = ( mOuterBoundsXMax < other->
xmax ? mOuterBoundsXMax : other->
xmax );
582 const double y1 = ( mOuterBoundsYMin > other->
ymin ? mOuterBoundsYMin : other->
ymin );
583 const double y2 = ( mOuterBoundsYMax < other->
ymax ? mOuterBoundsYMax : other->
ymax );
590 mHasObstacleConflict = conflicts;
592 mNextPart->setConflictsWithObstacle( conflicts );
597 mHasHardConflict = conflicts;
599 mNextPart->setHasHardObstacleConflict( conflicts );
614 if ( !mMultipartGeos )
615 createMultiPartGeosGeom();
617 return mMultipartGeos;
620void LabelPosition::createMultiPartGeosGeom()
const
624 std::vector< const GEOSGeometry * > geometries;
629 if ( !GEOSisEmpty_r( geosctxt, partGeos ) )
630 geometries.emplace_back( partGeos );
634 const std::size_t partCount = geometries.size();
636 for ( std::size_t i = 0; i < partCount; ++i )
638 geomarr[i] = GEOSGeom_clone_r( geosctxt, geometries[i] );
641 mMultipartGeos = GEOSGeom_createCollection_r( geosctxt, GEOS_MULTIPOLYGON, geomarr, partCount );
647 if ( !mMultipartGeos )
648 createMultiPartGeosGeom();
650 if ( !mMultipartPreparedGeos )
654 return mMultipartPreparedGeos;
659 return mPreparedOuterBoundsGeos;
669 bool contains =
false;
673 if ( useOuterBounds && mOuterBoundsGeos )
675 contains = mOuterBoundsX[0] <= xp && mOuterBoundsX[1] >= xp && mOuterBoundsY[0] <= yp && mOuterBoundsY[2] >= yp;
679 contains =
x[0] <= xp &&
x[1] >= xp &&
y[0] <= yp &&
y[2] >= yp;
684 if ( useOuterBounds && mPreparedOuterBoundsGeos )
689 contains = ( GEOSPreparedContainsProperly_r( geosctxt, mPreparedOuterBoundsGeos, point.get() ) == 1 );
691 catch ( QgsGeosException & )
702 double distance = -1;
707 if ( useOuterBounds && mOuterBoundsGeos )
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 );
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 );
722 if ( useOuterBounds && mPreparedOuterBoundsGeos )
726 geos::unique_ptr geosPt( GEOSGeom_createPointFromXY_r( geosctxt, xp, yp ) );
731 unsigned int nPoints = 0;
732 GEOSCoordSeq_getSize_r( geosctxt, nearestCoord.get(), &nPoints );
739 ( void ) GEOSCoordSeq_getXY_r( geosctxt, nearestCoord.get(), 0, &nx, &ny );
743 catch ( QgsGeosException &e )
745 qWarning(
"GEOS exception: %s", e.what() );
757 if ( mNextPart && distance > 0 )
758 return std::min( distance, mNextPart->getDistanceToPoint( xp, yp, useOuterBounds ) );
766 if ( !mOuterBoundsGeos && !
mGeos )
775 if ( GEOSPreparedIntersects_r( geosctxt, line->
preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) == 1 )
779 else if ( mNextPart )
781 return mNextPart->crossesLine( line );
784 catch ( QgsGeosException &e )
786 qWarning(
"GEOS exception: %s", e.what() );
797 if ( !mOuterBoundsGeos && !
mGeos )
800 if ( !polygon->
mGeos )
806 if ( GEOSPreparedIntersects_r( geosctxt, polygon->
preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) == 1
807 && GEOSPreparedContains_r( geosctxt, polygon->
preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) != 1 )
811 else if ( mNextPart )
813 return mNextPart->crossesBoundary( polygon );
816 catch ( QgsGeosException &e )
818 qWarning(
"GEOS exception: %s", e.what() );
829 const double totalCost = polygonIntersectionCostForParts( polygon );
830 const int n = partCount();
831 return std::ceil( totalCost / n );
837 if ( !mOuterBoundsGeos && !
mGeos )
840 if ( !polygon->
mGeos )
846 if ( GEOSPreparedIntersects_r( geosctxt, polygon->
preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) == 1 )
851 catch ( QgsGeosException &e )
853 qWarning(
"GEOS exception: %s", e.what() );
859 return mNextPart->intersectsWithPolygon( polygon );
867double LabelPosition::polygonIntersectionCostForParts(
PointSet *polygon )
const
870 if ( !mOuterBoundsGeos && !
mGeos )
873 if ( !polygon->
mGeos )
880 if ( GEOSPreparedIntersects_r( geosctxt, polygon->
preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) == 1 )
888 for (
int i = 0; i < 4; ++i )
893 for (
int a = 0; a < 2; ++a )
897 px = (
x[i] +
x[( i + 1 ) % 4] ) / 2.0;
898 py = (
y[i] +
y[( i + 1 ) % 4] ) / 2.0;
902 px = (
x[0] +
x[2] ) / 2.0;
903 py = (
y[0] +
y[2] ) / 2.0;
910 catch ( QgsGeosException &e )
912 qWarning(
"GEOS exception: %s", e.what() );
921 cost += mNextPart->polygonIntersectionCostForParts( polygon );
929 double angleDiff = 0.0;
930 double angleLast = 0.0;
936 double diff = std::fabs( tmp->
getAlpha() - angleLast );
937 if ( diff > 2 * M_PI )
939 diff = std::min( diff, 2 * M_PI - diff );
A rtree spatial index for use in the pal labeling engine.
void insert(T *data, const QgsRectangle &bounds)
Inserts new data into the spatial index, with the specified bounds.
void remove(T *data, const QgsRectangle &bounds)
Removes existing data from the spatial index, with the specified bounds.
LabelQuadrantPosition
Label quadrant positions.
@ AllowOverlapAtNoCost
Labels may freely overlap other labels, at no cost.
Abstract base class for labeling engine rules.
static double sqrDistance2D(double x1, double y1, double x2, double y2)
Returns the squared 2D distance between (x1, y1) and (x2, y2).
static GEOSContextHandle_t get()
Returns a thread local instance of a GEOS context, safe for use in the current thread.
double 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.
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.
~LabelPosition() override
bool crossesLine(PointSet *line) const
Returns true if this label crosses the specified line.
int getId() const
Returns the id.
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.
The underlying raw pal geometry class.
friend class LabelPosition
void createGeosGeom() const
bool boundingBoxIntersects(const PointSet *other) const
Returns true if the bounding box of this pointset intersects the bounding box of another pointset.
const GEOSPreparedGeometry * preparedGeom() const
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,...
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
std::unique_ptr< GEOSCoordSequence, GeosDeleter > coord_sequence_unique_ptr
Scoped GEOS coordinate sequence pointer.
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).
#define QgsDebugError(str)
struct GEOSGeom_t GEOSGeometry