QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
Loading...
Searching...
No Matches
qgsannotationlayerchunkloader_p.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsannotationlayerchunkloader_p.cpp
3 --------------------------------------
4 Date : September 2025
5 Copyright : (C) 2025 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 "qgs3dutils.h"
19#include "qgsabstract3dsymbol.h"
21#include "qgsannotationitem.h"
22#include "qgsannotationlayer.h"
28#include "qgschunknode.h"
29#include "qgseventtracing.h"
32#include "qgsgeos.h"
33#include "qgsgeotransform.h"
34#include "qgslinematerial_p.h"
35#include "qgslinevertexdata_p.h"
36#include "qgslogger.h"
37#include "qgsmarkersymbol.h"
40#include "qgstextdocument.h"
42
43#include <QString>
44#include <QTimer>
45#include <Qt3DCore/QTransform>
46#include <Qt3DRender/QGeometryRenderer>
47#include <QtConcurrentRun>
48
49#include "moc_qgsannotationlayerchunkloader_p.cpp"
50
51using namespace Qt::StringLiterals;
52
54
55
56QgsAnnotationLayerChunkLoader::QgsAnnotationLayerChunkLoader( const QgsAnnotationLayerChunkLoaderFactory *factory, QgsChunkNode *node )
57 : QgsChunkLoader( node )
58 , mFactory( factory )
59 , mRenderContext( factory->mRenderContext )
60{}
61
62
63struct Billboard
64{
65 QVector3D position;
66 int textureId = -1;
67 const QgsMarkerSymbol *markerSymbol = nullptr;
68};
69
70struct TextBillboard
71{
72 QVector3D position;
73 QString text;
74};
75
76
77void QgsAnnotationLayerChunkLoader::start()
78{
79 QgsChunkNode *node = chunk();
80 if ( node->level() < mFactory->mLeafLevel )
81 {
82 QTimer::singleShot( 0, this, &QgsAnnotationLayerChunkLoader::finished );
83 return;
84 }
85
86 QgsAnnotationLayer *layer = mFactory->mLayer;
87 mLayerName = mFactory->mLayer->name();
88
89 // only a subset of data to be queried
90 const QgsRectangle rect = node->box3D().toRectangle();
91 // origin for coordinates of the chunk - it is kind of arbitrary, but it should be
92 // picked so that the coordinates are relatively small to avoid numerical precision issues
93 mChunkOrigin = QgsVector3D( rect.center().x(), rect.center().y(), 0 );
94
95 QgsExpressionContext exprContext;
97 mRenderContext.setExpressionContext( exprContext );
98
99 QgsCoordinateTransform layerToMapTransform( layer->crs(), mRenderContext.crs(), mRenderContext.transformContext() );
100
101 QgsRectangle layerExtent;
102 try
103 {
104 layerExtent = layerToMapTransform.transformBoundingBox( rect, Qgis::TransformDirection::Reverse );
105 }
106 catch ( QgsCsException &e )
107 {
108 QgsDebugError( u"Error transforming annotation layer extent to 3d map extent: %1"_s.arg( e.what() ) );
109 return;
110 }
111
112 const double zOffset = mFactory->mZOffset;
113 const Qgis::AltitudeClamping altitudeClamping = mFactory->mClamping;
114 bool showCallouts = mFactory->mShowCallouts;
115 const QgsTextFormat textFormat = mFactory->mTextFormat;
116
117 // see logic from QgsAnnotationLayerRenderer
118 const QStringList itemsList = layer->queryIndex( layerExtent );
119 QSet< QString > itemIds( itemsList.begin(), itemsList.end() );
120
121 // we also have NO choice but to clone ALL non-indexed items (i.e. those with a scale-dependent bounding box)
122 // since these won't be in the layer's spatial index, and it's too expensive to determine their actual bounding box
123 // upfront (we are blocking the main thread right now!)
124
125 // TODO -- come up with some brilliant way to avoid this and also index scale-dependent items ;)
126 itemIds.unite( layer->mNonIndexedItems );
127
128 mItemsToRender.reserve( itemIds.size() );
129 std::transform( itemIds.begin(), itemIds.end(), std::back_inserter( mItemsToRender ), [layer]( const QString &id ) -> std::unique_ptr< QgsAnnotationItem > {
130 return std::unique_ptr< QgsAnnotationItem >( layer->item( id )->clone() );
131 } );
132
133 //
134 // this will be run in a background thread
135 //
136 mFutureWatcher = new QFutureWatcher<void>( this );
137 connect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
138
139 const QFuture<void> future = QtConcurrent::run( [this, rect, layerToMapTransform, zOffset, altitudeClamping, showCallouts, textFormat] {
140 const QgsEventTracing::ScopedEvent e( u"3D"_s, u"Annotation layer chunk load"_s );
141
142 std::vector< Billboard > billboards;
143 billboards.reserve( mItemsToRender.size() );
144 QVector< QImage > textures;
145 textures.reserve( mItemsToRender.size() );
146
147 std::vector< TextBillboard > textBillboards;
148 textBillboards.reserve( mItemsToRender.size() );
149 QStringList textBillboardTexts;
150 textBillboardTexts.reserve( mItemsToRender.size() );
151
152 auto addTextBillboard = [layerToMapTransform,
153 showCallouts,
154 rect,
155 zOffset,
156 altitudeClamping,
157 this,
158 &textBillboards,
159 &textBillboardTexts]( const QgsPointXY &p, const QString &annotationText, const QgsTextFormat &annotationTextFormat ) {
160 QString text = annotationText;
161 if ( annotationTextFormat.allowHtmlFormatting() )
162 {
163 // strip HTML characters, we don't support those in 3D
164 const QgsTextDocument document = QgsTextDocument::fromTextAndFormat( { text }, annotationTextFormat );
165 text = document.toPlainText().join( ' ' );
166 }
167 if ( !text.isEmpty() )
168 {
169 try
170 {
171 const QgsPointXY mapPoint = layerToMapTransform.transform( p );
172 if ( !rect.contains( mapPoint ) )
173 return;
174
175 double z = 0;
176 const float terrainZ = ( altitudeClamping == Qgis::AltitudeClamping::Absolute && !showCallouts ) ? 0
177 : mRenderContext.terrainRenderingEnabled() && mRenderContext.terrainGenerator()
178 ? static_cast<float>( mRenderContext.terrainGenerator()->heightAt( mapPoint.x(), mapPoint.y(), mRenderContext ) * mRenderContext.terrainSettings()->verticalScale() )
179 : 0.f;
180
181 switch ( altitudeClamping )
182 {
184 z = zOffset;
185 break;
187 z = terrainZ;
188 break;
190 z = terrainZ + zOffset;
191 break;
192 }
193
194 TextBillboard billboard;
195 billboard.position = ( QgsVector3D( mapPoint.x(), mapPoint.y(), z ) - mChunkOrigin ).toVector3D();
196 billboard.text = text;
197 textBillboards.emplace_back( std::move( billboard ) );
198 textBillboardTexts.append( text );
199
200 if ( showCallouts )
201 {
202 mCalloutLines << QgsLineString( { mapPoint.x(), mapPoint.x() }, { mapPoint.y(), mapPoint.y() }, { terrainZ, z } );
203 }
204
205 mZMax = std::max( mZMax, showCallouts ? std::max( 0.0, z ) : z );
206 mZMin = std::min( mZMin, showCallouts ? std::min( 0.0, z ) : z );
207 }
208 catch ( QgsCsException &e )
209 {
210 QgsDebugError( e.what() );
211 }
212 }
213 };
214
215 for ( const std::unique_ptr< QgsAnnotationItem > &item : std::as_const( mItemsToRender ) )
216 {
217 if ( mCanceled )
218 break;
219
220 QgsAnnotationItem *annotation = item.get();
221
222 if ( !annotation->enabled() )
223 continue;
224
225 if ( QgsAnnotationMarkerItem *marker = dynamic_cast< QgsAnnotationMarkerItem * >( annotation ) )
226 {
227 if ( marker->symbol() )
228 {
229 QgsPointXY p = marker->geometry();
230 try
231 {
232 const QgsPointXY mapPoint = layerToMapTransform.transform( p );
233 if ( !rect.contains( mapPoint ) )
234 continue;
235
236 double z = 0;
237 const float terrainZ = ( altitudeClamping == Qgis::AltitudeClamping::Absolute && !showCallouts ) ? 0
238 : mRenderContext.terrainRenderingEnabled() && mRenderContext.terrainGenerator()
239 ? static_cast<float>( mRenderContext.terrainGenerator()->heightAt( mapPoint.x(), mapPoint.y(), mRenderContext ) * mRenderContext.terrainSettings()->verticalScale() )
240 : 0.f;
241
242 switch ( altitudeClamping )
243 {
245 z = zOffset;
246 break;
248 z = terrainZ;
249 break;
251 z = terrainZ + zOffset;
252 break;
253 }
254
255 Billboard billboard;
256 billboard.position = ( QgsVector3D( mapPoint.x(), mapPoint.y(), z ) - mChunkOrigin ).toVector3D();
257 billboard.textureId = -1;
258
259 for ( const Billboard &existingBillboard : billboards )
260 {
261 if ( existingBillboard.markerSymbol && marker->symbol()->rendersIdenticallyTo( existingBillboard.markerSymbol ) )
262 {
263 // marker symbol has been reused => reuse existing texture to minimize size of texture atlas
264 billboard.textureId = existingBillboard.textureId;
265 break;
266 }
267 }
268
269 if ( billboard.textureId < 0 )
270 {
271 // could not match to previously considered marker, have to render and add to texture atlas
272 billboard.markerSymbol = marker->symbol();
273 billboard.textureId = textures.size();
274 textures.append( QgsPoint3DBillboardMaterial::renderSymbolToImage( marker->symbol(), mRenderContext ) );
275 }
276 billboards.emplace_back( std::move( billboard ) );
277
278 if ( showCallouts )
279 {
280 mCalloutLines << QgsLineString( { mapPoint.x(), mapPoint.x() }, { mapPoint.y(), mapPoint.y() }, { terrainZ, z } );
281 }
282
283 mZMax = std::max( mZMax, showCallouts ? std::max( 0.0, z ) : z );
284 mZMin = std::min( mZMin, showCallouts ? std::min( 0.0, z ) : z );
285 }
286 catch ( QgsCsException &e )
287 {
288 QgsDebugError( e.what() );
289 }
290 }
291 }
292 else if ( QgsAnnotationPointTextItem *pointText = dynamic_cast< QgsAnnotationPointTextItem * >( annotation ) )
293 {
294 addTextBillboard( pointText->point(), pointText->text(), pointText->format() );
295 }
296 else if ( QgsAnnotationLineTextItem *lineText = dynamic_cast< QgsAnnotationLineTextItem * >( annotation ) )
297 {
298 QgsGeos geos( lineText->geometry() );
299 std::unique_ptr< QgsPoint > point( geos.pointOnSurface() );
300 if ( point )
301 {
302 addTextBillboard( *point, lineText->text(), lineText->format() );
303 }
304 }
305 else if ( QgsAnnotationRectangleTextItem *rectText = dynamic_cast< QgsAnnotationRectangleTextItem * >( annotation ) )
306 {
307 switch ( rectText->placementMode() )
308 {
311 {
312 addTextBillboard( rectText->bounds().center(), rectText->text(), rectText->format() );
313 break;
314 }
316 // ignore these annotations, they don't have a fix map position
317 break;
318 }
319 }
320 }
321 // free memory
322 mItemsToRender.clear();
323
324 if ( !textures.isEmpty() )
325 {
326 const QgsTextureAtlas atlas = QgsTextureAtlasGenerator::createFromImages( textures, 2048 );
327 if ( atlas.isValid() )
328 {
329 mBillboardAtlas = atlas.renderAtlasTexture();
330 mBillboardPositions.reserve( static_cast< int >( billboards.size() ) );
331 for ( Billboard &billboard : billboards )
332 {
333 const QRect textureRect = atlas.rect( billboard.textureId );
335 geometry.position = billboard.position;
336 geometry.textureAtlasOffset = QVector2D(
337 static_cast< float >( textureRect.left() ) / static_cast< float>( mBillboardAtlas.width() ),
338 1 - ( static_cast< float >( textureRect.bottom() ) / static_cast< float>( mBillboardAtlas.height() ) )
339 );
340 geometry.textureAtlasSize = QVector2D(
341 static_cast< float >( textureRect.width() ) / static_cast< float>( mBillboardAtlas.width() ), static_cast< float>( textureRect.height() ) / static_cast< float>( mBillboardAtlas.height() )
342 );
343 geometry.pixelOffset = QPoint( 0, textureRect.height() / 2 );
344 mBillboardPositions.append( geometry );
345 }
346 }
347 else
348 {
349 QgsDebugError( u"Error encountered building texture atlas"_s );
350 mBillboardAtlas = QImage();
351 }
352 }
353 else
354 {
355 mBillboardAtlas = QImage();
356 mBillboardPositions.clear();
357 }
358
359
360 if ( !textBillboardTexts.isEmpty() )
361 {
362 const QgsFontTextureAtlas atlas = QgsFontTextureAtlasGenerator::create( textFormat, textBillboardTexts );
363 if ( atlas.isValid() )
364 {
365 mTextBillboardAtlas = atlas.renderAtlasTexture();
366 mTextBillboardPositions.reserve( static_cast< int >( textBillboards.size() ) );
367 for ( TextBillboard &billboard : textBillboards )
368 {
369 int graphemeIndex = 0;
370 const int graphemeCount = atlas.graphemeCount( billboard.text );
371 // horizontally center text over point
372 const double xOffset = atlas.totalWidth( billboard.text ) / 2.0;
373 for ( ; graphemeIndex < graphemeCount; ++graphemeIndex )
374 {
375 const QRect textureRect = atlas.textureRectForGrapheme( billboard.text, graphemeIndex );
377 geometry.position = billboard.position;
378 geometry.textureAtlasOffset = QVector2D(
379 static_cast< float >( textureRect.left() ) / static_cast< float>( mTextBillboardAtlas.width() ),
380 1 - ( static_cast< float >( textureRect.bottom() ) / static_cast< float>( mTextBillboardAtlas.height() ) )
381 );
382 geometry.textureAtlasSize = QVector2D(
383 static_cast< float >( textureRect.width() ) / static_cast< float>( mTextBillboardAtlas.width() ),
384 static_cast< float>( textureRect.height() ) / static_cast< float>( mTextBillboardAtlas.height() )
385 );
386 const QPointF pixelOffset = atlas.pixelOffsetForGrapheme( billboard.text, graphemeIndex );
387 geometry.pixelOffset
388 = QPoint( static_cast< int >( std::round( -xOffset + pixelOffset.x() + 0.5 * textureRect.width() ) ), static_cast< int >( std::round( pixelOffset.y() + 0.5 * textureRect.height() ) ) );
389 mTextBillboardPositions.append( geometry );
390 }
391 }
392 }
393 else
394 {
395 QgsDebugError( u"Error encountered building font texture atlas"_s );
396 mTextBillboardAtlas = QImage();
397 }
398 }
399 else
400 {
401 mTextBillboardAtlas = QImage();
402 mTextBillboardPositions.clear();
403 }
404 } );
405
406 // emit finished() as soon as the handler is populated with features
407 mFutureWatcher->setFuture( future );
408}
409
410QgsAnnotationLayerChunkLoader::~QgsAnnotationLayerChunkLoader()
411{
412 if ( mFutureWatcher && !mFutureWatcher->isFinished() )
413 {
414 disconnect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
415 mFutureWatcher->waitForFinished();
416 }
417}
418
419void QgsAnnotationLayerChunkLoader::cancel()
420{
421 mCanceled = true;
422}
423
424Qt3DCore::QEntity *QgsAnnotationLayerChunkLoader::createEntity( Qt3DCore::QEntity *parent )
425{
426 if ( mNode->level() < mFactory->mLeafLevel )
427 {
428 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity( parent ); // dummy entity
429 entity->setObjectName( mLayerName + "_CONTAINER_" + mNode->tileId().text() );
430 return entity;
431 }
432
433 if ( mBillboardPositions.empty() && mTextBillboardPositions.empty() )
434 {
435 // an empty node, so we return no entity. This tags the node as having no data and effectively removes it.
436 // we just make sure first that its initial estimated vertical range does not affect its parents' bboxes calculation
437 mNode->setExactBox3D( QgsBox3D() );
438 mNode->updateParentBoundingBoxesRecursively();
439 return nullptr;
440 }
441
442 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity( parent );
443 entity->setObjectName( mLayerName + "_" + mNode->tileId().text() );
444
445 QgsGeoTransform *billboardTransform = new QgsGeoTransform;
446 billboardTransform->setGeoTranslation( mChunkOrigin );
447 entity->addComponent( billboardTransform );
448
449 if ( !mBillboardPositions.empty() )
450 {
451 QgsBillboardGeometry *billboardGeometry = new QgsBillboardGeometry();
452 billboardGeometry->setBillboardData( mBillboardPositions, true );
453
454 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer = new Qt3DRender::QGeometryRenderer;
455 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
456 billboardGeometryRenderer->setGeometry( billboardGeometry );
457 billboardGeometryRenderer->setVertexCount( billboardGeometry->count() );
458
460 billboardMaterial->setTexture2DFromImage( mBillboardAtlas );
461
462
463 Qt3DCore::QEntity *billboardEntity = new Qt3DCore::QEntity;
464 billboardEntity->addComponent( billboardMaterial );
465 billboardEntity->addComponent( billboardGeometryRenderer );
466 billboardEntity->setParent( entity );
467 }
468
469 if ( !mTextBillboardPositions.empty() )
470 {
471 QgsBillboardGeometry *textBillboardGeometry = new QgsBillboardGeometry();
472 textBillboardGeometry->setBillboardData( mTextBillboardPositions, true );
473
474 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer = new Qt3DRender::QGeometryRenderer;
475 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
476 billboardGeometryRenderer->setGeometry( textBillboardGeometry );
477 billboardGeometryRenderer->setVertexCount( textBillboardGeometry->count() );
478
480 billboardMaterial->setTexture2DFromImage( mTextBillboardAtlas );
481
482 Qt3DCore::QEntity *billboardEntity = new Qt3DCore::QEntity;
483 billboardEntity->addComponent( billboardMaterial );
484 billboardEntity->addComponent( billboardGeometryRenderer );
485 billboardEntity->setParent( entity );
486 }
487
488
489 if ( mFactory->mShowCallouts )
490 {
491 QgsLineVertexData lineData;
492 lineData.withAdjacency = true;
493 lineData.geocentricCoordinates = false; // mMapSettings->sceneMode() == Qgis::SceneMode::Globe;
494 lineData.init( Qgis::AltitudeClamping::Absolute, Qgis::AltitudeBinding::Vertex, 0, mRenderContext, mChunkOrigin );
495
496 for ( const QgsLineString &line : mCalloutLines )
497 {
498 lineData.addLineString( line, 0, false );
499 }
500
501 QgsLineMaterial *mat = new QgsLineMaterial;
502 mat->setLineColor( mFactory->mCalloutLineColor );
503 mat->setLineWidth( mFactory->mCalloutLineWidth );
504
505 Qt3DCore::QEntity *calloutEntity = new Qt3DCore::QEntity;
506 calloutEntity->setObjectName( parent->objectName() + "_CALLOUTS" );
507
508 // geometry renderer
509 Qt3DRender::QGeometryRenderer *calloutRenderer = new Qt3DRender::QGeometryRenderer;
510 calloutRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
511 calloutRenderer->setGeometry( lineData.createGeometry( calloutEntity ) );
512 calloutRenderer->setVertexCount( lineData.indexes.count() );
513 calloutRenderer->setPrimitiveRestartEnabled( true );
514 calloutRenderer->setRestartIndexValue( 0 );
515
516 // make entity
517 calloutEntity->addComponent( calloutRenderer );
518 calloutEntity->addComponent( mat );
519
520 calloutEntity->setParent( entity );
521 }
522
523 // fix the vertical range of the node from the estimated vertical range to the true range
524 if ( mZMin != std::numeric_limits<float>::max() && mZMax != std::numeric_limits<float>::lowest() )
525 {
526 QgsBox3D box = mNode->box3D();
527 box.setZMinimum( mZMin );
528 box.setZMaximum( mZMax );
529 mNode->setExactBox3D( box );
530 mNode->updateParentBoundingBoxesRecursively();
531 }
532 return entity;
533}
534
535
537
538
539QgsAnnotationLayerChunkLoaderFactory::QgsAnnotationLayerChunkLoaderFactory(
540 const Qgs3DRenderContext &context,
541 QgsAnnotationLayer *layer,
542 int leafLevel,
543 Qgis::AltitudeClamping clamping,
544 double zOffset,
545 bool showCallouts,
546 const QColor &calloutLineColor,
547 double calloutLineWidth,
548 const QgsTextFormat &textFormat,
549 double zMin,
550 double zMax
551)
552 : mRenderContext( context )
553 , mLayer( layer )
554 , mLeafLevel( leafLevel )
555 , mClamping( clamping )
556 , mZOffset( zOffset )
557 , mShowCallouts( showCallouts )
558 , mCalloutLineColor( calloutLineColor )
559 , mCalloutLineWidth( calloutLineWidth )
560 , mTextFormat( textFormat )
561{
562 if ( context.crs().type() == Qgis::CrsType::Geocentric )
563 {
564 // TODO: add support for handling of annotation layers
565 // (we're using dummy quadtree here to make sure the empty extent does not break the scene completely)
566 QgsDebugError( u"Annotation layers in globe scenes are not supported yet!"_s );
567 setupQuadtree( QgsBox3D( -1e7, -1e7, -1e7, 1e7, 1e7, 1e7 ), -1, leafLevel );
568 return;
569 }
570
571 QgsBox3D rootBox3D( context.extent(), zMin, zMax );
572 // add small padding to avoid clipping of point features located at the edge of the bounding box
573 rootBox3D.grow( 1.0 );
574 setupQuadtree( rootBox3D, -1, leafLevel ); // negative root error means that the node does not contain anything
575}
576
577QgsChunkLoader *QgsAnnotationLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const
578{
579 return new QgsAnnotationLayerChunkLoader( this, node );
580}
581
582
584
585
586QgsAnnotationLayerChunkedEntity::QgsAnnotationLayerChunkedEntity(
587 Qgs3DMapSettings *map,
588 QgsAnnotationLayer *layer,
589 Qgis::AltitudeClamping clamping,
590 double zOffset,
591 bool showCallouts,
592 const QColor &calloutLineColor,
593 double calloutLineWidth,
594 const QgsTextFormat &textFormat,
595 double zMin,
596 double zMax
597)
598 : QgsChunkedEntity(
599 map,
600 -1, // max. allowed screen error (negative tau means that we need to go until leaves are reached)
601 new QgsAnnotationLayerChunkLoaderFactory( Qgs3DRenderContext::fromMapSettings( map ), layer, 3, clamping, zOffset, showCallouts, calloutLineColor, calloutLineWidth, textFormat, zMin, zMax ),
602 true
603 )
604{
605 mTransform = new Qt3DCore::QTransform;
606 if ( applyTerrainOffset() )
607 {
608 mTransform->setTranslation( QVector3D( 0.0f, 0.0f, static_cast<float>( map->terrainSettings()->elevationOffset() ) ) );
609 }
610 this->addComponent( mTransform );
611
612 connect( map, &Qgs3DMapSettings::terrainSettingsChanged, this, &QgsAnnotationLayerChunkedEntity::onTerrainElevationOffsetChanged );
613}
614
615QgsAnnotationLayerChunkedEntity::~QgsAnnotationLayerChunkedEntity()
616{
617 // cancel / wait for jobs
618 cancelActiveJobs();
619}
620
621// if the AltitudeClamping is `Absolute`, do not apply the offset
622bool QgsAnnotationLayerChunkedEntity::applyTerrainOffset() const
623{
624 if ( auto loaderFactory = static_cast<QgsAnnotationLayerChunkLoaderFactory *>( mChunkLoaderFactory ) )
625 {
626 return loaderFactory->mClamping != Qgis::AltitudeClamping::Absolute;
627 }
628 return true;
629}
630
631void QgsAnnotationLayerChunkedEntity::onTerrainElevationOffsetChanged()
632{
633 QgsDebugMsgLevel( u"QgsAnnotationLayerChunkedEntity::onTerrainElevationOffsetChanged"_s, 2 );
634 float newOffset = static_cast<float>( qobject_cast<Qgs3DMapSettings *>( sender() )->terrainSettings()->elevationOffset() );
635 if ( !applyTerrainOffset() )
636 {
637 newOffset = 0.0;
638 }
639 mTransform->setTranslation( QVector3D( 0.0f, 0.0f, newOffset ) );
640}
641
642
AltitudeClamping
Altitude clamping.
Definition qgis.h:4099
@ Relative
Elevation is relative to terrain height (final elevation = terrain elevation + feature elevation).
Definition qgis.h:4101
@ Terrain
Elevation is clamped to terrain (final elevation = terrain elevation).
Definition qgis.h:4102
@ Absolute
Elevation is taken directly from feature and is independent of terrain height (final elevation = feat...
Definition qgis.h:4100
@ Geocentric
Geocentric CRS.
Definition qgis.h:2412
@ Vertex
Clamp every vertex of feature.
Definition qgis.h:4113
@ SpatialBounds
Item is rendered inside fixed spatial bounds, and size will depend on map scale.
Definition qgis.h:2570
@ FixedSize
Item is rendered at a fixed size, regardless of map scale. Item's location is georeferenced to a spat...
Definition qgis.h:2571
@ RelativeToMapFrame
Items size and placement is relative to the map's frame, and the item will always be rendered in the ...
Definition qgis.h:2572
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2766
Definition of the world.
const QgsAbstractTerrainSettings * terrainSettings() const
Returns the terrain settings.
void terrainSettingsChanged()
Emitted when the terrain settings are changed.
Rendering context for preparation of 3D entities.
QgsCoordinateReferenceSystem crs() const
Returns the coordinate reference system used in the 3D scene.
QgsRectangle extent() const
Returns the 3D scene's 2D extent in the 3D scene's CRS.
double elevationOffset() const
Returns the elevation offset of the terrain (used to move the terrain up or down).
Abstract base class for annotation items which are drawn with QgsAnnotationLayers.
bool enabled() const
Returns true if the item is enabled and will be rendered in the layer.
Represents a map layer containing a set of georeferenced annotations, e.g.
An annotation item which renders text along a line geometry.
An annotation item which renders a marker symbol at a point location.
An annotation item which renders a text string at a point location.
An annotation item which renders paragraphs of text within a rectangle.
Geometry of the billboard rendering for points in 3D map view.
void setBillboardData(const QVector< QgsBillboardGeometry::BillboardAtlasData > &billboards, bool includePixelOffsets=false)
Set the position and texture data for the billboard.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
void setZMinimum(double z)
Sets the minimum z value.
Definition qgsbox3d.cpp:94
void setZMaximum(double z)
Sets the maximum z value.
Definition qgsbox3d.cpp:99
void grow(double delta)
Grows the box in place by the specified amount in all dimensions.
Definition qgsbox3d.cpp:302
Qgis::CrsType type() const
Returns the type of the CRS.
Handles coordinate transforms between two coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
QString what() const
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
static QgsFontTextureAtlas create(const QgsTextFormat &format, const QStringList &strings)
Creates the texture atlas for a set of strings, using the specified text format.
Encapsulates a font texture atlas.
int graphemeCount(const QString &string) const
Returns the number of graphemes to render for a given string.
bool isValid() const
Returns true if the atlas is valid.
QImage renderAtlasTexture() const
Renders the combined texture atlas, containing all required characters.
int totalWidth(const QString &string) const
Returns the total width (in pixels) required for a given string.
QRect textureRectForGrapheme(const QString &string, int graphemeIndex) const
Returns the packed rectangle for the texture for the matching grapheme.
QPoint pixelOffsetForGrapheme(const QString &string, int graphemeIndex) const
Returns the pixel offset at which the texture for the matching grapheme should be placed.
Does vector analysis using the GEOS library and handles import, export, and exception handling.
Definition qgsgeos.h:139
Line string geometry type, with support for z-dimension and m-values.
QString name
Definition qgsmaplayer.h:87
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
Material of the billboard rendering for points in 3D map view.
@ AtlasTextureWithPixelOffsets
Use a texture atlas, so each billboard has a different texture. Billboards have pixel-sized offsets f...
void setTexture2DFromImage(const QImage &image)
Set the texture2D of the billboard from an image.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
A rectangle specified with double values.
QgsPointXY center
Represents a document consisting of one or more QgsTextBlock objects.
QStringList toPlainText() const
Returns a list of plain text lines of text representing the document.
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.
static QgsTextureAtlas createFromImages(const QVector< QImage > &images, int maxSide=1000)
Creates a texture atlas for a set of images.
Encapsulates a texture atlas.
bool isValid() const
Returns true if the atlas is valid.
QRect rect(int index) const
Returns the packed rectangle for the texture with the specified index.
QImage renderAtlasTexture() const
Renders the combined texture atlas, containing all source images.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
Contains geos related utilities and functions.
Definition qgsgeos.h:76
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
Contains the billboard positions and texture information.
QPoint pixelOffset
Optional pixel offset for billboard.
QVector3D position
Vertex position for billboard placement.
QVector2D textureAtlasOffset
Texture atlas offset for associated billboard texture.
QVector2D textureAtlasSize
Texture atlas size for associated billboard texture.