QGIS API Documentation 3.99.0-Master (d270888f95f)
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
18#include "feature.h"
19#include "labelposition.h"
20#include "qgsapplication.h"
22#include "qgsgeometryutils.h"
23#include "qgsgeos.h"
24#include "qgslinestring.h"
25#include "qgsmarkersymbol.h"
27#include "qgspallabeling.h"
28#include "qgspolygon.h"
29#include "qgsrendercontext.h"
30#include "qgssymbollayerutils.h"
31#include "qgstextlabelfeature.h"
32#include "qgstextrenderer.h"
33#include "qgsunittypes.h"
34
35#include <QString>
36
37using namespace Qt::StringLiterals;
38
40class QgsTextLabelFeatureWithFormat : public QgsTextLabelFeature
41{
42 public:
43 QgsTextLabelFeatureWithFormat( QgsFeatureId id, geos::unique_ptr geometry, QSizeF size, const QgsTextFormat &format )
44 : QgsTextLabelFeature( id, std::move( geometry ), size )
45 , mFormat( format )
46 {}
47
48 QgsTextFormat mFormat;
49
50};
51
52class QgsLinearReferencingSymbolLayerLabelProvider final : public QgsAbstractLabelProvider
53{
54 public:
55 QgsLinearReferencingSymbolLayerLabelProvider()
56 : QgsAbstractLabelProvider( nullptr )
57 {
59 mFlags |= DrawLabels;
60
61 // always consider these labels highest priority
62 // TODO possibly expose as a setting for the symbol layer?
63 mPriority = 0;
64 }
65
66 ~QgsLinearReferencingSymbolLayerLabelProvider() override
67 {
68 qDeleteAll( mLabels );
69 }
70
71 void addLabel( const QPointF &painterPoint, double angleRadians, const QString &text, QgsRenderContext &context, const QgsTextFormat &format )
72 {
73 // labels must be registered in destination map units
74 QgsPoint mapPoint( painterPoint );
75 mapPoint.transform( context.mapToPixel().transform().inverted() );
76
77 const QgsTextDocument doc = QgsTextDocument::fromTextAndFormat( { text }, format );
78 QgsTextDocumentMetrics documentMetrics = QgsTextDocumentMetrics::calculateMetrics( doc, format, context );
79 const QSizeF size = documentMetrics.documentSize( Qgis::TextLayoutMode::Point, Qgis::TextOrientation::Horizontal );
80
81 double uPP = context.mapToPixel().mapUnitsPerPixel();
82 auto feature = std::make_unique< QgsTextLabelFeatureWithFormat >( mLabels.size(),
83 QgsGeos::asGeos( &mapPoint ), QSizeF( size.width() * uPP, size.height() * uPP ), format );
84
85 feature->setDocument( doc, documentMetrics );
86 feature->setFixedAngle( angleRadians );
87 feature->setHasFixedAngle( true );
88 // above right
89 // TODO: we could potentially expose this, and use a non-fixed mode to allow fallback positions
90 feature->setQuadOffset( QPointF( 1, 1 ) );
91
92 mLabels.append( feature.release() );
93 }
94
95 QList<QgsLabelFeature *> labelFeatures( QgsRenderContext & ) final
96 {
97 return mLabels;
98 }
99
100 void drawLabel( QgsRenderContext &context, pal::LabelPosition *label ) const final
101 {
102 // as per vector label rendering...
103 QgsMapToPixel xform = context.mapToPixel();
104 xform.setMapRotation( 0, 0, 0 );
105 const QPointF outPt = context.mapToPixel().transform( label->getX(), label->getY() ).toQPointF();
106
107 QgsTextLabelFeatureWithFormat *lf = qgis::down_cast<QgsTextLabelFeatureWithFormat *>( label->getFeaturePart()->feature() );
109 lf->mFormat, lf->document(), lf->documentMetrics(), context, Qgis::TextHorizontalAlignment::Left,
110 label->getAlpha() );
111 }
112
113 private:
114 QList<QgsLabelFeature *> mLabels;
115
116};
118
121{
122 mNumericFormat = std::make_unique< QgsBasicNumericFormat >();
123}
124
126
128{
129 auto res = std::make_unique< QgsLinearReferencingSymbolLayer >();
130 res->setPlacement( qgsEnumKeyToValue( properties.value( u"placement"_s ).toString(), Qgis::LinearReferencingPlacement::IntervalCartesian2D ) );
131 res->setLabelSource( qgsEnumKeyToValue( properties.value( u"source"_s ).toString(), Qgis::LinearReferencingLabelSource::CartesianDistance2D ) );
132 bool ok = false;
133 const double interval = properties.value( u"interval"_s ).toDouble( &ok );
134 if ( ok )
135 res->setInterval( interval );
136 const double skipMultiples = properties.value( u"skip_multiples"_s ).toDouble( &ok );
137 if ( ok )
138 res->setSkipMultiplesOf( skipMultiples );
139 res->setRotateLabels( properties.value( u"rotate"_s, true ).toBool() );
140 res->setShowMarker( properties.value( u"show_marker"_s, false ).toBool() );
141
142 // it's impossible to get the project's path resolver here :(
143 // TODO QGIS 5.0 -- force use of QgsReadWriteContext in create methods
144 QgsReadWriteContext rwContext;
145 //rwContext.setPathResolver( QgsProject::instance()->pathResolver() ); // skip-keyword-check
146
147 const QString textFormatXml = properties.value( u"text_format"_s ).toString();
148 if ( !textFormatXml.isEmpty() )
149 {
150 QDomDocument doc;
151 QDomElement elem;
152 doc.setContent( textFormatXml );
153 elem = doc.documentElement();
154
156 textFormat.readXml( elem, rwContext );
157 res->setTextFormat( textFormat );
158 }
159
160 const QString numericFormatXml = properties.value( u"numeric_format"_s ).toString();
161 if ( !numericFormatXml.isEmpty() )
162 {
163 QDomDocument doc;
164 doc.setContent( numericFormatXml );
165 res->setNumericFormat( QgsApplication::numericFormatRegistry()->createFromXml( doc.documentElement(), rwContext ) );
166 }
167
168 if ( properties.contains( u"label_offset"_s ) )
169 {
170 res->setLabelOffset( QgsSymbolLayerUtils::decodePoint( properties[u"label_offset"_s].toString() ) );
171 }
172 if ( properties.contains( u"label_offset_unit"_s ) )
173 {
174 res->setLabelOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"label_offset_unit"_s].toString() ) );
175 }
176 if ( properties.contains( ( u"label_offset_map_unit_scale"_s ) ) )
177 {
178 res->setLabelOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"label_offset_map_unit_scale"_s].toString() ) );
179 }
180 if ( properties.contains( u"average_angle_length"_s ) )
181 {
182 res->setAverageAngleLength( properties[u"average_angle_length"_s].toDouble() );
183 }
184 if ( properties.contains( u"average_angle_unit"_s ) )
185 {
186 res->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[u"average_angle_unit"_s].toString() ) );
187 }
188 if ( properties.contains( ( u"average_angle_map_unit_scale"_s ) ) )
189 {
190 res->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"average_angle_map_unit_scale"_s].toString() ) );
191 }
192
193 return res.release();
194}
195
197{
198 auto res = std::make_unique< QgsLinearReferencingSymbolLayer >();
199 res->setPlacement( mPlacement );
200 res->setLabelSource( mLabelSource );
201 res->setInterval( mInterval );
202 res->setSkipMultiplesOf( mSkipMultiplesOf );
203 res->setRotateLabels( mRotateLabels );
204 res->setLabelOffset( mLabelOffset );
205 res->setLabelOffsetUnit( mLabelOffsetUnit );
206 res->setLabelOffsetMapUnitScale( mLabelOffsetMapUnitScale );
207 res->setShowMarker( mShowMarker );
208 res->setAverageAngleLength( mAverageAngleLength );
209 res->setAverageAngleUnit( mAverageAngleLengthUnit );
210 res->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
211
212 res->mTextFormat = mTextFormat;
213 res->mMarkerSymbol.reset( mMarkerSymbol ? mMarkerSymbol->clone() : nullptr );
214 if ( mNumericFormat )
215 res->mNumericFormat.reset( mNumericFormat->clone() );
216
217 copyDataDefinedProperties( res.get() );
218 copyPaintEffect( res.get() );
219
220 return res.release();
221}
222
224{
225 QDomDocument textFormatDoc;
226 // it's impossible to get the project's path resolver here :(
227 // TODO QGIS 5.0 -- force use of QgsReadWriteContext in properties methods
228 QgsReadWriteContext rwContext;
229 // rwContext.setPathResolver( QgsProject::instance()->pathResolver() ); // skip-keyword-check
230 const QDomElement textElem = mTextFormat.writeXml( textFormatDoc, rwContext );
231 textFormatDoc.appendChild( textElem );
232
233 QDomDocument numericFormatDoc;
234 QDomElement numericFormatElem = numericFormatDoc.createElement( u"numericFormat"_s );
235 mNumericFormat->writeXml( numericFormatElem, numericFormatDoc, rwContext );
236 numericFormatDoc.appendChild( numericFormatElem );
237
238 QVariantMap res
239 {
240 {
241 u"placement"_s, qgsEnumValueToKey( mPlacement )
242 },
243 {
244 u"source"_s, qgsEnumValueToKey( mLabelSource )
245 },
246 {
247 u"interval"_s, mInterval
248 },
249 {
250 u"rotate"_s, mRotateLabels
251 },
252 {
253 u"show_marker"_s, mShowMarker
254 },
255 {
256 u"text_format"_s, textFormatDoc.toString()
257 },
258 {
259 u"numeric_format"_s, numericFormatDoc.toString()
260 },
261 {
262 u"label_offset"_s, QgsSymbolLayerUtils::encodePoint( mLabelOffset )
263 },
264 {
265 u"label_offset_unit"_s, QgsUnitTypes::encodeUnit( mLabelOffsetUnit )
266 },
267 {
268 u"label_offset_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mLabelOffsetMapUnitScale )
269 },
270 {
271 u"average_angle_length"_s, mAverageAngleLength
272 },
273 {
274 u"average_angle_unit"_s, QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit )
275 },
276 {
277 u"average_angle_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale )
278 },
279 };
280
281 if ( mSkipMultiplesOf >= 0 )
282 {
283 res.insert( u"skip_multiples"_s, mSkipMultiplesOf );
284 }
285
286 return res;
287}
288
290{
291 return u"LinearReferencing"_s;
292}
293
299
301{
302 return mShowMarker ? mMarkerSymbol.get() : nullptr;
303}
304
306{
307 if ( symbol && symbol->type() == Qgis::SymbolType::Marker )
308 {
309 mMarkerSymbol.reset( qgis::down_cast<QgsMarkerSymbol *>( symbol ) );
310 return true;
311 }
312 delete symbol;
313 return false;
314}
315
317{
318 if ( mMarkerSymbol )
319 {
320 Qgis::SymbolRenderHints hints = mMarkerSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol;
321 if ( mRotateLabels )
323 mMarkerSymbol->setRenderHints( hints );
324
325 mMarkerSymbol->startRender( context.renderContext(), context.fields() );
326 }
327
328 if ( QgsLabelingEngine *labelingEngine = context.renderContext().labelingEngine() )
329 {
330 // rendering with a labeling engine. In this scenario we will register rendered text as labels, so that they participate in the labeling problem
331 // for the map
332 QgsLinearReferencingSymbolLayerLabelProvider *provider = new QgsLinearReferencingSymbolLayerLabelProvider();
333 mLabelProviderId = labelingEngine->addProvider( provider );
334 }
335}
336
338{
339 if ( mMarkerSymbol )
340 {
341 mMarkerSymbol->stopRender( context.renderContext() );
342 }
343}
344
345void QgsLinearReferencingSymbolLayer::renderGeometryPart( QgsSymbolRenderContext &context, const QgsAbstractGeometry *geometry, double labelOffsetPainterUnitsX, double labelOffsetPainterUnitsY, double skipMultiples, double averageAngleDistancePainterUnits, bool showMarker )
346{
347 if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( geometry ) )
348 {
349 renderLineString( context, line, labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker );
350 }
351 else if ( const QgsPolygon *polygon = qgsgeometry_cast< const QgsPolygon * >( geometry ) )
352 {
353 renderLineString( context, qgsgeometry_cast< const QgsLineString *>( polygon->exteriorRing() ), labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker );
354 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
355 {
356 renderLineString( context, qgsgeometry_cast< const QgsLineString *>( polygon->interiorRing( i ) ), labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker );
357 }
358 }
359}
360
361void QgsLinearReferencingSymbolLayer::renderLineString( QgsSymbolRenderContext &context, const QgsLineString *line, double labelOffsetPainterUnitsX, double labelOffsetPainterUnitsY, double skipMultiples, double averageAngleDistancePainterUnits, bool showMarker )
362{
363 if ( !line )
364 return;
365
366 switch ( mPlacement )
367 {
371 renderPolylineInterval( line, context, skipMultiples, QPointF( labelOffsetPainterUnitsX, labelOffsetPainterUnitsY ), averageAngleDistancePainterUnits, showMarker );
372 break;
373
375 renderPolylineVertex( line, context, skipMultiples, QPointF( labelOffsetPainterUnitsX, labelOffsetPainterUnitsY ), averageAngleDistancePainterUnits, showMarker );
376 break;
377 }
378}
379
381{
382 QPainter *p = context.renderContext().painter();
383 if ( !p )
384 {
385 return;
386 }
387
388 // TODO (maybe?): if we don't have an original geometry, convert points to linestring and scale distance to painter units?
389 // in reality this line type makes no sense for rendering non-real feature geometries...
390 ( void )points;
391 const QgsAbstractGeometry *geometry = context.renderContext().geometry();
392 if ( !geometry )
393 return;
394
395 double skipMultiples = mSkipMultiplesOf;
397 {
398 context.setOriginalValueVariable( mSkipMultiplesOf );
399 skipMultiples = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::SkipMultiples, context.renderContext().expressionContext(), mSkipMultiplesOf );
400 }
401
402 double labelOffsetX = mLabelOffset.x();
403 double labelOffsetY = mLabelOffset.y();
404
405 double averageOver = mAverageAngleLength;
407 {
408 context.setOriginalValueVariable( mAverageAngleLength );
409 averageOver = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::AverageAngleLength, context.renderContext().expressionContext(), mAverageAngleLength );
410 }
411
412 bool showMarker = mShowMarker;
414 {
417 }
418
419 const double labelOffsetPainterUnitsX = context.renderContext().convertToPainterUnits( labelOffsetX, mLabelOffsetUnit, mLabelOffsetMapUnitScale );
420 const double labelOffsetPainterUnitsY = context.renderContext().convertToPainterUnits( labelOffsetY, mLabelOffsetUnit, mLabelOffsetMapUnitScale );
421 const double averageAngleDistancePainterUnits = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2;
422
423 for ( auto partIt = geometry->const_parts_begin(); partIt != geometry->const_parts_end(); ++partIt )
424 {
425 renderGeometryPart( context, *partIt, labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker );
426 }
427}
428
429
430double calculateAveragedAngle( double targetPointDistanceAlongSegment, double segmentLengthPainterUnits,
431 double averageAngleLengthPainterUnits, double prevXPainterUnits, double prevYPainterUnits,
432 double thisXPainterUnits, double thisYPainterUnits, const double *xPainterUnits,
433 const double *yPainterUnits, int totalPoints, int i )
434{
435
436 // track forward by averageAngleLengthPainterUnits
437 double painterDistRemaining = averageAngleLengthPainterUnits + targetPointDistanceAlongSegment;
438 double startAverageSegmentX = prevXPainterUnits;
439 double startAverageSegmentY = prevYPainterUnits;
440 double endAverageSegmentX = thisXPainterUnits;
441 double endAverageSegmentY = thisYPainterUnits;
442 double averagingSegmentLengthPainterUnits = segmentLengthPainterUnits;
443 const double *xAveragingData = xPainterUnits;
444 const double *yAveragingData = yPainterUnits;
445
446 int j = i;
447 while ( painterDistRemaining > averagingSegmentLengthPainterUnits )
448 {
449 if ( j >= totalPoints - 1 )
450 break;
451
452 painterDistRemaining -= averagingSegmentLengthPainterUnits;
453 startAverageSegmentX = endAverageSegmentX;
454 startAverageSegmentY = endAverageSegmentY;
455
456 endAverageSegmentX = *xAveragingData++;
457 endAverageSegmentY = *yAveragingData++;
458 j++;
459 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
460 }
461 // fits on this same segment
462 double endAverageXPainterUnits;
463 double endAverageYPainterUnits;
464 if ( painterDistRemaining < averagingSegmentLengthPainterUnits )
465 {
466 QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, endAverageXPainterUnits, endAverageYPainterUnits,
467 nullptr, nullptr, nullptr,
468 nullptr, nullptr, nullptr );
469 }
470 else
471 {
472 endAverageXPainterUnits = endAverageSegmentX;
473 endAverageYPainterUnits = endAverageSegmentY;
474 }
475
476 // also track back by averageAngleLengthPainterUnits
477 j = i;
478 painterDistRemaining = ( segmentLengthPainterUnits - targetPointDistanceAlongSegment ) + averageAngleLengthPainterUnits;
479 startAverageSegmentX = thisXPainterUnits;
480 startAverageSegmentY = thisYPainterUnits;
481 endAverageSegmentX = prevXPainterUnits;
482 endAverageSegmentY = prevYPainterUnits;
483 averagingSegmentLengthPainterUnits = segmentLengthPainterUnits;
484 xAveragingData = xPainterUnits - 2;
485 yAveragingData = yPainterUnits - 2;
486 while ( painterDistRemaining > averagingSegmentLengthPainterUnits )
487 {
488 if ( j < 1 )
489 break;
490
491 painterDistRemaining -= averagingSegmentLengthPainterUnits;
492 startAverageSegmentX = endAverageSegmentX;
493 startAverageSegmentY = endAverageSegmentY;
494
495 endAverageSegmentX = *xAveragingData--;
496 endAverageSegmentY = *yAveragingData--;
497 j--;
498 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
499 }
500 // fits on this same segment
501 double startAverageXPainterUnits;
502 double startAverageYPainterUnits;
503 if ( painterDistRemaining < averagingSegmentLengthPainterUnits )
504 {
505 QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, startAverageXPainterUnits, startAverageYPainterUnits,
506 nullptr, nullptr, nullptr,
507 nullptr, nullptr, nullptr );
508 }
509 else
510 {
511 startAverageXPainterUnits = endAverageSegmentX;
512 startAverageYPainterUnits = endAverageSegmentY;
513 }
514
515 double calculatedAngle = std::fmod( QgsGeometryUtilsBase::azimuth( startAverageXPainterUnits, startAverageYPainterUnits, endAverageXPainterUnits, endAverageYPainterUnits ) + 360, 360 );
516 if ( calculatedAngle > 90 && calculatedAngle < 270 )
517 calculatedAngle += 180;
518
519 return calculatedAngle;
520}
521
522typedef std::function<bool ( double x, double y, double z, double m, double distanceFromStart, double angle )> VisitPointFunction;
523typedef std::function< void( const QgsLineString *, const QgsLineString *, bool, double, double, const VisitPointFunction & ) > VisitPointAtDistanceFunction;
524
525void visitPointsByRegularDistance( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint )
526{
527 if ( distance < 0 )
528 return;
529
530 double distanceTraversed = 0;
531 const int totalPoints = line->numPoints();
532 if ( totalPoints == 0 )
533 return;
534
535 const double *x = line->xData();
536 const double *y = line->yData();
537 const double *z = line->is3D() ? line->zData() : nullptr;
538 const double *m = line->isMeasure() ? line->mData() : nullptr;
539
540 const double *xPainterUnits = linePainterUnits->xData();
541 const double *yPainterUnits = linePainterUnits->yData();
542
543 double prevX = *x++;
544 double prevY = *y++;
545 double prevZ = z ? *z++ : 0.0;
546 double prevM = m ? *m++ : 0.0;
547
548 double prevXPainterUnits = *xPainterUnits++;
549 double prevYPainterUnits = *yPainterUnits++;
550
551 if ( qgsDoubleNear( distance, 0.0 ) )
552 {
553 visitPoint( prevX, prevY, prevZ, prevM, 0, 0 );
554 return;
555 }
556
557 double pZ = std::numeric_limits<double>::quiet_NaN();
558 double pM = std::numeric_limits<double>::quiet_NaN();
559 double nextPointDistance = emitFirstPoint ? 0 : distance;
560 for ( int i = 1; i < totalPoints; ++i )
561 {
562 double thisX = *x++;
563 double thisY = *y++;
564 double thisZ = z ? *z++ : 0.0;
565 double thisM = m ? *m++ : 0.0;
566 double thisXPainterUnits = *xPainterUnits++;
567 double thisYPainterUnits = *yPainterUnits++;
568
569 double angle = std::fmod( QgsGeometryUtilsBase::azimuth( prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits ) + 360, 360 );
570 if ( angle > 90 && angle < 270 )
571 angle += 180;
572
573 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
574 const double segmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( thisXPainterUnits, thisYPainterUnits, prevXPainterUnits, prevYPainterUnits );
575
576 while ( nextPointDistance < distanceTraversed + segmentLength || qgsDoubleNear( nextPointDistance, distanceTraversed + segmentLength ) )
577 {
578 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
579 const double distanceToPoint = std::min( nextPointDistance - distanceTraversed, segmentLength );
580 double pX, pY;
581 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY,
582 z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &pZ : nullptr,
583 m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &pM : nullptr );
584
585 double calculatedAngle = angle;
586 if ( averageAngleLengthPainterUnits > 0 )
587 {
588 const double targetPointFractionAlongSegment = distanceToPoint / segmentLength;
589 const double targetPointDistanceAlongSegment = targetPointFractionAlongSegment * segmentLengthPainterUnits;
590
591 calculatedAngle = calculateAveragedAngle( targetPointDistanceAlongSegment, segmentLengthPainterUnits,
592 averageAngleLengthPainterUnits, prevXPainterUnits, prevYPainterUnits,
593 thisXPainterUnits, thisYPainterUnits, xPainterUnits,
594 yPainterUnits, totalPoints, i );
595 }
596
597 if ( !visitPoint( pX, pY, pZ, pM, nextPointDistance, calculatedAngle ) )
598 return;
599
600 nextPointDistance += distance;
601 }
602
603 distanceTraversed += segmentLength;
604 prevX = thisX;
605 prevY = thisY;
606 prevZ = thisZ;
607 prevM = thisM;
608 prevXPainterUnits = thisXPainterUnits;
609 prevYPainterUnits = thisYPainterUnits;
610 }
611}
612
613double interpolateValue( double a, double b, double fraction )
614{
615 return a + ( b - a ) * fraction;
616}
617
618
619void visitPointsByInterpolatedZM( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double step, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint, bool useZ )
620{
621 if ( step < 0 )
622 return;
623
624 double distanceTraversed = 0;
625 const int totalPoints = line->numPoints();
626 if ( totalPoints < 2 )
627 return;
628
629 const double *x = line->xData();
630 const double *y = line->yData();
631 const double *z = line->is3D() ? line->zData() : nullptr;
632 const double *m = line->isMeasure() ? line->mData() : nullptr;
633
634 const double *xPainterUnits = linePainterUnits->xData();
635 const double *yPainterUnits = linePainterUnits->yData();
636
637 double prevX = *x++;
638 double prevY = *y++;
639 double prevZ = z ? *z++ : 0.0;
640 double prevM = m ? *m++ : 0.0;
641
642 double prevXPainterUnits = *xPainterUnits++;
643 double prevYPainterUnits = *yPainterUnits++;
644
645 if ( qgsDoubleNear( step, 0.0 ) )
646 {
647 visitPoint( prevX, prevY, prevZ, prevM, 0, 0 );
648 return;
649 }
650
651 double prevValue = useZ ? prevZ : prevM;
652 bool isFirstPoint = true;
653 for ( int i = 1; i < totalPoints; ++i )
654 {
655 double thisX = *x++;
656 double thisY = *y++;
657 double thisZ = z ? *z++ : 0.0;
658 double thisM = m ? *m++ : 0.0;
659 const double thisValue = useZ ? thisZ : thisM;
660 double thisXPainterUnits = *xPainterUnits++;
661 double thisYPainterUnits = *yPainterUnits++;
662
663 double angle = std::fmod( QgsGeometryUtilsBase::azimuth( prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits ) + 360, 360 );
664 if ( angle > 90 && angle < 270 )
665 angle += 180;
666
667 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
668 const double segmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( thisXPainterUnits, thisYPainterUnits, prevXPainterUnits, prevYPainterUnits );
669
670 // direction for this segment
671 const int direction = ( thisValue > prevValue ) ? 1 : ( thisValue < prevValue ) ? -1 : 0;
672 if ( direction != 0 )
673 {
674 // non-constant segment
675 double nextStepValue = direction > 0 ? std::ceil( prevValue / step ) * step
676 : std::floor( prevValue / step ) * step;
677
678 while ( ( direction > 0 && ( nextStepValue <= thisValue || qgsDoubleNear( nextStepValue, thisValue ) ) ) ||
679 ( direction < 0 && ( nextStepValue >= thisValue || qgsDoubleNear( nextStepValue, thisValue ) ) ) )
680 {
681 const double targetPointFractionAlongSegment = ( nextStepValue - prevValue ) / ( thisValue - prevValue );
682 const double targetPointDistanceAlongSegment = targetPointFractionAlongSegment * segmentLengthPainterUnits;
683
684 double pX, pY;
685 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, targetPointFractionAlongSegment * segmentLength, pX, pY );
686
687 const double pZ = useZ ? nextStepValue : interpolateValue( prevZ, thisZ, targetPointFractionAlongSegment );
688 const double pM = useZ ? interpolateValue( prevM, thisM, targetPointFractionAlongSegment ) : nextStepValue;
689
690 double calculatedAngle = angle;
691 if ( averageAngleLengthPainterUnits > 0 )
692 {
693 calculatedAngle = calculateAveragedAngle(
694 targetPointDistanceAlongSegment,
695 segmentLengthPainterUnits, averageAngleLengthPainterUnits,
696 prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits,
697 xPainterUnits, yPainterUnits,
698 totalPoints, i );
699 }
700
701 if ( !qgsDoubleNear( targetPointFractionAlongSegment, 0 ) || isFirstPoint )
702 {
703 if ( !visitPoint( pX, pY, pZ, pM, distanceTraversed + segmentLength * targetPointFractionAlongSegment, calculatedAngle ) )
704 return;
705 }
706
707 nextStepValue += direction * step;
708 }
709 }
710 else if ( isFirstPoint && emitFirstPoint )
711 {
712 if ( !visitPoint( prevX, prevY, prevZ, prevM, distanceTraversed,
713 std::fmod( QgsGeometryUtilsBase::azimuth( prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits ) + 360, 360 ) ) )
714 return;
715 }
716 isFirstPoint = false;
717
718 prevX = thisX;
719 prevY = thisY;
720 prevZ = thisZ;
721 prevM = thisM;
722 prevXPainterUnits = thisXPainterUnits;
723 prevYPainterUnits = thisYPainterUnits;
724 prevValue = thisValue;
725 distanceTraversed += segmentLength;
726 }
727}
728
729void visitPointsByInterpolatedZ( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint )
730{
731 visitPointsByInterpolatedZM( line, linePainterUnits, emitFirstPoint, distance, averageAngleLengthPainterUnits, visitPoint, true );
732}
733
734void visitPointsByInterpolatedM( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint )
735{
736 visitPointsByInterpolatedZM( line, linePainterUnits, emitFirstPoint, distance, averageAngleLengthPainterUnits, visitPoint, false );
737}
738
739QPointF QgsLinearReferencingSymbolLayer::pointToPainter( QgsSymbolRenderContext &context, double x, double y, double z )
740{
741 QPointF pt;
742 if ( context.renderContext().coordinateTransform().isValid() )
743 {
745 pt = QPointF( x, y );
746
747 }
748 else
749 {
750 pt = QPointF( x, y );
751 }
752
753 context.renderContext().mapToPixel().transformInPlace( pt.rx(), pt.ry() );
754 return pt;
755}
756
757void QgsLinearReferencingSymbolLayer::renderPolylineInterval( const QgsLineString *line, QgsSymbolRenderContext &context, double skipMultiples, const QPointF &labelOffsetPainterUnits, double averageAngleLengthPainterUnits, bool showMarker )
758{
759 double distance = mInterval;
761 {
762 context.setOriginalValueVariable( mInterval );
763 distance = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::Interval, context.renderContext().expressionContext(), mInterval );
764 }
765
766 QgsNumericFormatContext numericContext;
767 numericContext.setExpressionContext( context.renderContext().expressionContext() );
768
769 std::unique_ptr< QgsLineString > painterUnitsGeometry( line->clone() );
770 if ( context.renderContext().coordinateTransform().isValid() )
771 {
772 painterUnitsGeometry->transform( context.renderContext().coordinateTransform() );
773 }
774 painterUnitsGeometry->transform( context.renderContext().mapToPixel().transform() );
775
776 const bool hasZ = line->is3D();
777 const bool hasM = line->isMeasure();
778 const bool emitFirstPoint = mLabelSource != Qgis::LinearReferencingLabelSource::CartesianDistance2D;
779
780 VisitPointAtDistanceFunction func = nullptr;
781
782 switch ( mPlacement )
783 {
786 break;
787
790 break;
791
794 break;
795
797 return;
798 }
799
800 QgsLinearReferencingSymbolLayerLabelProvider *labelProvider = nullptr;
801 if ( QgsLabelingEngine *labelingEngine = context.renderContext().labelingEngine() )
802 {
803 // rendering with a labeling engine. In this scenario we will register rendered text as labels, so that they participate in the labeling problem
804 // for the map
805 labelProvider = qgis::down_cast< QgsLinearReferencingSymbolLayerLabelProvider * >( labelingEngine->providerById( mLabelProviderId ) );
806 }
807
808 func( line, painterUnitsGeometry.get(), emitFirstPoint, distance, averageAngleLengthPainterUnits, [&context, &numericContext, skipMultiples, showMarker,
809 labelOffsetPainterUnits, hasZ, hasM, labelProvider, this]( double x, double y, double z, double m, double distanceFromStart, double angle ) -> bool
810 {
811 if ( context.renderContext().renderingStopped() )
812 return false;
813
814 double labelValue = 0;
815 bool labelVertex = true;
816 switch ( mLabelSource )
817 {
818 case Qgis::LinearReferencingLabelSource::CartesianDistance2D:
819 labelValue = distanceFromStart;
820 break;
821 case Qgis::LinearReferencingLabelSource::Z:
822 labelValue = z;
823 labelVertex = hasZ && !std::isnan( labelValue );
824 break;
825 case Qgis::LinearReferencingLabelSource::M:
826 labelValue = m;
827 labelVertex = hasM && !std::isnan( labelValue );
828 break;
829 }
830
831 if ( !labelVertex )
832 return true;
833
834 if ( skipMultiples > 0 && qgsDoubleNear( std::fmod( labelValue, skipMultiples ), 0 ) )
835 return true;
836
837 const QPointF pt = pointToPainter( context, x, y, z );
838
839 if ( mMarkerSymbol && showMarker )
840 {
841 if ( mRotateLabels )
842 mMarkerSymbol->setLineAngle( 90 - angle );
843 mMarkerSymbol->renderPoint( pt, context.feature(), context.renderContext() );
844 }
845
846 const double angleRadians = ( mRotateLabels ? angle : 0 ) * M_PI / 180.0;
847 const double dx = labelOffsetPainterUnits.x() * std::sin( angleRadians + M_PI_2 )
848 + labelOffsetPainterUnits.y() * std::sin( angleRadians );
849 const double dy = labelOffsetPainterUnits.x() * std::cos( angleRadians + M_PI_2 )
850 + labelOffsetPainterUnits.y() * std::cos( angleRadians );
851
852 const QString text = mNumericFormat->formatDouble( labelValue, numericContext );
853 if ( !labelProvider )
854 {
855 // render text directly
856 QgsTextRenderer::drawText( QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, Qgis::TextHorizontalAlignment::Left, { text }, context.renderContext(), mTextFormat );
857 }
858 else
859 {
860 // register as a label
861 labelProvider->addLabel(
862 QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, text, context.renderContext(), mTextFormat
863 );
864 }
865
866 return true;
867 } );
868}
869
870void QgsLinearReferencingSymbolLayer::renderPolylineVertex( const QgsLineString *line, QgsSymbolRenderContext &context, double skipMultiples, const QPointF &labelOffsetPainterUnits, double averageAngleLengthPainterUnits, bool showMarker )
871{
872 // let's simplify the logic by ALWAYS using the averaging approach for angles, and just
873 // use a very small distance if the user actually set this to 0. It'll be identical
874 // results anyway...
875 averageAngleLengthPainterUnits = std::max( averageAngleLengthPainterUnits, 0.1 );
876
877 QgsNumericFormatContext numericContext;
878 numericContext.setExpressionContext( context.renderContext().expressionContext() );
879
880 QgsLinearReferencingSymbolLayerLabelProvider *labelProvider = nullptr;
881 if ( QgsLabelingEngine *labelingEngine = context.renderContext().labelingEngine() )
882 {
883 // rendering with a labeling engine. In this scenario we will register rendered text as labels, so that they participate in the labeling problem
884 // for the map
885 labelProvider = qgis::down_cast< QgsLinearReferencingSymbolLayerLabelProvider * >( labelingEngine->providerById( mLabelProviderId ) );
886 }
887
888 const double *xData = line->xData();
889 const double *yData = line->yData();
890 const double *zData = line->is3D() ? line->zData() : nullptr;
891 const double *mData = line->isMeasure() ? line->mData() : nullptr;
892 const int size = line->numPoints();
893 if ( size < 2 )
894 return;
895
896 std::unique_ptr< QgsLineString > painterUnitsGeometry( line->clone() );
897 if ( context.renderContext().coordinateTransform().isValid() )
898 {
899 painterUnitsGeometry->transform( context.renderContext().coordinateTransform() );
900 }
901 painterUnitsGeometry->transform( context.renderContext().mapToPixel().transform() );
902 const double *xPainterUnits = painterUnitsGeometry->xData();
903 const double *yPainterUnits = painterUnitsGeometry->yData();
904
905 double currentDistance = 0;
906 double prevX = *xData;
907 double prevY = *yData;
908
909 for ( int i = 0; i < size; ++i )
910 {
911 if ( context.renderContext().renderingStopped() )
912 break;
913
914 double thisX = *xData++;
915 double thisY = *yData++;
916 double thisZ = zData ? *zData++ : 0;
917 double thisM = mData ? *mData++ : 0;
918 double thisXPainterUnits = *xPainterUnits++;
919 double thisYPainterUnits = *yPainterUnits++;
920
921 const double thisSegmentLength = QgsGeometryUtilsBase::distance2D( prevX, prevY, thisX, thisY );
922 currentDistance += thisSegmentLength;
923
924 if ( skipMultiples > 0 && qgsDoubleNear( std::fmod( currentDistance, skipMultiples ), 0 ) )
925 {
926 prevX = thisX;
927 prevY = thisY;
928 continue;
929 }
930
931 const QPointF pt = pointToPainter( context, thisX, thisY, thisZ );
932
933 // track forward by averageAngleLengthPainterUnits
934 double painterDistRemaining = averageAngleLengthPainterUnits;
935 double startAverageSegmentX = thisXPainterUnits;
936 double startAverageSegmentY = thisYPainterUnits;
937
938 const double *xAveragingData = xPainterUnits;
939 const double *yAveragingData = yPainterUnits;
940 double endAverageSegmentX = *xAveragingData;
941 double endAverageSegmentY = *yAveragingData;
942 double averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
943
944 int j = i;
945 while ( ( j < size - 1 ) && ( painterDistRemaining > averagingSegmentLengthPainterUnits ) )
946 {
947 painterDistRemaining -= averagingSegmentLengthPainterUnits;
948 startAverageSegmentX = endAverageSegmentX;
949 startAverageSegmentY = endAverageSegmentY;
950
951 endAverageSegmentX = *xAveragingData++;
952 endAverageSegmentY = *yAveragingData++;
953 j++;
954 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
955 }
956 // fits on this same segment
957 double endAverageXPainterUnits = thisXPainterUnits;
958 double endAverageYPainterUnits = thisYPainterUnits;
959 if ( ( j < size - 1 ) && painterDistRemaining < averagingSegmentLengthPainterUnits )
960 {
961 QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, endAverageXPainterUnits, endAverageYPainterUnits,
962 nullptr, nullptr, nullptr,
963 nullptr, nullptr, nullptr );
964 }
965 else if ( i < size - 2 )
966 {
967 endAverageXPainterUnits = endAverageSegmentX;
968 endAverageYPainterUnits = endAverageSegmentY;
969 }
970
971 // also track back by averageAngleLengthPainterUnits
972 j = i;
973 painterDistRemaining = averageAngleLengthPainterUnits;
974 startAverageSegmentX = thisXPainterUnits;
975 startAverageSegmentY = thisYPainterUnits;
976
977 xAveragingData = xPainterUnits - 2;
978 yAveragingData = yPainterUnits - 2;
979
980 endAverageSegmentX = *xAveragingData;
981 endAverageSegmentY = *yAveragingData;
982 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
983
984 while ( j > 0 && painterDistRemaining > averagingSegmentLengthPainterUnits )
985 {
986 painterDistRemaining -= averagingSegmentLengthPainterUnits;
987 startAverageSegmentX = endAverageSegmentX;
988 startAverageSegmentY = endAverageSegmentY;
989
990 endAverageSegmentX = *xAveragingData--;
991 endAverageSegmentY = *yAveragingData--;
992 j--;
993 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
994 }
995 // fits on this same segment
996 double startAverageXPainterUnits = thisXPainterUnits;
997 double startAverageYPainterUnits = thisYPainterUnits;
998 if ( j > 0 && painterDistRemaining < averagingSegmentLengthPainterUnits )
999 {
1000 QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, startAverageXPainterUnits, startAverageYPainterUnits,
1001 nullptr, nullptr, nullptr,
1002 nullptr, nullptr, nullptr );
1003 }
1004 else if ( j > 1 )
1005 {
1006 startAverageXPainterUnits = endAverageSegmentX;
1007 startAverageYPainterUnits = endAverageSegmentY;
1008 }
1009
1010 double calculatedAngle = std::fmod( QgsGeometryUtilsBase::azimuth( startAverageXPainterUnits, startAverageYPainterUnits, endAverageXPainterUnits, endAverageYPainterUnits ) + 360, 360 );
1011
1012 if ( calculatedAngle > 90 && calculatedAngle < 270 )
1013 calculatedAngle += 180;
1014
1015 if ( mMarkerSymbol && showMarker )
1016 {
1017 if ( mRotateLabels )
1018 mMarkerSymbol->setLineAngle( 90 - calculatedAngle );
1019 mMarkerSymbol->renderPoint( pt, context.feature(), context.renderContext() );
1020 }
1021
1022 const double angleRadians = mRotateLabels ? ( calculatedAngle * M_PI / 180.0 ) : 0;
1023 const double dx = labelOffsetPainterUnits.x() * std::sin( angleRadians + M_PI_2 )
1024 + labelOffsetPainterUnits.y() * std::sin( angleRadians );
1025 const double dy = labelOffsetPainterUnits.x() * std::cos( angleRadians + M_PI_2 )
1026 + labelOffsetPainterUnits.y() * std::cos( angleRadians );
1027
1028 double labelValue = 0;
1029 bool labelVertex = true;
1030 switch ( mLabelSource )
1031 {
1033 labelValue = currentDistance;
1034 break;
1036 labelValue = thisZ;
1037 labelVertex = static_cast< bool >( zData ) && !std::isnan( labelValue );
1038 break;
1040 labelValue = thisM;
1041 labelVertex = static_cast< bool >( mData ) && !std::isnan( labelValue );
1042 break;
1043 }
1044
1045 if ( !labelVertex )
1046 continue;
1047
1048 const QString text = mNumericFormat->formatDouble( labelValue, numericContext );
1049 if ( !labelProvider )
1050 {
1051 // render text directly
1052 QgsTextRenderer::drawText( QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, Qgis::TextHorizontalAlignment::Left, { text }, context.renderContext(), mTextFormat );
1053 }
1054 else
1055 {
1056 // register as a label
1057 labelProvider->addLabel(
1058 QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, text, context.renderContext(), mTextFormat
1059 );
1060 }
1061 prevX = thisX;
1062 prevY = thisY;
1063 }
1064}
1065
1067{
1068 return mTextFormat;
1069}
1070
1072{
1073 mTextFormat = format;
1074}
1075
1077{
1078 return mNumericFormat.get();
1079}
1080
1082{
1083 mNumericFormat.reset( format );
1084}
1085
1087{
1088 return mInterval;
1089}
1090
1092{
1093 mInterval = interval;
1094}
1095
1097{
1098 return mSkipMultiplesOf;
1099}
1100
1105
1107{
1108 return mShowMarker;
1109}
1110
1112{
1113 mShowMarker = show;
1114 if ( show && !mMarkerSymbol )
1115 {
1116 mMarkerSymbol = QgsMarkerSymbol::createSimple( {} );
1117 }
1118}
1119
1124
1129
1134
1139
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
Definition qgis.h:789
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer.
Definition qgis.h:790
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
Definition qgis.h:1227
@ DisableFeatureClipping
If present, indicates that features should never be clipped to the map extent during rendering.
Definition qgis.h:900
@ AffectsLabeling
If present, indicates that the symbol layer will participate in the map labeling problem.
Definition qgis.h:902
QFlags< SymbolLayerFlag > SymbolLayerFlags
Symbol layer flags.
Definition qgis.h:906
@ Point
Text at point of origin layout mode.
Definition qgis.h:2961
@ Horizontal
Horizontally oriented text.
Definition qgis.h:2945
LinearReferencingPlacement
Defines how/where the labels should be placed in a linear referencing symbol layer.
Definition qgis.h:3208
@ IntervalZ
Place labels at regular intervals, linearly interpolated using Z values.
Definition qgis.h:3210
@ Vertex
Place labels on every vertex in the line.
Definition qgis.h:3212
@ IntervalM
Place labels at regular intervals, linearly interpolated using M values.
Definition qgis.h:3211
@ IntervalCartesian2D
Place labels at regular intervals, using Cartesian distance calculations on a 2D plane.
Definition qgis.h:3209
LinearReferencingLabelSource
Defines what quantity to use for the labels shown in a linear referencing symbol layer.
Definition qgis.h:3222
@ CartesianDistance2D
Distance along line, calculated using Cartesian calculations on a 2D plane.
Definition qgis.h:3223
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:796
@ Marker
Marker symbol.
Definition qgis.h:630
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.
An abstract interface class for label providers.
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.
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
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.
QgsLineSymbolLayer(const QgsLineSymbolLayer &other)=delete
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.
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 std::unique_ptr< QgsMarkerSymbol > createSimple(const QVariantMap &properties)
Create a marker symbol with one symbol layer: SimpleMarker with specified properties.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context to use when evaluating QgsExpressions.
Abstract base class for numeric formatters, which allow for formatting a numeric value for display.
QPointF toQPointF() const
Converts a point to a QPointF.
Definition qgspointxy.h:167
A container for the context for various read/write operations on objects.
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.
@ 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
QgsSymbolLayer(const QgsSymbolLayer &other)
Encapsulates the context in which a symbol is being rendered.
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:294
static QgsTextDocumentMetrics calculateMetrics(const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor=1.0, const QgsTextDocumentRenderContext &documentContext=QgsTextDocumentRenderContext())
Returns precalculated text metrics for a text document, when rendered using the given base format and...
static QgsTextDocument fromTextAndFormat(const QStringList &lines, const QgsTextFormat &format)
Constructor for QgsTextDocument consisting of a set of lines, respecting settings from a text format.
Container for all settings relating to text rendering.
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.
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).
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:114
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:7110
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7091
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6900
T qgsgeometry_cast(QgsAbstractGeometry *geom)
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)