44using namespace Qt::StringLiterals;
55 , mDirectionToLine( directionToLine )
63 const double beta = this->alpha + M_PI_2;
65 double dx1, dx2, dy1, dy2;
67 dx1 = std::cos( this->alpha ) *
w;
68 dy1 = std::sin( this->alpha ) *
w;
70 dx2 = std::cos( beta ) *
h;
71 dy2 = std::sin( beta ) *
h;
79 x[2] = x1 + dx1 + dx2;
80 y[2] = y1 + dy1 + dy2;
86 if ( !
feature->layer()->isCurved() && this->alpha > M_PI_2 && this->alpha <= 3 * M_PI_2 )
88 if (
feature->onlyShowUprightLabels() )
111 if ( this->alpha < M_PI )
122 for (
int i = 0; i <
nbPoints; ++i )
130 createOuterBoundsGeom();
146 if ( other.mNextPart )
147 mNextPart = std::make_unique< LabelPosition >( *other.mNextPart );
151 mDirectionToLine = other.mDirectionToLine;
152 mQuadrant = other.mQuadrant;
153 mHasObstacleConflict = other.mHasObstacleConflict;
154 mUpsideDownCharCount = other.mUpsideDownCharCount;
156 createOuterBoundsGeom();
161 if ( mPreparedOuterBoundsGeos )
164 mPreparedOuterBoundsGeos =
nullptr;
171 if ( !mOuterBoundsGeos && !
mGeos )
176 if ( GEOSPreparedIntersects_r(
QgsGeosContext::get(), geometry, mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) == 1 )
180 else if ( mNextPart )
182 return mNextPart->intersects( geometry );
185 catch ( QgsGeosException &e )
187 qWarning(
"GEOS exception: %s", e.what() );
198 if ( !mOuterBoundsGeos && !
mGeos )
203 if ( GEOSPreparedContains_r(
QgsGeosContext::get(), geometry, mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) != 1 )
207 else if ( mNextPart )
209 return mNextPart->within( geometry );
212 catch ( QgsGeosException &e )
214 qWarning(
"GEOS exception: %s", e.what() );
237 if ( !noDuplicateDistanceApplies && allowOverlapAtNoCost )
243 if ( bothLabelsAreSinglePart )
248 if ( mOuterBoundsGeos )
258 if ( noDuplicateDistanceApplies )
260 const double minSeparationSquared = minDuplicateSeparation * minDuplicateSeparation;
261 for ( std::size_t i = 0; i < static_cast< std::size_t >(
nbPoints ); ++i )
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 )
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 )
281 if ( noDuplicateDistanceApplies )
285#if GEOS_VERSION_NUM >= GEOS_COMPUTE_VERSION( 3, 10, 0 )
294 catch ( QgsGeosException &e )
301 if ( !mOuterBoundsGeos && !
mGeos )
310 catch ( QgsGeosException &e )
312 qWarning(
"GEOS exception: %s", e.what() );
321 return isInConflictMultiPart( lp );
324bool LabelPosition::isInConflictMultiPart(
const LabelPosition *lp )
const
326 if ( !mMultipartGeos )
327 createMultiPartGeosGeom();
329 if ( !lp->mMultipartGeos )
330 lp->createMultiPartGeosGeom();
338 if ( noRepeatDistanceApplies )
344#if GEOS_VERSION_NUM >= GEOS_COMPUTE_VERSION( 3, 10, 0 )
353 catch ( QgsGeosException &e )
362 const bool result = ( GEOSPreparedIntersects_r( geosctxt,
preparedMultiPartGeom(), lp->mMultipartGeos ) == 1 );
365 catch ( QgsGeosException &e )
367 qWarning(
"GEOS exception: %s", e.what() );
376void LabelPosition::createOuterBoundsGeom()
379 if ( outerBounds.isNull() )
384 const double beta = this->
alpha + M_PI_2;
386 const double dx1 = std::cos( this->
alpha ) * outerBounds.width();
387 const double dy1 = std::sin( this->
alpha ) * outerBounds.width();
389 const double dx2 = std::cos( beta ) * outerBounds.height();
390 const double dy2 = std::sin( beta ) * outerBounds.height();
392 mOuterBoundsX.resize( 5 );
393 mOuterBoundsY.resize( 5 );
395 const double x1 =
x[0] + outerBounds.left();
396 const double y1 =
y[0] + outerBounds.top();
398 mOuterBoundsX[0] = x1;
399 mOuterBoundsY[0] = y1;
401 mOuterBoundsX[1] = x1 + dx1;
402 mOuterBoundsY[1] = y1 + dy1;
404 mOuterBoundsX[2] = x1 + dx1 + dx2;
405 mOuterBoundsY[2] = y1 + dy1 + dy2;
407 mOuterBoundsX[3] = x1 + dx2;
408 mOuterBoundsY[3] = y1 + dy2;
410 mOuterBoundsX[4] = mOuterBoundsX[0];
411 mOuterBoundsY[4] = mOuterBoundsY[0];
413 GEOSCoordSequence *coord =
nullptr;
414#if GEOS_VERSION_NUM >= GEOS_COMPUTE_VERSION( 3, 10, 0 )
416 coord = GEOSCoordSeq_copyFromArrays_r( geosctxt, mOuterBoundsX.data(), mOuterBoundsY.data(),
nullptr,
nullptr, 5 );
418 coord = GEOSCoordSeq_create_r( geosctxt, 5, 2 );
419 for (
int i = 0; i <
nbPoints; ++i )
421 GEOSCoordSeq_setXY_r( geosctxt, coord, i, mOuterBoundsX[i], mOuterBoundsY[i] );
425 mOuterBoundsGeos.reset( GEOSGeom_createPolygon_r( geosctxt, GEOSGeom_createLinearRing_r( geosctxt, coord ),
nullptr, 0 ) );
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;
437int LabelPosition::partCount()
const
440 return mNextPart->partCount() + 1;
452 return ( i >= 0 && i < 4 ?
x[i] : -1 );
457 return ( i >= 0 && i < 4 ?
y[i] : -1 );
469 mCost -= int( mCost );
483 mNextPart->getBoundingBox( amin, amax );
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();
492 for (
int c = 0;
c < 4;
c++ )
494 if ( mOuterBoundsGeos )
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];
507 if (
x[
c] < amin[0] )
509 if (
x[
c] > amax[0] )
511 if (
y[
c] < amin[1] )
513 if (
y[
c] > amax[1] )
524 return QgsRectangle( amin[0], amin[1], amax[0], amax[1] );
529 if ( !mBoundsForConflictIndex.isNull() )
530 return mBoundsForConflictIndex;
533 mBoundsForConflictIndex = bounds;
535 const double labelMarginDistance =
feature->feature()->thinningSettings().labelMarginDistance();
536 if ( labelMarginDistance > 0 )
538 mBoundsForConflictIndex.grow( labelMarginDistance );
541 const double noRepeatDistance =
feature->feature()->thinningSettings().noRepeatDistance();
542 if ( noRepeatDistance > 0 )
548 const QList< QgsAbstractLabelingEngineRule * > rules =
pal->rules();
551 const QgsRectangle modifiedBounds = rule->modifyCandidateConflictSearchBoundingBox( bounds );
554 return mBoundsForConflictIndex;
559 if ( other->mOuterBoundsGeos )
561 const double x1 = ( mOuterBoundsXMin > other->mOuterBoundsXMin ? mOuterBoundsXMin : other->mOuterBoundsXMin );
562 const double x2 = ( mOuterBoundsXMax < other->mOuterBoundsXMax ? mOuterBoundsXMax : other->mOuterBoundsXMax );
565 const double y1 = ( mOuterBoundsYMin > other->mOuterBoundsYMin ? mOuterBoundsYMin : other->mOuterBoundsYMin );
566 const double y2 = ( mOuterBoundsYMax < other->mOuterBoundsYMax ? mOuterBoundsYMax : other->mOuterBoundsYMax );
571 const double x1 = ( mOuterBoundsXMin > other->
xmin ? mOuterBoundsXMin : other->
xmin );
572 const double x2 = ( mOuterBoundsXMax < other->
xmax ? mOuterBoundsXMax : other->
xmax );
575 const double y1 = ( mOuterBoundsYMin > other->
ymin ? mOuterBoundsYMin : other->
ymin );
576 const double y2 = ( mOuterBoundsYMax < other->
ymax ? mOuterBoundsYMax : other->
ymax );
583 mHasObstacleConflict = conflicts;
585 mNextPart->setConflictsWithObstacle( conflicts );
590 mHasHardConflict = conflicts;
592 mNextPart->setHasHardObstacleConflict( conflicts );
607 if ( !mMultipartGeos )
608 createMultiPartGeosGeom();
610 return mMultipartGeos;
613void LabelPosition::createMultiPartGeosGeom()
const
617 std::vector< const GEOSGeometry * > geometries;
622 if ( !GEOSisEmpty_r( geosctxt, partGeos ) )
623 geometries.emplace_back( partGeos );
627 const std::size_t partCount = geometries.size();
629 for ( std::size_t i = 0; i < partCount; ++i )
631 geomarr[i] = GEOSGeom_clone_r( geosctxt, geometries[i] );
634 mMultipartGeos = GEOSGeom_createCollection_r( geosctxt, GEOS_MULTIPOLYGON, geomarr, partCount );
640 if ( !mMultipartGeos )
641 createMultiPartGeosGeom();
643 if ( !mMultipartPreparedGeos )
647 return mMultipartPreparedGeos;
652 return mPreparedOuterBoundsGeos;
662 bool contains =
false;
666 if ( useOuterBounds && mOuterBoundsGeos )
668 contains = mOuterBoundsX[0] <= xp && mOuterBoundsX[1] >= xp && mOuterBoundsY[0] <= yp && mOuterBoundsY[2] >= yp;
672 contains =
x[0] <= xp &&
x[1] >= xp &&
y[0] <= yp &&
y[2] >= yp;
677 if ( useOuterBounds && mPreparedOuterBoundsGeos )
682 contains = ( GEOSPreparedContainsProperly_r( geosctxt, mPreparedOuterBoundsGeos, point.get() ) == 1 );
684 catch ( QgsGeosException & )
695 double distance = -1;
700 if ( useOuterBounds && mOuterBoundsGeos )
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 );
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 );
715 if ( useOuterBounds && mPreparedOuterBoundsGeos )
719 geos::unique_ptr geosPt( GEOSGeom_createPointFromXY_r( geosctxt, xp, yp ) );
724 unsigned int nPoints = 0;
725 GEOSCoordSeq_getSize_r( geosctxt, nearestCoord.get(), &nPoints );
732 ( void ) GEOSCoordSeq_getXY_r( geosctxt, nearestCoord.get(), 0, &nx, &ny );
736 catch ( QgsGeosException &e )
738 qWarning(
"GEOS exception: %s", e.what() );
750 if ( mNextPart && distance > 0 )
751 return std::min( distance, mNextPart->getDistanceToPoint( xp, yp, useOuterBounds ) );
759 if ( !mOuterBoundsGeos && !
mGeos )
768 if ( GEOSPreparedIntersects_r( geosctxt, line->
preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) == 1 )
772 else if ( mNextPart )
774 return mNextPart->crossesLine( line );
777 catch ( QgsGeosException &e )
779 qWarning(
"GEOS exception: %s", e.what() );
790 if ( !mOuterBoundsGeos && !
mGeos )
793 if ( !polygon->
mGeos )
799 if ( GEOSPreparedIntersects_r( geosctxt, polygon->
preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) == 1
800 && GEOSPreparedContains_r( geosctxt, polygon->
preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) != 1 )
804 else if ( mNextPart )
806 return mNextPart->crossesBoundary( polygon );
809 catch ( QgsGeosException &e )
811 qWarning(
"GEOS exception: %s", e.what() );
822 const double totalCost = polygonIntersectionCostForParts( polygon );
823 const int n = partCount();
824 return std::ceil( totalCost / n );
830 if ( !mOuterBoundsGeos && !
mGeos )
833 if ( !polygon->
mGeos )
839 if ( GEOSPreparedIntersects_r( geosctxt, polygon->
preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) == 1 )
844 catch ( QgsGeosException &e )
846 qWarning(
"GEOS exception: %s", e.what() );
852 return mNextPart->intersectsWithPolygon( polygon );
860double LabelPosition::polygonIntersectionCostForParts(
PointSet *polygon )
const
863 if ( !mOuterBoundsGeos && !
mGeos )
866 if ( !polygon->
mGeos )
873 if ( GEOSPreparedIntersects_r( geosctxt, polygon->
preparedGeom(), mOuterBoundsGeos ? mOuterBoundsGeos.get() :
mGeos ) == 1 )
881 for (
int i = 0; i < 4; ++i )
886 for (
int a = 0; a < 2; ++a )
890 px = (
x[i] +
x[( i + 1 ) % 4] ) / 2.0;
891 py = (
y[i] +
y[( i + 1 ) % 4] ) / 2.0;
895 px = (
x[0] +
x[2] ) / 2.0;
896 py = (
y[0] +
y[2] ) / 2.0;
903 catch ( QgsGeosException &e )
905 qWarning(
"GEOS exception: %s", e.what() );
914 cost += mNextPart->polygonIntersectionCostForParts( polygon );
922 double angleDiff = 0.0;
923 double angleLast = 0.0;
929 double diff = std::fabs( tmp->
angleRadians() - angleLast );
930 if ( diff > 2 * M_PI )
932 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.
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.
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.
~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