39 #include <QDomDocument>
40 #include <QDomElement>
45 : mPenStyle( penStyle )
49 mCustomDashVector << 5 << 2;
59 mCustomDashPatternUnit = unit;
60 mDashPatternOffsetUnit = unit;
61 mTrimDistanceStartUnit = unit;
62 mTrimDistanceEndUnit = unit;
86 mCustomDashPatternMapUnitScale = scale;
106 if ( props.contains( QStringLiteral(
"line_color" ) ) )
110 else if ( props.contains( QStringLiteral(
"outline_color" ) ) )
114 else if ( props.contains( QStringLiteral(
"color" ) ) )
119 if ( props.contains( QStringLiteral(
"line_width" ) ) )
121 width = props[QStringLiteral(
"line_width" )].toDouble();
123 else if ( props.contains( QStringLiteral(
"outline_width" ) ) )
125 width = props[QStringLiteral(
"outline_width" )].toDouble();
127 else if ( props.contains( QStringLiteral(
"width" ) ) )
130 width = props[QStringLiteral(
"width" )].toDouble();
132 if ( props.contains( QStringLiteral(
"line_style" ) ) )
136 else if ( props.contains( QStringLiteral(
"outline_style" ) ) )
140 else if ( props.contains( QStringLiteral(
"penstyle" ) ) )
146 if ( props.contains( QStringLiteral(
"line_width_unit" ) ) )
150 else if ( props.contains( QStringLiteral(
"outline_width_unit" ) ) )
154 else if ( props.contains( QStringLiteral(
"width_unit" ) ) )
159 if ( props.contains( QStringLiteral(
"width_map_unit_scale" ) ) )
161 if ( props.contains( QStringLiteral(
"offset" ) ) )
162 l->
setOffset( props[QStringLiteral(
"offset" )].toDouble() );
163 if ( props.contains( QStringLiteral(
"offset_unit" ) ) )
165 if ( props.contains( QStringLiteral(
"offset_map_unit_scale" ) ) )
167 if ( props.contains( QStringLiteral(
"joinstyle" ) ) )
169 if ( props.contains( QStringLiteral(
"capstyle" ) ) )
172 if ( props.contains( QStringLiteral(
"use_custom_dash" ) ) )
176 if ( props.contains( QStringLiteral(
"customdash" ) ) )
180 if ( props.contains( QStringLiteral(
"customdash_unit" ) ) )
184 if ( props.contains( QStringLiteral(
"customdash_map_unit_scale" ) ) )
189 if ( props.contains( QStringLiteral(
"draw_inside_polygon" ) ) )
194 if ( props.contains( QStringLiteral(
"ring_filter" ) ) )
199 if ( props.contains( QStringLiteral(
"dash_pattern_offset" ) ) )
201 if ( props.contains( QStringLiteral(
"dash_pattern_offset_unit" ) ) )
203 if ( props.contains( QStringLiteral(
"dash_pattern_offset_map_unit_scale" ) ) )
206 if ( props.contains( QStringLiteral(
"trim_distance_start" ) ) )
208 if ( props.contains( QStringLiteral(
"trim_distance_start_unit" ) ) )
210 if ( props.contains( QStringLiteral(
"trim_distance_start_map_unit_scale" ) ) )
212 if ( props.contains( QStringLiteral(
"trim_distance_end" ) ) )
214 if ( props.contains( QStringLiteral(
"trim_distance_end_unit" ) ) )
216 if ( props.contains( QStringLiteral(
"trim_distance_end_map_unit_scale" ) ) )
219 if ( props.contains( QStringLiteral(
"align_dash_pattern" ) ) )
222 if ( props.contains( QStringLiteral(
"tweak_dash_pattern_on_corners" ) ) )
232 return QStringLiteral(
"SimpleLine" );
239 mPen.setColor( penColor );
241 mPen.setWidthF( scaledWidth );
245 const double dashWidthDiv = std::max( 1.0, scaledWidth );
246 if ( mUseCustomDashPattern )
248 mPen.setStyle( Qt::CustomDashLine );
252 QVector<qreal> scaledVector;
253 QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
254 for ( ; it != mCustomDashVector.constEnd(); ++it )
259 mPen.setDashPattern( scaledVector );
263 mPen.setStyle( mPenStyle );
266 if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
271 mPen.setJoinStyle( mPenJoinStyle );
272 mPen.setCapStyle( mPenCapStyle );
277 selColor.setAlphaF( context.
opacity() );
278 mSelPen.setColor( selColor );
295 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
302 if ( mDrawInsidePolygon )
310 if ( mDrawInsidePolygon )
313 QPainterPath clipPath;
314 clipPath.addPolygon( points );
319 for (
auto it = rings->constBegin(); it != rings->constEnd(); ++it )
321 QPolygonF ring = *it;
322 clipPath.addPolygon( ring );
327 p->setClipPath( clipPath, Qt::IntersectClip );
350 for (
const QPolygonF &ring : std::as_const( *rings ) )
366 if ( mDrawInsidePolygon )
382 QPolygonF points = pts;
384 double startTrim = mTrimDistanceStart;
390 double endTrim = mTrimDistanceEnd;
397 double totalLength = -1;
401 startTrim = startTrim * 0.01 * totalLength;
409 if ( totalLength < 0 )
411 endTrim = endTrim * 0.01 * totalLength;
424 mPen.setColor( penColor );
427 applyDataDefinedSymbology( context, mPen, mSelPen,
offset );
429 const QPen pen = context.
selected() ? mSelPen : mPen;
431 if ( !pen.dashPattern().isEmpty() )
434 const QVector<double> pattern = pen.dashPattern();
435 bool foundNonNull =
false;
436 for (
int i = 0; i < pattern.size(); ++i )
448 p->setBrush( Qt::NoBrush );
451 std::unique_ptr< QgsScopedQPainterState > painterState;
452 if ( points.size() <= 2 &&
455 ( p->renderHints() & QPainter::Antialiasing ) )
457 painterState = std::make_unique< QgsScopedQPainterState >( p );
458 p->setRenderHint( QPainter::Antialiasing,
false );
461 const bool applyPatternTweaks = mAlignDashPattern
462 && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
463 && pen.dashOffset() == 0;
467 if ( applyPatternTweaks )
469 drawPathWithDashPatternTweaks( p, points, pen );
475 path.addPolygon( points );
490 for (
const QPolygonF &part : mline )
492 if ( applyPatternTweaks )
494 drawPathWithDashPatternTweaks( p, part, pen );
500 path.addPolygon( part );
511 map[QStringLiteral(
"line_width" )] = QString::number(
mWidth );
517 map[QStringLiteral(
"offset" )] = QString::number(
mOffset );
520 map[QStringLiteral(
"use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral(
"1" ) : QStringLiteral(
"0" ) );
524 map[QStringLiteral(
"dash_pattern_offset" )] = QString::number( mDashPatternOffset );
527 map[QStringLiteral(
"trim_distance_start" )] = QString::number( mTrimDistanceStart );
530 map[QStringLiteral(
"trim_distance_end" )] = QString::number( mTrimDistanceEnd );
533 map[QStringLiteral(
"draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral(
"1" ) : QStringLiteral(
"0" ) );
534 map[QStringLiteral(
"ring_filter" )] = QString::number(
static_cast< int >(
mRingFilter ) );
535 map[QStringLiteral(
"align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral(
"1" ) : QStringLiteral(
"0" );
536 map[QStringLiteral(
"tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral(
"1" ) : QStringLiteral(
"0" );
575 if ( mPenStyle == Qt::NoPen )
578 QDomElement symbolizerElem = doc.createElement( QStringLiteral(
"se:LineSymbolizer" ) );
579 if ( !props.value( QStringLiteral(
"uom" ), QString() ).toString().isEmpty() )
580 symbolizerElem.setAttribute( QStringLiteral(
"uom" ), props.value( QStringLiteral(
"uom" ), QString() ).toString() );
581 element.appendChild( symbolizerElem );
587 QDomElement strokeElem = doc.createElement( QStringLiteral(
"se:Stroke" ) );
588 symbolizerElem.appendChild( strokeElem );
590 Qt::PenStyle
penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
599 QDomElement perpOffsetElem = doc.createElement( QStringLiteral(
"se:PerpendicularOffset" ) );
602 symbolizerElem.appendChild( perpOffsetElem );
608 if ( mUseCustomDashPattern )
611 mPen.color(), mPenJoinStyle,
612 mPenCapStyle,
mOffset, &mCustomDashVector );
625 QDomElement strokeElem = element.firstChildElement( QStringLiteral(
"Stroke" ) );
626 if ( strokeElem.isNull() )
643 QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral(
"PerpendicularOffset" ) );
644 if ( !perpOffsetElem.isNull() )
647 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
652 double scaleFactor = 1.0;
653 const QString uom = element.attribute( QStringLiteral(
"uom" ) );
668 void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology(
QgsSymbolRenderContext &context, QPen &pen, QPen &selPen,
double &offset )
674 bool hasStrokeWidthExpression =
false;
681 pen.setWidthF( scaledWidth );
682 selPen.setWidthF( scaledWidth );
683 hasStrokeWidthExpression =
true;
692 penColor.setAlphaF( context.
opacity() * penColor.alphaF() );
693 pen.setColor( penColor );
707 const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
711 QVector<qreal> dashVector;
713 if ( !exprVal.isNull() )
715 QStringList dashList = exprVal.toString().split(
';' );
716 QStringList::const_iterator dashIt = dashList.constBegin();
717 for ( ; dashIt != dashList.constEnd(); ++dashIt )
721 pen.setDashPattern( dashVector );
728 QVector<qreal> scaledVector;
729 for (
double v : std::as_const( mCustomDashVector ) )
734 mPen.setDashPattern( scaledVector );
738 double patternOffset = mDashPatternOffset;
751 if ( !exprVal.isNull() )
760 if ( !exprVal.isNull() )
769 if ( !exprVal.isNull() )
774 void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter,
const QPolygonF &points, QPen pen )
const
776 if ( pen.dashPattern().empty() || points.size() < 2 )
779 QVector< qreal > sourcePattern = pen.dashPattern();
780 const double dashWidthDiv = std::max( 1.0001, pen.widthF() );
782 for (
int i = 0; i < sourcePattern.size(); ++ i )
783 sourcePattern[i] *= pen.widthF();
785 if ( pen.widthF() <= 1.0 )
786 pen.setWidthF( 1.0001 );
788 QVector< qreal > buffer;
789 QPolygonF bufferedPoints;
790 QPolygonF previousSegmentBuffer;
795 auto ptIt = points.constBegin();
796 double totalBufferLength = 0;
797 int patternIndex = 0;
798 double currentRemainingDashLength = 0;
799 double currentRemainingGapLength = 0;
801 auto compressPattern = [](
const QVector< qreal > &buffer ) -> QVector< qreal >
803 QVector< qreal > result;
804 result.reserve( buffer.size() );
805 for (
auto it = buffer.begin(); it != buffer.end(); )
809 while ( dash == 0 && !result.empty() )
811 result.last() += gap;
813 if ( it == buffer.end() )
818 while ( gap == 0 && it != buffer.end() )
823 result << dash << gap;
828 double currentBufferLineLength = 0;
829 auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, ¤tRemainingDashLength, ¤tRemainingGapLength, ¤tBufferLineLength, &totalBufferLength,
830 dashWidthDiv, &compressPattern]( QPointF * nextPoint )
832 if ( buffer.empty() || bufferedPoints.size() < 2 )
837 if ( currentRemainingDashLength )
840 buffer << currentRemainingDashLength << 0.0;
841 totalBufferLength += currentRemainingDashLength;
843 QVector< qreal > compressed = compressPattern( buffer );
844 if ( !currentRemainingDashLength )
847 totalBufferLength -= compressed.last();
848 compressed.last() = 0;
852 const double scaleFactor = currentBufferLineLength / totalBufferLength;
854 bool shouldFlushPreviousSegmentBuffer =
false;
856 if ( !previousSegmentBuffer.empty() )
860 if ( !firstDashSubstring.empty() )
866 compressed = compressed.mid( 2 );
867 shouldFlushPreviousSegmentBuffer = !compressed.empty();
870 if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
872 QPen adjustedPen = pen;
873 adjustedPen.setStyle( Qt::SolidLine );
874 painter->setPen( adjustedPen );
876 path.addPolygon( previousSegmentBuffer );
877 painter->drawPath( path );
878 previousSegmentBuffer.clear();
881 double finalDash = 0;
888 if ( !compressed.empty() )
890 finalDash = compressed.at( compressed.size() - 2 );
891 const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
893 const QPolygonF thisPoints = bufferedPoints;
899 previousSegmentBuffer << bufferedPoints;
903 currentBufferLineLength = 0;
904 currentRemainingDashLength = 0;
905 currentRemainingGapLength = 0;
906 totalBufferLength = 0;
909 if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
911 QPen adjustedPen = pen;
912 if ( !compressed.empty() )
915 compressed = compressed.mid( 0, 32 );
916 std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
917 adjustedPen.setDashPattern( compressed );
921 adjustedPen.setStyle( Qt::SolidLine );
924 painter->setPen( adjustedPen );
926 path.addPolygon( bufferedPoints );
927 painter->drawPath( path );
930 bufferedPoints.clear();
936 bufferedPoints << p2;
937 for ( ; ptIt != points.constEnd(); ++ptIt )
945 double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
946 currentBufferLineLength += remainingSegmentDistance;
950 if ( currentRemainingDashLength > 0 )
953 if ( remainingSegmentDistance >= currentRemainingDashLength )
956 buffer << currentRemainingDashLength << 0.0;
957 totalBufferLength += currentRemainingDashLength;
958 remainingSegmentDistance -= currentRemainingDashLength;
960 currentRemainingDashLength = 0.0;
961 currentRemainingGapLength = sourcePattern.at( patternIndex );
966 buffer << remainingSegmentDistance << 0.0;
967 totalBufferLength += remainingSegmentDistance;
968 currentRemainingDashLength -= remainingSegmentDistance;
972 if ( currentRemainingGapLength > 0 )
975 if ( remainingSegmentDistance >= currentRemainingGapLength )
978 buffer << 0.0 << currentRemainingGapLength;
979 totalBufferLength += currentRemainingGapLength;
980 remainingSegmentDistance -= currentRemainingGapLength;
981 currentRemainingGapLength = 0.0;
987 buffer << 0.0 << remainingSegmentDistance;
988 totalBufferLength += remainingSegmentDistance;
989 currentRemainingGapLength -= remainingSegmentDistance;
994 if ( patternIndex >= sourcePattern.size() )
997 const double nextPatternDashLength = sourcePattern.at( patternIndex );
998 const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
999 if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
1001 buffer << nextPatternDashLength << nextPatternGapLength;
1002 remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
1003 totalBufferLength += nextPatternDashLength + nextPatternGapLength;
1006 else if ( nextPatternDashLength <= remainingSegmentDistance )
1009 buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1010 totalBufferLength += remainingSegmentDistance;
1011 currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1012 currentRemainingDashLength = 0;
1019 buffer << remainingSegmentDistance << 0.0;
1020 totalBufferLength += remainingSegmentDistance;
1021 currentRemainingGapLength = 0;
1022 currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1027 bufferedPoints << p1;
1028 if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1030 QPointF nextPoint = *( ptIt + 1 );
1036 flushBuffer( &nextPoint );
1037 bufferedPoints << p1;
1040 if ( patternIndex % 2 == 1 )
1044 currentRemainingDashLength = sourcePattern.at( patternIndex );
1051 flushBuffer(
nullptr );
1052 if ( !previousSegmentBuffer.empty() )
1054 QPen adjustedPen = pen;
1055 adjustedPen.setStyle( Qt::SolidLine );
1056 painter->setPen( adjustedPen );
1058 path.addPolygon( previousSegmentBuffer );
1059 painter->drawPath( path );
1060 previousSegmentBuffer.clear();
1066 if ( mDrawInsidePolygon )
1080 unit = mCustomDashPatternUnit;
1081 return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1118 return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1123 return mAlignDashPattern;
1133 return mPatternCartographicTweakOnSharpCorners;
1138 mPatternCartographicTweakOnSharpCorners =
enabled;
1166 MyLine( QPointF p1, QPointF p2 )
1167 : mVertical( false )
1168 , mIncreasing( false )
1180 mIncreasing = ( p2.y() > p1.y() );
1185 mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1186 mIncreasing = ( p2.x() > p1.x() );
1190 double x = ( p2.x() - p1.x() );
1191 double y = ( p2.y() - p1.y() );
1192 mLength = std::sqrt( x * x + y * y );
1198 double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1206 QPointF diffForInterval(
double interval )
1209 return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1211 double alpha = std::atan( mT );
1212 double dx = std::cos( alpha ) * interval;
1213 double dy = std::sin( alpha ) * interval;
1214 return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1217 double length()
const {
return mLength; }
1232 : mRotateSymbols( rotateSymbol )
1233 , mInterval( interval )
1271 if ( mRenderingFeature )
1275 mFeatureSymbolOpacity = context.
opacity();
1276 mCurrentFeatureIsSelected = context.
selected();
1292 if ( !exprVal.isNull() )
1294 QString placementString = exprVal.toString();
1295 if ( placementString.compare( QLatin1String(
"interval" ), Qt::CaseInsensitive ) == 0 )
1299 else if ( placementString.compare( QLatin1String(
"vertex" ), Qt::CaseInsensitive ) == 0 )
1303 else if ( placementString.compare( QLatin1String(
"innervertices" ), Qt::CaseInsensitive ) == 0 )
1307 else if ( placementString.compare( QLatin1String(
"lastvertex" ), Qt::CaseInsensitive ) == 0 )
1311 else if ( placementString.compare( QLatin1String(
"firstvertex" ), Qt::CaseInsensitive ) == 0 )
1315 else if ( placementString.compare( QLatin1String(
"centerpoint" ), Qt::CaseInsensitive ) == 0 )
1319 else if ( placementString.compare( QLatin1String(
"curvepoint" ), Qt::CaseInsensitive ) == 0 )
1323 else if ( placementString.compare( QLatin1String(
"segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1336 double averageOver = mAverageAngleLength;
1347 renderPolylineInterval( points, context, averageOver );
1349 renderPolylineCentral( points, context, averageOver );
1353 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1356 mHasRenderedFirstPart = mRenderingFeature;
1372 for (
int part = 0; part < mline.count(); ++part )
1374 const QPolygonF &points2 = mline[ part ];
1377 renderPolylineInterval( points2, context, averageOver );
1379 renderPolylineCentral( points2, context, averageOver );
1387 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1390 mHasRenderedFirstPart = mRenderingFeature;
1410 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1440 for (
int i = 0; i < rings->size(); ++i )
1473 mIntervalUnit = unit;
1474 mOffsetAlongLineUnit = unit;
1475 mAverageAngleLengthUnit = unit;
1500 map[QStringLiteral(
"rotate" )] = (
rotateSymbols() ? QStringLiteral(
"1" ) : QStringLiteral(
"0" ) );
1501 map[QStringLiteral(
"interval" )] = QString::number(
interval() );
1502 map[QStringLiteral(
"offset" )] = QString::number(
mOffset );
1503 map[QStringLiteral(
"offset_along_line" )] = QString::number(
offsetAlongLine() );
1510 map[QStringLiteral(
"average_angle_length" )] = QString::number( mAverageAngleLength );
1516 map[QStringLiteral(
"ring_filter" )] = QString::number(
static_cast< int >(
mRingFilter ) );
1517 map[QStringLiteral(
"place_on_every_part" )] = mPlaceOnEveryPart;
1523 return mPlaceOnEveryPart
1531 mRenderingFeature =
true;
1532 mHasRenderedFirstPart =
false;
1537 mRenderingFeature =
false;
1545 renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1546 mFeatureSymbolOpacity = 1;
1573 if (
properties.contains( QStringLiteral(
"offset" ) ) )
1577 if (
properties.contains( QStringLiteral(
"offset_unit" ) ) )
1581 if (
properties.contains( QStringLiteral(
"interval_unit" ) ) )
1585 if (
properties.contains( QStringLiteral(
"offset_along_line" ) ) )
1589 if (
properties.contains( QStringLiteral(
"offset_along_line_unit" ) ) )
1593 if (
properties.contains( ( QStringLiteral(
"offset_along_line_map_unit_scale" ) ) ) )
1598 if (
properties.contains( QStringLiteral(
"offset_map_unit_scale" ) ) )
1602 if (
properties.contains( QStringLiteral(
"interval_map_unit_scale" ) ) )
1607 if (
properties.contains( QStringLiteral(
"average_angle_length" ) ) )
1611 if (
properties.contains( QStringLiteral(
"average_angle_unit" ) ) )
1615 if (
properties.contains( ( QStringLiteral(
"average_angle_map_unit_scale" ) ) ) )
1620 if (
properties.contains( QStringLiteral(
"placement" ) ) )
1622 if (
properties[QStringLiteral(
"placement" )] == QLatin1String(
"vertex" ) )
1624 else if (
properties[QStringLiteral(
"placement" )] == QLatin1String(
"lastvertex" ) )
1626 else if (
properties[QStringLiteral(
"placement" )] == QLatin1String(
"firstvertex" ) )
1628 else if (
properties[QStringLiteral(
"placement" )] == QLatin1String(
"centralpoint" ) )
1630 else if (
properties[QStringLiteral(
"placement" )] == QLatin1String(
"curvepoint" ) )
1632 else if (
properties[QStringLiteral(
"placement" )] == QLatin1String(
"segmentcenter" ) )
1637 else if (
properties.contains( QStringLiteral(
"placements" ) ) )
1643 if (
properties.contains( QStringLiteral(
"ring_filter" ) ) )
1653 void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval(
const QPolygonF &points,
QgsSymbolRenderContext &context,
double averageOver )
1655 if ( points.isEmpty() )
1658 double lengthLeft = 0;
1690 if ( painterUnitInterval < 0 )
1693 double painterUnitOffsetAlongLine = 0;
1696 double totalLength = -1;
1717 if ( points.isClosed() )
1719 if ( painterUnitOffsetAlongLine > 0 )
1721 if ( totalLength < 0 )
1723 painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1725 else if ( painterUnitOffsetAlongLine < 0 )
1727 if ( totalLength < 0 )
1729 painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1741 lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1743 if ( averageOver > 0 && !
qgsDoubleNear( averageOver, 0.0 ) )
1745 QVector< QPointF > angleStartPoints;
1746 QVector< QPointF > symbolPoints;
1747 QVector< QPointF > angleEndPoints;
1755 collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
1757 if ( symbolPoints.empty() )
1763 if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1766 symbolPoints.pop_back();
1769 angleEndPoints.reserve( symbolPoints.size() );
1770 angleStartPoints.reserve( symbolPoints.size() );
1771 if ( averageOver <= painterUnitOffsetAlongLine )
1773 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
1777 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1779 collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
1782 for (
int i = 0; i < symbolPoints.size(); ++ i )
1787 const QPointF pt = symbolPoints[i];
1788 const QPointF startPt = angleStartPoints[i];
1789 const QPointF endPt = angleEndPoints[i];
1791 MyLine l( startPt, endPt );
1806 QPointF lastPt = points[0];
1807 for (
int i = 1; i < points.count(); ++i )
1812 const QPointF &pt = points[i];
1818 MyLine l( lastPt, pt );
1819 QPointF diff = l.diffForInterval( painterUnitInterval );
1823 double c = 1 - lengthLeft / painterUnitInterval;
1825 lengthLeft += l.length();
1834 while ( lengthLeft > painterUnitInterval )
1838 lengthLeft -= painterUnitInterval;
1850 static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1853 double a1 = MyLine( prevPt, pt ).angle();
1854 double a2 = MyLine( pt, nextPt ).angle();
1855 double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1857 return std::atan2( unitY, unitX );
1862 if ( points.isEmpty() )
1867 int i = -1, maxCount = 0;
1868 bool isRing =
false;
1882 double totalLength = -1;
1902 if ( points.isClosed() )
1906 if ( totalLength < 0 )
1912 if ( totalLength < 0 )
1985 i = points.count() - 1;
1987 maxCount = points.count();
1995 maxCount = points.count() - 1;
2003 maxCount = points.count();
2004 if ( points.first() == points.last() )
2021 renderOffsetVertexAlongLine( points, i, distance, context,
placement );
2028 prevPoint = points.at( 0 );
2030 QPointF symbolPoint;
2031 for ( ; i < maxCount; ++i )
2042 QPointF currentPoint = points.at( i );
2043 symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
2044 0.5 * ( currentPoint.y() + prevPoint.y() ) );
2047 double angle = std::atan2( currentPoint.y() - prevPoint.y(),
2048 currentPoint.x() - prevPoint.x() );
2051 prevPoint = currentPoint;
2055 symbolPoint = points.at( i );
2059 double angle = markerAngle( points, isRing, i );
2064 mFinalVertex = symbolPoint;
2070 double QgsTemplatedLineSymbolLayerBase::markerAngle(
const QPolygonF &points,
bool isRing,
int vertex )
2073 const QPointF &pt = points[vertex];
2075 if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2077 int prevIndex = vertex - 1;
2078 int nextIndex = vertex + 1;
2080 if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2082 prevIndex = points.count() - 2;
2086 QPointF prevPoint, nextPoint;
2087 while ( prevIndex >= 0 )
2089 prevPoint = points[ prevIndex ];
2090 if ( prevPoint != pt )
2097 while ( nextIndex < points.count() )
2099 nextPoint = points[ nextIndex ];
2100 if ( nextPoint != pt )
2107 if ( prevIndex >= 0 && nextIndex < points.count() )
2109 angle = _averageAngle( prevPoint, pt, nextPoint );
2116 while ( vertex < points.size() - 1 )
2118 const QPointF &nextPt = points[vertex + 1];
2121 angle = MyLine( pt, nextPt ).angle();
2130 while ( vertex >= 1 )
2132 const QPointF &prevPt = points[vertex - 1];
2135 angle = MyLine( prevPt, pt ).angle();
2147 if ( points.isEmpty() )
2156 bool isRing =
false;
2157 if ( points.first() == points.last() )
2159 double angle = markerAngle( points, isRing, vertex );
2162 mFinalVertex = points[vertex];
2168 int pointIncrement = distance > 0 ? 1 : -1;
2169 QPointF previousPoint = points[vertex];
2170 int startPoint = distance > 0 ? std::min( vertex + 1,
static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2171 int endPoint = distance > 0 ? points.count() - 1 : 0;
2172 double distanceLeft = std::fabs( distance );
2174 for (
int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2176 const QPointF &pt = points[i];
2178 if ( previousPoint == pt )
2182 MyLine l( previousPoint, pt );
2184 if ( distanceLeft < l.length() )
2187 QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2193 mFinalVertex = markerPoint;
2199 distanceLeft -= l.length();
2206 void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints(
const QVector<QPointF> &p, QVector<QPointF> &dest,
double intervalPainterUnits,
double initialOffset,
double initialLag,
int numberPointsRequired )
2211 QVector< QPointF > points = p;
2212 const bool closedRing = points.first() == points.last();
2214 double lengthLeft = initialOffset;
2216 double initialLagLeft = initialLag > 0 ? -initialLag : 1;
2217 if ( initialLagLeft < 0 && closedRing )
2220 QPointF lastPt = points.constLast();
2221 QVector< QPointF > pseudoPoints;
2222 for (
int i = points.count() - 2; i > 0; --i )
2224 if ( initialLagLeft >= 0 )
2229 const QPointF &pt = points[i];
2234 MyLine l( lastPt, pt );
2235 initialLagLeft += l.length();
2240 std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2242 points = pseudoPoints;
2247 while ( initialLagLeft < 0 )
2249 dest << points.constFirst();
2250 initialLagLeft += intervalPainterUnits;
2253 if ( initialLag > 0 )
2255 lengthLeft += intervalPainterUnits - initialLagLeft;
2258 QPointF lastPt = points[0];
2259 for (
int i = 1; i < points.count(); ++i )
2261 const QPointF &pt = points[i];
2265 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2274 MyLine l( lastPt, pt );
2275 QPointF diff = l.diffForInterval( intervalPainterUnits );
2279 double c = 1 - lengthLeft / intervalPainterUnits;
2281 lengthLeft += l.length();
2284 while ( lengthLeft > intervalPainterUnits ||
qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2288 lengthLeft -= intervalPainterUnits;
2291 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2296 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2300 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2307 if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2310 while ( dest.size() < numberPointsRequired )
2311 dest << points.constLast();
2315 void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral(
const QPolygonF &points,
QgsSymbolRenderContext &context,
double averageAngleOver )
2317 if ( !points.isEmpty() )
2321 QPolygonF::const_iterator it = points.constBegin();
2323 for ( ++it; it != points.constEnd(); ++it )
2325 length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2326 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2332 const double midPoint = length / 2;
2335 double thisSymbolAngle = 0;
2337 if ( averageAngleOver > 0 && !
qgsDoubleNear( averageAngleOver, 0.0 ) )
2339 QVector< QPointF > angleStartPoints;
2340 QVector< QPointF > symbolPoints;
2341 QVector< QPointF > angleEndPoints;
2343 collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
2344 collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
2345 collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
2347 pt = symbolPoints.at( 1 );
2348 MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2349 thisSymbolAngle = l.angle();
2354 it = points.constBegin();
2356 qreal last_at = 0, next_at = 0;
2359 for ( ++it; it != points.constEnd(); ++it )
2362 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2363 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2364 if ( next_at >= midPoint )
2372 MyLine l( last, next );
2373 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2374 pt = last + ( next - last ) * k;
2375 thisSymbolAngle = l.angle();
2427 if ( props.contains( QStringLiteral(
"interval" ) ) )
2428 interval = props[QStringLiteral(
"interval" )].toDouble();
2429 if ( props.contains( QStringLiteral(
"rotate" ) ) )
2430 rotate = ( props[QStringLiteral(
"rotate" )].toString() == QLatin1String(
"1" ) );
2432 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate,
interval );
2439 return QStringLiteral(
"MarkerLine" );
2456 Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2459 mMarker->setRenderHints( hints );
2472 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >(
rotateSymbols(),
interval() );
2479 for (
int i = 0; i <
mMarker->symbolLayerCount(); i++ )
2481 QDomElement symbolizerElem = doc.createElement( QStringLiteral(
"se:LineSymbolizer" ) );
2482 if ( !props.value( QStringLiteral(
"uom" ), QString() ).toString().isEmpty() )
2483 symbolizerElem.setAttribute( QStringLiteral(
"uom" ), props.value( QStringLiteral(
"uom" ), QString() ).toString() );
2484 element.appendChild( symbolizerElem );
2514 QDomElement strokeElem = doc.createElement( QStringLiteral(
"se:Stroke" ) );
2515 symbolizerElem.appendChild( strokeElem );
2518 QDomElement graphicStrokeElem = doc.createElement( QStringLiteral(
"se:GraphicStroke" ) );
2519 strokeElem.appendChild( graphicStrokeElem );
2524 markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
2528 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral(
"QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->
layerType() ) ) );
2532 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral(
"Missing marker line symbol layer. Skip it." ) ) );
2535 if ( !gap.isEmpty() )
2537 QDomElement gapElem = doc.createElement( QStringLiteral(
"se:Gap" ) );
2539 graphicStrokeElem.appendChild( gapElem );
2544 QDomElement perpOffsetElem = doc.createElement( QStringLiteral(
"se:PerpendicularOffset" ) );
2547 symbolizerElem.appendChild( perpOffsetElem );
2556 QDomElement strokeElem = element.firstChildElement( QStringLiteral(
"Stroke" ) );
2557 if ( strokeElem.isNull() )
2560 QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral(
"GraphicStroke" ) );
2561 if ( graphicStrokeElem.isNull() )
2569 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2571 if ( it.key() == QLatin1String(
"placement" ) )
2573 if ( it.value() == QLatin1String(
"points" ) )
2575 else if ( it.value() == QLatin1String(
"firstPoint" ) )
2577 else if ( it.value() == QLatin1String(
"lastPoint" ) )
2579 else if ( it.value() == QLatin1String(
"centralPoint" ) )
2582 else if ( it.value() == QLatin1String(
"rotateMarker" ) )
2588 std::unique_ptr< QgsMarkerSymbol > marker;
2602 QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral(
"Gap" ) );
2603 if ( !gapElem.isNull() )
2606 double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2612 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral(
"PerpendicularOffset" ) );
2613 if ( !perpOffsetElem.isNull() )
2616 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2621 double scaleFactor = 1.0;
2622 const QString uom = element.attribute( QStringLiteral(
"uom" ) );
2645 mMarker->setDataDefinedSize( property );
2652 const double prevOpacity =
mMarker->opacity();
2655 mMarker->setOpacity( prevOpacity );
2678 mMarker->renderPoint( point, feature, context, layer, selected );
2690 return mMarker->size( context );
2696 mMarker->setOutputUnit( unit );
2713 attr.unite(
mMarker->usedAttributes( context ) );
2728 return (
mMarker->size( context ) / 2.0 ) +
2750 if ( props.contains( QStringLiteral(
"interval" ) ) )
2751 interval = props[QStringLiteral(
"interval" )].toDouble();
2752 if ( props.contains( QStringLiteral(
"rotate" ) ) )
2753 rotate = ( props[QStringLiteral(
"rotate" )] == QLatin1String(
"1" ) );
2755 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotate,
interval );
2757 if ( props.contains( QStringLiteral(
"hash_angle" ) ) )
2759 x->setHashAngle( props[QStringLiteral(
"hash_angle" )].toDouble() );
2762 if ( props.contains( QStringLiteral(
"hash_length" ) ) )
2763 x->setHashLength( props[QStringLiteral(
"hash_length" )].toDouble() );
2765 if ( props.contains( QStringLiteral(
"hash_length_unit" ) ) )
2768 if ( props.contains( QStringLiteral(
"hash_length_map_unit_scale" ) ) )
2776 return QStringLiteral(
"HashLine" );
2782 Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2785 mHashSymbol->setRenderHints( hints );
2798 map[ QStringLiteral(
"hash_angle" ) ] = QString::number( mHashAngle );
2800 map[QStringLiteral(
"hash_length" )] = QString::number( mHashLength );
2809 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >(
rotateSymbols(),
interval() );
2811 x->setHashAngle( mHashAngle );
2812 x->setHashLength( mHashLength );
2813 x->setHashLengthUnit( mHashLengthUnit );
2814 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2820 mHashSymbol->setColor(
color );
2826 return mHashSymbol ? mHashSymbol->color() :
mColor;
2831 return mHashSymbol.get();
2842 mHashSymbol.reset(
static_cast<QgsLineSymbol *
>( symbol ) );
2843 mColor = mHashSymbol->color();
2849 mHashLength =
width;
2864 return ( mHashSymbol->width( context ) / 2.0 )
2872 mHashSymbol->setOutputUnit( unit );
2879 attr.unite( mHashSymbol->usedAttributes( context ) );
2887 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2896 mHashSymbol->setDataDefinedWidth( property );
2909 || ( mHashSymbol && mHashSymbol->usesMapUnits() );
2914 mSymbolLineAngle =
angle;
2919 return mSymbolAngle;
2924 mSymbolAngle =
angle;
2929 double lineLength = mHashLength;
2935 const double w = context.
convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2949 points << QPointF( start.
x(), start.
y() ) << QPointF( end.
x(), end.
y() );
2954 mHashSymbol->renderPolyline( points, feature, context, layer, selected );
2971 const double prevOpacity = mHashSymbol->opacity();
2972 mHashSymbol->setOpacity( mHashSymbol->opacity() * context.
opacity() );
2974 mHashSymbol->setOpacity( prevOpacity );
2993 QPolygonF offsetPoints;
2996 renderLine( points, context, patternThickness, patternLength, brush );
3005 renderLine( part, context, patternThickness, patternLength, brush );
3010 void QgsAbstractBrushedLineSymbolLayer::renderLine(
const QPolygonF &points,
QgsSymbolRenderContext &context,
const double lineThickness,
3011 const double patternLength,
const QBrush &sourceBrush )
3017 QBrush brush = sourceBrush;
3022 QPolygonF inputPoints;
3023 inputPoints.reserve( points.size() );
3025 double minX = std::numeric_limits< double >::max();
3026 double minY = std::numeric_limits< double >::max();
3027 double maxX = std::numeric_limits< double >::lowest();
3028 double maxY = std::numeric_limits< double >::lowest();
3030 for (
const QPointF &pt : std::as_const( points ) )
3037 minX = std::min( minX, pt.x() );
3038 minY = std::min( minY, pt.y() );
3039 maxX = std::max( maxX, pt.x() );
3040 maxY = std::max( maxY, pt.y() );
3043 if ( inputPoints.size() < 2 )
3047 constexpr
int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3049 constexpr
double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3052 const int imageWidth =
static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3053 const int imageHeight =
static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3055 const bool isClosedLine =
qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 )
3056 &&
qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3058 QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3059 if ( temporaryImage.isNull() )
3068 temporaryImage.fill( Qt::transparent );
3077 if ( !exprVal.isNull() )
3086 if ( !exprVal.isNull() )
3091 QPainterPathStroker stroker;
3092 stroker.setWidth( lineThickness );
3093 stroker.setCapStyle( cap );
3094 stroker.setJoinStyle( join );
3097 path.addPolygon( inputPoints );
3098 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3101 QPainter imagePainter;
3102 imagePainter.begin( &temporaryImage );
3104 imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3106 imagePainter.setClipPath( stroke, Qt::IntersectClip );
3107 imagePainter.setPen( Qt::NoPen );
3109 QPointF segmentStartPoint = inputPoints.at( 0 );
3112 double progressThroughImage = 0;
3114 QgsPoint prevSegmentPolygonEndLeft;
3115 QgsPoint prevSegmentPolygonEndRight;
3121 for (
int i = 1; i < inputPoints.size(); ++i )
3126 const QPointF segmentEndPoint = inputPoints.at( i );
3128 segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3131 QgsPoint thisSegmentPolygonEndLeft;
3132 QgsPoint thisSegmentPolygonEndRight;
3134 QgsPoint thisSegmentPolygonEndLeftForPainter;
3135 QgsPoint thisSegmentPolygonEndRightForPainter;
3143 const QgsPoint startPointLeft =
QgsPoint( segmentStartPoint ).
project( lineThickness / 2, segmentAngleDegrees );
3145 const QgsPoint startPointRight =
QgsPoint( segmentStartPoint ).
project( -lineThickness / 2, segmentAngleDegrees );
3146 const QgsPoint endPointRight =
QgsPoint( segmentEndPoint ).
project( -lineThickness / 2, segmentAngleDegrees );
3150 const double lastSegmentAngleDegrees = 180.0 / M_PI *
QgsGeometryUtils::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(),
3151 segmentStartPoint.x(), segmentStartPoint.y() ) - 90;
3154 const QgsPoint lastSegmentStartPointLeft =
QgsPoint( points.at( points.size() - 2 ) ).
project( lineThickness / 2, lastSegmentAngleDegrees );
3155 const QgsPoint lastSegmentEndPointLeft =
QgsPoint( segmentStartPoint ).
project( lineThickness / 2, lastSegmentAngleDegrees );
3156 const QgsPoint lastSegmentStartPointRight =
QgsPoint( points.at( points.size() - 2 ) ).
project( -lineThickness / 2, lastSegmentAngleDegrees );
3157 const QgsPoint lastSegmentEndPointRight =
QgsPoint( segmentStartPoint ).
project( -lineThickness / 2, lastSegmentAngleDegrees );
3163 bool isIntersection =
false;
3165 if ( !isIntersection )
3166 prevSegmentPolygonEndLeft = startPointLeft;
3167 isIntersection =
false;
3168 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8,
true );
3169 if ( !isIntersection )
3170 prevSegmentPolygonEndRight = startPointRight;
3172 startLinePolygonLeft = prevSegmentPolygonEndLeft;
3173 startLinePolygonRight = prevSegmentPolygonEndRight;
3177 prevSegmentPolygonEndLeft =
QgsPoint( segmentStartPoint ).
project( lineThickness / 2, segmentAngleDegrees );
3178 if ( cap != Qt::PenCapStyle::FlatCap )
3179 prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.
project( lineThickness / 2, segmentAngleDegrees - 90 );
3180 prevSegmentPolygonEndRight =
QgsPoint( segmentStartPoint ).
project( -lineThickness / 2, segmentAngleDegrees );
3181 if ( cap != Qt::PenCapStyle::FlatCap )
3182 prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.
project( lineThickness / 2, segmentAngleDegrees - 90 );
3186 if ( i < inputPoints.size() - 1 )
3191 const QgsPoint startPointLeft =
QgsPoint( segmentStartPoint ).
project( lineThickness / 2, segmentAngleDegrees );
3193 const QgsPoint startPointRight =
QgsPoint( segmentStartPoint ).
project( -lineThickness / 2, segmentAngleDegrees );
3194 const QgsPoint endPointRight =
QgsPoint( segmentEndPoint ).
project( -lineThickness / 2, segmentAngleDegrees );
3199 inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3202 const QgsPoint nextSegmentStartPointLeft =
QgsPoint( segmentEndPoint ).
project( lineThickness / 2, nextSegmentAngleDegrees );
3203 const QgsPoint nextSegmentEndPointLeft =
QgsPoint( inputPoints.at( i + 1 ) ).
project( lineThickness / 2, nextSegmentAngleDegrees );
3204 const QgsPoint nextSegmentStartPointRight =
QgsPoint( segmentEndPoint ).
project( -lineThickness / 2, nextSegmentAngleDegrees );
3205 const QgsPoint nextSegmentEndPointRight =
QgsPoint( inputPoints.at( i + 1 ) ).
project( -lineThickness / 2, nextSegmentAngleDegrees );
3211 bool isIntersection =
false;
3213 if ( !isIntersection )
3214 thisSegmentPolygonEndLeft = endPointLeft;
3215 isIntersection =
false;
3216 QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8,
true );
3217 if ( !isIntersection )
3218 thisSegmentPolygonEndRight = endPointRight;
3220 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.
project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3221 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.
project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3229 thisSegmentPolygonEndLeft = startLinePolygonLeft;
3230 thisSegmentPolygonEndRight = startLinePolygonRight;
3232 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.
project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3233 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.
project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3237 thisSegmentPolygonEndLeft =
QgsPoint( segmentEndPoint ).
project( lineThickness / 2, segmentAngleDegrees );
3238 if ( cap != Qt::PenCapStyle::FlatCap )
3239 thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.
project( lineThickness / 2, segmentAngleDegrees + 90 );
3240 thisSegmentPolygonEndRight =
QgsPoint( segmentEndPoint ).
project( -lineThickness / 2, segmentAngleDegrees );
3241 if ( cap != Qt::PenCapStyle::FlatCap )
3242 thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.
project( lineThickness / 2, segmentAngleDegrees + 90 );
3244 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3245 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3251 QTransform brushTransform;
3252 brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3253 brushTransform.rotate( -segmentAngleDegrees );
3254 if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3258 brushTransform.translate( -( lineThickness / 2 ), 0 );
3260 brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3262 brush.setTransform( brushTransform );
3263 imagePainter.setBrush( brush );
3266 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.
toQPointF()
3267 << thisSegmentPolygonEndLeftForPainter.
toQPointF()
3268 << thisSegmentPolygonEndRightForPainter.
toQPointF()
3269 << prevSegmentPolygonEndRight.
toQPointF()
3270 << prevSegmentPolygonEndLeft.
toQPointF() );
3272 #if 0 // for debugging, will draw the segment polygons
3273 imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3274 imagePainter.setBrush( Qt::NoBrush );
3275 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.
toQPointF()
3276 << thisSegmentPolygonEndLeftForPainter.
toQPointF()
3277 << thisSegmentPolygonEndRightForPainter.
toQPointF()
3278 << prevSegmentPolygonEndRight.
toQPointF()
3279 << prevSegmentPolygonEndLeft.
toQPointF() );
3280 imagePainter.setPen( Qt::NoPen );
3285 progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 )
3286 + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3287 + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 );
3288 progressThroughImage = fmod( progressThroughImage, patternLength );
3291 segmentStartPoint = segmentEndPoint;
3292 prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3293 prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3301 p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS,
3302 minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3319 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique<QgsRasterLineSymbolLayer>();
3321 if (
properties.contains( QStringLiteral(
"line_width" ) ) )
3323 res->setWidth(
properties[QStringLiteral(
"line_width" )].toDouble() );
3325 if (
properties.contains( QStringLiteral(
"line_width_unit" ) ) )
3329 if (
properties.contains( QStringLiteral(
"width_map_unit_scale" ) ) )
3334 if (
properties.contains( QStringLiteral(
"imageFile" ) ) )
3335 res->setPath(
properties[QStringLiteral(
"imageFile" )].toString() );
3337 if (
properties.contains( QStringLiteral(
"offset" ) ) )
3339 res->setOffset(
properties[QStringLiteral(
"offset" )].toDouble() );
3341 if (
properties.contains( QStringLiteral(
"offset_unit" ) ) )
3345 if (
properties.contains( QStringLiteral(
"offset_map_unit_scale" ) ) )
3350 if (
properties.contains( QStringLiteral(
"joinstyle" ) ) )
3352 if (
properties.contains( QStringLiteral(
"capstyle" ) ) )
3355 if (
properties.contains( QStringLiteral(
"alpha" ) ) )
3357 res->setOpacity(
properties[QStringLiteral(
"alpha" )].toDouble() );
3360 return res.release();
3367 map[QStringLiteral(
"imageFile" )] =
mPath;
3369 map[QStringLiteral(
"line_width" )] = QString::number(
mWidth );
3376 map[QStringLiteral(
"offset" )] = QString::number(
mOffset );
3380 map[QStringLiteral(
"alpha" )] = QString::number(
mOpacity );
3387 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique< QgsRasterLineSymbolLayer >(
mPath );
3399 return res.release();
3404 const QVariantMap::iterator it =
properties.find( QStringLiteral(
"imageFile" ) );
3405 if ( it !=
properties.end() && it.value().type() == QVariant::String )
3421 return QStringLiteral(
"RasterLine" );
3431 bool cached =
false;
3433 QSize(
static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3434 static_cast< int >( std::ceil( scaledHeight ) ) ),
3459 double strokeWidth =
mWidth;
3476 bool cached =
false;
3478 QSize(
static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3479 static_cast< int >( std::ceil( scaledHeight ) ) ),
3488 const QBrush brush( sourceImage );
3558 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique<QgsLineburstSymbolLayer>();
3560 if (
properties.contains( QStringLiteral(
"line_width" ) ) )
3562 res->setWidth(
properties[QStringLiteral(
"line_width" )].toDouble() );
3564 if (
properties.contains( QStringLiteral(
"line_width_unit" ) ) )
3568 if (
properties.contains( QStringLiteral(
"width_map_unit_scale" ) ) )
3573 if (
properties.contains( QStringLiteral(
"offset" ) ) )
3575 res->setOffset(
properties[QStringLiteral(
"offset" )].toDouble() );
3577 if (
properties.contains( QStringLiteral(
"offset_unit" ) ) )
3581 if (
properties.contains( QStringLiteral(
"offset_map_unit_scale" ) ) )
3586 if (
properties.contains( QStringLiteral(
"joinstyle" ) ) )
3588 if (
properties.contains( QStringLiteral(
"capstyle" ) ) )
3591 if (
properties.contains( QStringLiteral(
"color_type" ) ) )
3594 if (
properties.contains( QStringLiteral(
"color" ) ) )
3598 if (
properties.contains( QStringLiteral(
"gradient_color2" ) ) )
3613 return res.release();
3620 map[QStringLiteral(
"line_width" )] = QString::number(
mWidth );
3627 map[QStringLiteral(
"offset" )] = QString::number(
mOffset );
3633 map[QStringLiteral(
"color_type" )] = QString::number(
static_cast< int >(
mGradientColorType ) );
3636 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
3648 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique< QgsLineburstSymbolLayer >();
3664 return res.release();
3669 return QStringLiteral(
"Lineburst" );
3685 double strokeWidth =
mWidth;
3704 color1.setAlphaF( context.
opacity() * color1.alphaF() );
3715 QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3727 gradient.setColorAt( 0.0, color1 );
3728 gradient.setColorAt( 1.0,
color2 );
3730 const QBrush brush( gradient );