51using namespace Qt::StringLiterals;
65 for (
int i = 0; i <
mHoles.count(); i++ )
67 mHoles.at( i )->holeOf =
this;
74 , mTotalRepeats( other.mTotalRepeats )
75 , mCachedMaxLineCandidates( other.mCachedMaxLineCandidates )
76 , mCachedMaxPolygonCandidates( other.mCachedMaxPolygonCandidates )
80 mHoles << new FeaturePart( *hole );
81 mHoles.last()->holeOf = this;
95 const GEOSCoordSequence *coordSeq =
nullptr;
98 type = GEOSGeomTypeId_r( geosctxt, geom );
100 if (
type == GEOS_POLYGON )
102 if ( GEOSGetNumInteriorRings_r( geosctxt, geom ) > 0 )
104 int numHoles = GEOSGetNumInteriorRings_r( geosctxt, geom );
106 for (
int i = 0; i < numHoles; ++i )
108 const GEOSGeometry *interior = GEOSGetInteriorRingN_r( geosctxt, geom, i );
120 geom = GEOSGetExteriorRing_r( geosctxt, geom );
129 nbPoints = GEOSGetNumCoordinates_r( geosctxt, geom );
130 coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, geom );
133 xmin =
ymin = std::numeric_limits<double>::max();
134 xmax =
ymax = std::numeric_limits<double>::lowest();
141#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 10 )
142 GEOSCoordSeq_copyToArrays_r( geosctxt, coordSeq,
x.data(),
y.data(),
nullptr,
nullptr );
143 auto xminmax = std::minmax_element(
x.begin(),
x.end() );
144 xmin = *xminmax.first;
145 xmax = *xminmax.second;
146 auto yminmax = std::minmax_element(
y.begin(),
y.end() );
147 ymin = *yminmax.first;
148 ymax = *yminmax.second;
150 for (
int i = 0; i <
nbPoints; ++i )
152 GEOSCoordSeq_getXY_r( geosctxt, coordSeq, i, &
x[i], &
y[i] );
175 return mLF->subPartId();
180 return mLF->layer()->maximumPointLabelCandidates();
185 if ( mCachedMaxLineCandidates > 0 )
186 return mCachedMaxLineCandidates;
188 const double l =
length();
191 const std::size_t candidatesForLineLength =
static_cast< std::size_t
>( std::ceil(
mLF->layer()->mPal->maximumLineCandidatesPerMapUnit() * l ) );
192 const std::size_t maxForLayer =
mLF->layer()->maximumLineLabelCandidates();
193 if ( maxForLayer == 0 )
194 mCachedMaxLineCandidates = candidatesForLineLength;
196 mCachedMaxLineCandidates = std::min( candidatesForLineLength, maxForLayer );
200 mCachedMaxLineCandidates = 1;
202 return mCachedMaxLineCandidates;
207 if ( mCachedMaxPolygonCandidates > 0 )
208 return mCachedMaxPolygonCandidates;
210 const double a =
area();
213 const std::size_t candidatesForArea =
static_cast< std::size_t
>( std::ceil(
mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() * a ) );
214 const std::size_t maxForLayer =
mLF->layer()->maximumPolygonLabelCandidates();
215 if ( maxForLayer == 0 )
216 mCachedMaxPolygonCandidates = candidatesForArea;
218 mCachedMaxPolygonCandidates = std::min( candidatesForArea, maxForLayer );
222 mCachedMaxPolygonCandidates = 1;
224 return mCachedMaxPolygonCandidates;
239 int connectedFeatureId =
mLF->layer()->connectedFeatureId(
mLF->id() );
240 return connectedFeatureId >= 0 && connectedFeatureId ==
mLF->layer()->connectedFeatureId( part->
featureId() );
246 qreal quadOffsetX = quadOffset.x(), quadOffsetY = quadOffset.y();
248 if ( quadOffsetX < 0 )
250 if ( quadOffsetY < 0 )
254 else if ( quadOffsetY > 0 )
263 else if ( quadOffsetX > 0 )
265 if ( quadOffsetY < 0 )
269 else if ( quadOffsetY > 0 )
280 if ( quadOffsetY < 0 )
284 else if ( quadOffsetY > 0 )
297 return mTotalRepeats;
311 double cost = 0.00005;
312 int id = lPos.size();
314 double xdiff = -labelW / 2.0;
315 double ydiff = -labelH / 2.0;
319 double lx =
x + xdiff;
320 double ly =
y + ydiff;
322 if (
mLF->permissibleZonePrepared() )
340 double cost = 0.0001;
341 int id = lPos.size();
343 double xdiff = -labelW / 2.0;
344 double ydiff = -labelH / 2.0;
350 xdiff += labelW / 2.0 *
mLF->quadOffset().x();
354 ydiff += labelH / 2.0 *
mLF->quadOffset().y();
357 if ( !
mLF->hasFixedPosition() )
361 double xd = xdiff * std::cos( angle ) - ydiff * std::sin( angle );
362 double yd = xdiff * std::sin( angle ) + ydiff * std::cos( angle );
374 ydiff +=
mLF->quadOffset().y() *
mLF->distLabel();
378 xdiff +=
mLF->quadOffset().x() *
mLF->distLabel();
382 xdiff +=
mLF->quadOffset().x() * M_SQRT1_2 *
mLF->distLabel();
383 ydiff +=
mLF->quadOffset().y() * M_SQRT1_2 *
mLF->distLabel();
390 xdiff +=
mLF->positionOffset().x();
394 ydiff +=
mLF->positionOffset().y();
398 double lx =
x + xdiff;
399 double ly =
y + ydiff;
401 if (
mLF->permissibleZonePrepared() )
422 const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, pointGeom.get() );
423 unsigned int nPoints = 0;
424 GEOSCoordSeq_getSize_r( geosctxt, coordSeq, &nPoints );
427 GEOSCoordSeq_getXY_r( geosctxt, coordSeq, 0, &px, &py );
434 catch ( QgsGeosException &e )
436 qWarning(
"GEOS exception: %s", e.what() );
453 double distanceToLabel,
455 double symbolWidthOffset,
456 double symbolHeightOffset,
469 deltaX = -labelWidth + visualMargin.
right() - symbolWidthOffset;
470 deltaY = -visualMargin.
bottom() + symbolHeightOffset;
476 deltaX = -labelWidth / 4.0 - visualMargin.
left();
477 deltaY = -visualMargin.
bottom() + symbolHeightOffset;
483 deltaX = -labelWidth / 2.0;
484 deltaY = -visualMargin.
bottom() + symbolHeightOffset;
490 deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.
right();
491 deltaY = -visualMargin.
bottom() + symbolHeightOffset;
497 deltaX = -visualMargin.
left() + symbolWidthOffset;
498 deltaY = -visualMargin.
bottom() + symbolHeightOffset;
504 deltaX = -labelWidth + visualMargin.
right() - symbolWidthOffset;
505 deltaY = -labelHeight / 2.0;
511 deltaX = -visualMargin.
left() + symbolWidthOffset;
512 deltaY = -labelHeight / 2.0;
518 deltaX = -labelWidth + visualMargin.
right() - symbolWidthOffset;
519 deltaY = -labelHeight + visualMargin.
top() - symbolHeightOffset;
525 deltaX = -labelWidth / 4.0 - visualMargin.
left();
526 deltaY = -labelHeight + visualMargin.
top() - symbolHeightOffset;
532 deltaX = -labelWidth / 2.0;
533 deltaY = -labelHeight + visualMargin.
top() - symbolHeightOffset;
539 deltaX = -labelWidth * 3.0 / 4.0 + visualMargin.
right();
540 deltaY = -labelHeight + visualMargin.
top() - symbolHeightOffset;
546 deltaX = -visualMargin.
left() + symbolWidthOffset;
547 deltaY = -labelHeight + visualMargin.
top() - symbolHeightOffset;
554 deltaX = -labelWidth / 2.0;
555 deltaY = -labelHeight / 2.0;
561 QTransform transformRotation;
562 transformRotation.rotate( angle * 180 / M_PI );
563 transformRotation.map( deltaX, deltaY, &deltaX, &deltaY );
566 double referenceX = std::cos( alpha ) * distanceToLabel + x;
567 double referenceY = std::sin( alpha ) * distanceToLabel + y;
569 labelX = referenceX + deltaX;
570 labelY = referenceY + deltaY;
575 const QVector< Qgis::LabelPredefinedPointPosition > positions =
mLF->predefinedPositionOrder();
579 const double maximumDistanceToLabel =
mLF->maximumDistance();
583 double symbolWidthOffset { 0 };
584 double symbolHeightOffset { 0 };
589 if (
mLF->feature().geometry().constParts().hasNext() )
592 symbolWidthOffset = std::max( (
mLF->symbolSize().width() - geom.
boundingBox().
width() ) / 2.0, 0.0 );
593 symbolHeightOffset = std::max( (
mLF->symbolSize().height() - geom.
boundingBox().
height() ) / 2.0, 0.0 );
597 symbolWidthOffset =
mLF->symbolSize().width() / 2.0;
598 symbolHeightOffset =
mLF->symbolSize().height() / 2.0;
602 int candidatesPerPosition = 1;
603 double distanceStep = 0;
604 if ( maximumDistanceToLabel > distanceToLabel && !
qgsDoubleNear( maximumDistanceToLabel, 0 ) )
608 const double rayLength = maximumDistanceToLabel - distanceToLabel;
611 candidatesPerPosition = std::max( 2,
static_cast< int >( std::ceil(
mLF->layer()->mPal->maximumLineCandidatesPerMapUnit() * 1.5 * rayLength ) ) );
612 distanceStep = rayLength / ( candidatesPerPosition - 1 );
615 double cost = 0.0001;
616 std::size_t i = lPos.size();
619 const std::size_t maxNumberCandidates =
mLF->layer()->maximumPointLabelCandidates() * candidatesPerPosition;
620 std::size_t created = 0;
623 [
this,
x,
y, labelWidth, labelHeight, angle, visualMargin, symbolWidthOffset, symbolHeightOffset, &created, &cost, &lPos, &i, maxNumberCandidates](
Qgis::LabelPredefinedPointPosition position,
double distance )
629 createCandidateAtOrderedPositionOverPoint( labelX, labelY, quadrant,
x,
y, labelWidth, labelHeight, position, distance, visualMargin, symbolWidthOffset, symbolHeightOffset, angle );
638 if ( maxNumberCandidates > 0 && created >= maxNumberCandidates )
644 switch ( prioritization )
652 double currentDistance = distanceToLabel;
653 for (
int distanceIndex = 0; distanceIndex < candidatesPerPosition; ++distanceIndex, currentDistance += distanceStep )
655 if ( !addCandidate( position, currentDistance ) )
664 double currentDistance = distanceToLabel;
665 for (
int distanceIndex = 0; distanceIndex < candidatesPerPosition; ++distanceIndex, currentDistance += distanceStep )
669 if ( !addCandidate( position, currentDistance ) )
684 const double maximumDistanceToLabel =
mLF->maximumDistance();
688 QTransform transformRotation;
689 transformRotation.rotate( angle * 180 / M_PI );
691 int rayCount =
static_cast< int >(
mLF->layer()->maximumPointLabelCandidates() );
695 int candidatesPerRay = 0;
696 double rayStepDelta = 0;
697 if ( maximumDistanceToLabel > distanceToLabel && !
qgsDoubleNear( maximumDistanceToLabel, 0 ) )
701 const double rayLength = maximumDistanceToLabel - distanceToLabel;
704 candidatesPerRay = std::max( 2,
static_cast< int >( std::ceil(
mLF->layer()->mPal->maximumLineCandidatesPerMapUnit() * 1.5 * rayLength ) ) );
705 rayStepDelta = rayLength / ( candidatesPerRay - 1 );
709 candidatesPerRay = 1;
712 int id =
static_cast< int >( lPos.size() );
714 const double candidateAngleIncrement = 2 * M_PI /
static_cast< double >( rayCount );
717 constexpr double a90 = M_PI_2;
718 constexpr double a180 = M_PI;
719 constexpr double a270 = a180 + a90;
720 constexpr double a360 = 2 * M_PI;
722 double gamma1, gamma2;
724 if ( distanceToLabel > 0 )
726 gamma1 = std::atan2( labelHeight / 2, distanceToLabel + labelWidth / 2 );
727 gamma2 = std::atan2( labelWidth / 2, distanceToLabel + labelHeight / 2 );
731 gamma1 = gamma2 = a90 / 3.0;
734 if ( gamma1 > a90 / 3.0 )
737 if ( gamma2 > a90 / 3.0 )
740 std::size_t numberCandidatesGenerated = 0;
742 double angleToCandidate = M_PI_4;
744 int integerRayCost = 0;
745 int integerRayCostIncrement = 2;
747 for (
int rayIndex = 0; rayIndex < rayCount; ++rayIndex, angleToCandidate += candidateAngleIncrement )
752 if ( angleToCandidate > a360 )
753 angleToCandidate -= a360;
755 double rayDistance = distanceToLabel;
757 constexpr double RAY_ANGLE_COST_FACTOR = 0.0020;
761 const double scaledRayAngleCost = RAY_ANGLE_COST_FACTOR *
static_cast< double >( integerRayCost ) /
static_cast< double >( rayCount - 1 );
763 for (
int j = 0; j < candidatesPerRay; ++j, rayDistance += rayStepDelta )
767 if ( angleToCandidate < gamma1 || angleToCandidate > a360 - gamma1 )
769 deltaX = rayDistance;
770 double iota = ( angleToCandidate + gamma1 );
771 if ( iota > a360 - gamma1 )
774 deltaY = -labelHeight + labelHeight * iota / ( 2 * gamma1 );
778 else if ( angleToCandidate < a90 - gamma2 )
780 deltaX = rayDistance * std::cos( angleToCandidate );
781 deltaY = rayDistance * std::sin( angleToCandidate );
784 else if ( angleToCandidate < a90 + gamma2 )
786 deltaX = -labelWidth * ( angleToCandidate - a90 + gamma2 ) / ( 2 * gamma2 );
787 deltaY = rayDistance;
790 else if ( angleToCandidate < a180 - gamma1 )
792 deltaX = rayDistance * std::cos( angleToCandidate ) - labelWidth;
793 deltaY = rayDistance * std::sin( angleToCandidate );
796 else if ( angleToCandidate < a180 + gamma1 )
798 deltaX = -rayDistance - labelWidth;
799 deltaY = -( angleToCandidate - a180 + gamma1 ) * labelHeight / ( 2 * gamma1 );
802 else if ( angleToCandidate < a270 - gamma2 )
804 deltaX = rayDistance * std::cos( angleToCandidate ) - labelWidth;
805 deltaY = rayDistance * std::sin( angleToCandidate ) - labelHeight;
808 else if ( angleToCandidate < a270 + gamma2 )
810 deltaY = -rayDistance - labelHeight;
811 deltaX = -labelWidth + ( angleToCandidate - a270 + gamma2 ) * labelWidth / ( 2 * gamma2 );
814 else if ( angleToCandidate < a360 )
816 deltaX = rayDistance * std::cos( angleToCandidate );
817 deltaY = rayDistance * std::sin( angleToCandidate ) - labelHeight;
821 transformRotation.map( deltaX, deltaY, &deltaX, &deltaY );
823 double labelX =
x + deltaX;
824 double labelY =
y + deltaY;
831 cost = 0.0001 + scaledRayAngleCost;
838 cost += j * RAY_ANGLE_COST_FACTOR + RAY_ANGLE_COST_FACTOR / rayCount;
841 if (
mLF->permissibleZonePrepared() )
851 numberCandidatesGenerated++;
854 integerRayCost += integerRayCostIncrement;
856 if ( integerRayCost ==
static_cast< int >( rayCount ) )
858 integerRayCost =
static_cast< int >( rayCount ) - 1;
859 integerRayCostIncrement = -2;
861 else if ( integerRayCost >
static_cast< int >( rayCount ) )
863 integerRayCost =
static_cast< int >( rayCount ) - 2;
864 integerRayCostIncrement = -2;
868 return numberCandidatesGenerated;
875 double shapeLength = mapShape->
length();
886 std::size_t candidates = 0;
892 if ( candidates < candidateTargetCount )
907 std::vector< double > &
x = line->
x;
908 std::vector< double > &
y = line->
y;
910 std::vector< double > segmentLengths(
nbPoints - 1 );
911 std::vector< double > distanceToSegment(
nbPoints );
913 double totalLineLength = 0.0;
914 for (
int i = 0; i < line->
nbPoints - 1; i++ )
917 distanceToSegment[i] = 0;
919 distanceToSegment[i] = distanceToSegment[i - 1] + segmentLengths[i - 1];
922 totalLineLength += segmentLengths[i];
924 distanceToSegment[line->
nbPoints - 1] = totalLineLength;
927 double lineStepDistance = 0;
929 const double lineAnchorPoint = totalLineLength *
mLF->lineAnchorPercent();
930 double currentDistanceAlongLine = lineStepDistance;
931 switch (
mLF->lineAnchorType() )
934 lineStepDistance = totalLineLength / ( candidateTargetCount + 1 );
938 currentDistanceAlongLine = lineAnchorPoint;
939 lineStepDistance = -1;
945 const double cosAngle = std::cos( angle );
946 const double sinAngle = std::sin( angle );
947 const double halfHeightX = ( labelHeight / 2.0 ) * sinAngle;
948 const double halfHeightY = ( labelHeight / 2.0 ) * cosAngle;
950 double candidateCenterX, candidateCenterY;
952 while ( currentDistanceAlongLine <= totalLineLength )
954 if (
pal->isCanceled() )
959 line->
getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine, &candidateCenterX, &candidateCenterY );
962 double cost = totalLineLength > 0 ? std::fabs( lineAnchorPoint - currentDistanceAlongLine ) / totalLineLength : 0;
970 labelX = candidateCenterX + halfHeightX;
971 labelY = candidateCenterY - halfHeightY;
974 labelX = candidateCenterX - ( labelWidth / 2.0 ) * cosAngle + halfHeightX;
975 labelY = candidateCenterY - ( labelWidth / 2.0 ) * sinAngle - halfHeightY;
978 labelX = candidateCenterX - labelWidth * cosAngle + halfHeightX;
979 labelY = candidateCenterY - labelWidth * sinAngle - halfHeightY;
989 currentDistanceAlongLine += lineStepDistance;
993 if ( lineStepDistance < 0 )
1010 QVector< int > extremeAngleNodes;
1013 std::vector< double > &
x = line->
x;
1014 std::vector< double > &
y = line->
y;
1018 for (
int i = 1; i <= numberNodes - ( closedLine ? 1 : 2 ); ++i )
1020 double x1 =
x[i - 1];
1022 double x3 =
x[i == numberNodes - 1 ? 1 : i + 1];
1023 double y1 =
y[i - 1];
1025 double y3 =
y[i == numberNodes - 1 ? 1 : i + 1];
1030 double vertexAngle = M_PI - ( std::atan2( y3 - y2, x3 - x2 ) - std::atan2( y2 - y1, x2 - x1 ) );
1034 if ( vertexAngle < M_PI * 135.0 / 180.0 || vertexAngle > M_PI * 225.0 / 180.0 )
1035 extremeAngleNodes << i;
1037 extremeAngleNodes << numberNodes - 1;
1039 if ( extremeAngleNodes.isEmpty() )
1046 std::vector< double > segmentLengths( numberNodes - 1 );
1047 std::vector< double > distanceToSegment( numberNodes );
1048 double totalLineLength = 0.0;
1049 QVector< double > straightSegmentLengths;
1050 QVector< double > straightSegmentAngles;
1051 straightSegmentLengths.reserve( extremeAngleNodes.size() + 1 );
1052 straightSegmentAngles.reserve( extremeAngleNodes.size() + 1 );
1053 double currentStraightSegmentLength = 0;
1054 double longestSegmentLength = 0;
1055 double segmentStartX =
x[0];
1056 double segmentStartY =
y[0];
1057 for (
int i = 0; i < numberNodes - 1; i++ )
1060 distanceToSegment[i] = 0;
1062 distanceToSegment[i] = distanceToSegment[i - 1] + segmentLengths[i - 1];
1065 totalLineLength += segmentLengths[i];
1066 if ( extremeAngleNodes.contains( i ) )
1069 straightSegmentLengths << currentStraightSegmentLength;
1071 longestSegmentLength = std::max( longestSegmentLength, currentStraightSegmentLength );
1072 currentStraightSegmentLength = 0;
1073 segmentStartX =
x[i];
1074 segmentStartY =
y[i];
1076 currentStraightSegmentLength += segmentLengths[i];
1078 distanceToSegment[line->
nbPoints - 1] = totalLineLength;
1079 straightSegmentLengths << currentStraightSegmentLength;
1081 longestSegmentLength = std::max( longestSegmentLength, currentStraightSegmentLength );
1082 const double lineAnchorPoint = totalLineLength *
mLF->lineAnchorPercent();
1084 if ( totalLineLength < labelWidth )
1092 double lineStepDistance = ( totalLineLength - labelWidth );
1093 lineStepDistance = std::min( std::min( labelHeight, labelWidth ), lineStepDistance / candidateTargetCount );
1095 double distanceToEndOfSegment = 0.0;
1096 int lastNodeInSegment = 0;
1098 for (
int i = 0; i < straightSegmentLengths.count(); ++i )
1100 currentStraightSegmentLength = straightSegmentLengths.at( i );
1101 double currentSegmentAngle = straightSegmentAngles.at( i );
1102 lastNodeInSegment = extremeAngleNodes.at( i );
1103 double distanceToStartOfSegment = distanceToEndOfSegment;
1104 distanceToEndOfSegment = distanceToSegment[lastNodeInSegment];
1105 double distanceToCenterOfSegment = 0.5 * ( distanceToEndOfSegment + distanceToStartOfSegment );
1107 if ( currentStraightSegmentLength < labelWidth )
1111 double currentDistanceAlongLine = distanceToStartOfSegment;
1112 double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
1113 double candidateLength = 0.0;
1119 double segmentCost = 1.0 - ( distanceToEndOfSegment - distanceToStartOfSegment ) / longestSegmentLength;
1120 double segmentAngleCost = 1 - std::fabs( std::fmod( currentSegmentAngle, M_PI ) - M_PI_2 ) / M_PI_2;
1122 while ( currentDistanceAlongLine + labelWidth < distanceToEndOfSegment )
1124 if (
pal->isCanceled() )
1130 line->
getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine, &candidateStartX, &candidateStartY );
1131 line->
getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
1139 cost = candidateLength / labelWidth;
1145 cost = ( 1 - cost ) / 100;
1148 const double labelCenter = currentDistanceAlongLine + labelWidth / 2.0;
1149 double labelTextAnchor = 0;
1150 switch ( textPoint )
1153 labelTextAnchor = currentDistanceAlongLine;
1156 labelTextAnchor = currentDistanceAlongLine + labelWidth / 2.0;
1159 labelTextAnchor = currentDistanceAlongLine + labelWidth;
1166 const bool placementIsFlexible =
mLF->lineAnchorPercent() > 0.1 &&
mLF->lineAnchorPercent() < 0.9;
1168 if ( placementIsFlexible )
1171 double costCenter = 2 * std::fabs( labelCenter - distanceToCenterOfSegment ) / ( distanceToEndOfSegment - distanceToStartOfSegment );
1172 cost += costCenter * 0.0005;
1180 double costLineCenter = 2 * std::fabs( labelTextAnchor - lineAnchorPoint ) / totalLineLength;
1192 if ( labelTextAnchor > lineAnchorPoint )
1194 constexpr double PLACEMENT_TIEBREAKER = 0.000001234;
1195 cost += PLACEMENT_TIEBREAKER;
1198 cost += costLineCenter * 0.0005;
1201 if ( placementIsFlexible )
1203 cost += segmentCost * 0.0005;
1204 cost += segmentAngleCost * 0.0001;
1212 angle = std::atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
1216 beta = angle + M_PI_2;
1221 bool isRightToLeft = ( angle > M_PI_2 || angle <= -M_PI_2 );
1229 if ( !
mLF->permissibleZonePrepared()
1231 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
1233 const double candidateCost = cost + ( reversed ? 0 : 0.001 );
1235 std::make_unique< LabelPosition >(
1237 candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ),
1238 candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ),
1252 if ( !
mLF->permissibleZonePrepared()
1254 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX + std::cos( beta ) * distanceLineToLabel, candidateStartY + std::sin( beta ) * distanceLineToLabel, labelWidth, labelHeight, angle ) )
1256 const double candidateCost = cost + ( !reversed ? 0 : 0.001 );
1258 std::make_unique< LabelPosition >(
1260 candidateStartX + std::cos( beta ) * distanceLineToLabel,
1261 candidateStartY + std::sin( beta ) * distanceLineToLabel,
1275 if ( !
mLF->permissibleZonePrepared()
1277 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) )
1279 const double candidateCost = cost + 0.002;
1281 std::make_unique< LabelPosition >(
1283 candidateStartX - labelHeight * std::cos( beta ) / 2,
1284 candidateStartY - labelHeight * std::sin( beta ) / 2,
1310 currentDistanceAlongLine += lineStepDistance;
1333 std::vector< double > &
x = line->
x;
1334 std::vector< double > &
y = line->
y;
1336 std::vector< double > segmentLengths(
nbPoints - 1 );
1337 std::vector< double > distanceToSegment(
nbPoints );
1339 double totalLineLength = 0.0;
1340 for (
int i = 0; i < line->
nbPoints - 1; i++ )
1343 distanceToSegment[i] = 0;
1345 distanceToSegment[i] = distanceToSegment[i - 1] + segmentLengths[i - 1];
1348 totalLineLength += segmentLengths[i];
1350 distanceToSegment[line->
nbPoints - 1] = totalLineLength;
1352 double lineStepDistance = ( totalLineLength - labelWidth );
1353 double currentDistanceAlongLine = 0;
1359 if ( totalLineLength > labelWidth )
1361 lineStepDistance = std::min( std::min( labelHeight, labelWidth ), lineStepDistance / candidateTargetCount );
1365 currentDistanceAlongLine = -( labelWidth - totalLineLength ) / 2.0;
1366 lineStepDistance = -1;
1367 totalLineLength = labelWidth;
1372 currentDistanceAlongLine = std::numeric_limits< double >::max();
1375 const double lineAnchorPoint = totalLineLength * std::min( 0.99,
mLF->lineAnchorPercent() );
1377 switch (
mLF->lineAnchorType() )
1383 switch ( textPoint )
1386 currentDistanceAlongLine = std::min( lineAnchorPoint, totalLineLength * 0.99 - labelWidth );
1389 currentDistanceAlongLine = std::min( lineAnchorPoint - labelWidth / 2, totalLineLength * 0.99 - labelWidth );
1392 currentDistanceAlongLine = std::min( lineAnchorPoint - labelWidth, totalLineLength * 0.99 - labelWidth );
1398 lineStepDistance = -1;
1402 double candidateLength;
1404 double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
1408 if (
pal->isCanceled() )
1414 line->
getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine, &candidateStartX, &candidateStartY );
1415 line->
getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
1417 if ( currentDistanceAlongLine < 0 )
1427 cost = candidateLength / labelWidth;
1433 cost = ( 1 - cost ) / 100;
1437 double textAnchorPoint = 0;
1438 switch ( textPoint )
1441 textAnchorPoint = currentDistanceAlongLine;
1444 textAnchorPoint = currentDistanceAlongLine + labelWidth / 2;
1447 textAnchorPoint = currentDistanceAlongLine + labelWidth;
1453 double costCenter = totalLineLength > 0 ? std::fabs( lineAnchorPoint - textAnchorPoint ) / totalLineLength : 0;
1454 cost += costCenter / 1000;
1455 cost += initialCost;
1462 angle = std::atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
1466 beta = angle + M_PI_2;
1471 bool isRightToLeft = ( angle > M_PI_2 || angle <= -M_PI_2 );
1479 if ( !
mLF->permissibleZonePrepared()
1481 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX + std::cos( beta ) * distanceLineToLabel, candidateStartY + std::sin( beta ) * distanceLineToLabel, labelWidth, labelHeight, angle ) )
1483 const double candidateCost = cost + ( !reversed ? 0 : 0.001 );
1485 std::make_unique< LabelPosition >(
1487 candidateStartX + std::cos( beta ) * distanceLineToLabel,
1488 candidateStartY + std::sin( beta ) * distanceLineToLabel,
1502 if ( !
mLF->permissibleZonePrepared()
1504 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
1506 const double candidateCost = cost + ( !reversed ? 0.001 : 0 );
1508 std::make_unique< LabelPosition >(
1510 candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ),
1511 candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ),
1525 if ( !
mLF->permissibleZonePrepared()
1527 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) )
1529 const double candidateCost = cost + 0.002;
1531 std::make_unique< LabelPosition >(
1533 candidateStartX - labelHeight * std::cos( beta ) / 2,
1534 candidateStartY - labelHeight * std::sin( beta ) / 2,
1560 currentDistanceAlongLine += lineStepDistance;
1564 if ( lineStepDistance < 0 )
1573 const std::vector< double> &pathDistances,
1575 const double offsetAlongLine,
1576 bool &labeledLineSegmentIsRightToLeft,
1577 bool applyAngleConstraints,
1579 double additionalCharacterSpacing,
1580 double additionalWordSpacing
1584 Q_ASSERT( metrics );
1586 const double maximumCharacterAngleInside = applyAngleConstraints ? std::fabs( qgis::down_cast< QgsTextLabelFeature *>(
mLF )->maximumCharacterAngleInside() ) : -1;
1587 const double maximumCharacterAngleOutside = applyAngleConstraints ? std::fabs( qgis::down_cast< QgsTextLabelFeature *>(
mLF )->maximumCharacterAngleOutside() ) : -1;
1589 std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > placement(
1591 generateCurvedTextPlacement( *metrics, mapShape->
x.data(), mapShape->
y.data(), mapShape->
nbPoints, pathDistances, offsetAlongLine, direction, maximumCharacterAngleInside, maximumCharacterAngleOutside, flags, additionalCharacterSpacing, additionalWordSpacing )
1596 if ( placement->graphemePlacement.empty() )
1599 auto it = placement->graphemePlacement.constBegin();
1602 firstPosition->setUpsideDownCharCount( placement->upsideDownCharCount );
1603 firstPosition->setPartId( it->graphemeIndex );
1607 bool skipWhitespace =
false;
1608 switch (
mLF->whitespaceCollisionHandling() )
1614 skipWhitespace =
true;
1618 while ( it != placement->graphemePlacement.constEnd() )
1620 if ( skipWhitespace && it->isWhitespace )
1627 position->setPartId( it->graphemeIndex );
1630 previousPosition->
setNextPart( std::move( position ) );
1631 previousPosition = nextPosition;
1635 return firstPosition;
1647 const int characterCount = li->
count();
1648 if ( characterCount == 0 )
1651 switch (
mLF->curvedLabelMode() )
1666 const int characterCount = li->
count();
1669 double totalCharacterWidth = 0;
1671 for (
int i = 0; i < characterCount; ++i )
1674 if ( stretchWordSpacingToFit && li->
grapheme( i ) ==
' ' )
1679 if ( spaceCount == 0 )
1682 stretchWordSpacingToFit =
false;
1686 const bool usingStretchToFitMode = stretchCharacterSpacingToFit || stretchWordSpacingToFit;
1691 std::unique_ptr< PointSet > expanded;
1692 double shapeLength = mapShape->
length();
1697 allowOverrun =
false;
1700 if ( !usingStretchToFitMode )
1706 switch (
mLF->lineAnchorType() )
1709 overrun = std::min(
mLF->overrunDistance(), totalCharacterWidth * 0.95 );
1714 overrun = std::max(
mLF->overrunDistance(), totalCharacterWidth * 1.05 );
1718 if ( totalCharacterWidth > shapeLength )
1720 if ( !allowOverrun || shapeLength < totalCharacterWidth - 2 * overrun )
1731 if ( allowOverrun && overrun > 0 )
1734 expanded = mapShape->
clone();
1735 expanded->extendLineByDistance( overrun, overrun,
mLF->overrunSmoothDistance() );
1736 mapShape = expanded.get();
1737 shapeLength += 2 * overrun;
1746 std::unique_ptr< PointSet > mapShapeOffsetPositive;
1747 bool positiveShapeHasNegativeDistance =
false;
1748 std::unique_ptr< PointSet > mapShapeOffsetNegative;
1749 bool negativeShapeHasNegativeDistance =
false;
1750 if ( hasAboveBelowLinePlacement && !
qgsDoubleNear( offsetDistance, 0 ) )
1754 mapShapeOffsetPositive = mapShape->
clone();
1756 mapShapeOffsetNegative = mapShape->
clone();
1759 if ( mapShapeOffsetPositive )
1760 mapShapeOffsetPositive->offsetCurveByDistance( offsetDistance );
1761 positiveShapeHasNegativeDistance = offsetDistance < 0;
1762 if ( mapShapeOffsetNegative )
1763 mapShapeOffsetNegative->offsetCurveByDistance( offsetDistance * -1 );
1764 negativeShapeHasNegativeDistance = offsetDistance > 0;
1779 if ( mapShapeOffsetPositive )
1780 mapShapeOffsetPositive->offsetCurveByDistance( offsetDistance * -1 );
1781 positiveShapeHasNegativeDistance = offsetDistance > 0;
1782 if ( mapShapeOffsetNegative )
1783 mapShapeOffsetNegative->offsetCurveByDistance( offsetDistance );
1784 negativeShapeHasNegativeDistance = offsetDistance < 0;
1790 std::vector< std::unique_ptr< LabelPosition >> positions;
1791 std::unique_ptr< LabelPosition > backupPlacement;
1794 PointSet *currentMapShape =
nullptr;
1797 currentMapShape = mapShapeOffsetPositive.get();
1801 currentMapShape = mapShape;
1805 currentMapShape = mapShapeOffsetNegative.get();
1807 if ( !currentMapShape )
1811 const auto [pathDistances, totalDistance] = currentMapShape->
edgeDistances();
1815 double lineAnchorPoint = 0;
1816 if ( !usingStretchToFitMode )
1818 if ( originalPoint )
1823 lineAnchorPoint = currentMapShape->
lineLocatePoint( originalPoint.get() );
1827 lineAnchorPoint = totalDistance *
mLF->lineAnchorPercent();
1829 lineAnchorPoint = totalDistance - lineAnchorPoint;
1833 if (
pal->isCanceled() )
1837 double delta = std::max( li->
characterHeight( 0 ) / 6, totalDistance / candidateTargetCount );
1840 double distanceAlongLineToStartCandidate = 0;
1841 bool singleCandidateOnly =
false;
1842 double additionalCharacterSpacing = 0.0;
1843 double additionalWordSpacing = 0.0;
1844 if ( usingStretchToFitMode )
1847 double extraSpace = totalDistance - totalCharacterWidth;
1852 if ( extraSpace > 0 )
1853 extraSpace *= 0.995;
1855 extraSpace *= 1.005;
1857 if ( stretchWordSpacingToFit )
1859 if ( spaceCount > 0 )
1860 additionalWordSpacing = extraSpace / spaceCount;
1866 if ( characterCount > 1 )
1867 additionalCharacterSpacing = extraSpace / ( characterCount - 1 );
1871 distanceAlongLineToStartCandidate = 0;
1872 delta = totalDistance + 1.0;
1873 singleCandidateOnly =
true;
1877 switch (
mLF->lineAnchorType() )
1883 switch ( textPoint )
1886 distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint, 0.0, totalDistance * 0.999 );
1889 distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint -
getLabelWidth() / 2, 0.0, totalDistance * 0.999 -
getLabelWidth() / 2 );
1892 distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint -
getLabelWidth(), 0.0, totalDistance * 0.999 -
getLabelWidth() );
1898 singleCandidateOnly =
true;
1903 bool hasTestedFirstPlacement =
false;
1904 for ( ; distanceAlongLineToStartCandidate <= totalDistance; distanceAlongLineToStartCandidate += delta )
1906 if ( singleCandidateOnly && hasTestedFirstPlacement )
1909 if (
pal->isCanceled() )
1912 hasTestedFirstPlacement =
true;
1914 bool labeledLineSegmentIsRightToLeft =
false;
1921 std::unique_ptr< LabelPosition > labelPosition
1922 =
curvedPlacementAtOffset( currentMapShape, pathDistances, direction, distanceAlongLineToStartCandidate, labeledLineSegmentIsRightToLeft, !singleCandidateOnly, curvedTextFlags, additionalCharacterSpacing, additionalWordSpacing );
1923 if ( !labelPosition )
1929 bool isBackupPlacementOnly =
false;
1932 if ( ( currentMapShape == mapShapeOffsetPositive.get() && positiveShapeHasNegativeDistance ) || ( currentMapShape == mapShapeOffsetNegative.get() && negativeShapeHasNegativeDistance ) )
1934 labeledLineSegmentIsRightToLeft = !labeledLineSegmentIsRightToLeft;
1940 isBackupPlacementOnly =
true;
1947 isBackupPlacementOnly =
true;
1953 backupPlacement.reset();
1956 const double angleDiff = labelPosition->angleDifferential();
1957 const double angleDiffAvg = characterCount > 1 ? ( angleDiff / ( characterCount - 1 ) ) : 0;
1961 const bool anchorIsFlexiblePlacement = !singleCandidateOnly &&
mLF->lineAnchorPercent() > 0.1 &&
mLF->lineAnchorPercent() < 0.9;
1962 double cost = angleDiffAvg / 100;
1963 if ( cost < 0.0001 )
1967 if ( !usingStretchToFitMode )
1970 double labelTextAnchor = 0;
1971 switch ( textPoint )
1974 labelTextAnchor = distanceAlongLineToStartCandidate;
1977 labelTextAnchor = distanceAlongLineToStartCandidate +
getLabelWidth() / 2;
1980 labelTextAnchor = distanceAlongLineToStartCandidate +
getLabelWidth();
1986 double costCenter = std::fabs( lineAnchorPoint - labelTextAnchor ) / totalDistance;
1987 cost += costCenter / ( anchorIsFlexiblePlacement ? 100 : 10 );
1990 const bool isBelow = ( offset !=
NoOffset ) && labeledLineSegmentIsRightToLeft;
2002 labelPosition->setCost( cost );
2004 auto p = std::make_unique< LabelPosition >( *labelPosition );
2005 if ( p &&
mLF->permissibleZonePrepared() )
2009 while ( within && currentPos )
2012 currentPos = currentPos->
nextPart();
2022 if ( isBackupPlacementOnly )
2023 backupPlacement = std::move( p );
2025 positions.emplace_back( std::move( p ) );
2030 for ( std::unique_ptr< LabelPosition > &pos : positions )
2032 lPos.emplace_back( std::move( pos ) );
2035 if ( backupPlacement )
2036 lPos.emplace_back( std::move( backupPlacement ) );
2038 return positions.size();
2045 const int characterCount = metrics->
count();
2047 if ( characterCount == 0 || vertexCount == 0 )
2050 const double distLabel =
mLF->distLabel();
2052 std::unique_ptr< LabelPosition > firstPosition;
2055 int vertexIndex = 0;
2056 int characterIndex = -1;
2057 for ( ; vertexIndex < vertexCount; ++vertexIndex )
2059 if (
pal->isCanceled() )
2062 bool isWhiteSpace =
true;
2063 while ( isWhiteSpace )
2066 if ( characterIndex >= characterCount )
2069 isWhiteSpace = metrics->
grapheme( characterIndex ).trimmed().isEmpty() || metrics->
grapheme( characterIndex ) ==
'\t';
2072 if ( characterIndex >= characterCount )
2075 double x = mapShape->
x[vertexIndex];
2076 double y = mapShape->
y[vertexIndex];
2081 if ( vertexIndex < vertexCount - 1 )
2083 angle = std::atan2( mapShape->
y[vertexIndex + 1] -
y, mapShape->
x[vertexIndex + 1] -
x );
2085 else if ( vertexIndex > 0 )
2087 angle = std::atan2(
y - mapShape->
y[vertexIndex - 1],
x - mapShape->
x[vertexIndex - 1] );
2091 x -= std::sin( angle ) * distLabel;
2092 y += std::cos( angle ) * distLabel;
2098 currentPosition->setPartId( characterIndex );
2100 if ( !firstPosition )
2102 firstPosition = std::move( currentPosition );
2103 previousPosition = firstPosition.get();
2108 previousPosition->
setNextPart( std::move( currentPosition ) );
2109 previousPosition = rawCurrent;
2113 if ( !firstPosition )
2116 if (
mLF->permissibleZonePrepared() )
2120 while ( within && currentPos )
2123 currentPos = currentPos->
nextPart();
2131 lPos.emplace_back( std::move( firstPosition ) );
2152 const std::size_t maxPolygonCandidates =
mLF->layer()->maximumPolygonLabelCandidates();
2153 const std::size_t targetPolygonCandidates = maxPolygonCandidates > 0
2154 ? std::min( maxPolygonCandidates,
static_cast< std::size_t
>( std::ceil(
mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() *
area() ) ) )
2157 const double totalArea =
area();
2159 mapShape->
parent =
nullptr;
2161 if (
pal->isCanceled() )
2164 QVector<PointSet *> shapes_final =
splitPolygons( mapShape, labelWidth, labelHeight );
2167 for (
PointSet *ps : shapes_final )
2173 std::size_t nbp = 0;
2175 if ( !shapes_final.isEmpty() )
2183 double diago = std::sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
2185 std::vector< OrientedConvexHullBoundingBox > boxes;
2186 boxes.reserve( shapes_final.size() );
2189 while ( !shapes_final.isEmpty() )
2191 PointSet *shape = shapes_final.takeFirst();
2195 boxes.emplace_back( box );
2201 if (
pal->isCanceled() )
2204 double densityX = 1.0 / std::sqrt(
mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() );
2205 double densityY = densityX;
2210 int maxTry =
mLF->permissibleZonePrepared() ? 7 : 10;
2212 std::size_t numberCandidatesGenerated = 0;
2227 double dx = densityX;
2228 double dy = densityY;
2229 if ( numTry == 0 && maxPolygonCandidates > 0 )
2232 const double boxArea = box.width * box.length;
2233 double maxThisBox = targetPolygonCandidates * boxArea / totalArea;
2234 dx = std::max( dx, std::sqrt( boxArea / maxThisBox ) * 0.8 );
2238 if (
pal->isCanceled() )
2239 return numberCandidatesGenerated;
2250 if (
mLF->permissibleZone().boundingBox().width() < labelWidth ||
mLF->permissibleZone().boundingBox().height() < labelHeight )
2257 bool enoughPlace =
false;
2261 px = ( box.x[0] + box.x[2] ) / 2 - labelWidth;
2262 py = ( box.y[0] + box.y[2] ) / 2 - labelHeight;
2268 for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
2270 for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
2274 enoughPlace =
false;
2290 else if ( box.length > 1.5 * labelWidth && box.width > 1.5 * labelWidth )
2292 if ( box.alpha <= M_PI_4 )
2298 alpha = box.alpha - M_PI_2;
2301 else if ( box.length > box.width )
2303 alpha = box.alpha - M_PI_2;
2310 beta = std::atan2( labelHeight, labelWidth ) + alpha;
2316 dlx = std::cos( beta ) * diago;
2317 dly = std::sin( beta ) * diago;
2319 double px0 = box.width / 2.0;
2320 double py0 = box.length / 2.0;
2322 px0 -= std::ceil( px0 / dx ) * dx;
2323 py0 -= std::ceil( py0 / dy ) * dy;
2325 for ( px = px0; px <= box.width; px += dx )
2327 if (
pal->isCanceled() )
2330 for ( py = py0; py <= box.length; py += dy )
2332 rx = std::cos( box.alpha ) * px + std::cos( box.alpha - M_PI_2 ) * py;
2333 ry = std::sin( box.alpha ) * px + std::sin( box.alpha - M_PI_2 ) * py;
2338 if (
mLF->permissibleZonePrepared() )
2346 numberCandidatesGenerated++;
2357 auto potentialCandidate = std::make_unique<
2360 lPos.emplace_back( std::move( potentialCandidate ) );
2361 numberCandidatesGenerated++;
2368 nbp = numberCandidatesGenerated;
2369 if ( maxPolygonCandidates > 0 && nbp < targetPolygonCandidates )
2379 }
while ( numTry < maxTry );
2381 nbp = numberCandidatesGenerated;
2394 const std::size_t maxPolygonCandidates =
mLF->layer()->maximumPolygonLabelCandidates();
2395 std::size_t candidatesCreated = 0;
2457 return candidatesCreated;
2461 return candidatesCreated;
2465 const double ringLength = ring->
length();
2466 const double circleArea = std::pow( ringLength, 2 ) / ( 4 * M_PI );
2467 const std::size_t candidatesForArea =
static_cast< std::size_t
>( std::ceil(
mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() * circleArea ) );
2468 const std::size_t targetPolygonCandidates = std::max(
static_cast< std::size_t
>( 16 ), maxPolygonCandidates > 0 ? std::min( maxPolygonCandidates, candidatesForArea ) : candidatesForArea );
2471 const double delta = ringLength / targetPolygonCandidates;
2474 const double maxDistCentroidToLabelX = std::max(
xmax - cx, cx -
xmin ) + distanceToLabel;
2475 const double maxDistCentroidToLabelY = std::max(
ymax - cy, cy -
ymin ) + distanceToLabel;
2476 const double estimateOfMaxPossibleDistanceCentroidToLabel = std::sqrt( maxDistCentroidToLabelX * maxDistCentroidToLabelX + maxDistCentroidToLabelY * maxDistCentroidToLabelY );
2479 const double labelAngle = 0;
2481 std::size_t i = lPos.size();
2488 createCandidateAtOrderedPositionOverPoint( labelX, labelY, quadrant,
x,
y, labelWidth, labelHeight, position, distanceToLabel * 0.5, visualMargin, 0, 0, labelAngle );
2491 if ( candidate->intersects( preparedBuffer.get() ) )
2509 const double centroidDistance = candidate->getDistanceToPoint( cx, cy,
false );
2510 const double centroidCost = centroidDistance / estimateOfMaxPossibleDistanceCentroidToLabel;
2511 candidate->setCost( centroidCost );
2513 lPos.emplace_back( std::move( candidate ) );
2514 candidatesCreated++;
2518 ring->
visitPointsByRegularDistance( delta, [&](
double x,
double y,
double,
double,
double startSegmentX,
double startSegmentY,
double,
double,
double endSegmentX,
double endSegmentY,
double,
double ) {
2520 float angle = atan2(
static_cast< float >( endSegmentY - startSegmentY ),
static_cast< float >( endSegmentX - startSegmentX ) ) * 180 / M_PI;
2525 if ( angle >= 0 && angle <= 5 )
2530 else if ( angle <= 85 )
2534 else if ( angle <= 90 )
2540 else if ( angle <= 95 )
2545 else if ( angle <= 175 )
2549 else if ( angle <= 180 )
2555 else if ( angle <= 185 )
2560 else if ( angle <= 265 )
2564 else if ( angle <= 270 )
2569 else if ( angle <= 275 )
2574 else if ( angle <= 355 )
2584 return !
pal->isCanceled();
2587 return candidatesCreated;
2592 std::vector< std::unique_ptr< LabelPosition > > lPos;
2593 double angleInRadians =
mLF->hasFixedAngle() ?
mLF->fixedAngle() : 0.0;
2595 if (
mLF->hasFixedPosition() )
2615 case GEOS_LINESTRING:
2618 else if (
mLF->layer()->isCurved() )
2638 else if ( allowOutside && ( std::fabs(
xmax -
xmin ) < labelWidth || std::fabs(
ymax -
ymin ) < labelHeight ) )
2645 std::size_t created = 0;
2648 switch (
mLF->layer()->arrangement() )
2700 auto minIt = std::min_element( lPos.begin(), lPos.end(), [](
const std::unique_ptr< LabelPosition > &a,
const std::unique_ptr< LabelPosition > &b ) { return a->cost() < b->cost(); } );
2702 if ( minIt != lPos.end() )
2704 std::unique_ptr< LabelPosition > bestCandidate = std::move( *minIt );
2706 lPos.emplace_back( std::move( bestCandidate ) );
2719 int geomType = GEOSGeomTypeId_r( ctxt,
mGeos );
2721 double sizeCost = 0;
2722 if ( geomType == GEOS_LINESTRING )
2724 const double l =
length();
2727 double bbox_length = std::max( bbx[2] - bbx[0], bby[2] - bby[0] );
2728 if ( l >= bbox_length / 4 )
2731 sizeCost = 1 - ( l / ( bbox_length / 4 ) );
2733 else if ( geomType == GEOS_POLYGON )
2735 const double a =
area();
2738 double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
2739 if ( a >= bbox_area / 16 )
2742 sizeCost = 1 - ( a / ( bbox_area / 16 ) );
2748 for ( std::unique_ptr< LabelPosition > &pos : lPos )
2750 pos->setCost( pos->cost() + sizeCost / 100 );
2761 const double x1first =
x.front();
2762 const double x1last =
x.back();
2763 const double x2first = p2->
x.front();
2764 const double x2last = p2->
x.back();
2765 const double y1first =
y.front();
2766 const double y1last =
y.back();
2767 const double y2first = p2->
y.front();
2768 const double y2last = p2->
y.back();
2774 if ( ( !p2startTouches && !p2endTouches ) || ( p2startTouches && p2endTouches ) )
2780 const double p2otherX = p2startTouches ? x2last : x2first;
2781 const double p2otherY = p2startTouches ? y2last : y2first;
2787#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12 )
2788 return ( GEOSPreparedIntersectsXY_r( geosctxt,
preparedGeom(), p2otherX, p2otherY ) != 1 );
2790 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 1, 2 );
2791 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, p2otherX, p2otherY );
2793 return ( GEOSPreparedIntersects_r( geosctxt,
preparedGeom(), p2OtherEnd.get() ) != 1 );
2796 catch ( QgsGeosException &e )
2798 qWarning(
"GEOS exception: %s", e.what() );
2808 if ( !other->
mGeos )
2817 geos::unique_ptr g( GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 ) );
2820 if ( GEOSGeomTypeId_r( ctxt, gTmp.get() ) != GEOS_LINESTRING )
2828 mGeos = gTmp.release();
2837 catch ( QgsGeosException &e )
2839 qWarning(
"GEOS exception: %s", e.what() );
2847 if (
mLF->alwaysShow() )
2855 return mLF->priority() >= 0 ?
mLF->priority() :
mLF->layer()->priority();
2860 bool result =
false;
2862 switch (
mLF->layer()->upsidedownLabels() )
@ StretchCharacterSpacingToFitLine
Increases (or decreases) the character spacing used for each label in order to fit the entire text ov...
@ Default
Default curved placement, characters are placed in an optimal position along the line....
@ StretchWordSpacingToFitLine
Increases (or decreases) the word spacing used for each label in order to fit the entire text over th...
@ PlaceCharactersAtVertices
Each individual character from the label text is placed such that their left-baseline position is loc...
@ BelowLine
Labels can be placed below a line feature. Unless MapOrientation is also specified this mode respects...
@ MapOrientation
Signifies that the AboveLine and BelowLine flags should respect the map's orientation rather than the...
@ OnLine
Labels can be placed directly over a line feature.
@ AboveLine
Labels can be placed above a line feature. Unless MapOrientation is also specified this mode respects...
@ FromSymbolBounds
Offset distance applies from rendered symbol bounds.
LabelPrioritization
Label prioritization.
@ PreferCloser
Prefer closer labels, falling back to alternate positions before larger distances.
@ PreferPositionOrdering
Prefer labels follow position ordering, falling back to more distance labels before alternate positio...
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
@ AroundPoint
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
@ Free
Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the pol...
@ OrderedPositionsAroundPoint
Candidates are placed in predefined positions around a point. Preference is given to positions with g...
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature or along a line feature....
@ PerimeterCurved
Arranges candidates following the curvature of a polygon's boundary. Applies to polygon layers only.
@ OutsidePolygons
Candidates are placed outside of polygon boundaries. Applies to polygon layers only.
@ AllowPlacementInsideOfPolygon
Labels can be placed inside a polygon feature.
@ AllowPlacementOutsideOfPolygon
Labels can be placed outside of a polygon feature.
TextAnchorPoint
Anchor point of label text.
@ EndOfText
Anchor using end of text.
@ StartOfText
Anchor using start of text.
@ CenterOfText
Anchor using center of text.
@ FollowPlacement
Automatically set the anchor point based on the line anchor point value. Values <25% of line length w...
QFlags< LabelLinePlacementFlag > LabelLinePlacementFlags
Line placement flags, which control how candidates are generated for a linear feature.
LabelQuadrantPosition
Label quadrant positions.
@ SingleCandidateOnly
Generate only the single least-cost candidate for each feature. Useful for fast labeling,...
@ TreatWhitespaceAsCollision
Treat overlapping whitespace text in labels and whitespace overlapping obstacles as collisions.
@ IgnoreWhitespaceCollisions
Ignore overlapping whitespace text in labels and whitespace overlapping obstacles.
@ UprightCharactersOnly
Permit upright characters only. If not present then upside down text placement is permitted.
QFlags< CurvedTextFlag > CurvedTextFlags
Flags controlling behavior of curved text generation.
LabelPredefinedPointPosition
Positions for labels when using the Qgis::LabelPlacement::OrderedPositionsAroundPoint placement mode.
@ OverPoint
Label directly centered over point.
@ MiddleLeft
Label on left of point.
@ TopRight
Label on top-right of point.
@ MiddleRight
Label on right of point.
@ TopSlightlyRight
Label on top of point, slightly right of center.
@ TopMiddle
Label directly above point.
@ BottomSlightlyLeft
Label below point, slightly left of center.
@ BottomRight
Label on bottom right of point.
@ BottomLeft
Label on bottom-left of point.
@ BottomSlightlyRight
Label below point, slightly right of center.
@ TopLeft
Label on top-left of point.
@ BottomMiddle
Label directly below point.
@ TopSlightlyLeft
Label on top of point, slightly left of center.
@ FlipUpsideDownLabels
Upside-down labels (90 <= angle < 270) are shown upright.
@ AlwaysAllowUpsideDown
Show upside down for all labels, including dynamic ones.
@ AllowUpsideDownWhenRotationIsDefined
Show upside down when rotation is layer- or data-defined.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
static double distance2D(double x1, double y1, double x2, double y2)
Returns the 2D distance between (x1, y1) and (x2, y2).
static double normalizedAngle(double angle)
Ensures that an angle is in the range 0 <= angle < 2 pi.
A geometry is the spatial representation of a feature.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
static GEOSContextHandle_t get()
Returns a thread local instance of a GEOS context, safe for use in the current thread.
static std::unique_ptr< QgsAbstractGeometry > fromGeos(const GEOSGeometry *geos)
Create a geometry from a GEOSGeometry.
Describes a feature that should be used within the labeling engine.
QPointF quadOffset() const
Applies to "offset from point" placement strategy and "around point" (in case hasFixedQuadrant() retu...
void setAnchorPosition(const QgsPointXY &anchorPosition)
In case of quadrand or aligned positioning, this is set to the anchor point.
@ Strict
Line anchor is a strict placement, and other placements are not permitted.
@ HintOnly
Line anchor is a hint for preferred placement only, but other placements close to the hint are permit...
Line string geometry type, with support for z-dimension and m-values.
double length() const override
Returns the planar, 2-dimensional length of the geometry.
void visitPointsByRegularDistance(double distance, const std::function< bool(double x, double y, double z, double m, double startSegmentX, double startSegmentY, double startSegmentZ, double startSegmentM, double endSegmentX, double endSegmentY, double endSegmentZ, double endSegmentM) > &visitPoint) const
Visits regular points along the linestring, spaced by distance.
Defines the four margins of a rectangle.
double top() const
Returns the top margin.
double right() const
Returns the right margin.
double bottom() const
Returns the bottom margin.
double left() const
Returns the left margin.
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).
Contains precalculated properties regarding text metrics for text to be rendered at a later stage.
int count() const
Returns the total number of characters.
double characterWidth(int position) const
Returns the width of the character at the specified position.
QString grapheme(int index) const
Returns the grapheme at the specified index.
double characterHeight(int position) const
Returns the character height of the character at the specified position (actually font metrics height...
Utility functions for text rendering.
LabelLineDirection
Controls behavior of curved text with respect to line directions.
@ FollowLineDirection
Curved text placement will respect the line direction and ignore painter orientation.
@ RespectPainterOrientation
Curved text will be placed respecting the painter orientation, and the actual line direction will be ...
FeaturePart(QgsLabelFeature *lf, const GEOSGeometry *geom)
Creates a new generic feature.
std::size_t createCandidatesAroundPoint(double x, double y, std::vector< std::unique_ptr< LabelPosition > > &lPos, double angle)
Generate candidates for point feature, located around a specified point.
std::size_t createCandidatesOutsidePolygon(std::vector< std::unique_ptr< LabelPosition > > &lPos, Pal *pal)
Generate candidates outside of polygon features.
bool hasFixedRotation() const
Returns true if the feature's label has a fixed rotation.
std::unique_ptr< LabelPosition > curvedPlacementAtOffset(PointSet *mapShape, const std::vector< double > &pathDistances, QgsTextRendererUtils::LabelLineDirection direction, double distance, bool &labeledLineSegmentIsRightToLeft, bool applyAngleConstraints, Qgis::CurvedTextFlags flags, double additionalCharacterSpacing, double additionalWordSpacing)
Returns the label position for a curved label at a specific offset along a path.
double getLabelHeight(double angle=0.0) const
Returns the height of the label, optionally taking an angle (in radians) into account.
QList< FeaturePart * > mHoles
double getLabelDistance() const
Returns the distance from the anchor point to the label.
~FeaturePart() override
Deletes the feature.
std::size_t createHorizontalCandidatesAlongLine(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, Pal *pal, double angle)
Generate horizontal candidates for line feature.
bool hasFixedPosition() const
Returns true if the feature's label has a fixed position.
std::size_t createCurvedCandidateWithCharactersAtVertices(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, Pal *pal)
Generates a curved candidates for line features, placing individual characters on the line vertices.
std::size_t createCandidatesForPolygon(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, Pal *pal)
Generate candidates for polygon features.
void setTotalRepeats(int repeats)
Returns the total number of repeating labels associated with this label.
std::size_t maximumPolygonCandidates() const
Returns the maximum number of polygon candidates to generate for this feature.
std::size_t createDefaultCurvedCandidatesAlongLine(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, bool allowOverrun, Pal *pal)
Generate curved candidates for line features, using default placement.
QgsFeatureId featureId() const
Returns the unique ID of the feature.
std::size_t createCandidatesAlongLineNearStraightSegments(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, Pal *pal)
Generate candidates for line feature, by trying to place candidates towards the middle of the longest...
bool hasSameLabelFeatureAs(FeaturePart *part) const
Tests whether this feature part belongs to the same QgsLabelFeature as another feature part.
double fixedAngle() const
Returns the fixed angle for the feature's label.
std::size_t maximumLineCandidates() const
Returns the maximum number of line candidates to generate for this feature.
int subPartId() const
Returns the unique sub part ID for the feature, for features which register multiple labels.
std::size_t createCandidatesAlongLine(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, bool allowOverrun, Pal *pal)
Generate candidates for line feature.
bool mergeWithFeaturePart(FeaturePart *other)
Merge other (connected) part with this one and save the result in this part (other is unchanged).
std::size_t createCurvedCandidatesAlongLine(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, bool allowOverrun, Pal *pal)
Generate curved candidates for line features.
bool onlyShowUprightLabels() const
Returns true if feature's label must be displayed upright.
std::size_t createCandidatesOverPoint(double x, double y, std::vector< std::unique_ptr< LabelPosition > > &lPos, double angle)
Generate one candidate over or offset the specified point.
std::unique_ptr< LabelPosition > createCandidatePointOnSurface(PointSet *mapShape)
Creates a single candidate using the "point on sruface" algorithm.
double getLabelWidth(double angle=0.0) const
Returns the width of the label, optionally taking an angle (in radians) into account.
QgsLabelFeature * feature()
Returns the parent feature.
std::vector< std::unique_ptr< LabelPosition > > createCandidates(Pal *pal)
Generates a list of candidate positions for labels for this feature.
bool isConnected(FeaturePart *p2)
Check whether this part is connected with some other part.
Layer * layer()
Returns the layer that feature belongs to.
PathOffset
Path offset variances used in curved placement.
int totalRepeats() const
Returns the total number of repeating labels associated with this label.
std::size_t createCandidatesAlongLineNearMidpoint(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, double initialCost=0.0, Pal *pal=nullptr)
Generate candidates for line feature, by trying to place candidates as close as possible to the line'...
void addSizePenalty(std::vector< std::unique_ptr< LabelPosition > > &lPos, double bbx[4], double bby[4]) const
Increases the cost of the label candidates for this feature, based on the size of the feature.
void extractCoords(const GEOSGeometry *geom)
read coordinates from a GEOS geom
double calculatePriority() const
Calculates the priority for the feature.
std::size_t createCandidatesAtOrderedPositionsOverPoint(double x, double y, std::vector< std::unique_ptr< LabelPosition > > &lPos, double angle)
Generates candidates following a prioritized list of predefined positions around a point.
std::size_t createCandidateCenteredOverPoint(double x, double y, std::vector< std::unique_ptr< LabelPosition > > &lPos, double angle)
Generate one candidate centered over the specified point.
std::size_t maximumPointCandidates() const
Returns the maximum number of point candidates to generate for this feature.
Pal labeling engine geometry functions.
static bool reorderPolygon(std::vector< double > &x, std::vector< double > &y)
Reorder points to have cross prod ((x,y)[i], (x,y)[i+1), point) > 0 when point is outside.
static bool containsCandidate(const GEOSPreparedGeometry *geom, double x, double y, double width, double height, double alpha)
Returns true if a GEOS prepared geometry totally contains a label candidate.
double getAlpha() const
Returns the angle to rotate text (in radians).
void setNextPart(std::unique_ptr< LabelPosition > next)
Sets the next part of this label position (i.e.
double getX(int i=0) const
Returns the down-left x coordinate.
double getY(int i=0) const
Returns the down-left y coordinate.
LabelPosition * nextPart() const
Returns the next part of this label position (i.e.
QString name() const
Returns the layer's name.
geos::unique_ptr interpolatePoint(double distance) const
Returns a GEOS geometry representing the point interpolated on the shape by distance.
std::unique_ptr< PointSet > clone() const
Returns a copy of the point set.
friend class LabelPosition
double lineLocatePoint(const GEOSGeometry *point) const
Returns the distance along the geometry closest to the specified GEOS point.
double length() const
Returns length of line geometry.
double area() const
Returns area of polygon geometry.
bool isClosed() const
Returns true if pointset is closed.
static QVector< PointSet * > splitPolygons(PointSet *inputShape, double labelWidth, double labelHeight)
Split a polygon using some random logic into some other polygons.
void createGeosGeom() const
void getPointByDistance(double *d, double *ad, double dl, double *px, double *py) const
Gets a point a set distance along a line geometry.
void getCentroid(double &px, double &py, bool forceInside=false) const
OrientedConvexHullBoundingBox computeConvexHullOrientedBoundingBox(bool &ok) const
Computes an oriented bounding box for the shape's convex hull.
const GEOSPreparedGeometry * preparedGeom() const
const GEOSGeometry * geos() const
Returns the point set's GEOS geometry.
void invalidateGeos() const
bool containsPoint(double x, double y) const
Tests whether point set contains a specified point.
std::tuple< std::vector< double >, double > edgeDistances() const
Returns a vector of edge distances as well as its total length.
void createCandidateAtOrderedPositionOverPoint(double &labelX, double &labelY, Qgis::LabelQuadrantPosition &quadrant, double x, double y, double labelWidth, double labelHeight, Qgis::LabelPredefinedPointPosition position, double distanceToLabel, const QgsMargins &visualMargin, double symbolWidthOffset, double symbolHeightOffset, double angle)
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
std::unique_ptr< const GEOSPreparedGeometry, GeosDeleter > prepared_unique_ptr
Scoped GEOS prepared geometry pointer.
#define BUILTIN_UNREACHABLE
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
T qgsgeometry_cast(QgsAbstractGeometry *geom)
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugMsgLevel(str, level)
Represents the minimum area, oriented bounding box surrounding a convex hull.
struct GEOSGeom_t GEOSGeometry