44#include <Qt3DCore/QTransform>
45#include <QtConcurrent>
47#include "moc_qgsannotationlayerchunkloader_p.cpp"
49using namespace Qt::StringLiterals;
54QgsAnnotationLayerChunkLoader::QgsAnnotationLayerChunkLoader(
const QgsAnnotationLayerChunkLoaderFactory *factory, QgsChunkNode *node )
55 : QgsChunkLoader( node )
57 , mRenderContext( factory->mRenderContext )
66 const QgsMarkerSymbol *markerSymbol =
nullptr;
76void QgsAnnotationLayerChunkLoader::start()
78 QgsChunkNode *node = chunk();
79 if ( node->level() < mFactory->mLeafLevel )
81 QTimer::singleShot( 0,
this, &QgsAnnotationLayerChunkLoader::finished );
86 mLayerName = mFactory->mLayer->
name();
96 mRenderContext.setExpressionContext( exprContext );
107 QgsDebugError( u
"Error transforming annotation layer extent to 3d map extent: %1"_s.arg( e.
what() ) );
111 const double zOffset = mFactory->mZOffset;
113 bool showCallouts = mFactory->mShowCallouts;
117 const QStringList itemsList = layer->queryIndex( layerExtent );
118 QSet< QString > itemIds( itemsList.begin(), itemsList.end() );
125 itemIds.unite( layer->mNonIndexedItems );
127 mItemsToRender.reserve( itemIds.size() );
128 std::transform( itemIds.begin(), itemIds.end(), std::back_inserter( mItemsToRender ), [layer](
const QString &
id ) -> std::unique_ptr< QgsAnnotationItem > {
129 return std::unique_ptr< QgsAnnotationItem >( layer->item( id )->clone() );
135 mFutureWatcher =
new QFutureWatcher<void>(
this );
136 connect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsChunkQueueJob::finished );
138 const QFuture<void> future = QtConcurrent::run( [
this, rect, layerToMapTransform, zOffset, altitudeClamping, showCallouts, textFormat] {
139 const QgsEventTracing::ScopedEvent e( u
"3D"_s, u
"Annotation layer chunk load"_s );
141 std::vector< Billboard > billboards;
142 billboards.reserve( mItemsToRender.size() );
143 QVector< QImage > textures;
144 textures.reserve( mItemsToRender.size() );
146 std::vector< TextBillboard > textBillboards;
147 textBillboards.reserve( mItemsToRender.size() );
148 QStringList textBillboardTexts;
149 textBillboardTexts.reserve( mItemsToRender.size() );
151 auto addTextBillboard = [layerToMapTransform, showCallouts, rect, zOffset, altitudeClamping,
this, &textBillboards, &textBillboardTexts](
const QgsPointXY &p,
const QString &annotationText,
const QgsTextFormat &annotationTextFormat ) {
152 QString text = annotationText;
153 if ( annotationTextFormat.allowHtmlFormatting() )
159 if ( !text.isEmpty() )
163 const QgsPointXY mapPoint = layerToMapTransform.transform( p );
164 if ( !rect.contains( mapPoint ) )
168 const float terrainZ = ( altitudeClamping ==
Qgis::AltitudeClamping::Absolute && !showCallouts ) ? 0 : mRenderContext.terrainRenderingEnabled() && mRenderContext.terrainGenerator() ? static_cast<float>( mRenderContext.terrainGenerator()->heightAt( mapPoint.x(), mapPoint.y(), mRenderContext ) * mRenderContext.terrainSettings()->verticalScale() )
171 switch ( altitudeClamping )
180 z = terrainZ + zOffset;
184 TextBillboard billboard;
185 billboard.position = (
QgsVector3D( mapPoint.
x(), mapPoint.
y(), z ) - mChunkOrigin ).toVector3D();
186 billboard.text = text;
187 textBillboards.emplace_back( std::move( billboard ) );
188 textBillboardTexts.append( text );
192 mCalloutLines <<
QgsLineString( { mapPoint.
x(), mapPoint.
x() }, { mapPoint.
y(), mapPoint.
y() }, { terrainZ, z } );
195 mZMax = std::max( mZMax, showCallouts ? std::max( 0.0, z ) : z );
196 mZMin = std::min( mZMin, showCallouts ? std::min( 0.0, z ) : z );
205 for (
const std::unique_ptr< QgsAnnotationItem > &item : std::as_const( mItemsToRender ) )
217 if ( marker->symbol() )
222 const QgsPointXY mapPoint = layerToMapTransform.transform( p );
223 if ( !rect.contains( mapPoint ) )
227 const float terrainZ = ( altitudeClamping ==
Qgis::AltitudeClamping::Absolute && !showCallouts ) ? 0 : mRenderContext.terrainRenderingEnabled() && mRenderContext.terrainGenerator() ? static_cast<float>( mRenderContext.terrainGenerator()->heightAt( mapPoint.x(), mapPoint.y(), mRenderContext ) * mRenderContext.terrainSettings()->verticalScale() )
230 switch ( altitudeClamping )
239 z = terrainZ + zOffset;
244 billboard.position = (
QgsVector3D( mapPoint.
x(), mapPoint.
y(), z ) - mChunkOrigin ).toVector3D();
245 billboard.textureId = -1;
247 for (
const Billboard &existingBillboard : billboards )
249 if ( existingBillboard.markerSymbol && marker->symbol()->rendersIdenticallyTo( existingBillboard.markerSymbol ) )
252 billboard.textureId = existingBillboard.textureId;
257 if ( billboard.textureId < 0 )
260 billboard.markerSymbol = marker->symbol();
261 billboard.textureId = textures.size();
262 textures.append( QgsPoint3DBillboardMaterial::renderSymbolToImage( marker->symbol(), mRenderContext ) );
264 billboards.emplace_back( std::move( billboard ) );
268 mCalloutLines <<
QgsLineString( { mapPoint.
x(), mapPoint.
x() }, { mapPoint.
y(), mapPoint.
y() }, { terrainZ, z } );
271 mZMax = std::max( mZMax, showCallouts ? std::max( 0.0, z ) : z );
272 mZMin = std::min( mZMin, showCallouts ? std::min( 0.0, z ) : z );
282 addTextBillboard( pointText->point(), pointText->text(), pointText->format() );
287 std::unique_ptr< QgsPoint > point(
geos.pointOnSurface() );
290 addTextBillboard( *point, lineText->text(), lineText->format() );
295 switch ( rectText->placementMode() )
300 addTextBillboard( rectText->bounds().center(), rectText->text(), rectText->format() );
310 mItemsToRender.clear();
312 if ( !textures.isEmpty() )
318 mBillboardPositions.reserve(
static_cast< int >( billboards.size() ) );
319 for ( Billboard &billboard : billboards )
321 const QRect textureRect = atlas.
rect( billboard.textureId );
323 geometry.
position = billboard.position;
324 geometry.
textureAtlasOffset = QVector2D(
static_cast< float >( textureRect.left() ) /
static_cast< float>( mBillboardAtlas.width() ), 1 - (
static_cast< float >( textureRect.bottom() ) /
static_cast< float>( mBillboardAtlas.height() ) ) );
325 geometry.
textureAtlasSize = QVector2D(
static_cast< float >( textureRect.width() ) /
static_cast< float>( mBillboardAtlas.width() ),
static_cast< float>( textureRect.height() ) /
static_cast< float>( mBillboardAtlas.height() ) );
326 geometry.
pixelOffset = QPoint( 0, textureRect.height() / 2 );
327 mBillboardPositions.append( geometry );
332 QgsDebugError( u
"Error encountered building texture atlas"_s );
333 mBillboardAtlas = QImage();
338 mBillboardAtlas = QImage();
339 mBillboardPositions.clear();
343 if ( !textBillboardTexts.isEmpty() )
349 mTextBillboardPositions.reserve(
static_cast< int >( textBillboards.size() ) );
350 for ( TextBillboard &billboard : textBillboards )
352 int graphemeIndex = 0;
353 const int graphemeCount = atlas.
graphemeCount( billboard.text );
355 const double xOffset = atlas.
totalWidth( billboard.text ) / 2.0;
356 for ( ; graphemeIndex < graphemeCount; ++graphemeIndex )
360 geometry.
position = billboard.position;
361 geometry.
textureAtlasOffset = QVector2D(
static_cast< float >( textureRect.left() ) /
static_cast< float>( mTextBillboardAtlas.width() ), 1 - (
static_cast< float >( textureRect.bottom() ) /
static_cast< float>( mTextBillboardAtlas.height() ) ) );
362 geometry.
textureAtlasSize = QVector2D(
static_cast< float >( textureRect.width() ) /
static_cast< float>( mTextBillboardAtlas.width() ),
static_cast< float>( textureRect.height() ) /
static_cast< float>( mTextBillboardAtlas.height() ) );
364 geometry.
pixelOffset = QPoint(
static_cast< int >( std::round( -xOffset + pixelOffset.x() + 0.5 * textureRect.width() ) ),
static_cast< int >( std::round( pixelOffset.y() + 0.5 * textureRect.height() ) ) );
365 mTextBillboardPositions.append( geometry );
371 QgsDebugError( u
"Error encountered building font texture atlas"_s );
372 mTextBillboardAtlas = QImage();
377 mTextBillboardAtlas = QImage();
378 mTextBillboardPositions.clear();
383 mFutureWatcher->setFuture( future );
386QgsAnnotationLayerChunkLoader::~QgsAnnotationLayerChunkLoader()
388 if ( mFutureWatcher && !mFutureWatcher->isFinished() )
390 disconnect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsChunkQueueJob::finished );
391 mFutureWatcher->waitForFinished();
395void QgsAnnotationLayerChunkLoader::cancel()
400Qt3DCore::QEntity *QgsAnnotationLayerChunkLoader::createEntity( Qt3DCore::QEntity *parent )
402 if ( mNode->level() < mFactory->mLeafLevel )
404 Qt3DCore::QEntity *entity =
new Qt3DCore::QEntity( parent );
405 entity->setObjectName( mLayerName +
"_CONTAINER_" + mNode->tileId().text() );
409 if ( mBillboardPositions.empty() && mTextBillboardPositions.empty() )
414 mNode->updateParentBoundingBoxesRecursively();
418 Qt3DCore::QEntity *entity =
new Qt3DCore::QEntity( parent );
419 entity->setObjectName( mLayerName +
"_" + mNode->tileId().text() );
421 QgsGeoTransform *billboardTransform =
new QgsGeoTransform;
422 billboardTransform->setGeoTranslation( mChunkOrigin );
423 entity->addComponent( billboardTransform );
425 if ( !mBillboardPositions.empty() )
430 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer =
new Qt3DRender::QGeometryRenderer;
431 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
432 billboardGeometryRenderer->setGeometry( billboardGeometry );
433 billboardGeometryRenderer->setVertexCount( billboardGeometry->
count() );
439 Qt3DCore::QEntity *billboardEntity =
new Qt3DCore::QEntity;
440 billboardEntity->addComponent( billboardMaterial );
441 billboardEntity->addComponent( billboardGeometryRenderer );
442 billboardEntity->setParent( entity );
445 if ( !mTextBillboardPositions.empty() )
450 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer =
new Qt3DRender::QGeometryRenderer;
451 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
452 billboardGeometryRenderer->setGeometry( textBillboardGeometry );
453 billboardGeometryRenderer->setVertexCount( textBillboardGeometry->
count() );
458 Qt3DCore::QEntity *billboardEntity =
new Qt3DCore::QEntity;
459 billboardEntity->addComponent( billboardMaterial );
460 billboardEntity->addComponent( billboardGeometryRenderer );
461 billboardEntity->setParent( entity );
465 if ( mFactory->mShowCallouts )
467 QgsLineVertexData lineData;
468 lineData.withAdjacency =
true;
469 lineData.geocentricCoordinates =
false;
474 lineData.addLineString( line, 0,
false );
477 QgsLineMaterial *mat =
new QgsLineMaterial;
478 mat->setLineColor( mFactory->mCalloutLineColor );
479 mat->setLineWidth( mFactory->mCalloutLineWidth );
481 Qt3DCore::QEntity *calloutEntity =
new Qt3DCore::QEntity;
482 calloutEntity->setObjectName( parent->objectName() +
"_CALLOUTS" );
485 Qt3DRender::QGeometryRenderer *calloutRenderer =
new Qt3DRender::QGeometryRenderer;
486 calloutRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
487 calloutRenderer->setGeometry( lineData.createGeometry( calloutEntity ) );
488 calloutRenderer->setVertexCount( lineData.indexes.count() );
489 calloutRenderer->setPrimitiveRestartEnabled(
true );
490 calloutRenderer->setRestartIndexValue( 0 );
493 calloutEntity->addComponent( calloutRenderer );
494 calloutEntity->addComponent( mat );
496 calloutEntity->setParent( entity );
500 if ( mZMin != std::numeric_limits<float>::max() && mZMax != std::numeric_limits<float>::lowest() )
505 mNode->setExactBox3D( box );
506 mNode->updateParentBoundingBoxesRecursively();
515QgsAnnotationLayerChunkLoaderFactory::QgsAnnotationLayerChunkLoaderFactory(
const Qgs3DRenderContext &context,
QgsAnnotationLayer *layer,
int leafLevel,
Qgis::AltitudeClamping clamping,
double zOffset,
bool showCallouts,
const QColor &calloutLineColor,
double calloutLineWidth,
const QgsTextFormat &textFormat,
double zMin,
double zMax )
516 : mRenderContext( context )
518 , mLeafLevel( leafLevel )
519 , mClamping( clamping )
520 , mZOffset( zOffset )
521 , mShowCallouts( showCallouts )
522 , mCalloutLineColor( calloutLineColor )
523 , mCalloutLineWidth( calloutLineWidth )
524 , mTextFormat( textFormat )
530 QgsDebugError( u
"Annotation layers in globe scenes are not supported yet!"_s );
531 setupQuadtree( QgsBox3D( -1e7, -1e7, -1e7, 1e7, 1e7, 1e7 ), -1, leafLevel );
537 rootBox3D.
grow( 1.0 );
538 setupQuadtree( rootBox3D, -1, leafLevel );
541QgsChunkLoader *QgsAnnotationLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node )
const
543 return new QgsAnnotationLayerChunkLoader(
this, node );
551 : QgsChunkedEntity( map,
553 new QgsAnnotationLayerChunkLoaderFactory(
Qgs3DRenderContext::fromMapSettings( map ), layer, 3, clamping, zOffset, showCallouts, calloutLineColor, calloutLineWidth, textFormat, zMin, zMax ), true )
555 mTransform =
new Qt3DCore::QTransform;
556 if ( applyTerrainOffset() )
560 this->addComponent( mTransform );
565QgsAnnotationLayerChunkedEntity::~QgsAnnotationLayerChunkedEntity()
572bool QgsAnnotationLayerChunkedEntity::applyTerrainOffset()
const
574 if (
auto loaderFactory =
static_cast<QgsAnnotationLayerChunkLoaderFactory *
>( mChunkLoaderFactory ) )
581void QgsAnnotationLayerChunkedEntity::onTerrainElevationOffsetChanged()
583 QgsDebugMsgLevel( u
"QgsAnnotationLayerChunkedEntity::onTerrainElevationOffsetChanged"_s, 2 );
584 float newOffset =
static_cast<float>( qobject_cast<Qgs3DMapSettings *>( sender() )->terrainSettings()->elevationOffset() );
585 if ( !applyTerrainOffset() )
589 mTransform->setTranslation( QVector3D( 0.0f, 0.0f, newOffset ) );
AltitudeClamping
Altitude clamping.
@ Relative
Elevation is relative to terrain height (final elevation = terrain elevation + feature elevation).
@ Terrain
Elevation is clamped to terrain (final elevation = terrain elevation).
@ Absolute
Elevation is taken directly from feature and is independent of terrain height (final elevation = feat...
@ Geocentric
Geocentric CRS.
@ Vertex
Clamp every vertex of feature.
@ SpatialBounds
Item is rendered inside fixed spatial bounds, and size will depend on map scale.
@ FixedSize
Item is rendered at a fixed size, regardless of map scale. Item's location is georeferenced to a spat...
@ RelativeToMapFrame
Items size and placement is relative to the map's frame, and the item will always be rendered in the ...
@ Reverse
Reverse/inverse transform (from destination to source).
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.
void setZMinimum(double z)
Sets the minimum z value.
void setZMaximum(double z)
Sets the maximum z value.
void grow(double delta)
Grows the box in place by the specified amount in all dimensions.
Qgis::CrsType type() const
Returns the type of the CRS.
Custom exception class for Coordinate Reference System related exceptions.
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.
Line string geometry type, with support for z-dimension and m-values.
QgsCoordinateReferenceSystem crs
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.
A rectangle specified with double values.
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...
Contains geos related utilities and functions.
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
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.