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;
1181 cost += costLineCenter * 0.0005;
1184 if ( placementIsFlexible )
1186 cost += segmentCost * 0.0005;
1187 cost += segmentAngleCost * 0.0001;
1195 angle = std::atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
1199 beta = angle + M_PI_2;
1204 bool isRightToLeft = ( angle > M_PI_2 || angle <= -M_PI_2 );
1212 if ( !
mLF->permissibleZonePrepared()
1214 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
1216 const double candidateCost = cost + ( reversed ? 0 : 0.001 );
1218 std::make_unique< LabelPosition >(
1220 candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ),
1221 candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ),
1235 if ( !
mLF->permissibleZonePrepared()
1237 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX + std::cos( beta ) * distanceLineToLabel, candidateStartY + std::sin( beta ) * distanceLineToLabel, labelWidth, labelHeight, angle ) )
1239 const double candidateCost = cost + ( !reversed ? 0 : 0.001 );
1241 std::make_unique< LabelPosition >(
1243 candidateStartX + std::cos( beta ) * distanceLineToLabel,
1244 candidateStartY + std::sin( beta ) * distanceLineToLabel,
1258 if ( !
mLF->permissibleZonePrepared()
1260 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) )
1262 const double candidateCost = cost + 0.002;
1264 std::make_unique< LabelPosition >(
1266 candidateStartX - labelHeight * std::cos( beta ) / 2,
1267 candidateStartY - labelHeight * std::sin( beta ) / 2,
1293 currentDistanceAlongLine += lineStepDistance;
1316 std::vector< double > &
x = line->
x;
1317 std::vector< double > &
y = line->
y;
1319 std::vector< double > segmentLengths(
nbPoints - 1 );
1320 std::vector< double > distanceToSegment(
nbPoints );
1322 double totalLineLength = 0.0;
1323 for (
int i = 0; i < line->
nbPoints - 1; i++ )
1326 distanceToSegment[i] = 0;
1328 distanceToSegment[i] = distanceToSegment[i - 1] + segmentLengths[i - 1];
1331 totalLineLength += segmentLengths[i];
1333 distanceToSegment[line->
nbPoints - 1] = totalLineLength;
1335 double lineStepDistance = ( totalLineLength - labelWidth );
1336 double currentDistanceAlongLine = 0;
1342 if ( totalLineLength > labelWidth )
1344 lineStepDistance = std::min( std::min( labelHeight, labelWidth ), lineStepDistance / candidateTargetCount );
1348 currentDistanceAlongLine = -( labelWidth - totalLineLength ) / 2.0;
1349 lineStepDistance = -1;
1350 totalLineLength = labelWidth;
1355 currentDistanceAlongLine = std::numeric_limits< double >::max();
1358 const double lineAnchorPoint = totalLineLength * std::min( 0.99,
mLF->lineAnchorPercent() );
1360 switch (
mLF->lineAnchorType() )
1366 switch ( textPoint )
1369 currentDistanceAlongLine = std::min( lineAnchorPoint, totalLineLength * 0.99 - labelWidth );
1372 currentDistanceAlongLine = std::min( lineAnchorPoint - labelWidth / 2, totalLineLength * 0.99 - labelWidth );
1375 currentDistanceAlongLine = std::min( lineAnchorPoint - labelWidth, totalLineLength * 0.99 - labelWidth );
1381 lineStepDistance = -1;
1385 double candidateLength;
1387 double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
1391 if (
pal->isCanceled() )
1397 line->
getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine, &candidateStartX, &candidateStartY );
1398 line->
getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
1400 if ( currentDistanceAlongLine < 0 )
1410 cost = candidateLength / labelWidth;
1416 cost = ( 1 - cost ) / 100;
1420 double textAnchorPoint = 0;
1421 switch ( textPoint )
1424 textAnchorPoint = currentDistanceAlongLine;
1427 textAnchorPoint = currentDistanceAlongLine + labelWidth / 2;
1430 textAnchorPoint = currentDistanceAlongLine + labelWidth;
1436 double costCenter = totalLineLength > 0 ? std::fabs( lineAnchorPoint - textAnchorPoint ) / totalLineLength : 0;
1437 cost += costCenter / 1000;
1438 cost += initialCost;
1445 angle = std::atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
1449 beta = angle + M_PI_2;
1454 bool isRightToLeft = ( angle > M_PI_2 || angle <= -M_PI_2 );
1462 if ( !
mLF->permissibleZonePrepared()
1464 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX + std::cos( beta ) * distanceLineToLabel, candidateStartY + std::sin( beta ) * distanceLineToLabel, labelWidth, labelHeight, angle ) )
1466 const double candidateCost = cost + ( !reversed ? 0 : 0.001 );
1468 std::make_unique< LabelPosition >(
1470 candidateStartX + std::cos( beta ) * distanceLineToLabel,
1471 candidateStartY + std::sin( beta ) * distanceLineToLabel,
1485 if ( !
mLF->permissibleZonePrepared()
1487 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
1489 const double candidateCost = cost + ( !reversed ? 0.001 : 0 );
1491 std::make_unique< LabelPosition >(
1493 candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ),
1494 candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ),
1508 if ( !
mLF->permissibleZonePrepared()
1510 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) )
1512 const double candidateCost = cost + 0.002;
1514 std::make_unique< LabelPosition >(
1516 candidateStartX - labelHeight * std::cos( beta ) / 2,
1517 candidateStartY - labelHeight * std::sin( beta ) / 2,
1543 currentDistanceAlongLine += lineStepDistance;
1547 if ( lineStepDistance < 0 )
1556 const std::vector< double> &pathDistances,
1558 const double offsetAlongLine,
1559 bool &labeledLineSegmentIsRightToLeft,
1560 bool applyAngleConstraints,
1562 double additionalCharacterSpacing,
1563 double additionalWordSpacing
1567 Q_ASSERT( metrics );
1569 const double maximumCharacterAngleInside = applyAngleConstraints ? std::fabs( qgis::down_cast< QgsTextLabelFeature *>(
mLF )->maximumCharacterAngleInside() ) : -1;
1570 const double maximumCharacterAngleOutside = applyAngleConstraints ? std::fabs( qgis::down_cast< QgsTextLabelFeature *>(
mLF )->maximumCharacterAngleOutside() ) : -1;
1572 std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > placement(
1574 generateCurvedTextPlacement( *metrics, mapShape->
x.data(), mapShape->
y.data(), mapShape->
nbPoints, pathDistances, offsetAlongLine, direction, maximumCharacterAngleInside, maximumCharacterAngleOutside, flags, additionalCharacterSpacing, additionalWordSpacing )
1579 if ( placement->graphemePlacement.empty() )
1582 auto it = placement->graphemePlacement.constBegin();
1585 firstPosition->setUpsideDownCharCount( placement->upsideDownCharCount );
1586 firstPosition->setPartId( it->graphemeIndex );
1590 bool skipWhitespace =
false;
1591 switch (
mLF->whitespaceCollisionHandling() )
1597 skipWhitespace =
true;
1601 while ( it != placement->graphemePlacement.constEnd() )
1603 if ( skipWhitespace && it->isWhitespace )
1610 position->setPartId( it->graphemeIndex );
1613 previousPosition->
setNextPart( std::move( position ) );
1614 previousPosition = nextPosition;
1618 return firstPosition;
1630 const int characterCount = li->
count();
1631 if ( characterCount == 0 )
1634 switch (
mLF->curvedLabelMode() )
1649 const int characterCount = li->
count();
1652 double totalCharacterWidth = 0;
1654 for (
int i = 0; i < characterCount; ++i )
1657 if ( stretchWordSpacingToFit && li->
grapheme( i ) ==
' ' )
1662 if ( spaceCount == 0 )
1665 stretchWordSpacingToFit =
false;
1669 const bool usingStretchToFitMode = stretchCharacterSpacingToFit || stretchWordSpacingToFit;
1674 std::unique_ptr< PointSet > expanded;
1675 double shapeLength = mapShape->
length();
1680 allowOverrun =
false;
1683 if ( !usingStretchToFitMode )
1689 switch (
mLF->lineAnchorType() )
1692 overrun = std::min(
mLF->overrunDistance(), totalCharacterWidth * 0.95 );
1697 overrun = std::max(
mLF->overrunDistance(), totalCharacterWidth * 1.05 );
1701 if ( totalCharacterWidth > shapeLength )
1703 if ( !allowOverrun || shapeLength < totalCharacterWidth - 2 * overrun )
1714 if ( allowOverrun && overrun > 0 )
1717 expanded = mapShape->
clone();
1718 expanded->extendLineByDistance( overrun, overrun,
mLF->overrunSmoothDistance() );
1719 mapShape = expanded.get();
1720 shapeLength += 2 * overrun;
1729 std::unique_ptr< PointSet > mapShapeOffsetPositive;
1730 bool positiveShapeHasNegativeDistance =
false;
1731 std::unique_ptr< PointSet > mapShapeOffsetNegative;
1732 bool negativeShapeHasNegativeDistance =
false;
1733 if ( hasAboveBelowLinePlacement && !
qgsDoubleNear( offsetDistance, 0 ) )
1737 mapShapeOffsetPositive = mapShape->
clone();
1739 mapShapeOffsetNegative = mapShape->
clone();
1742 if ( mapShapeOffsetPositive )
1743 mapShapeOffsetPositive->offsetCurveByDistance( offsetDistance );
1744 positiveShapeHasNegativeDistance = offsetDistance < 0;
1745 if ( mapShapeOffsetNegative )
1746 mapShapeOffsetNegative->offsetCurveByDistance( offsetDistance * -1 );
1747 negativeShapeHasNegativeDistance = offsetDistance > 0;
1762 if ( mapShapeOffsetPositive )
1763 mapShapeOffsetPositive->offsetCurveByDistance( offsetDistance * -1 );
1764 positiveShapeHasNegativeDistance = offsetDistance > 0;
1765 if ( mapShapeOffsetNegative )
1766 mapShapeOffsetNegative->offsetCurveByDistance( offsetDistance );
1767 negativeShapeHasNegativeDistance = offsetDistance < 0;
1773 std::vector< std::unique_ptr< LabelPosition >> positions;
1774 std::unique_ptr< LabelPosition > backupPlacement;
1777 PointSet *currentMapShape =
nullptr;
1780 currentMapShape = mapShapeOffsetPositive.get();
1784 currentMapShape = mapShape;
1788 currentMapShape = mapShapeOffsetNegative.get();
1790 if ( !currentMapShape )
1794 const auto [pathDistances, totalDistance] = currentMapShape->
edgeDistances();
1798 double lineAnchorPoint = 0;
1799 if ( !usingStretchToFitMode )
1801 if ( originalPoint )
1806 lineAnchorPoint = currentMapShape->
lineLocatePoint( originalPoint.get() );
1810 lineAnchorPoint = totalDistance *
mLF->lineAnchorPercent();
1812 lineAnchorPoint = totalDistance - lineAnchorPoint;
1816 if (
pal->isCanceled() )
1820 double delta = std::max( li->
characterHeight( 0 ) / 6, totalDistance / candidateTargetCount );
1823 double distanceAlongLineToStartCandidate = 0;
1824 bool singleCandidateOnly =
false;
1825 double additionalCharacterSpacing = 0.0;
1826 double additionalWordSpacing = 0.0;
1827 if ( usingStretchToFitMode )
1830 double extraSpace = totalDistance - totalCharacterWidth;
1835 if ( extraSpace > 0 )
1836 extraSpace *= 0.995;
1838 extraSpace *= 1.005;
1840 if ( stretchWordSpacingToFit )
1842 if ( spaceCount > 0 )
1843 additionalWordSpacing = extraSpace / spaceCount;
1849 if ( characterCount > 1 )
1850 additionalCharacterSpacing = extraSpace / ( characterCount - 1 );
1854 distanceAlongLineToStartCandidate = 0;
1855 delta = totalDistance + 1.0;
1856 singleCandidateOnly =
true;
1860 switch (
mLF->lineAnchorType() )
1866 switch ( textPoint )
1869 distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint, 0.0, totalDistance * 0.999 );
1872 distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint -
getLabelWidth() / 2, 0.0, totalDistance * 0.999 -
getLabelWidth() / 2 );
1875 distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint -
getLabelWidth(), 0.0, totalDistance * 0.999 -
getLabelWidth() );
1881 singleCandidateOnly =
true;
1886 bool hasTestedFirstPlacement =
false;
1887 for ( ; distanceAlongLineToStartCandidate <= totalDistance; distanceAlongLineToStartCandidate += delta )
1889 if ( singleCandidateOnly && hasTestedFirstPlacement )
1892 if (
pal->isCanceled() )
1895 hasTestedFirstPlacement =
true;
1897 bool labeledLineSegmentIsRightToLeft =
false;
1904 std::unique_ptr< LabelPosition > labelPosition
1905 =
curvedPlacementAtOffset( currentMapShape, pathDistances, direction, distanceAlongLineToStartCandidate, labeledLineSegmentIsRightToLeft, !singleCandidateOnly, curvedTextFlags, additionalCharacterSpacing, additionalWordSpacing );
1906 if ( !labelPosition )
1912 bool isBackupPlacementOnly =
false;
1915 if ( ( currentMapShape == mapShapeOffsetPositive.get() && positiveShapeHasNegativeDistance ) || ( currentMapShape == mapShapeOffsetNegative.get() && negativeShapeHasNegativeDistance ) )
1917 labeledLineSegmentIsRightToLeft = !labeledLineSegmentIsRightToLeft;
1923 isBackupPlacementOnly =
true;
1930 isBackupPlacementOnly =
true;
1936 backupPlacement.reset();
1939 const double angleDiff = labelPosition->angleDifferential();
1940 const double angleDiffAvg = characterCount > 1 ? ( angleDiff / ( characterCount - 1 ) ) : 0;
1944 const bool anchorIsFlexiblePlacement = !singleCandidateOnly &&
mLF->lineAnchorPercent() > 0.1 &&
mLF->lineAnchorPercent() < 0.9;
1945 double cost = angleDiffAvg / 100;
1946 if ( cost < 0.0001 )
1950 if ( !usingStretchToFitMode )
1953 double labelTextAnchor = 0;
1954 switch ( textPoint )
1957 labelTextAnchor = distanceAlongLineToStartCandidate;
1960 labelTextAnchor = distanceAlongLineToStartCandidate +
getLabelWidth() / 2;
1963 labelTextAnchor = distanceAlongLineToStartCandidate +
getLabelWidth();
1969 double costCenter = std::fabs( lineAnchorPoint - labelTextAnchor ) / totalDistance;
1970 cost += costCenter / ( anchorIsFlexiblePlacement ? 100 : 10 );
1973 const bool isBelow = ( offset !=
NoOffset ) && labeledLineSegmentIsRightToLeft;
1985 labelPosition->setCost( cost );
1987 auto p = std::make_unique< LabelPosition >( *labelPosition );
1988 if ( p &&
mLF->permissibleZonePrepared() )
1992 while ( within && currentPos )
1995 currentPos = currentPos->
nextPart();
2005 if ( isBackupPlacementOnly )
2006 backupPlacement = std::move( p );
2008 positions.emplace_back( std::move( p ) );
2013 for ( std::unique_ptr< LabelPosition > &pos : positions )
2015 lPos.emplace_back( std::move( pos ) );
2018 if ( backupPlacement )
2019 lPos.emplace_back( std::move( backupPlacement ) );
2021 return positions.size();
2028 const int characterCount = metrics->
count();
2030 if ( characterCount == 0 || vertexCount == 0 )
2033 const double distLabel =
mLF->distLabel();
2035 std::unique_ptr< LabelPosition > firstPosition;
2038 int vertexIndex = 0;
2039 int characterIndex = -1;
2040 for ( ; vertexIndex < vertexCount; ++vertexIndex )
2042 if (
pal->isCanceled() )
2045 bool isWhiteSpace =
true;
2046 while ( isWhiteSpace )
2049 if ( characterIndex >= characterCount )
2052 isWhiteSpace = metrics->
grapheme( characterIndex ).trimmed().isEmpty() || metrics->
grapheme( characterIndex ) ==
'\t';
2055 if ( characterIndex >= characterCount )
2058 double x = mapShape->
x[vertexIndex];
2059 double y = mapShape->
y[vertexIndex];
2064 if ( vertexIndex < vertexCount - 1 )
2066 angle = std::atan2( mapShape->
y[vertexIndex + 1] -
y, mapShape->
x[vertexIndex + 1] -
x );
2068 else if ( vertexIndex > 0 )
2070 angle = std::atan2(
y - mapShape->
y[vertexIndex - 1],
x - mapShape->
x[vertexIndex - 1] );
2074 x -= std::sin( angle ) * distLabel;
2075 y += std::cos( angle ) * distLabel;
2081 currentPosition->setPartId( characterIndex );
2083 if ( !firstPosition )
2085 firstPosition = std::move( currentPosition );
2086 previousPosition = firstPosition.get();
2091 previousPosition->
setNextPart( std::move( currentPosition ) );
2092 previousPosition = rawCurrent;
2096 if ( !firstPosition )
2099 if (
mLF->permissibleZonePrepared() )
2103 while ( within && currentPos )
2106 currentPos = currentPos->
nextPart();
2114 lPos.emplace_back( std::move( firstPosition ) );
2135 const std::size_t maxPolygonCandidates =
mLF->layer()->maximumPolygonLabelCandidates();
2136 const std::size_t targetPolygonCandidates = maxPolygonCandidates > 0
2137 ? std::min( maxPolygonCandidates,
static_cast< std::size_t
>( std::ceil(
mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() *
area() ) ) )
2140 const double totalArea =
area();
2142 mapShape->
parent =
nullptr;
2144 if (
pal->isCanceled() )
2147 QVector<PointSet *> shapes_final =
splitPolygons( mapShape, labelWidth, labelHeight );
2150 for (
PointSet *ps : shapes_final )
2156 std::size_t nbp = 0;
2158 if ( !shapes_final.isEmpty() )
2166 double diago = std::sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
2168 std::vector< OrientedConvexHullBoundingBox > boxes;
2169 boxes.reserve( shapes_final.size() );
2172 while ( !shapes_final.isEmpty() )
2174 PointSet *shape = shapes_final.takeFirst();
2178 boxes.emplace_back( box );
2184 if (
pal->isCanceled() )
2187 double densityX = 1.0 / std::sqrt(
mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() );
2188 double densityY = densityX;
2193 int maxTry =
mLF->permissibleZonePrepared() ? 7 : 10;
2195 std::size_t numberCandidatesGenerated = 0;
2210 double dx = densityX;
2211 double dy = densityY;
2212 if ( numTry == 0 && maxPolygonCandidates > 0 )
2215 const double boxArea = box.width * box.length;
2216 double maxThisBox = targetPolygonCandidates * boxArea / totalArea;
2217 dx = std::max( dx, std::sqrt( boxArea / maxThisBox ) * 0.8 );
2221 if (
pal->isCanceled() )
2222 return numberCandidatesGenerated;
2233 if (
mLF->permissibleZone().boundingBox().width() < labelWidth ||
mLF->permissibleZone().boundingBox().height() < labelHeight )
2240 bool enoughPlace =
false;
2244 px = ( box.x[0] + box.x[2] ) / 2 - labelWidth;
2245 py = ( box.y[0] + box.y[2] ) / 2 - labelHeight;
2251 for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
2253 for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
2257 enoughPlace =
false;
2273 else if ( box.length > 1.5 * labelWidth && box.width > 1.5 * labelWidth )
2275 if ( box.alpha <= M_PI_4 )
2281 alpha = box.alpha - M_PI_2;
2284 else if ( box.length > box.width )
2286 alpha = box.alpha - M_PI_2;
2293 beta = std::atan2( labelHeight, labelWidth ) + alpha;
2299 dlx = std::cos( beta ) * diago;
2300 dly = std::sin( beta ) * diago;
2302 double px0 = box.width / 2.0;
2303 double py0 = box.length / 2.0;
2305 px0 -= std::ceil( px0 / dx ) * dx;
2306 py0 -= std::ceil( py0 / dy ) * dy;
2308 for ( px = px0; px <= box.width; px += dx )
2310 if (
pal->isCanceled() )
2313 for ( py = py0; py <= box.length; py += dy )
2315 rx = std::cos( box.alpha ) * px + std::cos( box.alpha - M_PI_2 ) * py;
2316 ry = std::sin( box.alpha ) * px + std::sin( box.alpha - M_PI_2 ) * py;
2321 if (
mLF->permissibleZonePrepared() )
2329 numberCandidatesGenerated++;
2340 auto potentialCandidate = std::make_unique<
2343 lPos.emplace_back( std::move( potentialCandidate ) );
2344 numberCandidatesGenerated++;
2351 nbp = numberCandidatesGenerated;
2352 if ( maxPolygonCandidates > 0 && nbp < targetPolygonCandidates )
2362 }
while ( numTry < maxTry );
2364 nbp = numberCandidatesGenerated;
2377 const std::size_t maxPolygonCandidates =
mLF->layer()->maximumPolygonLabelCandidates();
2378 std::size_t candidatesCreated = 0;
2440 return candidatesCreated;
2444 return candidatesCreated;
2448 const double ringLength = ring->
length();
2449 const double circleArea = std::pow( ringLength, 2 ) / ( 4 * M_PI );
2450 const std::size_t candidatesForArea =
static_cast< std::size_t
>( std::ceil(
mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() * circleArea ) );
2451 const std::size_t targetPolygonCandidates = std::max(
static_cast< std::size_t
>( 16 ), maxPolygonCandidates > 0 ? std::min( maxPolygonCandidates, candidatesForArea ) : candidatesForArea );
2454 const double delta = ringLength / targetPolygonCandidates;
2457 const double maxDistCentroidToLabelX = std::max(
xmax - cx, cx -
xmin ) + distanceToLabel;
2458 const double maxDistCentroidToLabelY = std::max(
ymax - cy, cy -
ymin ) + distanceToLabel;
2459 const double estimateOfMaxPossibleDistanceCentroidToLabel = std::sqrt( maxDistCentroidToLabelX * maxDistCentroidToLabelX + maxDistCentroidToLabelY * maxDistCentroidToLabelY );
2462 const double labelAngle = 0;
2464 std::size_t i = lPos.size();
2471 createCandidateAtOrderedPositionOverPoint( labelX, labelY, quadrant,
x,
y, labelWidth, labelHeight, position, distanceToLabel * 0.5, visualMargin, 0, 0, labelAngle );
2474 if ( candidate->intersects( preparedBuffer.get() ) )
2492 const double centroidDistance = candidate->getDistanceToPoint( cx, cy,
false );
2493 const double centroidCost = centroidDistance / estimateOfMaxPossibleDistanceCentroidToLabel;
2494 candidate->setCost( centroidCost );
2496 lPos.emplace_back( std::move( candidate ) );
2497 candidatesCreated++;
2501 ring->
visitPointsByRegularDistance( delta, [&](
double x,
double y,
double,
double,
double startSegmentX,
double startSegmentY,
double,
double,
double endSegmentX,
double endSegmentY,
double,
double ) {
2503 float angle = atan2(
static_cast< float >( endSegmentY - startSegmentY ),
static_cast< float >( endSegmentX - startSegmentX ) ) * 180 / M_PI;
2508 if ( angle >= 0 && angle <= 5 )
2513 else if ( angle <= 85 )
2517 else if ( angle <= 90 )
2523 else if ( angle <= 95 )
2528 else if ( angle <= 175 )
2532 else if ( angle <= 180 )
2538 else if ( angle <= 185 )
2543 else if ( angle <= 265 )
2547 else if ( angle <= 270 )
2552 else if ( angle <= 275 )
2557 else if ( angle <= 355 )
2567 return !
pal->isCanceled();
2570 return candidatesCreated;
2575 std::vector< std::unique_ptr< LabelPosition > > lPos;
2576 double angleInRadians =
mLF->hasFixedAngle() ?
mLF->fixedAngle() : 0.0;
2578 if (
mLF->hasFixedPosition() )
2598 case GEOS_LINESTRING:
2601 else if (
mLF->layer()->isCurved() )
2621 else if ( allowOutside && ( std::fabs(
xmax -
xmin ) < labelWidth || std::fabs(
ymax -
ymin ) < labelHeight ) )
2628 std::size_t created = 0;
2631 switch (
mLF->layer()->arrangement() )
2686 int geomType = GEOSGeomTypeId_r( ctxt,
mGeos );
2688 double sizeCost = 0;
2689 if ( geomType == GEOS_LINESTRING )
2691 const double l =
length();
2694 double bbox_length = std::max( bbx[2] - bbx[0], bby[2] - bby[0] );
2695 if ( l >= bbox_length / 4 )
2698 sizeCost = 1 - ( l / ( bbox_length / 4 ) );
2700 else if ( geomType == GEOS_POLYGON )
2702 const double a =
area();
2705 double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
2706 if ( a >= bbox_area / 16 )
2709 sizeCost = 1 - ( a / ( bbox_area / 16 ) );
2715 for ( std::unique_ptr< LabelPosition > &pos : lPos )
2717 pos->setCost( pos->cost() + sizeCost / 100 );
2728 const double x1first =
x.front();
2729 const double x1last =
x.back();
2730 const double x2first = p2->
x.front();
2731 const double x2last = p2->
x.back();
2732 const double y1first =
y.front();
2733 const double y1last =
y.back();
2734 const double y2first = p2->
y.front();
2735 const double y2last = p2->
y.back();
2741 if ( ( !p2startTouches && !p2endTouches ) || ( p2startTouches && p2endTouches ) )
2747 const double p2otherX = p2startTouches ? x2last : x2first;
2748 const double p2otherY = p2startTouches ? y2last : y2first;
2754#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12 )
2755 return ( GEOSPreparedIntersectsXY_r( geosctxt,
preparedGeom(), p2otherX, p2otherY ) != 1 );
2757 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 1, 2 );
2758 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, p2otherX, p2otherY );
2760 return ( GEOSPreparedIntersects_r( geosctxt,
preparedGeom(), p2OtherEnd.get() ) != 1 );
2763 catch ( QgsGeosException &e )
2765 qWarning(
"GEOS exception: %s", e.what() );
2775 if ( !other->
mGeos )
2784 geos::unique_ptr g( GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 ) );
2787 if ( GEOSGeomTypeId_r( ctxt, gTmp.get() ) != GEOS_LINESTRING )
2795 mGeos = gTmp.release();
2804 catch ( QgsGeosException &e )
2806 qWarning(
"GEOS exception: %s", e.what() );
2814 if (
mLF->alwaysShow() )
2822 return mLF->priority() >= 0 ?
mLF->priority() :
mLF->layer()->priority();
2827 bool result =
false;
2829 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.
@ 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