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