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() )
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 double candidateCenterX, candidateCenterY;
947 while ( currentDistanceAlongLine <= totalLineLength )
949 if (
pal->isCanceled() )
954 line->
getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine, &candidateCenterX, &candidateCenterY );
957 double cost = totalLineLength > 0 ? std::fabs( lineAnchorPoint - currentDistanceAlongLine ) / totalLineLength : 0;
964 labelX = candidateCenterX;
967 labelX = candidateCenterX - labelWidth / 2;
970 labelX = candidateCenterX - labelWidth;
981 currentDistanceAlongLine += lineStepDistance;
985 if ( lineStepDistance < 0 )
1002 QVector< int > extremeAngleNodes;
1005 std::vector< double > &
x = line->
x;
1006 std::vector< double > &
y = line->
y;
1010 for (
int i = 1; i <= numberNodes - ( closedLine ? 1 : 2 ); ++i )
1012 double x1 =
x[i - 1];
1014 double x3 =
x[i == numberNodes - 1 ? 1 : i + 1];
1015 double y1 =
y[i - 1];
1017 double y3 =
y[i == numberNodes - 1 ? 1 : i + 1];
1022 double vertexAngle = M_PI - ( std::atan2( y3 - y2, x3 - x2 ) - std::atan2( y2 - y1, x2 - x1 ) );
1026 if ( vertexAngle < M_PI * 135.0 / 180.0 || vertexAngle > M_PI * 225.0 / 180.0 )
1027 extremeAngleNodes << i;
1029 extremeAngleNodes << numberNodes - 1;
1031 if ( extremeAngleNodes.isEmpty() )
1038 std::vector< double > segmentLengths( numberNodes - 1 );
1039 std::vector< double > distanceToSegment( numberNodes );
1040 double totalLineLength = 0.0;
1041 QVector< double > straightSegmentLengths;
1042 QVector< double > straightSegmentAngles;
1043 straightSegmentLengths.reserve( extremeAngleNodes.size() + 1 );
1044 straightSegmentAngles.reserve( extremeAngleNodes.size() + 1 );
1045 double currentStraightSegmentLength = 0;
1046 double longestSegmentLength = 0;
1047 double segmentStartX =
x[0];
1048 double segmentStartY =
y[0];
1049 for (
int i = 0; i < numberNodes - 1; i++ )
1052 distanceToSegment[i] = 0;
1054 distanceToSegment[i] = distanceToSegment[i - 1] + segmentLengths[i - 1];
1057 totalLineLength += segmentLengths[i];
1058 if ( extremeAngleNodes.contains( i ) )
1061 straightSegmentLengths << currentStraightSegmentLength;
1063 longestSegmentLength = std::max( longestSegmentLength, currentStraightSegmentLength );
1064 currentStraightSegmentLength = 0;
1065 segmentStartX =
x[i];
1066 segmentStartY =
y[i];
1068 currentStraightSegmentLength += segmentLengths[i];
1070 distanceToSegment[line->
nbPoints - 1] = totalLineLength;
1071 straightSegmentLengths << currentStraightSegmentLength;
1073 longestSegmentLength = std::max( longestSegmentLength, currentStraightSegmentLength );
1074 const double lineAnchorPoint = totalLineLength *
mLF->lineAnchorPercent();
1076 if ( totalLineLength < labelWidth )
1084 double lineStepDistance = ( totalLineLength - labelWidth );
1085 lineStepDistance = std::min( std::min( labelHeight, labelWidth ), lineStepDistance / candidateTargetCount );
1087 double distanceToEndOfSegment = 0.0;
1088 int lastNodeInSegment = 0;
1090 for (
int i = 0; i < straightSegmentLengths.count(); ++i )
1092 currentStraightSegmentLength = straightSegmentLengths.at( i );
1093 double currentSegmentAngle = straightSegmentAngles.at( i );
1094 lastNodeInSegment = extremeAngleNodes.at( i );
1095 double distanceToStartOfSegment = distanceToEndOfSegment;
1096 distanceToEndOfSegment = distanceToSegment[lastNodeInSegment];
1097 double distanceToCenterOfSegment = 0.5 * ( distanceToEndOfSegment + distanceToStartOfSegment );
1099 if ( currentStraightSegmentLength < labelWidth )
1103 double currentDistanceAlongLine = distanceToStartOfSegment;
1104 double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
1105 double candidateLength = 0.0;
1111 double segmentCost = 1.0 - ( distanceToEndOfSegment - distanceToStartOfSegment ) / longestSegmentLength;
1112 double segmentAngleCost = 1 - std::fabs( std::fmod( currentSegmentAngle, M_PI ) - M_PI_2 ) / M_PI_2;
1114 while ( currentDistanceAlongLine + labelWidth < distanceToEndOfSegment )
1116 if (
pal->isCanceled() )
1122 line->
getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine, &candidateStartX, &candidateStartY );
1123 line->
getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
1131 cost = candidateLength / labelWidth;
1137 cost = ( 1 - cost ) / 100;
1140 const double labelCenter = currentDistanceAlongLine + labelWidth / 2.0;
1141 double labelTextAnchor = 0;
1142 switch ( textPoint )
1145 labelTextAnchor = currentDistanceAlongLine;
1148 labelTextAnchor = currentDistanceAlongLine + labelWidth / 2.0;
1151 labelTextAnchor = currentDistanceAlongLine + labelWidth;
1158 const bool placementIsFlexible =
mLF->lineAnchorPercent() > 0.1 &&
mLF->lineAnchorPercent() < 0.9;
1160 if ( placementIsFlexible )
1163 double costCenter = 2 * std::fabs( labelCenter - distanceToCenterOfSegment ) / ( distanceToEndOfSegment - distanceToStartOfSegment );
1164 cost += costCenter * 0.0005;
1172 double costLineCenter = 2 * std::fabs( labelTextAnchor - lineAnchorPoint ) / totalLineLength;
1173 cost += costLineCenter * 0.0005;
1176 if ( placementIsFlexible )
1178 cost += segmentCost * 0.0005;
1179 cost += segmentAngleCost * 0.0001;
1187 angle = std::atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
1191 beta = angle + M_PI_2;
1196 bool isRightToLeft = ( angle > M_PI_2 || angle <= -M_PI_2 );
1204 if ( !
mLF->permissibleZonePrepared()
1206 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
1208 const double candidateCost = cost + ( reversed ? 0 : 0.001 );
1210 std::make_unique< LabelPosition >(
1212 candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ),
1213 candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ),
1227 if ( !
mLF->permissibleZonePrepared()
1229 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX + std::cos( beta ) * distanceLineToLabel, candidateStartY + std::sin( beta ) * distanceLineToLabel, labelWidth, labelHeight, angle ) )
1231 const double candidateCost = cost + ( !reversed ? 0 : 0.001 );
1233 std::make_unique< LabelPosition >(
1235 candidateStartX + std::cos( beta ) * distanceLineToLabel,
1236 candidateStartY + std::sin( beta ) * distanceLineToLabel,
1250 if ( !
mLF->permissibleZonePrepared()
1252 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) )
1254 const double candidateCost = cost + 0.002;
1256 std::make_unique< LabelPosition >(
1258 candidateStartX - labelHeight * std::cos( beta ) / 2,
1259 candidateStartY - labelHeight * std::sin( beta ) / 2,
1284 currentDistanceAlongLine += lineStepDistance;
1307 std::vector< double > &
x = line->
x;
1308 std::vector< double > &
y = line->
y;
1310 std::vector< double > segmentLengths(
nbPoints - 1 );
1311 std::vector< double > distanceToSegment(
nbPoints );
1313 double totalLineLength = 0.0;
1314 for (
int i = 0; i < line->
nbPoints - 1; i++ )
1317 distanceToSegment[i] = 0;
1319 distanceToSegment[i] = distanceToSegment[i - 1] + segmentLengths[i - 1];
1322 totalLineLength += segmentLengths[i];
1324 distanceToSegment[line->
nbPoints - 1] = totalLineLength;
1326 double lineStepDistance = ( totalLineLength - labelWidth );
1327 double currentDistanceAlongLine = 0;
1333 if ( totalLineLength > labelWidth )
1335 lineStepDistance = std::min( std::min( labelHeight, labelWidth ), lineStepDistance / candidateTargetCount );
1339 currentDistanceAlongLine = -( labelWidth - totalLineLength ) / 2.0;
1340 lineStepDistance = -1;
1341 totalLineLength = labelWidth;
1346 currentDistanceAlongLine = std::numeric_limits< double >::max();
1349 const double lineAnchorPoint = totalLineLength * std::min( 0.99,
mLF->lineAnchorPercent() );
1351 switch (
mLF->lineAnchorType() )
1357 switch ( textPoint )
1360 currentDistanceAlongLine = std::min( lineAnchorPoint, totalLineLength * 0.99 - labelWidth );
1363 currentDistanceAlongLine = std::min( lineAnchorPoint - labelWidth / 2, totalLineLength * 0.99 - labelWidth );
1366 currentDistanceAlongLine = std::min( lineAnchorPoint - labelWidth, totalLineLength * 0.99 - labelWidth );
1372 lineStepDistance = -1;
1376 double candidateLength;
1378 double candidateStartX, candidateStartY, candidateEndX, candidateEndY;
1382 if (
pal->isCanceled() )
1388 line->
getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine, &candidateStartX, &candidateStartY );
1389 line->
getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
1391 if ( currentDistanceAlongLine < 0 )
1401 cost = candidateLength / labelWidth;
1407 cost = ( 1 - cost ) / 100;
1411 double textAnchorPoint = 0;
1412 switch ( textPoint )
1415 textAnchorPoint = currentDistanceAlongLine;
1418 textAnchorPoint = currentDistanceAlongLine + labelWidth / 2;
1421 textAnchorPoint = currentDistanceAlongLine + labelWidth;
1427 double costCenter = totalLineLength > 0 ? std::fabs( lineAnchorPoint - textAnchorPoint ) / totalLineLength : 0;
1428 cost += costCenter / 1000;
1429 cost += initialCost;
1436 angle = std::atan2( candidateEndY - candidateStartY, candidateEndX - candidateStartX );
1440 beta = angle + M_PI_2;
1445 bool isRightToLeft = ( angle > M_PI_2 || angle <= -M_PI_2 );
1453 if ( !
mLF->permissibleZonePrepared()
1455 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX + std::cos( beta ) * distanceLineToLabel, candidateStartY + std::sin( beta ) * distanceLineToLabel, labelWidth, labelHeight, angle ) )
1457 const double candidateCost = cost + ( !reversed ? 0 : 0.001 );
1459 std::make_unique< LabelPosition >(
1461 candidateStartX + std::cos( beta ) * distanceLineToLabel,
1462 candidateStartY + std::sin( beta ) * distanceLineToLabel,
1476 if ( !
mLF->permissibleZonePrepared()
1478 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) )
1480 const double candidateCost = cost + ( !reversed ? 0.001 : 0 );
1482 std::make_unique< LabelPosition >(
1484 candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ),
1485 candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ),
1499 if ( !
mLF->permissibleZonePrepared()
1501 containsCandidate(
mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) )
1503 const double candidateCost = cost + 0.002;
1505 std::make_unique< LabelPosition >(
1507 candidateStartX - labelHeight * std::cos( beta ) / 2,
1508 candidateStartY - labelHeight * std::sin( beta ) / 2,
1533 currentDistanceAlongLine += lineStepDistance;
1537 if ( lineStepDistance < 0 )
1546 const std::vector< double> &pathDistances,
1548 const double offsetAlongLine,
1549 bool &labeledLineSegmentIsRightToLeft,
1550 bool applyAngleConstraints,
1552 double additionalCharacterSpacing,
1553 double additionalWordSpacing
1557 Q_ASSERT( metrics );
1559 const double maximumCharacterAngleInside = applyAngleConstraints ? std::fabs( qgis::down_cast< QgsTextLabelFeature *>(
mLF )->maximumCharacterAngleInside() ) : -1;
1560 const double maximumCharacterAngleOutside = applyAngleConstraints ? std::fabs( qgis::down_cast< QgsTextLabelFeature *>(
mLF )->maximumCharacterAngleOutside() ) : -1;
1562 std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > placement(
1564 generateCurvedTextPlacement( *metrics, mapShape->
x.data(), mapShape->
y.data(), mapShape->
nbPoints, pathDistances, offsetAlongLine, direction, maximumCharacterAngleInside, maximumCharacterAngleOutside, flags, additionalCharacterSpacing, additionalWordSpacing )
1569 if ( placement->graphemePlacement.empty() )
1572 auto it = placement->graphemePlacement.constBegin();
1575 firstPosition->setUpsideDownCharCount( placement->upsideDownCharCount );
1576 firstPosition->setPartId( it->graphemeIndex );
1580 bool skipWhitespace =
false;
1581 switch (
mLF->whitespaceCollisionHandling() )
1587 skipWhitespace =
true;
1591 while ( it != placement->graphemePlacement.constEnd() )
1593 if ( skipWhitespace && it->isWhitespace )
1600 position->setPartId( it->graphemeIndex );
1603 previousPosition->
setNextPart( std::move( position ) );
1604 previousPosition = nextPosition;
1608 return firstPosition;
1620 const int characterCount = li->
count();
1621 if ( characterCount == 0 )
1624 switch (
mLF->curvedLabelMode() )
1639 const int characterCount = li->
count();
1642 double totalCharacterWidth = 0;
1644 for (
int i = 0; i < characterCount; ++i )
1647 if ( stretchWordSpacingToFit && li->
grapheme( i ) ==
' ' )
1652 if ( spaceCount == 0 )
1655 stretchWordSpacingToFit =
false;
1659 const bool usingStretchToFitMode = stretchCharacterSpacingToFit || stretchWordSpacingToFit;
1664 std::unique_ptr< PointSet > expanded;
1665 double shapeLength = mapShape->
length();
1670 allowOverrun =
false;
1673 if ( !usingStretchToFitMode )
1679 switch (
mLF->lineAnchorType() )
1682 overrun = std::min(
mLF->overrunDistance(), totalCharacterWidth * 0.95 );
1687 overrun = std::max(
mLF->overrunDistance(), totalCharacterWidth * 1.05 );
1691 if ( totalCharacterWidth > shapeLength )
1693 if ( !allowOverrun || shapeLength < totalCharacterWidth - 2 * overrun )
1704 if ( allowOverrun && overrun > 0 )
1707 expanded = mapShape->
clone();
1708 expanded->extendLineByDistance( overrun, overrun,
mLF->overrunSmoothDistance() );
1709 mapShape = expanded.get();
1710 shapeLength += 2 * overrun;
1719 std::unique_ptr< PointSet > mapShapeOffsetPositive;
1720 bool positiveShapeHasNegativeDistance =
false;
1721 std::unique_ptr< PointSet > mapShapeOffsetNegative;
1722 bool negativeShapeHasNegativeDistance =
false;
1723 if ( hasAboveBelowLinePlacement && !
qgsDoubleNear( offsetDistance, 0 ) )
1727 mapShapeOffsetPositive = mapShape->
clone();
1729 mapShapeOffsetNegative = mapShape->
clone();
1732 if ( mapShapeOffsetPositive )
1733 mapShapeOffsetPositive->offsetCurveByDistance( offsetDistance );
1734 positiveShapeHasNegativeDistance = offsetDistance < 0;
1735 if ( mapShapeOffsetNegative )
1736 mapShapeOffsetNegative->offsetCurveByDistance( offsetDistance * -1 );
1737 negativeShapeHasNegativeDistance = offsetDistance > 0;
1752 if ( mapShapeOffsetPositive )
1753 mapShapeOffsetPositive->offsetCurveByDistance( offsetDistance * -1 );
1754 positiveShapeHasNegativeDistance = offsetDistance > 0;
1755 if ( mapShapeOffsetNegative )
1756 mapShapeOffsetNegative->offsetCurveByDistance( offsetDistance );
1757 negativeShapeHasNegativeDistance = offsetDistance < 0;
1763 std::vector< std::unique_ptr< LabelPosition >> positions;
1764 std::unique_ptr< LabelPosition > backupPlacement;
1767 PointSet *currentMapShape =
nullptr;
1770 currentMapShape = mapShapeOffsetPositive.get();
1774 currentMapShape = mapShape;
1778 currentMapShape = mapShapeOffsetNegative.get();
1780 if ( !currentMapShape )
1784 const auto [pathDistances, totalDistance] = currentMapShape->
edgeDistances();
1788 double lineAnchorPoint = 0;
1789 if ( !usingStretchToFitMode )
1791 if ( originalPoint )
1796 lineAnchorPoint = currentMapShape->
lineLocatePoint( originalPoint.get() );
1800 lineAnchorPoint = totalDistance *
mLF->lineAnchorPercent();
1802 lineAnchorPoint = totalDistance - lineAnchorPoint;
1806 if (
pal->isCanceled() )
1810 double delta = std::max( li->
characterHeight( 0 ) / 6, totalDistance / candidateTargetCount );
1813 double distanceAlongLineToStartCandidate = 0;
1814 bool singleCandidateOnly =
false;
1815 double additionalCharacterSpacing = 0.0;
1816 double additionalWordSpacing = 0.0;
1817 if ( usingStretchToFitMode )
1820 double extraSpace = totalDistance - totalCharacterWidth;
1825 if ( extraSpace > 0 )
1826 extraSpace *= 0.995;
1828 extraSpace *= 1.005;
1830 if ( stretchWordSpacingToFit )
1832 if ( spaceCount > 0 )
1833 additionalWordSpacing = extraSpace / spaceCount;
1839 if ( characterCount > 1 )
1840 additionalCharacterSpacing = extraSpace / ( characterCount - 1 );
1844 distanceAlongLineToStartCandidate = 0;
1845 delta = totalDistance + 1.0;
1846 singleCandidateOnly =
true;
1850 switch (
mLF->lineAnchorType() )
1856 switch ( textPoint )
1859 distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint, 0.0, totalDistance * 0.999 );
1862 distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint -
getLabelWidth() / 2, 0.0, totalDistance * 0.999 -
getLabelWidth() / 2 );
1865 distanceAlongLineToStartCandidate = std::clamp( lineAnchorPoint -
getLabelWidth(), 0.0, totalDistance * 0.999 -
getLabelWidth() );
1871 singleCandidateOnly =
true;
1876 bool hasTestedFirstPlacement =
false;
1877 for ( ; distanceAlongLineToStartCandidate <= totalDistance; distanceAlongLineToStartCandidate += delta )
1879 if ( singleCandidateOnly && hasTestedFirstPlacement )
1882 if (
pal->isCanceled() )
1885 hasTestedFirstPlacement =
true;
1887 bool labeledLineSegmentIsRightToLeft =
false;
1894 std::unique_ptr< LabelPosition > labelPosition
1895 =
curvedPlacementAtOffset( currentMapShape, pathDistances, direction, distanceAlongLineToStartCandidate, labeledLineSegmentIsRightToLeft, !singleCandidateOnly, curvedTextFlags, additionalCharacterSpacing, additionalWordSpacing );
1896 if ( !labelPosition )
1902 bool isBackupPlacementOnly =
false;
1905 if ( ( currentMapShape == mapShapeOffsetPositive.get() && positiveShapeHasNegativeDistance ) || ( currentMapShape == mapShapeOffsetNegative.get() && negativeShapeHasNegativeDistance ) )
1907 labeledLineSegmentIsRightToLeft = !labeledLineSegmentIsRightToLeft;
1913 isBackupPlacementOnly =
true;
1920 isBackupPlacementOnly =
true;
1926 backupPlacement.reset();
1929 const double angleDiff = labelPosition->angleDifferential();
1930 const double angleDiffAvg = characterCount > 1 ? ( angleDiff / ( characterCount - 1 ) ) : 0;
1934 const bool anchorIsFlexiblePlacement = !singleCandidateOnly &&
mLF->lineAnchorPercent() > 0.1 &&
mLF->lineAnchorPercent() < 0.9;
1935 double cost = angleDiffAvg / 100;
1936 if ( cost < 0.0001 )
1940 if ( !usingStretchToFitMode )
1943 double labelTextAnchor = 0;
1944 switch ( textPoint )
1947 labelTextAnchor = distanceAlongLineToStartCandidate;
1950 labelTextAnchor = distanceAlongLineToStartCandidate +
getLabelWidth() / 2;
1953 labelTextAnchor = distanceAlongLineToStartCandidate +
getLabelWidth();
1959 double costCenter = std::fabs( lineAnchorPoint - labelTextAnchor ) / totalDistance;
1960 cost += costCenter / ( anchorIsFlexiblePlacement ? 100 : 10 );
1963 const bool isBelow = ( offset !=
NoOffset ) && labeledLineSegmentIsRightToLeft;
1975 labelPosition->setCost( cost );
1977 auto p = std::make_unique< LabelPosition >( *labelPosition );
1978 if ( p &&
mLF->permissibleZonePrepared() )
1982 while ( within && currentPos )
1985 currentPos = currentPos->
nextPart();
1995 if ( isBackupPlacementOnly )
1996 backupPlacement = std::move( p );
1998 positions.emplace_back( std::move( p ) );
2003 for ( std::unique_ptr< LabelPosition > &pos : positions )
2005 lPos.emplace_back( std::move( pos ) );
2008 if ( backupPlacement )
2009 lPos.emplace_back( std::move( backupPlacement ) );
2011 return positions.size();
2018 const int characterCount = metrics->
count();
2020 if ( characterCount == 0 || vertexCount == 0 )
2023 const double distLabel =
mLF->distLabel();
2025 std::unique_ptr< LabelPosition > firstPosition;
2028 int vertexIndex = 0;
2029 int characterIndex = -1;
2030 for ( ; vertexIndex < vertexCount; ++vertexIndex )
2032 if (
pal->isCanceled() )
2035 bool isWhiteSpace =
true;
2036 while ( isWhiteSpace )
2039 if ( characterIndex >= characterCount )
2042 isWhiteSpace = metrics->
grapheme( characterIndex ).trimmed().isEmpty() || metrics->
grapheme( characterIndex ) ==
'\t';
2045 if ( characterIndex >= characterCount )
2048 double x = mapShape->
x[vertexIndex];
2049 double y = mapShape->
y[vertexIndex];
2054 if ( vertexIndex < vertexCount - 1 )
2056 angle = std::atan2( mapShape->
y[vertexIndex + 1] -
y, mapShape->
x[vertexIndex + 1] -
x );
2058 else if ( vertexIndex > 0 )
2060 angle = std::atan2(
y - mapShape->
y[vertexIndex - 1],
x - mapShape->
x[vertexIndex - 1] );
2064 x -= std::sin( angle ) * distLabel;
2065 y += std::cos( angle ) * distLabel;
2071 currentPosition->setPartId( characterIndex );
2073 if ( !firstPosition )
2075 firstPosition = std::move( currentPosition );
2076 previousPosition = firstPosition.get();
2081 previousPosition->
setNextPart( std::move( currentPosition ) );
2082 previousPosition = rawCurrent;
2086 if ( !firstPosition )
2089 if (
mLF->permissibleZonePrepared() )
2093 while ( within && currentPos )
2096 currentPos = currentPos->
nextPart();
2104 lPos.emplace_back( std::move( firstPosition ) );
2125 const std::size_t maxPolygonCandidates =
mLF->layer()->maximumPolygonLabelCandidates();
2126 const std::size_t targetPolygonCandidates = maxPolygonCandidates > 0
2127 ? std::min( maxPolygonCandidates,
static_cast< std::size_t
>( std::ceil(
mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() *
area() ) ) )
2130 const double totalArea =
area();
2132 mapShape->
parent =
nullptr;
2134 if (
pal->isCanceled() )
2137 QVector<PointSet *> shapes_final =
splitPolygons( mapShape, labelWidth, labelHeight );
2140 for (
PointSet *ps : shapes_final )
2146 std::size_t nbp = 0;
2148 if ( !shapes_final.isEmpty() )
2156 double diago = std::sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
2158 std::vector< OrientedConvexHullBoundingBox > boxes;
2159 boxes.reserve( shapes_final.size() );
2162 while ( !shapes_final.isEmpty() )
2164 PointSet *shape = shapes_final.takeFirst();
2168 boxes.emplace_back( box );
2174 if (
pal->isCanceled() )
2177 double densityX = 1.0 / std::sqrt(
mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() );
2178 double densityY = densityX;
2183 int maxTry =
mLF->permissibleZonePrepared() ? 7 : 10;
2185 std::size_t numberCandidatesGenerated = 0;
2200 double dx = densityX;
2201 double dy = densityY;
2202 if ( numTry == 0 && maxPolygonCandidates > 0 )
2205 const double boxArea = box.width * box.length;
2206 double maxThisBox = targetPolygonCandidates * boxArea / totalArea;
2207 dx = std::max( dx, std::sqrt( boxArea / maxThisBox ) * 0.8 );
2211 if (
pal->isCanceled() )
2212 return numberCandidatesGenerated;
2223 if (
mLF->permissibleZone().boundingBox().width() < labelWidth ||
mLF->permissibleZone().boundingBox().height() < labelHeight )
2230 bool enoughPlace =
false;
2234 px = ( box.x[0] + box.x[2] ) / 2 - labelWidth;
2235 py = ( box.y[0] + box.y[2] ) / 2 - labelHeight;
2241 for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
2243 for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
2247 enoughPlace =
false;
2263 else if ( box.length > 1.5 * labelWidth && box.width > 1.5 * labelWidth )
2265 if ( box.alpha <= M_PI_4 )
2271 alpha = box.alpha - M_PI_2;
2274 else if ( box.length > box.width )
2276 alpha = box.alpha - M_PI_2;
2283 beta = std::atan2( labelHeight, labelWidth ) + alpha;
2289 dlx = std::cos( beta ) * diago;
2290 dly = std::sin( beta ) * diago;
2292 double px0 = box.width / 2.0;
2293 double py0 = box.length / 2.0;
2295 px0 -= std::ceil( px0 / dx ) * dx;
2296 py0 -= std::ceil( py0 / dy ) * dy;
2298 for ( px = px0; px <= box.width; px += dx )
2300 if (
pal->isCanceled() )
2303 for ( py = py0; py <= box.length; py += dy )
2305 rx = std::cos( box.alpha ) * px + std::cos( box.alpha - M_PI_2 ) * py;
2306 ry = std::sin( box.alpha ) * px + std::sin( box.alpha - M_PI_2 ) * py;
2311 if (
mLF->permissibleZonePrepared() )
2319 numberCandidatesGenerated++;
2330 auto potentialCandidate = std::make_unique<
2333 lPos.emplace_back( std::move( potentialCandidate ) );
2334 numberCandidatesGenerated++;
2341 nbp = numberCandidatesGenerated;
2342 if ( maxPolygonCandidates > 0 && nbp < targetPolygonCandidates )
2352 }
while ( numTry < maxTry );
2354 nbp = numberCandidatesGenerated;
2367 const std::size_t maxPolygonCandidates =
mLF->layer()->maximumPolygonLabelCandidates();
2368 std::size_t candidatesCreated = 0;
2430 return candidatesCreated;
2434 return candidatesCreated;
2438 const double ringLength = ring->
length();
2439 const double circleArea = std::pow( ringLength, 2 ) / ( 4 * M_PI );
2440 const std::size_t candidatesForArea =
static_cast< std::size_t
>( std::ceil(
mLF->layer()->mPal->maximumPolygonCandidatesPerMapUnitSquared() * circleArea ) );
2441 const std::size_t targetPolygonCandidates = std::max(
static_cast< std::size_t
>( 16 ), maxPolygonCandidates > 0 ? std::min( maxPolygonCandidates, candidatesForArea ) : candidatesForArea );
2444 const double delta = ringLength / targetPolygonCandidates;
2447 const double maxDistCentroidToLabelX = std::max(
xmax - cx, cx -
xmin ) + distanceToLabel;
2448 const double maxDistCentroidToLabelY = std::max(
ymax - cy, cy -
ymin ) + distanceToLabel;
2449 const double estimateOfMaxPossibleDistanceCentroidToLabel = std::sqrt( maxDistCentroidToLabelX * maxDistCentroidToLabelX + maxDistCentroidToLabelY * maxDistCentroidToLabelY );
2452 const double labelAngle = 0;
2454 std::size_t i = lPos.size();
2461 createCandidateAtOrderedPositionOverPoint( labelX, labelY, quadrant,
x,
y, labelWidth, labelHeight, position, distanceToLabel * 0.5, visualMargin, 0, 0, labelAngle );
2464 if ( candidate->intersects( preparedBuffer.get() ) )
2482 const double centroidDistance = candidate->getDistanceToPoint( cx, cy,
false );
2483 const double centroidCost = centroidDistance / estimateOfMaxPossibleDistanceCentroidToLabel;
2484 candidate->setCost( centroidCost );
2486 lPos.emplace_back( std::move( candidate ) );
2487 candidatesCreated++;
2491 ring->
visitPointsByRegularDistance( delta, [&](
double x,
double y,
double,
double,
double startSegmentX,
double startSegmentY,
double,
double,
double endSegmentX,
double endSegmentY,
double,
double ) {
2493 float angle = atan2(
static_cast< float >( endSegmentY - startSegmentY ),
static_cast< float >( endSegmentX - startSegmentX ) ) * 180 / M_PI;
2498 if ( angle >= 0 && angle <= 5 )
2503 else if ( angle <= 85 )
2507 else if ( angle <= 90 )
2513 else if ( angle <= 95 )
2518 else if ( angle <= 175 )
2522 else if ( angle <= 180 )
2528 else if ( angle <= 185 )
2533 else if ( angle <= 265 )
2537 else if ( angle <= 270 )
2542 else if ( angle <= 275 )
2547 else if ( angle <= 355 )
2557 return !
pal->isCanceled();
2560 return candidatesCreated;
2565 std::vector< std::unique_ptr< LabelPosition > > lPos;
2566 double angleInRadians =
mLF->hasFixedAngle() ?
mLF->fixedAngle() : 0.0;
2568 if (
mLF->hasFixedPosition() )
2588 case GEOS_LINESTRING:
2591 else if (
mLF->layer()->isCurved() )
2611 else if ( allowOutside && ( std::fabs(
xmax -
xmin ) < labelWidth || std::fabs(
ymax -
ymin ) < labelHeight ) )
2618 std::size_t created = 0;
2621 switch (
mLF->layer()->arrangement() )
2676 int geomType = GEOSGeomTypeId_r( ctxt,
mGeos );
2678 double sizeCost = 0;
2679 if ( geomType == GEOS_LINESTRING )
2681 const double l =
length();
2684 double bbox_length = std::max( bbx[2] - bbx[0], bby[2] - bby[0] );
2685 if ( l >= bbox_length / 4 )
2688 sizeCost = 1 - ( l / ( bbox_length / 4 ) );
2690 else if ( geomType == GEOS_POLYGON )
2692 const double a =
area();
2695 double bbox_area = ( bbx[2] - bbx[0] ) * ( bby[2] - bby[0] );
2696 if ( a >= bbox_area / 16 )
2699 sizeCost = 1 - ( a / ( bbox_area / 16 ) );
2705 for ( std::unique_ptr< LabelPosition > &pos : lPos )
2707 pos->setCost( pos->cost() + sizeCost / 100 );
2718 const double x1first =
x.front();
2719 const double x1last =
x.back();
2720 const double x2first = p2->
x.front();
2721 const double x2last = p2->
x.back();
2722 const double y1first =
y.front();
2723 const double y1last =
y.back();
2724 const double y2first = p2->
y.front();
2725 const double y2last = p2->
y.back();
2731 if ( ( !p2startTouches && !p2endTouches ) || ( p2startTouches && p2endTouches ) )
2737 const double p2otherX = p2startTouches ? x2last : x2first;
2738 const double p2otherY = p2startTouches ? y2last : y2first;
2744#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12 )
2745 return ( GEOSPreparedIntersectsXY_r( geosctxt,
preparedGeom(), p2otherX, p2otherY ) != 1 );
2747 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 1, 2 );
2748 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, p2otherX, p2otherY );
2750 return ( GEOSPreparedIntersects_r( geosctxt,
preparedGeom(), p2OtherEnd.get() ) != 1 );
2753 catch ( QgsGeosException &e )
2755 qWarning(
"GEOS exception: %s", e.what() );
2765 if ( !other->
mGeos )
2774 geos::unique_ptr g( GEOSGeom_createCollection_r( ctxt, GEOS_MULTILINESTRING, geoms, 2 ) );
2777 if ( GEOSGeomTypeId_r( ctxt, gTmp.get() ) != GEOS_LINESTRING )
2785 mGeos = gTmp.release();
2794 catch ( QgsGeosException &e )
2796 qWarning(
"GEOS exception: %s", e.what() );
2804 if (
mLF->alwaysShow() )
2812 return mLF->priority() >= 0 ?
mLF->priority() :
mLF->layer()->priority();
2817 bool result =
false;
2819 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. Applies to polygon layers only...
@ 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.
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...
AnchorTextPoint
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 lineAnchorPercent() value. Values <25% will use the s...
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.
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.
std::size_t createHorizontalCandidatesAlongLine(std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, Pal *pal)
Generate horizontal candidates for line 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