QGIS API Documentation 3.99.0-Master (e9821da5c6b)
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 copyCommonProperties( res.get() );
218
219 return res.release();
220}
221
223{
224 QDomDocument textFormatDoc;
225 // it's impossible to get the project's path resolver here :(
226 // TODO QGIS 5.0 -- force use of QgsReadWriteContext in properties methods
227 QgsReadWriteContext rwContext;
228 // rwContext.setPathResolver( QgsProject::instance()->pathResolver() ); // skip-keyword-check
229 const QDomElement textElem = mTextFormat.writeXml( textFormatDoc, rwContext );
230 textFormatDoc.appendChild( textElem );
231
232 QDomDocument numericFormatDoc;
233 QDomElement numericFormatElem = numericFormatDoc.createElement( u"numericFormat"_s );
234 mNumericFormat->writeXml( numericFormatElem, numericFormatDoc, rwContext );
235 numericFormatDoc.appendChild( numericFormatElem );
236
237 QVariantMap res
238 {
239 {
240 u"placement"_s, qgsEnumValueToKey( mPlacement )
241 },
242 {
243 u"source"_s, qgsEnumValueToKey( mLabelSource )
244 },
245 {
246 u"interval"_s, mInterval
247 },
248 {
249 u"rotate"_s, mRotateLabels
250 },
251 {
252 u"show_marker"_s, mShowMarker
253 },
254 {
255 u"text_format"_s, textFormatDoc.toString()
256 },
257 {
258 u"numeric_format"_s, numericFormatDoc.toString()
259 },
260 {
261 u"label_offset"_s, QgsSymbolLayerUtils::encodePoint( mLabelOffset )
262 },
263 {
264 u"label_offset_unit"_s, QgsUnitTypes::encodeUnit( mLabelOffsetUnit )
265 },
266 {
267 u"label_offset_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mLabelOffsetMapUnitScale )
268 },
269 {
270 u"average_angle_length"_s, mAverageAngleLength
271 },
272 {
273 u"average_angle_unit"_s, QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit )
274 },
275 {
276 u"average_angle_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale )
277 },
278 };
279
280 if ( mSkipMultiplesOf >= 0 )
281 {
282 res.insert( u"skip_multiples"_s, mSkipMultiplesOf );
283 }
284
285 return res;
286}
287
289{
290 return u"LinearReferencing"_s;
291}
292
298
300{
301 return mShowMarker ? mMarkerSymbol.get() : nullptr;
302}
303
305{
306 if ( symbol && symbol->type() == Qgis::SymbolType::Marker )
307 {
308 mMarkerSymbol.reset( qgis::down_cast<QgsMarkerSymbol *>( symbol ) );
309 return true;
310 }
311 delete symbol;
312 return false;
313}
314
316{
317 if ( mMarkerSymbol )
318 {
319 Qgis::SymbolRenderHints hints = mMarkerSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol;
320 if ( mRotateLabels )
322 mMarkerSymbol->setRenderHints( hints );
323
324 mMarkerSymbol->startRender( context.renderContext(), context.fields() );
325 }
326
327 if ( QgsLabelingEngine *labelingEngine = context.renderContext().labelingEngine() )
328 {
329 // rendering with a labeling engine. In this scenario we will register rendered text as labels, so that they participate in the labeling problem
330 // for the map
331 QgsLinearReferencingSymbolLayerLabelProvider *provider = new QgsLinearReferencingSymbolLayerLabelProvider();
332 mLabelProviderId = labelingEngine->addProvider( provider );
333 }
334}
335
337{
338 if ( mMarkerSymbol )
339 {
340 mMarkerSymbol->stopRender( context.renderContext() );
341 }
342}
343
344void QgsLinearReferencingSymbolLayer::renderGeometryPart( QgsSymbolRenderContext &context, const QgsAbstractGeometry *geometry, double labelOffsetPainterUnitsX, double labelOffsetPainterUnitsY, double skipMultiples, double averageAngleDistancePainterUnits, bool showMarker )
345{
346 if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( geometry ) )
347 {
348 renderLineString( context, line, labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker );
349 }
350 else if ( const QgsPolygon *polygon = qgsgeometry_cast< const QgsPolygon * >( geometry ) )
351 {
352 renderLineString( context, qgsgeometry_cast< const QgsLineString *>( polygon->exteriorRing() ), labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker );
353 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
354 {
355 renderLineString( context, qgsgeometry_cast< const QgsLineString *>( polygon->interiorRing( i ) ), labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker );
356 }
357 }
358}
359
360void QgsLinearReferencingSymbolLayer::renderLineString( QgsSymbolRenderContext &context, const QgsLineString *line, double labelOffsetPainterUnitsX, double labelOffsetPainterUnitsY, double skipMultiples, double averageAngleDistancePainterUnits, bool showMarker )
361{
362 if ( !line )
363 return;
364
365 switch ( mPlacement )
366 {
370 renderPolylineInterval( line, context, skipMultiples, QPointF( labelOffsetPainterUnitsX, labelOffsetPainterUnitsY ), averageAngleDistancePainterUnits, showMarker );
371 break;
372
374 renderPolylineVertex( line, context, skipMultiples, QPointF( labelOffsetPainterUnitsX, labelOffsetPainterUnitsY ), averageAngleDistancePainterUnits, showMarker );
375 break;
376 }
377}
378
380{
381 QPainter *p = context.renderContext().painter();
382 if ( !p )
383 {
384 return;
385 }
386
387 // TODO (maybe?): if we don't have an original geometry, convert points to linestring and scale distance to painter units?
388 // in reality this line type makes no sense for rendering non-real feature geometries...
389 ( void )points;
390 const QgsAbstractGeometry *geometry = context.renderContext().geometry();
391 if ( !geometry )
392 return;
393
394 double skipMultiples = mSkipMultiplesOf;
396 {
397 context.setOriginalValueVariable( mSkipMultiplesOf );
398 skipMultiples = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::SkipMultiples, context.renderContext().expressionContext(), mSkipMultiplesOf );
399 }
400
401 double labelOffsetX = mLabelOffset.x();
402 double labelOffsetY = mLabelOffset.y();
403
404 double averageOver = mAverageAngleLength;
406 {
407 context.setOriginalValueVariable( mAverageAngleLength );
408 averageOver = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::AverageAngleLength, context.renderContext().expressionContext(), mAverageAngleLength );
409 }
410
411 bool showMarker = mShowMarker;
413 {
416 }
417
418 const double labelOffsetPainterUnitsX = context.renderContext().convertToPainterUnits( labelOffsetX, mLabelOffsetUnit, mLabelOffsetMapUnitScale );
419 const double labelOffsetPainterUnitsY = context.renderContext().convertToPainterUnits( labelOffsetY, mLabelOffsetUnit, mLabelOffsetMapUnitScale );
420 const double averageAngleDistancePainterUnits = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2;
421
422 for ( auto partIt = geometry->const_parts_begin(); partIt != geometry->const_parts_end(); ++partIt )
423 {
424 renderGeometryPart( context, *partIt, labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker );
425 }
426}
427
428
429double calculateAveragedAngle( double targetPointDistanceAlongSegment, double segmentLengthPainterUnits,
430 double averageAngleLengthPainterUnits, double prevXPainterUnits, double prevYPainterUnits,
431 double thisXPainterUnits, double thisYPainterUnits, const double *xPainterUnits,
432 const double *yPainterUnits, int totalPoints, int i )
433{
434
435 // track forward by averageAngleLengthPainterUnits
436 double painterDistRemaining = averageAngleLengthPainterUnits + targetPointDistanceAlongSegment;
437 double startAverageSegmentX = prevXPainterUnits;
438 double startAverageSegmentY = prevYPainterUnits;
439 double endAverageSegmentX = thisXPainterUnits;
440 double endAverageSegmentY = thisYPainterUnits;
441 double averagingSegmentLengthPainterUnits = segmentLengthPainterUnits;
442 const double *xAveragingData = xPainterUnits;
443 const double *yAveragingData = yPainterUnits;
444
445 int j = i;
446 while ( painterDistRemaining > averagingSegmentLengthPainterUnits )
447 {
448 if ( j >= totalPoints - 1 )
449 break;
450
451 painterDistRemaining -= averagingSegmentLengthPainterUnits;
452 startAverageSegmentX = endAverageSegmentX;
453 startAverageSegmentY = endAverageSegmentY;
454
455 endAverageSegmentX = *xAveragingData++;
456 endAverageSegmentY = *yAveragingData++;
457 j++;
458 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
459 }
460 // fits on this same segment
461 double endAverageXPainterUnits;
462 double endAverageYPainterUnits;
463 if ( painterDistRemaining < averagingSegmentLengthPainterUnits )
464 {
465 QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, endAverageXPainterUnits, endAverageYPainterUnits,
466 nullptr, nullptr, nullptr,
467 nullptr, nullptr, nullptr );
468 }
469 else
470 {
471 endAverageXPainterUnits = endAverageSegmentX;
472 endAverageYPainterUnits = endAverageSegmentY;
473 }
474
475 // also track back by averageAngleLengthPainterUnits
476 j = i;
477 painterDistRemaining = ( segmentLengthPainterUnits - targetPointDistanceAlongSegment ) + averageAngleLengthPainterUnits;
478 startAverageSegmentX = thisXPainterUnits;
479 startAverageSegmentY = thisYPainterUnits;
480 endAverageSegmentX = prevXPainterUnits;
481 endAverageSegmentY = prevYPainterUnits;
482 averagingSegmentLengthPainterUnits = segmentLengthPainterUnits;
483 xAveragingData = xPainterUnits - 2;
484 yAveragingData = yPainterUnits - 2;
485 while ( painterDistRemaining > averagingSegmentLengthPainterUnits )
486 {
487 if ( j < 1 )
488 break;
489
490 painterDistRemaining -= averagingSegmentLengthPainterUnits;
491 startAverageSegmentX = endAverageSegmentX;
492 startAverageSegmentY = endAverageSegmentY;
493
494 endAverageSegmentX = *xAveragingData--;
495 endAverageSegmentY = *yAveragingData--;
496 j--;
497 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
498 }
499 // fits on this same segment
500 double startAverageXPainterUnits;
501 double startAverageYPainterUnits;
502 if ( painterDistRemaining < averagingSegmentLengthPainterUnits )
503 {
504 QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, startAverageXPainterUnits, startAverageYPainterUnits,
505 nullptr, nullptr, nullptr,
506 nullptr, nullptr, nullptr );
507 }
508 else
509 {
510 startAverageXPainterUnits = endAverageSegmentX;
511 startAverageYPainterUnits = endAverageSegmentY;
512 }
513
514 double calculatedAngle = std::fmod( QgsGeometryUtilsBase::azimuth( startAverageXPainterUnits, startAverageYPainterUnits, endAverageXPainterUnits, endAverageYPainterUnits ) + 360, 360 );
515 if ( calculatedAngle > 90 && calculatedAngle < 270 )
516 calculatedAngle += 180;
517
518 return calculatedAngle;
519}
520
521typedef std::function<bool ( double x, double y, double z, double m, double distanceFromStart, double angle )> VisitPointFunction;
522typedef std::function< void( const QgsLineString *, const QgsLineString *, bool, double, double, const VisitPointFunction & ) > VisitPointAtDistanceFunction;
523
524void visitPointsByRegularDistance( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint )
525{
526 if ( distance < 0 )
527 return;
528
529 double distanceTraversed = 0;
530 const int totalPoints = line->numPoints();
531 if ( totalPoints == 0 )
532 return;
533
534 const double *x = line->xData();
535 const double *y = line->yData();
536 const double *z = line->is3D() ? line->zData() : nullptr;
537 const double *m = line->isMeasure() ? line->mData() : nullptr;
538
539 const double *xPainterUnits = linePainterUnits->xData();
540 const double *yPainterUnits = linePainterUnits->yData();
541
542 double prevX = *x++;
543 double prevY = *y++;
544 double prevZ = z ? *z++ : 0.0;
545 double prevM = m ? *m++ : 0.0;
546
547 double prevXPainterUnits = *xPainterUnits++;
548 double prevYPainterUnits = *yPainterUnits++;
549
550 if ( qgsDoubleNear( distance, 0.0 ) )
551 {
552 visitPoint( prevX, prevY, prevZ, prevM, 0, 0 );
553 return;
554 }
555
556 double pZ = std::numeric_limits<double>::quiet_NaN();
557 double pM = std::numeric_limits<double>::quiet_NaN();
558 double nextPointDistance = emitFirstPoint ? 0 : distance;
559 for ( int i = 1; i < totalPoints; ++i )
560 {
561 double thisX = *x++;
562 double thisY = *y++;
563 double thisZ = z ? *z++ : 0.0;
564 double thisM = m ? *m++ : 0.0;
565 double thisXPainterUnits = *xPainterUnits++;
566 double thisYPainterUnits = *yPainterUnits++;
567
568 double angle = std::fmod( QgsGeometryUtilsBase::azimuth( prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits ) + 360, 360 );
569 if ( angle > 90 && angle < 270 )
570 angle += 180;
571
572 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
573 const double segmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( thisXPainterUnits, thisYPainterUnits, prevXPainterUnits, prevYPainterUnits );
574
575 while ( nextPointDistance < distanceTraversed + segmentLength || qgsDoubleNear( nextPointDistance, distanceTraversed + segmentLength ) )
576 {
577 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
578 const double distanceToPoint = std::min( nextPointDistance - distanceTraversed, segmentLength );
579 double pX, pY;
580 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY,
581 z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &pZ : nullptr,
582 m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &pM : nullptr );
583
584 double calculatedAngle = angle;
585 if ( averageAngleLengthPainterUnits > 0 )
586 {
587 const double targetPointFractionAlongSegment = distanceToPoint / segmentLength;
588 const double targetPointDistanceAlongSegment = targetPointFractionAlongSegment * segmentLengthPainterUnits;
589
590 calculatedAngle = calculateAveragedAngle( targetPointDistanceAlongSegment, segmentLengthPainterUnits,
591 averageAngleLengthPainterUnits, prevXPainterUnits, prevYPainterUnits,
592 thisXPainterUnits, thisYPainterUnits, xPainterUnits,
593 yPainterUnits, totalPoints, i );
594 }
595
596 if ( !visitPoint( pX, pY, pZ, pM, nextPointDistance, calculatedAngle ) )
597 return;
598
599 nextPointDistance += distance;
600 }
601
602 distanceTraversed += segmentLength;
603 prevX = thisX;
604 prevY = thisY;
605 prevZ = thisZ;
606 prevM = thisM;
607 prevXPainterUnits = thisXPainterUnits;
608 prevYPainterUnits = thisYPainterUnits;
609 }
610}
611
612double interpolateValue( double a, double b, double fraction )
613{
614 return a + ( b - a ) * fraction;
615}
616
617
618void visitPointsByInterpolatedZM( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double step, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint, bool useZ )
619{
620 if ( step < 0 )
621 return;
622
623 double distanceTraversed = 0;
624 const int totalPoints = line->numPoints();
625 if ( totalPoints < 2 )
626 return;
627
628 const double *x = line->xData();
629 const double *y = line->yData();
630 const double *z = line->is3D() ? line->zData() : nullptr;
631 const double *m = line->isMeasure() ? line->mData() : nullptr;
632
633 const double *xPainterUnits = linePainterUnits->xData();
634 const double *yPainterUnits = linePainterUnits->yData();
635
636 double prevX = *x++;
637 double prevY = *y++;
638 double prevZ = z ? *z++ : 0.0;
639 double prevM = m ? *m++ : 0.0;
640
641 double prevXPainterUnits = *xPainterUnits++;
642 double prevYPainterUnits = *yPainterUnits++;
643
644 if ( qgsDoubleNear( step, 0.0 ) )
645 {
646 visitPoint( prevX, prevY, prevZ, prevM, 0, 0 );
647 return;
648 }
649
650 double prevValue = useZ ? prevZ : prevM;
651 bool isFirstPoint = true;
652 for ( int i = 1; i < totalPoints; ++i )
653 {
654 double thisX = *x++;
655 double thisY = *y++;
656 double thisZ = z ? *z++ : 0.0;
657 double thisM = m ? *m++ : 0.0;
658 const double thisValue = useZ ? thisZ : thisM;
659 double thisXPainterUnits = *xPainterUnits++;
660 double thisYPainterUnits = *yPainterUnits++;
661
662 double angle = std::fmod( QgsGeometryUtilsBase::azimuth( prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits ) + 360, 360 );
663 if ( angle > 90 && angle < 270 )
664 angle += 180;
665
666 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
667 const double segmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( thisXPainterUnits, thisYPainterUnits, prevXPainterUnits, prevYPainterUnits );
668
669 // direction for this segment
670 const int direction = ( thisValue > prevValue ) ? 1 : ( thisValue < prevValue ) ? -1 : 0;
671 if ( direction != 0 )
672 {
673 // non-constant segment
674 double nextStepValue = direction > 0 ? std::ceil( prevValue / step ) * step
675 : std::floor( prevValue / step ) * step;
676
677 while ( ( direction > 0 && ( nextStepValue <= thisValue || qgsDoubleNear( nextStepValue, thisValue ) ) ) ||
678 ( direction < 0 && ( nextStepValue >= thisValue || qgsDoubleNear( nextStepValue, thisValue ) ) ) )
679 {
680 const double targetPointFractionAlongSegment = ( nextStepValue - prevValue ) / ( thisValue - prevValue );
681 const double targetPointDistanceAlongSegment = targetPointFractionAlongSegment * segmentLengthPainterUnits;
682
683 double pX, pY;
684 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, targetPointFractionAlongSegment * segmentLength, pX, pY );
685
686 const double pZ = useZ ? nextStepValue : interpolateValue( prevZ, thisZ, targetPointFractionAlongSegment );
687 const double pM = useZ ? interpolateValue( prevM, thisM, targetPointFractionAlongSegment ) : nextStepValue;
688
689 double calculatedAngle = angle;
690 if ( averageAngleLengthPainterUnits > 0 )
691 {
692 calculatedAngle = calculateAveragedAngle(
693 targetPointDistanceAlongSegment,
694 segmentLengthPainterUnits, averageAngleLengthPainterUnits,
695 prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits,
696 xPainterUnits, yPainterUnits,
697 totalPoints, i );
698 }
699
700 if ( !qgsDoubleNear( targetPointFractionAlongSegment, 0 ) || isFirstPoint )
701 {
702 if ( !visitPoint( pX, pY, pZ, pM, distanceTraversed + segmentLength * targetPointFractionAlongSegment, calculatedAngle ) )
703 return;
704 }
705
706 nextStepValue += direction * step;
707 }
708 }
709 else if ( isFirstPoint && emitFirstPoint )
710 {
711 if ( !visitPoint( prevX, prevY, prevZ, prevM, distanceTraversed,
712 std::fmod( QgsGeometryUtilsBase::azimuth( prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits ) + 360, 360 ) ) )
713 return;
714 }
715 isFirstPoint = false;
716
717 prevX = thisX;
718 prevY = thisY;
719 prevZ = thisZ;
720 prevM = thisM;
721 prevXPainterUnits = thisXPainterUnits;
722 prevYPainterUnits = thisYPainterUnits;
723 prevValue = thisValue;
724 distanceTraversed += segmentLength;
725 }
726}
727
728void visitPointsByInterpolatedZ( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint )
729{
730 visitPointsByInterpolatedZM( line, linePainterUnits, emitFirstPoint, distance, averageAngleLengthPainterUnits, visitPoint, true );
731}
732
733void visitPointsByInterpolatedM( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint )
734{
735 visitPointsByInterpolatedZM( line, linePainterUnits, emitFirstPoint, distance, averageAngleLengthPainterUnits, visitPoint, false );
736}
737
738QPointF QgsLinearReferencingSymbolLayer::pointToPainter( QgsSymbolRenderContext &context, double x, double y, double z )
739{
740 QPointF pt;
741 if ( context.renderContext().coordinateTransform().isValid() )
742 {
744 pt = QPointF( x, y );
745
746 }
747 else
748 {
749 pt = QPointF( x, y );
750 }
751
752 context.renderContext().mapToPixel().transformInPlace( pt.rx(), pt.ry() );
753 return pt;
754}
755
756void QgsLinearReferencingSymbolLayer::renderPolylineInterval( const QgsLineString *line, QgsSymbolRenderContext &context, double skipMultiples, const QPointF &labelOffsetPainterUnits, double averageAngleLengthPainterUnits, bool showMarker )
757{
758 double distance = mInterval;
760 {
761 context.setOriginalValueVariable( mInterval );
762 distance = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::Interval, context.renderContext().expressionContext(), mInterval );
763 }
764
765 QgsNumericFormatContext numericContext;
766 numericContext.setExpressionContext( context.renderContext().expressionContext() );
767
768 std::unique_ptr< QgsLineString > painterUnitsGeometry( line->clone() );
769 if ( context.renderContext().coordinateTransform().isValid() )
770 {
771 painterUnitsGeometry->transform( context.renderContext().coordinateTransform() );
772 }
773 painterUnitsGeometry->transform( context.renderContext().mapToPixel().transform() );
774
775 const bool hasZ = line->is3D();
776 const bool hasM = line->isMeasure();
777 const bool emitFirstPoint = mLabelSource != Qgis::LinearReferencingLabelSource::CartesianDistance2D;
778
779 VisitPointAtDistanceFunction func = nullptr;
780
781 switch ( mPlacement )
782 {
785 break;
786
789 break;
790
793 break;
794
796 return;
797 }
798
799 QgsLinearReferencingSymbolLayerLabelProvider *labelProvider = nullptr;
800 if ( QgsLabelingEngine *labelingEngine = context.renderContext().labelingEngine() )
801 {
802 // rendering with a labeling engine. In this scenario we will register rendered text as labels, so that they participate in the labeling problem
803 // for the map
804 labelProvider = qgis::down_cast< QgsLinearReferencingSymbolLayerLabelProvider * >( labelingEngine->providerById( mLabelProviderId ) );
805 }
806
807 func( line, painterUnitsGeometry.get(), emitFirstPoint, distance, averageAngleLengthPainterUnits, [&context, &numericContext, skipMultiples, showMarker,
808 labelOffsetPainterUnits, hasZ, hasM, labelProvider, this]( double x, double y, double z, double m, double distanceFromStart, double angle ) -> bool
809 {
810 if ( context.renderContext().renderingStopped() )
811 return false;
812
813 double labelValue = 0;
814 bool labelVertex = true;
815 switch ( mLabelSource )
816 {
817 case Qgis::LinearReferencingLabelSource::CartesianDistance2D:
818 labelValue = distanceFromStart;
819 break;
820 case Qgis::LinearReferencingLabelSource::Z:
821 labelValue = z;
822 labelVertex = hasZ && !std::isnan( labelValue );
823 break;
824 case Qgis::LinearReferencingLabelSource::M:
825 labelValue = m;
826 labelVertex = hasM && !std::isnan( labelValue );
827 break;
828 }
829
830 if ( !labelVertex )
831 return true;
832
833 if ( skipMultiples > 0 && qgsDoubleNear( std::fmod( labelValue, skipMultiples ), 0 ) )
834 return true;
835
836 const QPointF pt = pointToPainter( context, x, y, z );
837
838 if ( mMarkerSymbol && showMarker )
839 {
840 if ( mRotateLabels )
841 mMarkerSymbol->setLineAngle( 90 - angle );
842 mMarkerSymbol->renderPoint( pt, context.feature(), context.renderContext() );
843 }
844
845 const double angleRadians = ( mRotateLabels ? angle : 0 ) * M_PI / 180.0;
846 const double dx = labelOffsetPainterUnits.x() * std::sin( angleRadians + M_PI_2 )
847 + labelOffsetPainterUnits.y() * std::sin( angleRadians );
848 const double dy = labelOffsetPainterUnits.x() * std::cos( angleRadians + M_PI_2 )
849 + labelOffsetPainterUnits.y() * std::cos( angleRadians );
850
851 const QString text = mNumericFormat->formatDouble( labelValue, numericContext );
852 if ( !labelProvider )
853 {
854 // render text directly
855 QgsTextRenderer::drawText( QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, Qgis::TextHorizontalAlignment::Left, { text }, context.renderContext(), mTextFormat );
856 }
857 else
858 {
859 // register as a label
860 labelProvider->addLabel(
861 QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, text, context.renderContext(), mTextFormat
862 );
863 }
864
865 return true;
866 } );
867}
868
869void QgsLinearReferencingSymbolLayer::renderPolylineVertex( const QgsLineString *line, QgsSymbolRenderContext &context, double skipMultiples, const QPointF &labelOffsetPainterUnits, double averageAngleLengthPainterUnits, bool showMarker )
870{
871 // let's simplify the logic by ALWAYS using the averaging approach for angles, and just
872 // use a very small distance if the user actually set this to 0. It'll be identical
873 // results anyway...
874 averageAngleLengthPainterUnits = std::max( averageAngleLengthPainterUnits, 0.1 );
875
876 QgsNumericFormatContext numericContext;
877 numericContext.setExpressionContext( context.renderContext().expressionContext() );
878
879 QgsLinearReferencingSymbolLayerLabelProvider *labelProvider = nullptr;
880 if ( QgsLabelingEngine *labelingEngine = context.renderContext().labelingEngine() )
881 {
882 // rendering with a labeling engine. In this scenario we will register rendered text as labels, so that they participate in the labeling problem
883 // for the map
884 labelProvider = qgis::down_cast< QgsLinearReferencingSymbolLayerLabelProvider * >( labelingEngine->providerById( mLabelProviderId ) );
885 }
886
887 const double *xData = line->xData();
888 const double *yData = line->yData();
889 const double *zData = line->is3D() ? line->zData() : nullptr;
890 const double *mData = line->isMeasure() ? line->mData() : nullptr;
891 const int size = line->numPoints();
892 if ( size < 2 )
893 return;
894
895 std::unique_ptr< QgsLineString > painterUnitsGeometry( line->clone() );
896 if ( context.renderContext().coordinateTransform().isValid() )
897 {
898 painterUnitsGeometry->transform( context.renderContext().coordinateTransform() );
899 }
900 painterUnitsGeometry->transform( context.renderContext().mapToPixel().transform() );
901 const double *xPainterUnits = painterUnitsGeometry->xData();
902 const double *yPainterUnits = painterUnitsGeometry->yData();
903
904 double currentDistance = 0;
905 double prevX = *xData;
906 double prevY = *yData;
907
908 for ( int i = 0; i < size; ++i )
909 {
910 if ( context.renderContext().renderingStopped() )
911 break;
912
913 double thisX = *xData++;
914 double thisY = *yData++;
915 double thisZ = zData ? *zData++ : 0;
916 double thisM = mData ? *mData++ : 0;
917 double thisXPainterUnits = *xPainterUnits++;
918 double thisYPainterUnits = *yPainterUnits++;
919
920 const double thisSegmentLength = QgsGeometryUtilsBase::distance2D( prevX, prevY, thisX, thisY );
921 currentDistance += thisSegmentLength;
922
923 if ( skipMultiples > 0 && qgsDoubleNear( std::fmod( currentDistance, skipMultiples ), 0 ) )
924 {
925 prevX = thisX;
926 prevY = thisY;
927 continue;
928 }
929
930 const QPointF pt = pointToPainter( context, thisX, thisY, thisZ );
931
932 // track forward by averageAngleLengthPainterUnits
933 double painterDistRemaining = averageAngleLengthPainterUnits;
934 double startAverageSegmentX = thisXPainterUnits;
935 double startAverageSegmentY = thisYPainterUnits;
936
937 const double *xAveragingData = xPainterUnits;
938 const double *yAveragingData = yPainterUnits;
939 double endAverageSegmentX = *xAveragingData;
940 double endAverageSegmentY = *yAveragingData;
941 double averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
942
943 int j = i;
944 while ( ( j < size - 1 ) && ( painterDistRemaining > averagingSegmentLengthPainterUnits ) )
945 {
946 painterDistRemaining -= averagingSegmentLengthPainterUnits;
947 startAverageSegmentX = endAverageSegmentX;
948 startAverageSegmentY = endAverageSegmentY;
949
950 endAverageSegmentX = *xAveragingData++;
951 endAverageSegmentY = *yAveragingData++;
952 j++;
953 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
954 }
955 // fits on this same segment
956 double endAverageXPainterUnits = thisXPainterUnits;
957 double endAverageYPainterUnits = thisYPainterUnits;
958 if ( ( j < size - 1 ) && painterDistRemaining < averagingSegmentLengthPainterUnits )
959 {
960 QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, endAverageXPainterUnits, endAverageYPainterUnits,
961 nullptr, nullptr, nullptr,
962 nullptr, nullptr, nullptr );
963 }
964 else if ( i < size - 2 )
965 {
966 endAverageXPainterUnits = endAverageSegmentX;
967 endAverageYPainterUnits = endAverageSegmentY;
968 }
969
970 // also track back by averageAngleLengthPainterUnits
971 j = i;
972 painterDistRemaining = averageAngleLengthPainterUnits;
973 startAverageSegmentX = thisXPainterUnits;
974 startAverageSegmentY = thisYPainterUnits;
975
976 xAveragingData = xPainterUnits - 2;
977 yAveragingData = yPainterUnits - 2;
978
979 endAverageSegmentX = *xAveragingData;
980 endAverageSegmentY = *yAveragingData;
981 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
982
983 while ( j > 0 && painterDistRemaining > averagingSegmentLengthPainterUnits )
984 {
985 painterDistRemaining -= averagingSegmentLengthPainterUnits;
986 startAverageSegmentX = endAverageSegmentX;
987 startAverageSegmentY = endAverageSegmentY;
988
989 endAverageSegmentX = *xAveragingData--;
990 endAverageSegmentY = *yAveragingData--;
991 j--;
992 averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY );
993 }
994 // fits on this same segment
995 double startAverageXPainterUnits = thisXPainterUnits;
996 double startAverageYPainterUnits = thisYPainterUnits;
997 if ( j > 0 && painterDistRemaining < averagingSegmentLengthPainterUnits )
998 {
999 QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, startAverageXPainterUnits, startAverageYPainterUnits,
1000 nullptr, nullptr, nullptr,
1001 nullptr, nullptr, nullptr );
1002 }
1003 else if ( j > 1 )
1004 {
1005 startAverageXPainterUnits = endAverageSegmentX;
1006 startAverageYPainterUnits = endAverageSegmentY;
1007 }
1008
1009 double calculatedAngle = std::fmod( QgsGeometryUtilsBase::azimuth( startAverageXPainterUnits, startAverageYPainterUnits, endAverageXPainterUnits, endAverageYPainterUnits ) + 360, 360 );
1010
1011 if ( calculatedAngle > 90 && calculatedAngle < 270 )
1012 calculatedAngle += 180;
1013
1014 if ( mMarkerSymbol && showMarker )
1015 {
1016 if ( mRotateLabels )
1017 mMarkerSymbol->setLineAngle( 90 - calculatedAngle );
1018 mMarkerSymbol->renderPoint( pt, context.feature(), context.renderContext() );
1019 }
1020
1021 const double angleRadians = mRotateLabels ? ( calculatedAngle * M_PI / 180.0 ) : 0;
1022 const double dx = labelOffsetPainterUnits.x() * std::sin( angleRadians + M_PI_2 )
1023 + labelOffsetPainterUnits.y() * std::sin( angleRadians );
1024 const double dy = labelOffsetPainterUnits.x() * std::cos( angleRadians + M_PI_2 )
1025 + labelOffsetPainterUnits.y() * std::cos( angleRadians );
1026
1027 double labelValue = 0;
1028 bool labelVertex = true;
1029 switch ( mLabelSource )
1030 {
1032 labelValue = currentDistance;
1033 break;
1035 labelValue = thisZ;
1036 labelVertex = static_cast< bool >( zData ) && !std::isnan( labelValue );
1037 break;
1039 labelValue = thisM;
1040 labelVertex = static_cast< bool >( mData ) && !std::isnan( labelValue );
1041 break;
1042 }
1043
1044 if ( !labelVertex )
1045 continue;
1046
1047 const QString text = mNumericFormat->formatDouble( labelValue, numericContext );
1048 if ( !labelProvider )
1049 {
1050 // render text directly
1051 QgsTextRenderer::drawText( QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, Qgis::TextHorizontalAlignment::Left, { text }, context.renderContext(), mTextFormat );
1052 }
1053 else
1054 {
1055 // register as a label
1056 labelProvider->addLabel(
1057 QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, text, context.renderContext(), mTextFormat
1058 );
1059 }
1060 prevX = thisX;
1061 prevY = thisY;
1062 }
1063}
1064
1066{
1067 return mTextFormat;
1068}
1069
1071{
1072 mTextFormat = format;
1073}
1074
1076{
1077 return mNumericFormat.get();
1078}
1079
1081{
1082 mNumericFormat.reset( format );
1083}
1084
1086{
1087 return mInterval;
1088}
1089
1091{
1092 mInterval = interval;
1093}
1094
1096{
1097 return mSkipMultiplesOf;
1098}
1099
1104
1106{
1107 return mShowMarker;
1108}
1109
1111{
1112 mShowMarker = show;
1113 if ( show && !mMarkerSymbol )
1114 {
1115 mMarkerSymbol = QgsMarkerSymbol::createSimple( {} );
1116 }
1117}
1118
1123
1128
1133
1138
@ 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:3220
@ IntervalZ
Place labels at regular intervals, linearly interpolated using Z values.
Definition qgis.h:3222
@ Vertex
Place labels on every vertex in the line.
Definition qgis.h:3224
@ IntervalM
Place labels at regular intervals, linearly interpolated using M values.
Definition qgis.h:3223
@ IntervalCartesian2D
Place labels at regular intervals, using Cartesian distance calculations on a 2D plane.
Definition qgis.h:3221
LinearReferencingLabelSource
Defines what quantity to use for the labels shown in a linear referencing symbol layer.
Definition qgis.h:3234
@ CartesianDistance2D
Distance along line, calculated using Cartesian calculations on a 2D plane.
Definition qgis.h:3235
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 copyCommonProperties(QgsSymbolLayer *destLayer) const
Copies all common base class properties from this layer to another symbol layer.
@ SkipMultiples
Skip multiples of.
@ AverageAngleLength
Length to average symbol angles over.
@ Interval
Line marker interval.
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:7134
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7115
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6924
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)