45#include <Qt3DCore/QTransform>
46#include <Qt3DRender/QGeometryRenderer>
47#include <QtConcurrentRun>
49#include "moc_qgsannotationlayerchunkloader_p.cpp"
51using namespace Qt::StringLiterals;
56QgsAnnotationLayerChunkLoader::QgsAnnotationLayerChunkLoader(
const QgsAnnotationLayerChunkLoaderFactory *factory, QgsChunkNode *node )
57 : QgsChunkLoader( node )
59 , mRenderContext( factory->mRenderContext )
68 const QgsMarkerSymbol *markerSymbol =
nullptr;
78void QgsAnnotationLayerChunkLoader::start()
80 QgsChunkNode *node = chunk();
81 if ( node->level() < mFactory->mLeafLevel )
83 QTimer::singleShot( 0,
this, &QgsAnnotationLayerChunkLoader::finished );
88 mLayerName = mFactory->mLayer->
name();
98 mRenderContext.setExpressionContext( exprContext );
109 QgsDebugError( u
"Error transforming annotation layer extent to 3d map extent: %1"_s.arg( e.
what() ) );
113 const double zOffset = mFactory->mZOffset;
115 bool showCallouts = mFactory->mShowCallouts;
119 const QStringList itemsList = layer->queryIndex( layerExtent );
120 QSet< QString > itemIds( itemsList.begin(), itemsList.end() );
127 itemIds.unite( layer->mNonIndexedItems );
129 mItemsToRender.reserve( itemIds.size() );
130 std::transform( itemIds.begin(), itemIds.end(), std::back_inserter( mItemsToRender ), [layer](
const QString &
id ) -> std::unique_ptr< QgsAnnotationItem > {
131 return std::unique_ptr< QgsAnnotationItem >( layer->item( id )->clone() );
137 mFutureWatcher =
new QFutureWatcher<void>(
this );
138 connect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsChunkQueueJob::finished );
140 const QFuture<void> future = QtConcurrent::run( [
this, rect, layerToMapTransform, zOffset, altitudeClamping, showCallouts, textFormat] {
141 const QgsEventTracing::ScopedEvent e( u
"3D"_s, u
"Annotation layer chunk load"_s );
143 std::vector< Billboard > billboards;
144 billboards.reserve( mItemsToRender.size() );
145 QVector< QImage > textures;
146 textures.reserve( mItemsToRender.size() );
148 std::vector< TextBillboard > textBillboards;
149 textBillboards.reserve( mItemsToRender.size() );
150 QStringList textBillboardTexts;
151 textBillboardTexts.reserve( mItemsToRender.size() );
153 auto addTextBillboard = [layerToMapTransform, showCallouts, rect, zOffset, altitudeClamping,
this, &textBillboards, &textBillboardTexts](
const QgsPointXY &p,
const QString &annotationText,
const QgsTextFormat &annotationTextFormat ) {
154 QString text = annotationText;
155 if ( annotationTextFormat.allowHtmlFormatting() )
161 if ( !text.isEmpty() )
165 const QgsPointXY mapPoint = layerToMapTransform.transform( p );
166 if ( !rect.contains( mapPoint ) )
170 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() )
173 switch ( altitudeClamping )
182 z = terrainZ + zOffset;
186 TextBillboard billboard;
187 billboard.position = (
QgsVector3D( mapPoint.
x(), mapPoint.
y(), z ) - mChunkOrigin ).toVector3D();
188 billboard.text = text;
189 textBillboards.emplace_back( std::move( billboard ) );
190 textBillboardTexts.append( text );
194 mCalloutLines <<
QgsLineString( { mapPoint.
x(), mapPoint.
x() }, { mapPoint.
y(), mapPoint.
y() }, { terrainZ, z } );
197 mZMax = std::max( mZMax, showCallouts ? std::max( 0.0, z ) : z );
198 mZMin = std::min( mZMin, showCallouts ? std::min( 0.0, z ) : z );
207 for (
const std::unique_ptr< QgsAnnotationItem > &item : std::as_const( mItemsToRender ) )
219 if ( marker->symbol() )
224 const QgsPointXY mapPoint = layerToMapTransform.transform( p );
225 if ( !rect.contains( mapPoint ) )
229 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() )
232 switch ( altitudeClamping )
241 z = terrainZ + zOffset;
246 billboard.position = (
QgsVector3D( mapPoint.
x(), mapPoint.
y(), z ) - mChunkOrigin ).toVector3D();
247 billboard.textureId = -1;
249 for (
const Billboard &existingBillboard : billboards )
251 if ( existingBillboard.markerSymbol && marker->symbol()->rendersIdenticallyTo( existingBillboard.markerSymbol ) )
254 billboard.textureId = existingBillboard.textureId;
259 if ( billboard.textureId < 0 )
262 billboard.markerSymbol = marker->symbol();
263 billboard.textureId = textures.size();
264 textures.append( QgsPoint3DBillboardMaterial::renderSymbolToImage( marker->symbol(), mRenderContext ) );
266 billboards.emplace_back( std::move( billboard ) );
270 mCalloutLines <<
QgsLineString( { mapPoint.
x(), mapPoint.
x() }, { mapPoint.
y(), mapPoint.
y() }, { terrainZ, z } );
273 mZMax = std::max( mZMax, showCallouts ? std::max( 0.0, z ) : z );
274 mZMin = std::min( mZMin, showCallouts ? std::min( 0.0, z ) : z );
284 addTextBillboard( pointText->point(), pointText->text(), pointText->format() );
289 std::unique_ptr< QgsPoint > point(
geos.pointOnSurface() );
292 addTextBillboard( *point, lineText->text(), lineText->format() );
297 switch ( rectText->placementMode() )
302 addTextBillboard( rectText->bounds().center(), rectText->text(), rectText->format() );
312 mItemsToRender.clear();
314 if ( !textures.isEmpty() )
320 mBillboardPositions.reserve(
static_cast< int >( billboards.size() ) );
321 for ( Billboard &billboard : billboards )
323 const QRect textureRect = atlas.
rect( billboard.textureId );
325 geometry.
position = billboard.position;
326 geometry.
textureAtlasOffset = QVector2D(
static_cast< float >( textureRect.left() ) /
static_cast< float>( mBillboardAtlas.width() ), 1 - (
static_cast< float >( textureRect.bottom() ) /
static_cast< float>( mBillboardAtlas.height() ) ) );
327 geometry.
textureAtlasSize = QVector2D(
static_cast< float >( textureRect.width() ) /
static_cast< float>( mBillboardAtlas.width() ),
static_cast< float>( textureRect.height() ) /
static_cast< float>( mBillboardAtlas.height() ) );
328 geometry.
pixelOffset = QPoint( 0, textureRect.height() / 2 );
329 mBillboardPositions.append( geometry );
334 QgsDebugError( u
"Error encountered building texture atlas"_s );
335 mBillboardAtlas = QImage();
340 mBillboardAtlas = QImage();
341 mBillboardPositions.clear();
345 if ( !textBillboardTexts.isEmpty() )
351 mTextBillboardPositions.reserve(
static_cast< int >( textBillboards.size() ) );
352 for ( TextBillboard &billboard : textBillboards )
354 int graphemeIndex = 0;
355 const int graphemeCount = atlas.
graphemeCount( billboard.text );
357 const double xOffset = atlas.
totalWidth( billboard.text ) / 2.0;
358 for ( ; graphemeIndex < graphemeCount; ++graphemeIndex )
362 geometry.
position = billboard.position;
363 geometry.
textureAtlasOffset = QVector2D(
static_cast< float >( textureRect.left() ) /
static_cast< float>( mTextBillboardAtlas.width() ), 1 - (
static_cast< float >( textureRect.bottom() ) /
static_cast< float>( mTextBillboardAtlas.height() ) ) );
364 geometry.
textureAtlasSize = QVector2D(
static_cast< float >( textureRect.width() ) /
static_cast< float>( mTextBillboardAtlas.width() ),
static_cast< float>( textureRect.height() ) /
static_cast< float>( mTextBillboardAtlas.height() ) );
366 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() ) ) );
367 mTextBillboardPositions.append( geometry );
373 QgsDebugError( u
"Error encountered building font texture atlas"_s );
374 mTextBillboardAtlas = QImage();
379 mTextBillboardAtlas = QImage();
380 mTextBillboardPositions.clear();
385 mFutureWatcher->setFuture( future );
388QgsAnnotationLayerChunkLoader::~QgsAnnotationLayerChunkLoader()
390 if ( mFutureWatcher && !mFutureWatcher->isFinished() )
392 disconnect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsChunkQueueJob::finished );
393 mFutureWatcher->waitForFinished();
397void QgsAnnotationLayerChunkLoader::cancel()
402Qt3DCore::QEntity *QgsAnnotationLayerChunkLoader::createEntity( Qt3DCore::QEntity *parent )
404 if ( mNode->level() < mFactory->mLeafLevel )
406 Qt3DCore::QEntity *entity =
new Qt3DCore::QEntity( parent );
407 entity->setObjectName( mLayerName +
"_CONTAINER_" + mNode->tileId().text() );
411 if ( mBillboardPositions.empty() && mTextBillboardPositions.empty() )
416 mNode->updateParentBoundingBoxesRecursively();
420 Qt3DCore::QEntity *entity =
new Qt3DCore::QEntity( parent );
421 entity->setObjectName( mLayerName +
"_" + mNode->tileId().text() );
423 QgsGeoTransform *billboardTransform =
new QgsGeoTransform;
424 billboardTransform->setGeoTranslation( mChunkOrigin );
425 entity->addComponent( billboardTransform );
427 if ( !mBillboardPositions.empty() )
432 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer =
new Qt3DRender::QGeometryRenderer;
433 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
434 billboardGeometryRenderer->setGeometry( billboardGeometry );
435 billboardGeometryRenderer->setVertexCount( billboardGeometry->
count() );
441 Qt3DCore::QEntity *billboardEntity =
new Qt3DCore::QEntity;
442 billboardEntity->addComponent( billboardMaterial );
443 billboardEntity->addComponent( billboardGeometryRenderer );
444 billboardEntity->setParent( entity );
447 if ( !mTextBillboardPositions.empty() )
452 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer =
new Qt3DRender::QGeometryRenderer;
453 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
454 billboardGeometryRenderer->setGeometry( textBillboardGeometry );
455 billboardGeometryRenderer->setVertexCount( textBillboardGeometry->
count() );
460 Qt3DCore::QEntity *billboardEntity =
new Qt3DCore::QEntity;
461 billboardEntity->addComponent( billboardMaterial );
462 billboardEntity->addComponent( billboardGeometryRenderer );
463 billboardEntity->setParent( entity );
467 if ( mFactory->mShowCallouts )
469 QgsLineVertexData lineData;
470 lineData.withAdjacency =
true;
471 lineData.geocentricCoordinates =
false;
476 lineData.addLineString( line, 0,
false );
479 QgsLineMaterial *mat =
new QgsLineMaterial;
480 mat->setLineColor( mFactory->mCalloutLineColor );
481 mat->setLineWidth( mFactory->mCalloutLineWidth );
483 Qt3DCore::QEntity *calloutEntity =
new Qt3DCore::QEntity;
484 calloutEntity->setObjectName( parent->objectName() +
"_CALLOUTS" );
487 Qt3DRender::QGeometryRenderer *calloutRenderer =
new Qt3DRender::QGeometryRenderer;
488 calloutRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
489 calloutRenderer->setGeometry( lineData.createGeometry( calloutEntity ) );
490 calloutRenderer->setVertexCount( lineData.indexes.count() );
491 calloutRenderer->setPrimitiveRestartEnabled(
true );
492 calloutRenderer->setRestartIndexValue( 0 );
495 calloutEntity->addComponent( calloutRenderer );
496 calloutEntity->addComponent( mat );
498 calloutEntity->setParent( entity );
502 if ( mZMin != std::numeric_limits<float>::max() && mZMax != std::numeric_limits<float>::lowest() )
507 mNode->setExactBox3D( box );
508 mNode->updateParentBoundingBoxesRecursively();
517QgsAnnotationLayerChunkLoaderFactory::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 )
518 : mRenderContext( context )
520 , mLeafLevel( leafLevel )
521 , mClamping( clamping )
522 , mZOffset( zOffset )
523 , mShowCallouts( showCallouts )
524 , mCalloutLineColor( calloutLineColor )
525 , mCalloutLineWidth( calloutLineWidth )
526 , mTextFormat( textFormat )
532 QgsDebugError( u
"Annotation layers in globe scenes are not supported yet!"_s );
533 setupQuadtree( QgsBox3D( -1e7, -1e7, -1e7, 1e7, 1e7, 1e7 ), -1, leafLevel );
539 rootBox3D.
grow( 1.0 );
540 setupQuadtree( rootBox3D, -1, leafLevel );
543QgsChunkLoader *QgsAnnotationLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node )
const
545 return new QgsAnnotationLayerChunkLoader(
this, node );
553 : QgsChunkedEntity( map,
555 new QgsAnnotationLayerChunkLoaderFactory(
Qgs3DRenderContext::fromMapSettings( map ), layer, 3, clamping, zOffset, showCallouts, calloutLineColor, calloutLineWidth, textFormat, zMin, zMax ), true )
557 mTransform =
new Qt3DCore::QTransform;
558 if ( applyTerrainOffset() )
562 this->addComponent( mTransform );
567QgsAnnotationLayerChunkedEntity::~QgsAnnotationLayerChunkedEntity()
574bool QgsAnnotationLayerChunkedEntity::applyTerrainOffset()
const
576 if (
auto loaderFactory =
static_cast<QgsAnnotationLayerChunkLoaderFactory *
>( mChunkLoaderFactory ) )
583void QgsAnnotationLayerChunkedEntity::onTerrainElevationOffsetChanged()
585 QgsDebugMsgLevel( u
"QgsAnnotationLayerChunkedEntity::onTerrainElevationOffsetChanged"_s, 2 );
586 float newOffset =
static_cast<float>( qobject_cast<Qgs3DMapSettings *>( sender() )->terrainSettings()->elevationOffset() );
587 if ( !applyTerrainOffset() )
591 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.