QGIS API Documentation 3.39.0-Master (d85f3c2a281)
Loading...
Searching...
No Matches
qgslinearreferencingsymbollayer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslinearreferencingsymbollayer.cpp
3 ---------------------
4 begin : August 2024
5 copyright : (C) 2024 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17#include "qgsrendercontext.h"
18#include "qgstextrenderer.h"
19#include "qgslinestring.h"
20#include "qgspolygon.h"
21#include "qgsmarkersymbol.h"
23#include "qgsapplication.h"
25#include "qgsgeometryutils.h"
26#include "qgsunittypes.h"
27#include "qgssymbollayerutils.h"
28#include "qgstextlabelfeature.h"
29#include "qgsgeos.h"
30#include "qgspallabeling.h"
31#include "labelposition.h"
32#include "feature.h"
33
35class QgsTextLabelFeatureWithFormat : public QgsTextLabelFeature
36{
37 public:
38 QgsTextLabelFeatureWithFormat( QgsFeatureId id, geos::unique_ptr geometry, QSizeF size, const QgsTextFormat &format )
39 : QgsTextLabelFeature( id, std::move( geometry ), size )
40 , mFormat( format )
41 {}
42
43 QgsTextFormat mFormat;
44
45};
46
47class QgsLinearReferencingSymbolLayerLabelProvider final : public QgsAbstractLabelProvider
48{
49 public:
50 QgsLinearReferencingSymbolLayerLabelProvider()
51 : QgsAbstractLabelProvider( nullptr )
52 {
54 mFlags |= DrawLabels;
55
56 // always consider these labels highest priority
57 // TODO possibly expose as a setting for the symbol layer?
58 mPriority = 0;
59 }
60
61 ~QgsLinearReferencingSymbolLayerLabelProvider()
62 {
63 qDeleteAll( mLabels );
64 }
65
66 void addLabel( const QPointF &painterPoint, double angleRadians, const QString &text, QgsRenderContext &context, const QgsTextFormat &format )
67 {
68 // labels must be registered in destination map units
69 QgsPoint mapPoint( painterPoint );
70 mapPoint.transform( context.mapToPixel().transform().inverted() );
71
73 if ( format.allowHtmlFormatting() && !text.isEmpty() )
74 {
75 doc = QgsTextDocument::fromHtml( QStringList() << text );
76 }
77 else
78 {
79 doc = QgsTextDocument::fromPlainText( { text } );
80 }
81 QgsTextDocumentMetrics documentMetrics = QgsTextDocumentMetrics::calculateMetrics( doc, format, context );
83
84 double uPP = context.mapToPixel().mapUnitsPerPixel();
85 std::unique_ptr< QgsTextLabelFeatureWithFormat > feature = std::make_unique< QgsTextLabelFeatureWithFormat >( mLabels.size(),
86 QgsGeos::asGeos( &mapPoint ), QSizeF( size.width() * uPP, size.height() * uPP ), format );
87
88 feature->setDocument( doc, documentMetrics );
89 feature->setFixedAngle( angleRadians );
90 feature->setHasFixedAngle( true );
91 // above right
92 // TODO: we could potentially expose this, and use a non-fixed mode to allow fallback positions
93 feature->setQuadOffset( QPointF( 1, 1 ) );
94
95 mLabels.append( feature.release() );
96 }
97
98 QList<QgsLabelFeature *> labelFeatures( QgsRenderContext & ) final
99 {
100 return mLabels;
101 }
102
103 void drawLabel( QgsRenderContext &context, pal::LabelPosition *label ) const final
104 {
105 // as per vector label rendering...
106 QgsMapToPixel xform = context.mapToPixel();
107 xform.setMapRotation( 0, 0, 0 );
108 const QPointF outPt = context.mapToPixel().transform( label->getX(), label->getY() ).toQPointF();
109
110 QgsTextLabelFeatureWithFormat *lf = qgis::down_cast<QgsTextLabelFeatureWithFormat *>( label->getFeaturePart()->feature() );
112 lf->mFormat, lf->document(), lf->documentMetrics(), context, Qgis::TextHorizontalAlignment::Left,
113 label->getAlpha() );
114 }
115
116 private:
117 QList<QgsLabelFeature *> mLabels;
118
119};
121
124{
125 mNumericFormat = std::make_unique< QgsBasicNumericFormat >();
126}
127
129
131{
132 std::unique_ptr< QgsLinearReferencingSymbolLayer > res = std::make_unique< QgsLinearReferencingSymbolLayer >();
133 res->setPlacement( qgsEnumKeyToValue( properties.value( QStringLiteral( "placement" ) ).toString(), Qgis::LinearReferencingPlacement::IntervalCartesian2D ) );
134 res->setLabelSource( qgsEnumKeyToValue( properties.value( QStringLiteral( "source" ) ).toString(), Qgis::LinearReferencingLabelSource::CartesianDistance2D ) );
135 bool ok = false;
136 const double interval = properties.value( QStringLiteral( "interval" ) ).toDouble( &ok );
137 if ( ok )
138 res->setInterval( interval );
139 const double skipMultiples = properties.value( QStringLiteral( "skip_multiples" ) ).toDouble( &ok );
140 if ( ok )
141 res->setSkipMultiplesOf( skipMultiples );
142 res->setRotateLabels( properties.value( QStringLiteral( "rotate" ), true ).toBool() );
143 res->setShowMarker( properties.value( QStringLiteral( "show_marker" ), false ).toBool() );
144
145 // it's impossible to get the project's path resolver here :(
146 // TODO QGIS 4.0 -- force use of QgsReadWriteContext in create methods
147 QgsReadWriteContext rwContext;
148 //rwContext.setPathResolver( QgsProject::instance()->pathResolver() );
149
150 const QString textFormatXml = properties.value( QStringLiteral( "text_format" ) ).toString();
151 if ( !textFormatXml.isEmpty() )
152 {
153 QDomDocument doc;
154 QDomElement elem;
155 doc.setContent( textFormatXml );
156 elem = doc.documentElement();
157
159 textFormat.readXml( elem, rwContext );
160 res->setTextFormat( textFormat );
161 }
162
163 const QString numericFormatXml = properties.value( QStringLiteral( "numeric_format" ) ).toString();
164 if ( !numericFormatXml.isEmpty() )
165 {
166 QDomDocument doc;
167 doc.setContent( numericFormatXml );
168 res->setNumericFormat( QgsApplication::numericFormatRegistry()->createFromXml( doc.documentElement(), rwContext ) );
169 }
170
171 if ( properties.contains( QStringLiteral( "label_offset" ) ) )
172 {
173 res->setLabelOffset( QgsSymbolLayerUtils::decodePoint( properties[QStringLiteral( "label_offset" )].toString() ) );
174 }
175 if ( properties.contains( QStringLiteral( "label_offset_unit" ) ) )
176 {
177 res->setLabelOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "label_offset_unit" )].toString() ) );
178 }
179 if ( properties.contains( ( QStringLiteral( "label_offset_map_unit_scale" ) ) ) )
180 {
181 res->setLabelOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "label_offset_map_unit_scale" )].toString() ) );
182 }
183 if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
184 {
185 res->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
186 }
187 if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
188 {
189 res->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) );
190 }
191 if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
192 {
193 res->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) );
194 }
195
196 return res.release();
197}
198
200{
201 std::unique_ptr< QgsLinearReferencingSymbolLayer > res = std::make_unique< QgsLinearReferencingSymbolLayer >();
202 res->setPlacement( mPlacement );
203 res->setLabelSource( mLabelSource );
204 res->setInterval( mInterval );
205 res->setSkipMultiplesOf( mSkipMultiplesOf );
206 res->setRotateLabels( mRotateLabels );
207 res->setLabelOffset( mLabelOffset );
208 res->setLabelOffsetUnit( mLabelOffsetUnit );
209 res->setLabelOffsetMapUnitScale( mLabelOffsetMapUnitScale );
210 res->setShowMarker( mShowMarker );
211 res->setAverageAngleLength( mAverageAngleLength );
212 res->setAverageAngleUnit( mAverageAngleLengthUnit );
213 res->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
214
215 res->mTextFormat = mTextFormat;
216 res->mMarkerSymbol.reset( mMarkerSymbol ? mMarkerSymbol->clone() : nullptr );
217 if ( mNumericFormat )
218 res->mNumericFormat.reset( mNumericFormat->clone() );
219
220 copyDataDefinedProperties( res.get() );
221 copyPaintEffect( res.get() );
222
223 return res.release();
224}
225
227{
228 QDomDocument textFormatDoc;
229 // it's impossible to get the project's path resolver here :(
230 // TODO QGIS 4.0 -- force use of QgsReadWriteContext in properties methods
231 QgsReadWriteContext rwContext;
232 // rwContext.setPathResolver( QgsProject::instance()->pathResolver() );
233 const QDomElement textElem = mTextFormat.writeXml( textFormatDoc, rwContext );
234 textFormatDoc.appendChild( textElem );
235
236 QDomDocument numericFormatDoc;
237 QDomElement numericFormatElem = numericFormatDoc.createElement( QStringLiteral( "numericFormat" ) );
238 mNumericFormat->writeXml( numericFormatElem, numericFormatDoc, rwContext );
239 numericFormatDoc.appendChild( numericFormatElem );
240
241 QVariantMap res
242 {
243 {
244 QStringLiteral( "placement" ), qgsEnumValueToKey( mPlacement )
245 },
246 {
247 QStringLiteral( "source" ), qgsEnumValueToKey( mLabelSource )
248 },
249 {
250 QStringLiteral( "interval" ), mInterval
251 },
252 {
253 QStringLiteral( "rotate" ), mRotateLabels
254 },
255 {
256 QStringLiteral( "show_marker" ), mShowMarker
257 },
258 {
259 QStringLiteral( "text_format" ), textFormatDoc.toString()
260 },
261 {
262 QStringLiteral( "numeric_format" ), numericFormatDoc.toString()
263 },
264 {
265 QStringLiteral( "label_offset" ), QgsSymbolLayerUtils::encodePoint( mLabelOffset )
266 },
267 {
268 QStringLiteral( "label_offset_unit" ), QgsUnitTypes::encodeUnit( mLabelOffsetUnit )
269 },
270 {
271 QStringLiteral( "label_offset_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mLabelOffsetMapUnitScale )
272 },
273 {
274 QStringLiteral( "average_angle_length" ), mAverageAngleLength
275 },
276 {
277 QStringLiteral( "average_angle_unit" ), QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit )
278 },
279 {
280 QStringLiteral( "average_angle_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale )
281 },
282 };
283
284 if ( mSkipMultiplesOf >= 0 )
285 {
286 res.insert( QStringLiteral( "skip_multiples" ), mSkipMultiplesOf );
287 }
288
289 return res;
290}
291
293{
294 return QStringLiteral( "LinearReferencing" );
295}
296
302
304{
305 return mShowMarker ? mMarkerSymbol.get() : nullptr;
306}
307
309{
310 if ( symbol && symbol->type() == Qgis::SymbolType::Marker )
311 {
312 mMarkerSymbol.reset( qgis::down_cast<QgsMarkerSymbol *>( symbol ) );
313 return true;
314 }
315 delete symbol;
316 return false;
317}
318
320{
321 if ( mMarkerSymbol )
322 {
323 Qgis::SymbolRenderHints hints = mMarkerSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol;
324 if ( mRotateLabels )
326 mMarkerSymbol->setRenderHints( hints );
327
328 mMarkerSymbol->startRender( context.renderContext(), context.fields() );
329 }
330
331 if ( QgsLabelingEngine *labelingEngine = context.renderContext().labelingEngine() )
332 {
333 // rendering with a labeling engine. In this scenario we will register rendered text as labels, so that they participate in the labeling problem
334 // for the map
335 QgsLinearReferencingSymbolLayerLabelProvider *provider = new QgsLinearReferencingSymbolLayerLabelProvider();
336 mLabelProviderId = labelingEngine->addProvider( provider );
337 }
338}
339
341{
342 if ( mMarkerSymbol )
343 {
344 mMarkerSymbol->stopRender( context.renderContext() );
345 }
346}
347
348void QgsLinearReferencingSymbolLayer::renderGeometryPart( QgsSymbolRenderContext &context, const QgsAbstractGeometry *geometry, double labelOffsetPainterUnitsX, double labelOffsetPainterUnitsY, double skipMultiples, double averageAngleDistancePainterUnits, bool showMarker )
349{
350 if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( geometry ) )
351 {
352 renderLineString( context, line, labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker );
353 }
354 else if ( const QgsPolygon *polygon = qgsgeometry_cast< const QgsPolygon * >( geometry ) )
355 {
356 renderLineString( context, qgsgeometry_cast< const QgsLineString *>( polygon->exteriorRing() ), labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker );
357 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
358 {
359 renderLineString( context, qgsgeometry_cast< const QgsLineString *>( polygon->interiorRing( i ) ), labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker );
360 }
361 }
362}
363
364void QgsLinearReferencingSymbolLayer::renderLineString( QgsSymbolRenderContext &context, const QgsLineString *line, double labelOffsetPainterUnitsX, double labelOffsetPainterUnitsY, double skipMultiples, double averageAngleDistancePainterUnits, bool showMarker )
365{
366 if ( !line )
367 return;
368
369 switch ( mPlacement )
370 {
374 renderPolylineInterval( line, context, skipMultiples, QPointF( labelOffsetPainterUnitsX, labelOffsetPainterUnitsY ), averageAngleDistancePainterUnits, showMarker );
375 break;
376
378 renderPolylineVertex( line, context, skipMultiples, QPointF( labelOffsetPainterUnitsX, labelOffsetPainterUnitsY ), averageAngleDistancePainterUnits, showMarker );
379 break;
380 }
381}
382
384{
385 QPainter *p = context.renderContext().painter();
386 if ( !p )
387 {
388 return;
389 }
390
391 // TODO (maybe?): if we don't have an original geometry, convert points to linestring and scale distance to painter units?
392 // in reality this line type makes no sense for rendering non-real feature geometries...
393 ( void )points;
394 const QgsAbstractGeometry *geometry = context.renderContext().geometry();
395 if ( !geometry )
396 return;
397
398 double skipMultiples = mSkipMultiplesOf;
400 {
401 context.setOriginalValueVariable( mSkipMultiplesOf );
403 }
404
405 double labelOffsetX = mLabelOffset.x();
406 double labelOffsetY = mLabelOffset.y();
407
408 double averageOver = mAverageAngleLength;
410 {
411 context.setOriginalValueVariable( mAverageAngleLength );
413 }
414
415 bool showMarker = mShowMarker;
417 {
420 }
421
422 const double labelOffsetPainterUnitsX = context.renderContext().convertToPainterUnits( labelOffsetX, mLabelOffsetUnit, mLabelOffsetMapUnitScale );
423 const double labelOffsetPainterUnitsY = context.renderContext().convertToPainterUnits( labelOffsetY, mLabelOffsetUnit, mLabelOffsetMapUnitScale );
424 const double averageAngleDistancePainterUnits = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2;
425
426 for ( auto partIt = geometry->const_parts_begin(); partIt != geometry->const_parts_end(); ++partIt )
427 {
428 renderGeometryPart( context, *partIt, labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker );
429 }
430}
431
432
433double calculateAveragedAngle( double targetPointDistanceAlongSegment, double segmentLengthPainterUnits,
434 double averageAngleLengthPainterUnits, double prevXPainterUnits, double prevYPainterUnits,
435 double thisXPainterUnits, double thisYPainterUnits, const double *xPainterUnits,
436 const double *yPainterUnits, int totalPoints, int i )
437{
438
439 // track forward by averageAngleLengthPainterUnits
440 double painterDistRemaining = averageAngleLengthPainterUnits + targetPointDistanceAlongSegment;
441 double startAverageSegmentX = prevXPainterUnits;
442 double startAverageSegmentY = prevYPainterUnits;
443 double endAverageSegmentX = thisXPainterUnits;
444 double endAverageSegmentY = thisYPainterUnits;
445 double averagingSegmentLengthPainterUnits = segmentLengthPainterUnits;
446 const double *xAveragingData = xPainterUnits;
447 const double *yAveragingData = yPainterUnits;
448
449 int j = i;
450 while ( painterDistRemaining > averagingSegmentLengthPainterUnits )
451 {
452 if ( j >= totalPoints - 1 )
453 break;
454
455 painterDistRemaining -= averagingSegmentLengthPainterUnits;
456 startAverageSegmentX = endAverageSegmentX;
457 startAverageSegmentY = endAverageSegmentY;
458
459 endAverageSegmentX = *xAveragingData++;
460 endAverageSegmentY = *yAveragingData++;
461 j++;
462 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
463 }
464 // fits on this same segment
465 double endAverageXPainterUnits;
466 double endAverageYPainterUnits;
467 if ( painterDistRemaining < averagingSegmentLengthPainterUnits )
468 {
469 QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, endAverageXPainterUnits, endAverageYPainterUnits,
470 nullptr, nullptr, nullptr,
471 nullptr, nullptr, nullptr );
472 }
473 else
474 {
475 endAverageXPainterUnits = endAverageSegmentX;
476 endAverageYPainterUnits = endAverageSegmentY;
477 }
478
479 // also track back by averageAngleLengthPainterUnits
480 j = i;
481 painterDistRemaining = ( segmentLengthPainterUnits - targetPointDistanceAlongSegment ) + averageAngleLengthPainterUnits;
482 startAverageSegmentX = thisXPainterUnits;
483 startAverageSegmentY = thisYPainterUnits;
484 endAverageSegmentX = prevXPainterUnits;
485 endAverageSegmentY = prevYPainterUnits;
486 averagingSegmentLengthPainterUnits = segmentLengthPainterUnits;
487 xAveragingData = xPainterUnits - 2;
488 yAveragingData = yPainterUnits - 2;
489 while ( painterDistRemaining > averagingSegmentLengthPainterUnits )
490 {
491 if ( j < 1 )
492 break;
493
494 painterDistRemaining -= averagingSegmentLengthPainterUnits;
495 startAverageSegmentX = endAverageSegmentX;
496 startAverageSegmentY = endAverageSegmentY;
497
498 endAverageSegmentX = *xAveragingData--;
499 endAverageSegmentY = *yAveragingData--;
500 j--;
501 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
502 }
503 // fits on this same segment
504 double startAverageXPainterUnits;
505 double startAverageYPainterUnits;
506 if ( painterDistRemaining < averagingSegmentLengthPainterUnits )
507 {
508 QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, startAverageXPainterUnits, startAverageYPainterUnits,
509 nullptr, nullptr, nullptr,
510 nullptr, nullptr, nullptr );
511 }
512 else
513 {
514 startAverageXPainterUnits = endAverageSegmentX;
515 startAverageYPainterUnits = endAverageSegmentY;
516 }
517
518 double calculatedAngle = std::fmod( QgsGeometryUtilsBase::azimuth( startAverageXPainterUnits, startAverageYPainterUnits, endAverageXPainterUnits, endAverageYPainterUnits ) + 360, 360 );
519 if ( calculatedAngle > 90 && calculatedAngle < 270 )
520 calculatedAngle += 180;
521
522 return calculatedAngle;
523}
524
525typedef std::function<bool ( double x, double y, double z, double m, double distanceFromStart, double angle )> VisitPointFunction;
526typedef std::function< void( const QgsLineString *, const QgsLineString *, bool, double, double, const VisitPointFunction & ) > VisitPointAtDistanceFunction;
527
528void visitPointsByRegularDistance( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint )
529{
530 if ( distance < 0 )
531 return;
532
533 double distanceTraversed = 0;
534 const int totalPoints = line->numPoints();
535 if ( totalPoints == 0 )
536 return;
537
538 const double *x = line->xData();
539 const double *y = line->yData();
540 const double *z = line->is3D() ? line->zData() : nullptr;
541 const double *m = line->isMeasure() ? line->mData() : nullptr;
542
543 const double *xPainterUnits = linePainterUnits->xData();
544 const double *yPainterUnits = linePainterUnits->yData();
545
546 double prevX = *x++;
547 double prevY = *y++;
548 double prevZ = z ? *z++ : 0.0;
549 double prevM = m ? *m++ : 0.0;
550
551 double prevXPainterUnits = *xPainterUnits++;
552 double prevYPainterUnits = *yPainterUnits++;
553
554 if ( qgsDoubleNear( distance, 0.0 ) )
555 {
556 visitPoint( prevX, prevY, prevZ, prevM, 0, 0 );
557 return;
558 }
559
560 double pZ = std::numeric_limits<double>::quiet_NaN();
561 double pM = std::numeric_limits<double>::quiet_NaN();
562 double nextPointDistance = emitFirstPoint ? 0 : distance;
563 for ( int i = 1; i < totalPoints; ++i )
564 {
565 double thisX = *x++;
566 double thisY = *y++;
567 double thisZ = z ? *z++ : 0.0;
568 double thisM = m ? *m++ : 0.0;
569 double thisXPainterUnits = *xPainterUnits++;
570 double thisYPainterUnits = *yPainterUnits++;
571
572 double angle = std::fmod( QgsGeometryUtilsBase::azimuth( prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits ) + 360, 360 );
573 if ( angle > 90 && angle < 270 )
574 angle += 180;
575
576 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
577 const double segmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( thisXPainterUnits, thisYPainterUnits, prevXPainterUnits, prevYPainterUnits );
578
579 while ( nextPointDistance < distanceTraversed + segmentLength || qgsDoubleNear( nextPointDistance, distanceTraversed + segmentLength ) )
580 {
581 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
582 const double distanceToPoint = std::min( nextPointDistance - distanceTraversed, segmentLength );
583 double pX, pY;
584 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY,
585 z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &pZ : nullptr,
586 m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &pM : nullptr );
587
588 double calculatedAngle = angle;
589 if ( averageAngleLengthPainterUnits > 0 )
590 {
591 const double targetPointFractionAlongSegment = distanceToPoint / segmentLength;
592 const double targetPointDistanceAlongSegment = targetPointFractionAlongSegment * segmentLengthPainterUnits;
593
594 calculatedAngle = calculateAveragedAngle( targetPointDistanceAlongSegment, segmentLengthPainterUnits,
595 averageAngleLengthPainterUnits, prevXPainterUnits, prevYPainterUnits,
596 thisXPainterUnits, thisYPainterUnits, xPainterUnits,
597 yPainterUnits, totalPoints, i );
598 }
599
600 if ( !visitPoint( pX, pY, pZ, pM, nextPointDistance, calculatedAngle ) )
601 return;
602
603 nextPointDistance += distance;
604 }
605
606 distanceTraversed += segmentLength;
607 prevX = thisX;
608 prevY = thisY;
609 prevZ = thisZ;
610 prevM = thisM;
611 prevXPainterUnits = thisXPainterUnits;
612 prevYPainterUnits = thisYPainterUnits;
613 }
614}
615
616double interpolateValue( double a, double b, double fraction )
617{
618 return a + ( b - a ) * fraction;
619}
620
621
622void visitPointsByInterpolatedZM( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double step, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint, bool useZ )
623{
624 if ( step < 0 )
625 return;
626
627 double distanceTraversed = 0;
628 const int totalPoints = line->numPoints();
629 if ( totalPoints < 2 )
630 return;
631
632 const double *x = line->xData();
633 const double *y = line->yData();
634 const double *z = line->is3D() ? line->zData() : nullptr;
635 const double *m = line->isMeasure() ? line->mData() : nullptr;
636
637 const double *xPainterUnits = linePainterUnits->xData();
638 const double *yPainterUnits = linePainterUnits->yData();
639
640 double prevX = *x++;
641 double prevY = *y++;
642 double prevZ = z ? *z++ : 0.0;
643 double prevM = m ? *m++ : 0.0;
644
645 double prevXPainterUnits = *xPainterUnits++;
646 double prevYPainterUnits = *yPainterUnits++;
647
648 if ( qgsDoubleNear( step, 0.0 ) )
649 {
650 visitPoint( prevX, prevY, prevZ, prevM, 0, 0 );
651 return;
652 }
653
654 double prevValue = useZ ? prevZ : prevM;
655 bool isFirstPoint = true;
656 for ( int i = 1; i < totalPoints; ++i )
657 {
658 double thisX = *x++;
659 double thisY = *y++;
660 double thisZ = z ? *z++ : 0.0;
661 double thisM = m ? *m++ : 0.0;
662 const double thisValue = useZ ? thisZ : thisM;
663 double thisXPainterUnits = *xPainterUnits++;
664 double thisYPainterUnits = *yPainterUnits++;
665
666 double angle = std::fmod( QgsGeometryUtilsBase::azimuth( prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits ) + 360, 360 );
667 if ( angle > 90 && angle < 270 )
668 angle += 180;
669
670 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
671 const double segmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( thisXPainterUnits, thisYPainterUnits, prevXPainterUnits, prevYPainterUnits );
672
673 // direction for this segment
674 const int direction = ( thisValue > prevValue ) ? 1 : ( thisValue < prevValue ) ? -1 : 0;
675 if ( direction != 0 )
676 {
677 // non-constant segment
678 double nextStepValue = direction > 0 ? std::ceil( prevValue / step ) * step
679 : std::floor( prevValue / step ) * step;
680
681 while ( ( direction > 0 && ( nextStepValue <= thisValue || qgsDoubleNear( nextStepValue, thisValue ) ) ) ||
682 ( direction < 0 && ( nextStepValue >= thisValue || qgsDoubleNear( nextStepValue, thisValue ) ) ) )
683 {
684 const double targetPointFractionAlongSegment = ( nextStepValue - prevValue ) / ( thisValue - prevValue );
685 const double targetPointDistanceAlongSegment = targetPointFractionAlongSegment * segmentLengthPainterUnits;
686
687 double pX, pY;
688 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, targetPointFractionAlongSegment * segmentLength, pX, pY );
689
690 const double pZ = useZ ? nextStepValue : interpolateValue( prevZ, thisZ, targetPointFractionAlongSegment );
691 const double pM = useZ ? interpolateValue( prevM, thisM, targetPointFractionAlongSegment ) : nextStepValue;
692
693 double calculatedAngle = angle;
694 if ( averageAngleLengthPainterUnits > 0 )
695 {
696 calculatedAngle = calculateAveragedAngle(
697 targetPointDistanceAlongSegment,
698 segmentLengthPainterUnits, averageAngleLengthPainterUnits,
699 prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits,
700 xPainterUnits, yPainterUnits,
701 totalPoints, i );
702 }
703
704 if ( !qgsDoubleNear( targetPointFractionAlongSegment, 0 ) || isFirstPoint )
705 {
706 if ( !visitPoint( pX, pY, pZ, pM, distanceTraversed + segmentLength * targetPointFractionAlongSegment, calculatedAngle ) )
707 return;
708 }
709
710 nextStepValue += direction * step;
711 }
712 }
713 else if ( isFirstPoint && emitFirstPoint )
714 {
715 if ( !visitPoint( prevX, prevY, prevZ, prevM, distanceTraversed,
716 std::fmod( QgsGeometryUtilsBase::azimuth( prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits ) + 360, 360 ) ) )
717 return;
718 }
719 isFirstPoint = false;
720
721 prevX = thisX;
722 prevY = thisY;
723 prevZ = thisZ;
724 prevM = thisM;
725 prevXPainterUnits = thisXPainterUnits;
726 prevYPainterUnits = thisYPainterUnits;
727 prevValue = thisValue;
728 distanceTraversed += segmentLength;
729 }
730}
731
732void visitPointsByInterpolatedZ( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint )
733{
734 visitPointsByInterpolatedZM( line, linePainterUnits, emitFirstPoint, distance, averageAngleLengthPainterUnits, visitPoint, true );
735}
736
737void visitPointsByInterpolatedM( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint )
738{
739 visitPointsByInterpolatedZM( line, linePainterUnits, emitFirstPoint, distance, averageAngleLengthPainterUnits, visitPoint, false );
740}
741
742QPointF QgsLinearReferencingSymbolLayer::pointToPainter( QgsSymbolRenderContext &context, double x, double y, double z )
743{
744 QPointF pt;
745 if ( context.renderContext().coordinateTransform().isValid() )
746 {
748 pt = QPointF( x, y );
749
750 }
751 else
752 {
753 pt = QPointF( x, y );
754 }
755
756 context.renderContext().mapToPixel().transformInPlace( pt.rx(), pt.ry() );
757 return pt;
758}
759
760void QgsLinearReferencingSymbolLayer::renderPolylineInterval( const QgsLineString *line, QgsSymbolRenderContext &context, double skipMultiples, const QPointF &labelOffsetPainterUnits, double averageAngleLengthPainterUnits, bool showMarker )
761{
762 double distance = mInterval;
764 {
765 context.setOriginalValueVariable( mInterval );
767 }
768
769 QgsNumericFormatContext numericContext;
770 numericContext.setExpressionContext( context.renderContext().expressionContext() );
771
772 std::unique_ptr< QgsLineString > painterUnitsGeometry( line->clone() );
773 if ( context.renderContext().coordinateTransform().isValid() )
774 {
775 painterUnitsGeometry->transform( context.renderContext().coordinateTransform() );
776 }
777 painterUnitsGeometry->transform( context.renderContext().mapToPixel().transform() );
778
779 const bool hasZ = line->is3D();
780 const bool hasM = line->isMeasure();
781 const bool emitFirstPoint = mLabelSource != Qgis::LinearReferencingLabelSource::CartesianDistance2D;
782
783 VisitPointAtDistanceFunction func = nullptr;
784
785 switch ( mPlacement )
786 {
789 break;
790
793 break;
794
797 break;
798
800 return;
801 }
802
803 QgsLinearReferencingSymbolLayerLabelProvider *labelProvider = nullptr;
804 if ( QgsLabelingEngine *labelingEngine = context.renderContext().labelingEngine() )
805 {
806 // rendering with a labeling engine. In this scenario we will register rendered text as labels, so that they participate in the labeling problem
807 // for the map
808 labelProvider = qgis::down_cast< QgsLinearReferencingSymbolLayerLabelProvider * >( labelingEngine->providerById( mLabelProviderId ) );
809 }
810
811 func( line, painterUnitsGeometry.get(), emitFirstPoint, distance, averageAngleLengthPainterUnits, [&context, &numericContext, skipMultiples, showMarker,
812 labelOffsetPainterUnits, hasZ, hasM, labelProvider, this]( double x, double y, double z, double m, double distanceFromStart, double angle ) -> bool
813 {
814 if ( context.renderContext().renderingStopped() )
815 return false;
816
817 double labelValue = 0;
818 bool labelVertex = true;
819 switch ( mLabelSource )
820 {
821 case Qgis::LinearReferencingLabelSource::CartesianDistance2D:
822 labelValue = distanceFromStart;
823 break;
824 case Qgis::LinearReferencingLabelSource::Z:
825 labelValue = z;
826 labelVertex = hasZ && !std::isnan( labelValue );
827 break;
828 case Qgis::LinearReferencingLabelSource::M:
829 labelValue = m;
830 labelVertex = hasM && !std::isnan( labelValue );
831 break;
832 }
833
834 if ( !labelVertex )
835 return true;
836
837 if ( skipMultiples > 0 && qgsDoubleNear( std::fmod( labelValue, skipMultiples ), 0 ) )
838 return true;
839
840 const QPointF pt = pointToPainter( context, x, y, z );
841
842 if ( mMarkerSymbol && showMarker )
843 {
844 if ( mRotateLabels )
845 mMarkerSymbol->setLineAngle( 90 - angle );
846 mMarkerSymbol->renderPoint( pt, context.feature(), context.renderContext() );
847 }
848
849 const double angleRadians = ( mRotateLabels ? angle : 0 ) * M_PI / 180.0;
850 const double dx = labelOffsetPainterUnits.x() * std::sin( angleRadians + M_PI_2 )
851 + labelOffsetPainterUnits.y() * std::sin( angleRadians );
852 const double dy = labelOffsetPainterUnits.x() * std::cos( angleRadians + M_PI_2 )
853 + labelOffsetPainterUnits.y() * std::cos( angleRadians );
854
855 const QString text = mNumericFormat->formatDouble( labelValue, numericContext );
856 if ( !labelProvider )
857 {
858 // render text directly
859 QgsTextRenderer::drawText( QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, Qgis::TextHorizontalAlignment::Left, { text }, context.renderContext(), mTextFormat );
860 }
861 else
862 {
863 // register as a label
864 labelProvider->addLabel(
865 QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, text, context.renderContext(), mTextFormat
866 );
867 }
868
869 return true;
870 } );
871}
872
873void QgsLinearReferencingSymbolLayer::renderPolylineVertex( const QgsLineString *line, QgsSymbolRenderContext &context, double skipMultiples, const QPointF &labelOffsetPainterUnits, double averageAngleLengthPainterUnits, bool showMarker )
874{
875 // let's simplify the logic by ALWAYS using the averaging approach for angles, and just
876 // use a very small distance if the user actually set this to 0. It'll be identical
877 // results anyway...
878 averageAngleLengthPainterUnits = std::max( averageAngleLengthPainterUnits, 0.1 );
879
880 QgsNumericFormatContext numericContext;
881 numericContext.setExpressionContext( context.renderContext().expressionContext() );
882
883 QgsLinearReferencingSymbolLayerLabelProvider *labelProvider = nullptr;
884 if ( QgsLabelingEngine *labelingEngine = context.renderContext().labelingEngine() )
885 {
886 // rendering with a labeling engine. In this scenario we will register rendered text as labels, so that they participate in the labeling problem
887 // for the map
888 labelProvider = qgis::down_cast< QgsLinearReferencingSymbolLayerLabelProvider * >( labelingEngine->providerById( mLabelProviderId ) );
889 }
890
891 const double *xData = line->xData();
892 const double *yData = line->yData();
893 const double *zData = line->is3D() ? line->zData() : nullptr;
894 const double *mData = line->isMeasure() ? line->mData() : nullptr;
895 const int size = line->numPoints();
896 if ( size < 2 )
897 return;
898
899 std::unique_ptr< QgsLineString > painterUnitsGeometry( line->clone() );
900 if ( context.renderContext().coordinateTransform().isValid() )
901 {
902 painterUnitsGeometry->transform( context.renderContext().coordinateTransform() );
903 }
904 painterUnitsGeometry->transform( context.renderContext().mapToPixel().transform() );
905 const double *xPainterUnits = painterUnitsGeometry->xData();
906 const double *yPainterUnits = painterUnitsGeometry->yData();
907
908 double currentDistance = 0;
909 double prevX = *xData;
910 double prevY = *yData;
911
912 for ( int i = 0; i < size; ++i )
913 {
914 if ( context.renderContext().renderingStopped() )
915 break;
916
917 double thisX = *xData++;
918 double thisY = *yData++;
919 double thisZ = zData ? *zData++ : 0;
920 double thisM = mData ? *mData++ : 0;
921 double thisXPainterUnits = *xPainterUnits++;
922 double thisYPainterUnits = *yPainterUnits++;
923
924 const double thisSegmentLength = QgsGeometryUtilsBase::distance2D( prevX, prevY, thisX, thisY );
925 currentDistance += thisSegmentLength;
926
927 if ( skipMultiples > 0 && qgsDoubleNear( std::fmod( currentDistance, skipMultiples ), 0 ) )
928 {
929 prevX = thisX;
930 prevY = thisY;
931 continue;
932 }
933
934 const QPointF pt = pointToPainter( context, thisX, thisY, thisZ );
935
936 // track forward by averageAngleLengthPainterUnits
937 double painterDistRemaining = averageAngleLengthPainterUnits;
938 double startAverageSegmentX = thisXPainterUnits;
939 double startAverageSegmentY = thisYPainterUnits;
940
941 const double *xAveragingData = xPainterUnits;
942 const double *yAveragingData = yPainterUnits;
943 double endAverageSegmentX = *xAveragingData;
944 double endAverageSegmentY = *yAveragingData;
945 double averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
946
947 int j = i;
948 while ( ( j < size - 1 ) && ( painterDistRemaining > averagingSegmentLengthPainterUnits ) )
949 {
950 painterDistRemaining -= averagingSegmentLengthPainterUnits;
951 startAverageSegmentX = endAverageSegmentX;
952 startAverageSegmentY = endAverageSegmentY;
953
954 endAverageSegmentX = *xAveragingData++;
955 endAverageSegmentY = *yAveragingData++;
956 j++;
957 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
958 }
959 // fits on this same segment
960 double endAverageXPainterUnits = thisXPainterUnits;
961 double endAverageYPainterUnits = thisYPainterUnits;
962 if ( ( j < size - 1 ) && painterDistRemaining < averagingSegmentLengthPainterUnits )
963 {
964 QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, endAverageXPainterUnits, endAverageYPainterUnits,
965 nullptr, nullptr, nullptr,
966 nullptr, nullptr, nullptr );
967 }
968 else if ( i < size - 2 )
969 {
970 endAverageXPainterUnits = endAverageSegmentX;
971 endAverageYPainterUnits = endAverageSegmentY;
972 }
973
974 // also track back by averageAngleLengthPainterUnits
975 j = i;
976 painterDistRemaining = averageAngleLengthPainterUnits;
977 startAverageSegmentX = thisXPainterUnits;
978 startAverageSegmentY = thisYPainterUnits;
979
980 xAveragingData = xPainterUnits - 2;
981 yAveragingData = yPainterUnits - 2;
982
983 endAverageSegmentX = *xAveragingData;
984 endAverageSegmentY = *yAveragingData;
985 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
986
987 while ( j > 0 && painterDistRemaining > averagingSegmentLengthPainterUnits )
988 {
989 painterDistRemaining -= averagingSegmentLengthPainterUnits;
990 startAverageSegmentX = endAverageSegmentX;
991 startAverageSegmentY = endAverageSegmentY;
992
993 endAverageSegmentX = *xAveragingData--;
994 endAverageSegmentY = *yAveragingData--;
995 j--;
996 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
997 }
998 // fits on this same segment
999 double startAverageXPainterUnits = thisXPainterUnits;
1000 double startAverageYPainterUnits = thisYPainterUnits;
1001 if ( j > 0 && painterDistRemaining < averagingSegmentLengthPainterUnits )
1002 {
1003 QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, startAverageXPainterUnits, startAverageYPainterUnits,
1004 nullptr, nullptr, nullptr,
1005 nullptr, nullptr, nullptr );
1006 }
1007 else if ( j > 1 )
1008 {
1009 startAverageXPainterUnits = endAverageSegmentX;
1010 startAverageYPainterUnits = endAverageSegmentY;
1011 }
1012
1013 double calculatedAngle = std::fmod( QgsGeometryUtilsBase::azimuth( startAverageXPainterUnits, startAverageYPainterUnits, endAverageXPainterUnits, endAverageYPainterUnits ) + 360, 360 );
1014
1015 if ( calculatedAngle > 90 && calculatedAngle < 270 )
1016 calculatedAngle += 180;
1017
1018 if ( mMarkerSymbol && showMarker )
1019 {
1020 if ( mRotateLabels )
1021 mMarkerSymbol->setLineAngle( 90 - calculatedAngle );
1022 mMarkerSymbol->renderPoint( pt, context.feature(), context.renderContext() );
1023 }
1024
1025 const double angleRadians = mRotateLabels ? ( calculatedAngle * M_PI / 180.0 ) : 0;
1026 const double dx = labelOffsetPainterUnits.x() * std::sin( angleRadians + M_PI_2 )
1027 + labelOffsetPainterUnits.y() * std::sin( angleRadians );
1028 const double dy = labelOffsetPainterUnits.x() * std::cos( angleRadians + M_PI_2 )
1029 + labelOffsetPainterUnits.y() * std::cos( angleRadians );
1030
1031 double labelValue = 0;
1032 bool labelVertex = true;
1033 switch ( mLabelSource )
1034 {
1036 labelValue = currentDistance;
1037 break;
1039 labelValue = thisZ;
1040 labelVertex = static_cast< bool >( zData ) && !std::isnan( labelValue );
1041 break;
1043 labelValue = thisM;
1044 labelVertex = static_cast< bool >( mData ) && !std::isnan( labelValue );
1045 break;
1046 }
1047
1048 if ( !labelVertex )
1049 continue;
1050
1051 const QString text = mNumericFormat->formatDouble( labelValue, numericContext );
1052 if ( !labelProvider )
1053 {
1054 // render text directly
1055 QgsTextRenderer::drawText( QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, Qgis::TextHorizontalAlignment::Left, { text }, context.renderContext(), mTextFormat );
1056 }
1057 else
1058 {
1059 // register as a label
1060 labelProvider->addLabel(
1061 QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, text, context.renderContext(), mTextFormat
1062 );
1063 }
1064 prevX = thisX;
1065 prevY = thisY;
1066 }
1067}
1068
1070{
1071 return mTextFormat;
1072}
1073
1075{
1076 mTextFormat = format;
1077}
1078
1080{
1081 return mNumericFormat.get();
1082}
1083
1085{
1086 mNumericFormat.reset( format );
1087}
1088
1090{
1091 return mInterval;
1092}
1093
1095{
1096 mInterval = interval;
1097}
1098
1100{
1101 return mSkipMultiplesOf;
1102}
1103
1105{
1106 mSkipMultiplesOf = skipMultiplesOf;
1107}
1108
1110{
1111 return mShowMarker;
1112}
1113
1115{
1116 mShowMarker = show;
1117 if ( show && !mMarkerSymbol )
1118 {
1119 mMarkerSymbol.reset( QgsMarkerSymbol::createSimple( {} ) );
1120 }
1121}
1122
1127
1132
1137
1142
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer.
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
@ DisableFeatureClipping
If present, indicates that features should never be clipped to the map extent during rendering.
@ AffectsLabeling
If present, indicates that the symbol layer will participate in the map labeling problem.
QFlags< SymbolLayerFlag > SymbolLayerFlags
Symbol layer flags.
Definition qgis.h:821
@ Point
Text at point of origin layout mode.
@ Horizontal
Horizontally oriented text.
LinearReferencingPlacement
Defines how/where the labels should be placed in a linear referencing symbol layer.
Definition qgis.h:2916
@ IntervalZ
Place labels at regular intervals, linearly interpolated using Z values.
@ Vertex
Place labels on every vertex in the line.
@ IntervalM
Place labels at regular intervals, linearly interpolated using M values.
@ IntervalCartesian2D
Place labels at regular intervals, using Cartesian distance calculations on a 2D plane.
LinearReferencingLabelSource
Defines what quantity to use for the labels shown in a linear referencing symbol layer.
Definition qgis.h:2930
@ CartesianDistance2D
Distance along line, calculated using Cartesian calculations on a 2D plane.
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:741
@ Marker
Marker symbol.
Abstract base class for all geometries.
bool isMeasure() const
Returns true if the geometry contains m values.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary const part after the last part of the geometry.
const_part_iterator const_parts_begin() const
Returns STL-style iterator pointing to the const first part of the geometry.
The QgsAbstractLabelProvider class is an interface class.
virtual QList< QgsLabelFeature * > labelFeatures(QgsRenderContext &context)=0
Returns list of label features (they are owned by the provider and thus deleted on its destruction)
virtual void drawLabel(QgsRenderContext &context, pal::LabelPosition *label) const =0
Draw this label at the position determined by the labeling engine.
@ DrawLabels
Whether the labels should be rendered.
bool valueAsBool(int key, const QgsExpressionContext &context, bool defaultValue=false, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an boolean.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
static QgsNumericFormatRegistry * numericFormatRegistry()
Gets the registry of available numeric formats.
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
static void pointOnLineWithDistance(double x1, double y1, double x2, double y2, double distance, double &x, double &y, double *z1=nullptr, double *z2=nullptr, double *z=nullptr, double *m1=nullptr, double *m2=nullptr, double *m=nullptr)
Calculates the point a specified distance from (x1, y1) toward a second point (x2,...
static double distance2D(double x1, double y1, double x2, double y2)
Returns the 2D distance between (x1, y1) and (x2, y2).
static double azimuth(double x1, double y1, double x2, double y2)
Calculates Cartesian azimuth between points (x1, y1) and (x2, y2) (clockwise in degree,...
static geos::unique_ptr asGeos(const QgsGeometry &geometry, double precision=0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlags())
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition qgsgeos.cpp:256
The QgsLabelingEngine class provides map labeling functionality.
Line string geometry type, with support for z-dimension and m-values.
const double * yData() const
Returns a const pointer to the y vertex data.
const double * xData() const
Returns a const pointer to the x vertex data.
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the linestring does not have z values.
int numPoints() const override
Returns the number of points in the curve.
const double * mData() const
Returns a const pointer to the m vertex data, or nullptr if the linestring does not have m values.
QgsLineString * clone() const override
Clones the geometry by performing a deep copy.
Abstract base class for line symbol layers.
Line symbol layer used for decorating accordingly to linear referencing.
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setShowMarker(bool show)
Sets whether a marker symbol should be shown corresponding to the labeled point on line.
void setSkipMultiplesOf(double multiple)
Sets the multiple distance to skip labels for.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
QgsTextFormat textFormat() const
Returns the text format used to render the layer.
QString layerType() const override
Returns a string that represents this layer type.
bool showMarker() const
Returns true if a marker symbol should be shown corresponding to the labeled point on line.
void setNumericFormat(QgsNumericFormat *format)
Sets the numeric format used to format the labels for the layer.
QgsNumericFormat * numericFormat() const
Returns the numeric format used to format the labels for the layer.
void setInterval(double interval)
Sets the interval between labels.
void setLabelSource(Qgis::LinearReferencingLabelSource source)
Sets the label source, which dictates what quantity to use for the labels shown.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
void setTextFormat(const QgsTextFormat &format)
Sets the text format used to render the layer.
Qgis::LinearReferencingLabelSource labelSource() const
Returns the label source, which dictates what quantity to use for the labels shown.
double skipMultiplesOf() const
Returns the multiple distance to skip labels for.
QgsLinearReferencingSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void setPlacement(Qgis::LinearReferencingPlacement placement)
Sets the placement mode for the labels.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsLinearReferencingSymbolLayer, using the specified properties.
double interval() const
Returns the interval between labels.
Qgis::LinearReferencingPlacement placement() const
Returns the placement mode for the labels.
Perform transforms between map coordinates and device coordinates.
void setMapRotation(double degrees, double cx, double cy)
Sets map rotation in degrees (clockwise).
double mapUnitsPerPixel() const
Returns the current map units per pixel.
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device coordinates.
void transformInPlace(double &x, double &y) const
Transforms map coordinates to device coordinates.
static QgsMarkerSymbol * createSimple(const QVariantMap &properties)
Create a marker symbol with one symbol layer: SimpleMarker with specified properties.
A context for numeric formats.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context to use when evaluating QgsExpressions.
A numeric formatter allows for formatting a numeric value for display, using a variety of different f...
QPointF toQPointF() const
Converts a point to a QPointF.
Definition qgspointxy.h:165
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
Polygon geometry type.
Definition qgspolygon.h:33
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
The class is used as a container of context for various read/write operations on other objects.
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
QgsLabelingEngine * labelingEngine() const
Gets access to new labeling engine (may be nullptr).
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
const QgsAbstractGeometry * geometry() const
Returns pointer to the unsegmentized geometry.
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QString encodePoint(QPointF point)
Encodes a QPointF to a string.
static QPointF decodePoint(const QString &string)
Decodes a QSizeF from a string.
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
@ SkipMultiples
Skip multiples of.
@ ShowMarker
Show markers.
@ AverageAngleLength
Length to average symbol angles over.
@ Interval
Line marker interval.
void copyPaintEffect(QgsSymbolLayer *destLayer) const
Copies paint effect of this layer to another symbol layer.
QgsPropertyCollection mDataDefinedProperties
const QgsFeature * feature() const
Returns the current feature being rendered.
QgsFields fields() const
Fields of the layer.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:231
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:293
Contains pre-calculated metrics of a QgsTextDocument.
QSizeF documentSize(Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation) const
Returns the overall size of the document.
static QgsTextDocumentMetrics calculateMetrics(const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor=1.0)
Returns precalculated text metrics for a text document, when rendered using the given base format and...
Represents a document consisting of one or more QgsTextBlock objects.
static QgsTextDocument fromHtml(const QStringList &lines)
Constructor for QgsTextDocument consisting of a set of HTML formatted lines.
static QgsTextDocument fromPlainText(const QStringList &lines)
Constructor for QgsTextDocument consisting of a set of plain text lines.
Container for all settings relating to text rendering.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
bool allowHtmlFormatting() const
Returns true if text should be treated as a HTML document and HTML tags should be used for formatting...
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
Class that adds extra information to QgsLabelFeature for text labels.
static void drawDocument(const QRectF &rect, const QgsTextFormat &format, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, QgsRenderContext &context, Qgis::TextHorizontalAlignment horizontalAlignment=Qgis::TextHorizontalAlignment::Left, Qgis::TextVerticalAlignment verticalAlignment=Qgis::TextVerticalAlignment::Top, double rotation=0, Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags())
Draws a text document within a rectangle using the specified settings.
static void drawText(const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, Qgis::TextVerticalAlignment vAlignment=Qgis::TextVerticalAlignment::Top, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle)
Draws text within a rectangle using the specified settings.
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
LabelPosition is a candidate feature label position.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:6067
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6048
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5857
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
std::function< bool(double x, double y, double z, double m, double distanceFromStart, double angle)> VisitPointFunction
void visitPointsByInterpolatedM(const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint)
void visitPointsByRegularDistance(const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint)
double calculateAveragedAngle(double targetPointDistanceAlongSegment, double segmentLengthPainterUnits, double averageAngleLengthPainterUnits, double prevXPainterUnits, double prevYPainterUnits, double thisXPainterUnits, double thisYPainterUnits, const double *xPainterUnits, const double *yPainterUnits, int totalPoints, int i)
std::function< void(const QgsLineString *, const QgsLineString *, bool, double, double, const VisitPointFunction &) > VisitPointAtDistanceFunction
void visitPointsByInterpolatedZM(const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double step, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint, bool useZ)
void visitPointsByInterpolatedZ(const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint)
double interpolateValue(double a, double b, double fraction)