32 #include <QDomDocument>
33 #include <QDomElement>
38 : mPenStyle( penStyle )
42 mCustomDashVector << 5 << 2;
50 mCustomDashPatternUnit = unit;
68 mCustomDashPatternMapUnitScale = scale;
88 if ( props.contains( QStringLiteral(
"line_color" ) ) )
92 else if ( props.contains( QStringLiteral(
"outline_color" ) ) )
96 else if ( props.contains( QStringLiteral(
"color" ) ) )
101 if ( props.contains( QStringLiteral(
"line_width" ) ) )
103 width = props[QStringLiteral(
"line_width" )].toDouble();
105 else if ( props.contains( QStringLiteral(
"outline_width" ) ) )
107 width = props[QStringLiteral(
"outline_width" )].toDouble();
109 else if ( props.contains( QStringLiteral(
"width" ) ) )
112 width = props[QStringLiteral(
"width" )].toDouble();
114 if ( props.contains( QStringLiteral(
"line_style" ) ) )
118 else if ( props.contains( QStringLiteral(
"outline_style" ) ) )
122 else if ( props.contains( QStringLiteral(
"penstyle" ) ) )
128 if ( props.contains( QStringLiteral(
"line_width_unit" ) ) )
132 else if ( props.contains( QStringLiteral(
"outline_width_unit" ) ) )
136 else if ( props.contains( QStringLiteral(
"width_unit" ) ) )
141 if ( props.contains( QStringLiteral(
"width_map_unit_scale" ) ) )
143 if ( props.contains( QStringLiteral(
"offset" ) ) )
144 l->
setOffset( props[QStringLiteral(
"offset" )].toDouble() );
145 if ( props.contains( QStringLiteral(
"offset_unit" ) ) )
147 if ( props.contains( QStringLiteral(
"offset_map_unit_scale" ) ) )
149 if ( props.contains( QStringLiteral(
"joinstyle" ) ) )
151 if ( props.contains( QStringLiteral(
"capstyle" ) ) )
154 if ( props.contains( QStringLiteral(
"use_custom_dash" ) ) )
158 if ( props.contains( QStringLiteral(
"customdash" ) ) )
162 if ( props.contains( QStringLiteral(
"customdash_unit" ) ) )
166 if ( props.contains( QStringLiteral(
"customdash_map_unit_scale" ) ) )
171 if ( props.contains( QStringLiteral(
"draw_inside_polygon" ) ) )
176 if ( props.contains( QStringLiteral(
"ring_filter" ) ) )
181 if ( props.contains( QStringLiteral(
"dash_pattern_offset" ) ) )
183 if ( props.contains( QStringLiteral(
"dash_pattern_offset_unit" ) ) )
185 if ( props.contains( QStringLiteral(
"dash_pattern_offset_map_unit_scale" ) ) )
188 if ( props.contains( QStringLiteral(
"align_dash_pattern" ) ) )
191 if ( props.contains( QStringLiteral(
"tweak_dash_pattern_on_corners" ) ) )
202 return QStringLiteral(
"SimpleLine" );
209 mPen.setColor( penColor );
211 mPen.setWidthF( scaledWidth );
215 const double dashWidthDiv = std::max( 1.0, scaledWidth );
216 if ( mUseCustomDashPattern )
218 mPen.setStyle( Qt::CustomDashLine );
222 QVector<qreal> scaledVector;
223 QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
224 for ( ; it != mCustomDashVector.constEnd(); ++it )
229 mPen.setDashPattern( scaledVector );
233 mPen.setStyle( mPenStyle );
236 if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
241 mPen.setJoinStyle( mPenJoinStyle );
242 mPen.setCapStyle( mPenCapStyle );
247 selColor.setAlphaF( context.
opacity() );
248 mSelPen.setColor( selColor );
264 if ( mDrawInsidePolygon )
272 if ( mDrawInsidePolygon )
275 QPainterPath clipPath;
276 clipPath.addPolygon( points );
281 for (
auto it = rings->constBegin(); it != rings->constEnd(); ++it )
283 QPolygonF ring = *it;
284 clipPath.addPolygon( ring );
289 p->setClipPath( clipPath, Qt::IntersectClip );
308 for (
const QPolygonF &ring : qgis::as_const( *rings ) )
318 if ( mDrawInsidePolygon )
335 applyDataDefinedSymbology( context, mPen, mSelPen,
offset );
337 const QPen pen = context.
selected() ? mSelPen : mPen;
338 p->setBrush( Qt::NoBrush );
341 std::unique_ptr< QgsScopedQPainterState > painterState;
342 if ( points.size() <= 2 &&
345 ( p->renderHints() & QPainter::Antialiasing ) )
347 painterState = qgis::make_unique< QgsScopedQPainterState >( p );
348 p->setRenderHint( QPainter::Antialiasing,
false );
351 const bool applyPatternTweaks = mAlignDashPattern
352 && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
353 && pen.dashOffset() == 0;
357 if ( applyPatternTweaks )
359 drawPathWithDashPatternTweaks( p, points, pen );
365 path.addPolygon( points );
380 for (
const QPolygonF &part : mline )
382 if ( applyPatternTweaks )
384 drawPathWithDashPatternTweaks( p, part, pen );
390 path.addPolygon( part );
401 map[QStringLiteral(
"line_width" )] = QString::number(
mWidth );
407 map[QStringLiteral(
"offset" )] = QString::number(
mOffset );
410 map[QStringLiteral(
"use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral(
"1" ) : QStringLiteral(
"0" ) );
414 map[QStringLiteral(
"dash_pattern_offset" )] = QString::number( mDashPatternOffset );
417 map[QStringLiteral(
"draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral(
"1" ) : QStringLiteral(
"0" ) );
418 map[QStringLiteral(
"ring_filter" )] = QString::number(
static_cast< int >(
mRingFilter ) );
419 map[QStringLiteral(
"align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral(
"1" ) : QStringLiteral(
"0" );
420 map[QStringLiteral(
"tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral(
"1" ) : QStringLiteral(
"0" );
453 if ( mPenStyle == Qt::NoPen )
456 QDomElement symbolizerElem = doc.createElement( QStringLiteral(
"se:LineSymbolizer" ) );
457 if ( !props.value( QStringLiteral(
"uom" ), QString() ).isEmpty() )
458 symbolizerElem.setAttribute( QStringLiteral(
"uom" ), props.value( QStringLiteral(
"uom" ), QString() ) );
459 element.appendChild( symbolizerElem );
465 QDomElement strokeElem = doc.createElement( QStringLiteral(
"se:Stroke" ) );
466 symbolizerElem.appendChild( strokeElem );
468 Qt::PenStyle
penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
477 QDomElement perpOffsetElem = doc.createElement( QStringLiteral(
"se:PerpendicularOffset" ) );
480 symbolizerElem.appendChild( perpOffsetElem );
486 if ( mUseCustomDashPattern )
489 mPen.color(), mPenJoinStyle,
490 mPenCapStyle,
mOffset, &mCustomDashVector );
503 QDomElement strokeElem = element.firstChildElement( QStringLiteral(
"Stroke" ) );
504 if ( strokeElem.isNull() )
521 QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral(
"PerpendicularOffset" ) );
522 if ( !perpOffsetElem.isNull() )
525 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
530 QString uom = element.attribute( QStringLiteral(
"uom" ) );
544 void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology(
QgsSymbolRenderContext &context, QPen &pen, QPen &selPen,
double &offset )
550 bool hasStrokeWidthExpression =
false;
557 pen.setWidthF( scaledWidth );
558 selPen.setWidthF( scaledWidth );
559 hasStrokeWidthExpression =
true;
580 const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
584 QVector<qreal> dashVector;
586 if ( exprVal.isValid() )
588 QStringList dashList = exprVal.toString().split(
';' );
589 QStringList::const_iterator dashIt = dashList.constBegin();
590 for ( ; dashIt != dashList.constEnd(); ++dashIt )
594 pen.setDashPattern( dashVector );
601 QVector<qreal> scaledVector;
602 for (
double v : mCustomDashVector )
607 mPen.setDashPattern( scaledVector );
611 double patternOffset = mDashPatternOffset;
624 if ( exprVal.isValid() )
633 if ( exprVal.isValid() )
642 if ( exprVal.isValid() )
647 void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter,
const QPolygonF &points, QPen pen )
const
649 if ( pen.dashPattern().empty() || points.size() < 2 )
652 QVector< qreal > sourcePattern = pen.dashPattern();
653 const double dashWidthDiv = std::max( 1.0001, pen.widthF() );
655 for (
int i = 0; i < sourcePattern.size(); ++ i )
656 sourcePattern[i] *= pen.widthF();
658 if ( pen.widthF() <= 1.0 )
659 pen.setWidthF( 1.0001 );
661 QVector< qreal > buffer;
662 QPolygonF bufferedPoints;
663 QPolygonF previousSegmentBuffer;
668 auto ptIt = points.constBegin();
669 double totalBufferLength = 0;
670 int patternIndex = 0;
671 double currentRemainingDashLength = 0;
672 double currentRemainingGapLength = 0;
674 auto compressPattern = [](
const QVector< qreal > &buffer ) -> QVector< qreal >
676 QVector< qreal > result;
677 result.reserve( buffer.size() );
678 for (
auto it = buffer.begin(); it != buffer.end(); )
682 while ( dash == 0 && !result.empty() )
684 result.last() += gap;
686 if ( it == buffer.end() )
691 while ( gap == 0 && it != buffer.end() )
696 result << dash << gap;
701 double currentBufferLineLength = 0;
702 auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, ¤tRemainingDashLength, ¤tRemainingGapLength, ¤tBufferLineLength, &totalBufferLength,
703 dashWidthDiv, &compressPattern]( QPointF * nextPoint )
705 if ( buffer.empty() || bufferedPoints.size() < 2 )
710 if ( currentRemainingDashLength )
713 buffer << currentRemainingDashLength << 0.0;
714 totalBufferLength += currentRemainingDashLength;
716 QVector< qreal > compressed = compressPattern( buffer );
717 if ( !currentRemainingDashLength )
720 totalBufferLength -= compressed.last();
721 compressed.last() = 0;
725 const double scaleFactor = currentBufferLineLength / totalBufferLength;
727 bool shouldFlushPreviousSegmentBuffer =
false;
729 if ( !previousSegmentBuffer.empty() )
733 if ( !firstDashSubstring.empty() )
739 compressed = compressed.mid( 2 );
740 shouldFlushPreviousSegmentBuffer = !compressed.empty();
743 if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
745 QPen adjustedPen = pen;
746 adjustedPen.setStyle( Qt::SolidLine );
747 painter->setPen( adjustedPen );
749 path.addPolygon( previousSegmentBuffer );
750 painter->drawPath( path );
751 previousSegmentBuffer.clear();
754 double finalDash = 0;
761 if ( !compressed.empty() )
763 finalDash = compressed.at( compressed.size() - 2 );
764 const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
766 const QPolygonF thisPoints = bufferedPoints;
772 previousSegmentBuffer << bufferedPoints;
776 currentBufferLineLength = 0;
777 currentRemainingDashLength = 0;
778 currentRemainingGapLength = 0;
779 totalBufferLength = 0;
782 if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
784 QPen adjustedPen = pen;
785 if ( !compressed.empty() )
788 compressed = compressed.mid( 0, 32 );
789 std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
790 adjustedPen.setDashPattern( compressed );
794 adjustedPen.setStyle( Qt::SolidLine );
797 painter->setPen( adjustedPen );
799 path.addPolygon( bufferedPoints );
800 painter->drawPath( path );
803 bufferedPoints.clear();
809 bufferedPoints << p2;
810 for ( ; ptIt != points.constEnd(); ++ptIt )
818 double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
819 currentBufferLineLength += remainingSegmentDistance;
823 if ( currentRemainingDashLength > 0 )
826 if ( remainingSegmentDistance >= currentRemainingDashLength )
829 buffer << currentRemainingDashLength << 0.0;
830 totalBufferLength += currentRemainingDashLength;
831 remainingSegmentDistance -= currentRemainingDashLength;
833 currentRemainingDashLength = 0.0;
834 currentRemainingGapLength = sourcePattern.at( patternIndex );
839 buffer << remainingSegmentDistance << 0.0;
840 totalBufferLength += remainingSegmentDistance;
841 currentRemainingDashLength -= remainingSegmentDistance;
845 if ( currentRemainingGapLength > 0 )
848 if ( remainingSegmentDistance >= currentRemainingGapLength )
851 buffer << 0.0 << currentRemainingGapLength;
852 totalBufferLength += currentRemainingGapLength;
853 remainingSegmentDistance -= currentRemainingGapLength;
854 currentRemainingGapLength = 0.0;
860 buffer << 0.0 << remainingSegmentDistance;
861 totalBufferLength += remainingSegmentDistance;
862 currentRemainingGapLength -= remainingSegmentDistance;
867 if ( patternIndex >= sourcePattern.size() )
870 const double nextPatternDashLength = sourcePattern.at( patternIndex );
871 const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
872 if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
874 buffer << nextPatternDashLength << nextPatternGapLength;
875 remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
876 totalBufferLength += nextPatternDashLength + nextPatternGapLength;
879 else if ( nextPatternDashLength <= remainingSegmentDistance )
882 buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
883 totalBufferLength += remainingSegmentDistance;
884 currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
885 currentRemainingDashLength = 0;
892 buffer << remainingSegmentDistance << 0.0;
893 totalBufferLength += remainingSegmentDistance;
894 currentRemainingGapLength = 0;
895 currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
900 bufferedPoints << p1;
901 if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
903 QPointF nextPoint = *( ptIt + 1 );
909 flushBuffer( &nextPoint );
910 bufferedPoints << p1;
913 if ( patternIndex % 2 == 1 )
917 currentRemainingDashLength = sourcePattern.at( patternIndex );
924 flushBuffer(
nullptr );
925 if ( !previousSegmentBuffer.empty() )
927 QPen adjustedPen = pen;
928 adjustedPen.setStyle( Qt::SolidLine );
929 painter->setPen( adjustedPen );
931 path.addPolygon( previousSegmentBuffer );
932 painter->drawPath( path );
933 previousSegmentBuffer.clear();
939 if ( mDrawInsidePolygon )
953 unit = mCustomDashPatternUnit;
954 return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
991 return mAlignDashPattern;
1001 return mPatternCartographicTweakOnSharpCorners;
1006 mPatternCartographicTweakOnSharpCorners =
enabled;
1035 MyLine( QPointF p1, QPointF p2 )
1036 : mVertical( false )
1037 , mIncreasing( false )
1049 mIncreasing = ( p2.y() > p1.y() );
1054 mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1055 mIncreasing = ( p2.x() > p1.x() );
1059 double x = ( p2.x() - p1.x() );
1060 double y = ( p2.y() - p1.y() );
1061 mLength = std::sqrt( x * x + y * y );
1067 double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1075 QPointF diffForInterval(
double interval )
1078 return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1080 double alpha = std::atan( mT );
1081 double dx = std::cos( alpha ) * interval;
1082 double dy = std::sin( alpha ) * interval;
1083 return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1086 double length() {
return mLength; }
1101 : mRotateSymbols( rotateSymbol )
1102 , mInterval( interval )
1122 if ( exprVal.isValid() )
1124 QString placementString = exprVal.toString();
1125 if ( placementString.compare( QLatin1String(
"interval" ), Qt::CaseInsensitive ) == 0 )
1129 else if ( placementString.compare( QLatin1String(
"vertex" ), Qt::CaseInsensitive ) == 0 )
1133 else if ( placementString.compare( QLatin1String(
"lastvertex" ), Qt::CaseInsensitive ) == 0 )
1137 else if ( placementString.compare( QLatin1String(
"firstvertex" ), Qt::CaseInsensitive ) == 0 )
1141 else if ( placementString.compare( QLatin1String(
"centerpoint" ), Qt::CaseInsensitive ) == 0 )
1145 else if ( placementString.compare( QLatin1String(
"curvepoint" ), Qt::CaseInsensitive ) == 0 )
1149 else if ( placementString.compare( QLatin1String(
"segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1162 double averageOver = mAverageAngleLength;
1175 renderPolylineInterval( points, context, averageOver );
1179 renderPolylineCentral( points, context, averageOver );
1187 renderPolylineVertex( points, context,
placement );
1196 for (
int part = 0; part < mline.count(); ++part )
1198 const QPolygonF &points2 = mline[ part ];
1203 renderPolylineInterval( points2, context, averageOver );
1207 renderPolylineCentral( points2, context, averageOver );
1215 renderPolylineVertex( points2, context,
placement );
1249 for (
int i = 0; i < rings->size(); ++i )
1298 map[QStringLiteral(
"rotate" )] = (
rotateSymbols() ? QStringLiteral(
"1" ) : QStringLiteral(
"0" ) );
1299 map[QStringLiteral(
"interval" )] = QString::number(
interval() );
1300 map[QStringLiteral(
"offset" )] = QString::number(
mOffset );
1301 map[QStringLiteral(
"offset_along_line" )] = QString::number(
offsetAlongLine() );
1308 map[QStringLiteral(
"average_angle_length" )] = QString::number( mAverageAngleLength );
1312 switch ( mPlacement )
1315 map[QStringLiteral(
"placement" )] = QStringLiteral(
"vertex" );
1318 map[QStringLiteral(
"placement" )] = QStringLiteral(
"lastvertex" );
1321 map[QStringLiteral(
"placement" )] = QStringLiteral(
"firstvertex" );
1324 map[QStringLiteral(
"placement" )] = QStringLiteral(
"centralpoint" );
1327 map[QStringLiteral(
"placement" )] = QStringLiteral(
"curvepoint" );
1330 map[QStringLiteral(
"placement" )] = QStringLiteral(
"interval" );
1333 map[QStringLiteral(
"placement" )] = QStringLiteral(
"segmentcenter" );
1337 map[QStringLiteral(
"ring_filter" )] = QString::number(
static_cast< int >(
mRingFilter ) );
1363 if (
properties.contains( QStringLiteral(
"offset" ) ) )
1367 if (
properties.contains( QStringLiteral(
"offset_unit" ) ) )
1371 if (
properties.contains( QStringLiteral(
"interval_unit" ) ) )
1375 if (
properties.contains( QStringLiteral(
"offset_along_line" ) ) )
1379 if (
properties.contains( QStringLiteral(
"offset_along_line_unit" ) ) )
1383 if (
properties.contains( ( QStringLiteral(
"offset_along_line_map_unit_scale" ) ) ) )
1388 if (
properties.contains( QStringLiteral(
"offset_map_unit_scale" ) ) )
1392 if (
properties.contains( QStringLiteral(
"interval_map_unit_scale" ) ) )
1397 if (
properties.contains( QStringLiteral(
"average_angle_length" ) ) )
1401 if (
properties.contains( QStringLiteral(
"average_angle_unit" ) ) )
1405 if (
properties.contains( ( QStringLiteral(
"average_angle_map_unit_scale" ) ) ) )
1410 if (
properties.contains( QStringLiteral(
"placement" ) ) )
1412 if (
properties[QStringLiteral(
"placement" )] == QLatin1String(
"vertex" ) )
1414 else if (
properties[QStringLiteral(
"placement" )] == QLatin1String(
"lastvertex" ) )
1416 else if (
properties[QStringLiteral(
"placement" )] == QLatin1String(
"firstvertex" ) )
1418 else if (
properties[QStringLiteral(
"placement" )] == QLatin1String(
"centralpoint" ) )
1420 else if (
properties[QStringLiteral(
"placement" )] == QLatin1String(
"curvepoint" ) )
1422 else if (
properties[QStringLiteral(
"placement" )] == QLatin1String(
"segmentcenter" ) )
1428 if (
properties.contains( QStringLiteral(
"ring_filter" ) ) )
1436 void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval(
const QPolygonF &points,
QgsSymbolRenderContext &context,
double averageOver )
1438 if ( points.isEmpty() )
1441 double lengthLeft = 0;
1473 if ( painterUnitInterval < 0 )
1484 lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1486 if ( averageOver > 0 && !
qgsDoubleNear( averageOver, 0.0 ) )
1488 QVector< QPointF > angleStartPoints;
1489 QVector< QPointF > symbolPoints;
1490 QVector< QPointF > angleEndPoints;
1498 collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
1500 if ( symbolPoints.empty() )
1506 if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1509 symbolPoints.pop_back();
1512 angleEndPoints.reserve( symbolPoints.size() );
1513 angleStartPoints.reserve( symbolPoints.size() );
1514 if ( averageOver <= painterUnitOffsetAlongLine )
1516 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
1520 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1522 collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
1525 for (
int i = 0; i < symbolPoints.size(); ++ i )
1530 const QPointF pt = symbolPoints[i];
1531 const QPointF startPt = angleStartPoints[i];
1532 const QPointF endPt = angleEndPoints[i];
1534 MyLine l( startPt, endPt );
1549 QPointF lastPt = points[0];
1550 for (
int i = 1; i < points.count(); ++i )
1555 const QPointF &pt = points[i];
1561 MyLine l( lastPt, pt );
1562 QPointF diff = l.diffForInterval( painterUnitInterval );
1566 double c = 1 - lengthLeft / painterUnitInterval;
1568 lengthLeft += l.length();
1577 while ( lengthLeft > painterUnitInterval )
1581 lengthLeft -= painterUnitInterval;
1593 static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1596 double a1 = MyLine( prevPt, pt ).angle();
1597 double a2 = MyLine( pt, nextPt ).angle();
1598 double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1600 return std::atan2( unitY, unitX );
1605 if ( points.isEmpty() )
1611 int i = -1, maxCount = 0;
1612 bool isRing =
false;
1685 i = points.count() - 1;
1686 maxCount = points.count();
1694 maxCount = points.count();
1695 if ( points.first() == points.last() )
1712 renderOffsetVertexAlongLine( points, i, distance, context );
1722 prevPoint = points.at( 0 );
1724 QPointF symbolPoint;
1725 for ( ; i < maxCount; ++i )
1736 QPointF currentPoint = points.at( i );
1737 symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
1738 0.5 * ( currentPoint.y() + prevPoint.y() ) );
1741 double angle = std::atan2( currentPoint.y() - prevPoint.y(),
1742 currentPoint.x() - prevPoint.x() );
1745 prevPoint = currentPoint;
1749 symbolPoint = points.at( i );
1753 double angle = markerAngle( points, isRing, i );
1765 double QgsTemplatedLineSymbolLayerBase::markerAngle(
const QPolygonF &points,
bool isRing,
int vertex )
1768 const QPointF &pt = points[vertex];
1770 if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
1772 int prevIndex = vertex - 1;
1773 int nextIndex = vertex + 1;
1775 if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
1777 prevIndex = points.count() - 2;
1781 QPointF prevPoint, nextPoint;
1782 while ( prevIndex >= 0 )
1784 prevPoint = points[ prevIndex ];
1785 if ( prevPoint != pt )
1792 while ( nextIndex < points.count() )
1794 nextPoint = points[ nextIndex ];
1795 if ( nextPoint != pt )
1802 if ( prevIndex >= 0 && nextIndex < points.count() )
1804 angle = _averageAngle( prevPoint, pt, nextPoint );
1811 while ( vertex < points.size() - 1 )
1813 const QPointF &nextPt = points[vertex + 1];
1816 angle = MyLine( pt, nextPt ).angle();
1825 while ( vertex >= 1 )
1827 const QPointF &prevPt = points[vertex - 1];
1830 angle = MyLine( prevPt, pt ).angle();
1840 void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine(
const QPolygonF &points,
int vertex,
double distance,
QgsSymbolRenderContext &context )
1842 if ( points.isEmpty() )
1852 bool isRing =
false;
1853 if ( points.first() == points.last() )
1855 double angle = markerAngle( points, isRing, vertex );
1862 int pointIncrement = distance > 0 ? 1 : -1;
1863 QPointF previousPoint = points[vertex];
1864 int startPoint = distance > 0 ? std::min( vertex + 1, points.count() - 1 ) : std::max( vertex - 1, 0 );
1865 int endPoint = distance > 0 ? points.count() - 1 : 0;
1866 double distanceLeft = std::fabs( distance );
1868 for (
int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
1870 const QPointF &pt = points[i];
1872 if ( previousPoint == pt )
1876 MyLine l( previousPoint, pt );
1878 if ( distanceLeft < l.length() )
1881 QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
1891 distanceLeft -= l.length();
1898 void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints(
const QVector<QPointF> &p, QVector<QPointF> &dest,
double intervalPainterUnits,
double initialOffset,
double initialLag,
int numberPointsRequired )
1903 QVector< QPointF > points = p;
1904 const bool closedRing = points.first() == points.last();
1906 double lengthLeft = initialOffset;
1908 double initialLagLeft = initialLag > 0 ? -initialLag : 1;
1909 if ( initialLagLeft < 0 && closedRing )
1912 QPointF lastPt = points.constLast();
1913 QVector< QPointF > pseudoPoints;
1914 for (
int i = points.count() - 2; i > 0; --i )
1916 if ( initialLagLeft >= 0 )
1921 const QPointF &pt = points[i];
1926 MyLine l( lastPt, pt );
1927 initialLagLeft += l.length();
1932 std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
1934 points = pseudoPoints;
1939 while ( initialLagLeft < 0 )
1941 dest << points.constFirst();
1942 initialLagLeft += intervalPainterUnits;
1945 if ( initialLag > 0 )
1947 lengthLeft += intervalPainterUnits - initialLagLeft;
1950 QPointF lastPt = points[0];
1951 for (
int i = 1; i < points.count(); ++i )
1953 const QPointF &pt = points[i];
1957 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
1966 MyLine l( lastPt, pt );
1967 QPointF diff = l.diffForInterval( intervalPainterUnits );
1971 double c = 1 - lengthLeft / intervalPainterUnits;
1973 lengthLeft += l.length();
1976 while ( lengthLeft > intervalPainterUnits ||
qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
1980 lengthLeft -= intervalPainterUnits;
1983 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
1988 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
1992 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
1999 if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2002 while ( dest.size() < numberPointsRequired )
2003 dest << points.constLast();
2007 void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral(
const QPolygonF &points,
QgsSymbolRenderContext &context,
double averageAngleOver )
2009 if ( !points.isEmpty() )
2013 QPolygonF::const_iterator it = points.constBegin();
2015 for ( ++it; it != points.constEnd(); ++it )
2017 length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2018 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2024 const double midPoint = length / 2;
2027 double thisSymbolAngle = 0;
2029 if ( averageAngleOver > 0 && !
qgsDoubleNear( averageAngleOver, 0.0 ) )
2031 QVector< QPointF > angleStartPoints;
2032 QVector< QPointF > symbolPoints;
2033 QVector< QPointF > angleEndPoints;
2035 collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
2036 collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
2037 collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
2039 pt = symbolPoints.at( 1 );
2040 MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2041 thisSymbolAngle = l.angle();
2046 it = points.constBegin();
2048 qreal last_at = 0, next_at = 0;
2051 for ( ++it; it != points.constEnd(); ++it )
2054 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2055 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2056 if ( next_at >= midPoint )
2064 MyLine l( last, next );
2065 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2066 pt = last + ( next - last ) * k;
2067 thisSymbolAngle = l.angle();
2117 if ( props.contains( QStringLiteral(
"interval" ) ) )
2118 interval = props[QStringLiteral(
"interval" )].toDouble();
2119 if ( props.contains( QStringLiteral(
"rotate" ) ) )
2120 rotate = ( props[QStringLiteral(
"rotate" )] == QLatin1String(
"1" ) );
2122 std::unique_ptr< QgsMarkerLineSymbolLayer > x = qgis::make_unique< QgsMarkerLineSymbolLayer >( rotate,
interval );
2129 return QStringLiteral(
"MarkerLine" );
2148 QgsSymbol::RenderHints hints = QgsSymbol::RenderHints();
2151 mMarker->setRenderHints( hints );
2164 std::unique_ptr< QgsMarkerLineSymbolLayer > x = qgis::make_unique< QgsMarkerLineSymbolLayer >(
rotateSymbols(),
interval() );
2171 for (
int i = 0; i <
mMarker->symbolLayerCount(); i++ )
2173 QDomElement symbolizerElem = doc.createElement( QStringLiteral(
"se:LineSymbolizer" ) );
2174 if ( !props.value( QStringLiteral(
"uom" ), QString() ).isEmpty() )
2175 symbolizerElem.setAttribute( QStringLiteral(
"uom" ), props.value( QStringLiteral(
"uom" ), QString() ) );
2176 element.appendChild( symbolizerElem );
2211 QDomElement strokeElem = doc.createElement( QStringLiteral(
"se:Stroke" ) );
2212 symbolizerElem.appendChild( strokeElem );
2215 QDomElement graphicStrokeElem = doc.createElement( QStringLiteral(
"se:GraphicStroke" ) );
2216 strokeElem.appendChild( graphicStrokeElem );
2222 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral(
"MarkerSymbolLayerV2 expected, %1 found. Skip it." ).arg( layer->
layerType() ) ) );
2229 if ( !gap.isEmpty() )
2231 QDomElement gapElem = doc.createElement( QStringLiteral(
"se:Gap" ) );
2233 graphicStrokeElem.appendChild( gapElem );
2238 QDomElement perpOffsetElem = doc.createElement( QStringLiteral(
"se:PerpendicularOffset" ) );
2241 symbolizerElem.appendChild( perpOffsetElem );
2250 QDomElement strokeElem = element.firstChildElement( QStringLiteral(
"Stroke" ) );
2251 if ( strokeElem.isNull() )
2254 QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral(
"GraphicStroke" ) );
2255 if ( graphicStrokeElem.isNull() )
2263 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2265 if ( it.key() == QLatin1String(
"placement" ) )
2267 if ( it.value() == QLatin1String(
"points" ) )
2269 else if ( it.value() == QLatin1String(
"firstPoint" ) )
2271 else if ( it.value() == QLatin1String(
"lastPoint" ) )
2273 else if ( it.value() == QLatin1String(
"centralPoint" ) )
2276 else if ( it.value() == QLatin1String(
"rotateMarker" ) )
2282 std::unique_ptr< QgsMarkerSymbol > marker;
2296 QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral(
"Gap" ) );
2297 if ( !gapElem.isNull() )
2300 double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2306 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral(
"PerpendicularOffset" ) );
2307 if ( !perpOffsetElem.isNull() )
2310 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2315 QString uom = element.attribute( QStringLiteral(
"uom" ) );
2337 mMarker->setDataDefinedSize( property );
2359 mMarker->renderPoint( point, feature, context, layer, selected );
2369 return mMarker->size( context );
2375 mMarker->setOutputUnit( unit );
2386 attr.unite(
mMarker->usedAttributes( context ) );
2401 return (
mMarker->size( context ) / 2.0 ) +
2421 if ( props.contains( QStringLiteral(
"interval" ) ) )
2422 interval = props[QStringLiteral(
"interval" )].toDouble();
2423 if ( props.contains( QStringLiteral(
"rotate" ) ) )
2424 rotate = ( props[QStringLiteral(
"rotate" )] == QLatin1String(
"1" ) );
2426 std::unique_ptr< QgsHashedLineSymbolLayer > x = qgis::make_unique< QgsHashedLineSymbolLayer >( rotate,
interval );
2428 if ( props.contains( QStringLiteral(
"hash_angle" ) ) )
2430 x->setHashAngle( props[QStringLiteral(
"hash_angle" )].toDouble() );
2433 if ( props.contains( QStringLiteral(
"hash_length" ) ) )
2434 x->setHashLength( props[QStringLiteral(
"hash_length" )].toDouble() );
2436 if ( props.contains( QStringLiteral(
"hash_length_unit" ) ) )
2439 if ( props.contains( QStringLiteral(
"hash_length_map_unit_scale" ) ) )
2447 return QStringLiteral(
"HashLine" );
2452 mHashSymbol->setOpacity( context.
opacity() );
2455 QgsSymbol::RenderHints hints = QgsSymbol::RenderHints();
2458 mHashSymbol->setRenderHints( hints );
2471 map[ QStringLiteral(
"hash_angle" ) ] = QString::number( mHashAngle );
2473 map[QStringLiteral(
"hash_length" )] = QString::number( mHashLength );
2482 std::unique_ptr< QgsHashedLineSymbolLayer > x = qgis::make_unique< QgsHashedLineSymbolLayer >(
rotateSymbols(),
interval() );
2484 x->setHashAngle( mHashAngle );
2485 x->setHashLength( mHashLength );
2486 x->setHashLengthUnit( mHashLengthUnit );
2487 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2493 mHashSymbol->setColor(
color );
2499 return mHashSymbol ? mHashSymbol->color() :
mColor;
2504 return mHashSymbol.get();
2515 mHashSymbol.reset(
static_cast<QgsLineSymbol *
>( symbol ) );
2516 mColor = mHashSymbol->color();
2522 mHashLength =
width;
2537 return ( mHashSymbol->width( context ) / 2.0 )
2545 mHashSymbol->setOutputUnit( unit );
2555 attr.unite( mHashSymbol->usedAttributes( context ) );
2563 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2572 mHashSymbol->setDataDefinedWidth( property );
2579 mSymbolLineAngle =
angle;
2584 return mSymbolAngle;
2589 mSymbolAngle =
angle;
2594 double lineLength = mHashLength;
2600 const double w = context.
convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2614 points << QPointF( start.
x(), start.
y() ) << QPointF( end.
x(), end.
y() );
2616 mHashSymbol->renderPolyline( points, feature, context, layer, selected );