43#include <Qt3DCore/QTransform>
44#include <QtConcurrent>
46#include "moc_qgsannotationlayerchunkloader_p.cpp"
51QgsAnnotationLayerChunkLoader::QgsAnnotationLayerChunkLoader(
const QgsAnnotationLayerChunkLoaderFactory *factory, QgsChunkNode *node )
52 : QgsChunkLoader( node )
54 , mRenderContext( factory->mRenderContext )
63 const QgsMarkerSymbol *markerSymbol =
nullptr;
73void QgsAnnotationLayerChunkLoader::start()
75 QgsChunkNode *node = chunk();
76 if ( node->level() < mFactory->mLeafLevel )
78 QTimer::singleShot( 0,
this, &QgsAnnotationLayerChunkLoader::finished );
83 mLayerName = mFactory->mLayer->
name();
93 mRenderContext.setExpressionContext( exprContext );
104 QgsDebugError( QStringLiteral(
"Error transforming annotation layer extent to 3d map extent: %1" ).arg( e.
what() ) );
108 const double zOffset = mFactory->mZOffset;
110 bool showCallouts = mFactory->mShowCallouts;
114 const QStringList itemsList = layer->queryIndex( layerExtent );
115 QSet< QString > itemIds( itemsList.begin(), itemsList.end() );
122 itemIds.unite( layer->mNonIndexedItems );
124 mItemsToRender.reserve( itemIds.size() );
125 std::transform( itemIds.begin(), itemIds.end(), std::back_inserter( mItemsToRender ), [layer](
const QString &
id ) -> std::unique_ptr< QgsAnnotationItem > {
126 return std::unique_ptr< QgsAnnotationItem >( layer->item( id )->clone() );
132 mFutureWatcher =
new QFutureWatcher<void>(
this );
133 connect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsChunkQueueJob::finished );
135 const QFuture<void> future = QtConcurrent::run( [
this, rect, layerToMapTransform, zOffset, altitudeClamping, showCallouts, textFormat] {
136 const QgsEventTracing::ScopedEvent e( QStringLiteral(
"3D" ), QStringLiteral(
"Annotation layer chunk load" ) );
138 std::vector< Billboard > billboards;
139 billboards.reserve( mItemsToRender.size() );
140 QVector< QImage > textures;
141 textures.reserve( mItemsToRender.size() );
143 std::vector< TextBillboard > textBillboards;
144 textBillboards.reserve( mItemsToRender.size() );
145 QStringList textBillboardTexts;
146 textBillboardTexts.reserve( mItemsToRender.size() );
148 auto addTextBillboard = [layerToMapTransform, showCallouts, rect, zOffset, altitudeClamping,
this, &textBillboards, &textBillboardTexts](
const QgsPointXY &p,
const QString &annotationText,
const QgsTextFormat &annotationTextFormat ) {
149 QString text = annotationText;
150 if ( annotationTextFormat.allowHtmlFormatting() )
156 if ( !text.isEmpty() )
160 const QgsPointXY mapPoint = layerToMapTransform.transform( p );
161 if ( !rect.contains( mapPoint ) )
165 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() )
168 switch ( altitudeClamping )
177 z = terrainZ + zOffset;
181 TextBillboard billboard;
182 billboard.position = (
QgsVector3D( mapPoint.
x(), mapPoint.
y(), z ) - mChunkOrigin ).toVector3D();
183 billboard.text = text;
184 textBillboards.emplace_back( std::move( billboard ) );
185 textBillboardTexts.append( text );
189 mCalloutLines <<
QgsLineString( { mapPoint.
x(), mapPoint.
x() }, { mapPoint.
y(), mapPoint.
y() }, { terrainZ, z } );
192 mZMax = std::max( mZMax, showCallouts ? std::max( 0.0, z ) : z );
193 mZMin = std::min( mZMin, showCallouts ? std::min( 0.0, z ) : z );
202 for (
const std::unique_ptr< QgsAnnotationItem > &item : std::as_const( mItemsToRender ) )
214 if ( marker->symbol() )
219 const QgsPointXY mapPoint = layerToMapTransform.transform( p );
220 if ( !rect.contains( mapPoint ) )
224 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() )
227 switch ( altitudeClamping )
236 z = terrainZ + zOffset;
241 billboard.position = (
QgsVector3D( mapPoint.
x(), mapPoint.
y(), z ) - mChunkOrigin ).toVector3D();
242 billboard.textureId = -1;
244 for (
const Billboard &existingBillboard : billboards )
246 if ( existingBillboard.markerSymbol && marker->symbol()->rendersIdenticallyTo( existingBillboard.markerSymbol ) )
249 billboard.textureId = existingBillboard.textureId;
254 if ( billboard.textureId < 0 )
257 billboard.markerSymbol = marker->symbol();
258 billboard.textureId = textures.size();
259 textures.append( QgsPoint3DBillboardMaterial::renderSymbolToImage( marker->symbol(), mRenderContext ) );
261 billboards.emplace_back( std::move( billboard ) );
265 mCalloutLines <<
QgsLineString( { mapPoint.
x(), mapPoint.
x() }, { mapPoint.
y(), mapPoint.
y() }, { terrainZ, z } );
268 mZMax = std::max( mZMax, showCallouts ? std::max( 0.0, z ) : z );
269 mZMin = std::min( mZMin, showCallouts ? std::min( 0.0, z ) : z );
279 addTextBillboard( pointText->point(), pointText->text(), pointText->format() );
284 std::unique_ptr< QgsPoint > point(
geos.pointOnSurface() );
287 addTextBillboard( *point, lineText->text(), lineText->format() );
292 switch ( rectText->placementMode() )
297 addTextBillboard( rectText->bounds().center(), rectText->text(), rectText->format() );
307 mItemsToRender.clear();
309 if ( !textures.isEmpty() )
315 mBillboardPositions.reserve(
static_cast< int >( billboards.size() ) );
316 for ( Billboard &billboard : billboards )
318 const QRect textureRect = atlas.
rect( billboard.textureId );
320 geometry.
position = billboard.position;
321 geometry.
textureAtlasOffset = QVector2D(
static_cast< float >( textureRect.left() ) /
static_cast< float>( mBillboardAtlas.width() ), 1 - (
static_cast< float >( textureRect.bottom() ) /
static_cast< float>( mBillboardAtlas.height() ) ) );
322 geometry.
textureAtlasSize = QVector2D(
static_cast< float >( textureRect.width() ) /
static_cast< float>( mBillboardAtlas.width() ),
static_cast< float>( textureRect.height() ) /
static_cast< float>( mBillboardAtlas.height() ) );
323 geometry.
pixelOffset = QPoint( 0, textureRect.height() / 2 );
324 mBillboardPositions.append( geometry );
329 QgsDebugError( QStringLiteral(
"Error encountered building texture atlas" ) );
330 mBillboardAtlas = QImage();
335 mBillboardAtlas = QImage();
336 mBillboardPositions.clear();
340 if ( !textBillboardTexts.isEmpty() )
346 mTextBillboardPositions.reserve(
static_cast< int >( textBillboards.size() ) );
347 for ( TextBillboard &billboard : textBillboards )
349 int graphemeIndex = 0;
350 const int graphemeCount = atlas.
graphemeCount( billboard.text );
352 const double xOffset = atlas.
totalWidth( billboard.text ) / 2.0;
353 for ( ; graphemeIndex < graphemeCount; ++graphemeIndex )
357 geometry.
position = billboard.position;
358 geometry.
textureAtlasOffset = QVector2D(
static_cast< float >( textureRect.left() ) /
static_cast< float>( mTextBillboardAtlas.width() ), 1 - (
static_cast< float >( textureRect.bottom() ) /
static_cast< float>( mTextBillboardAtlas.height() ) ) );
359 geometry.
textureAtlasSize = QVector2D(
static_cast< float >( textureRect.width() ) /
static_cast< float>( mTextBillboardAtlas.width() ),
static_cast< float>( textureRect.height() ) /
static_cast< float>( mTextBillboardAtlas.height() ) );
361 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() ) ) );
362 mTextBillboardPositions.append( geometry );
368 QgsDebugError( QStringLiteral(
"Error encountered building font texture atlas" ) );
369 mTextBillboardAtlas = QImage();
374 mTextBillboardAtlas = QImage();
375 mTextBillboardPositions.clear();
380 mFutureWatcher->setFuture( future );
383QgsAnnotationLayerChunkLoader::~QgsAnnotationLayerChunkLoader()
385 if ( mFutureWatcher && !mFutureWatcher->isFinished() )
387 disconnect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsChunkQueueJob::finished );
388 mFutureWatcher->waitForFinished();
392void QgsAnnotationLayerChunkLoader::cancel()
397Qt3DCore::QEntity *QgsAnnotationLayerChunkLoader::createEntity( Qt3DCore::QEntity *parent )
399 if ( mNode->level() < mFactory->mLeafLevel )
401 Qt3DCore::QEntity *entity =
new Qt3DCore::QEntity( parent );
402 entity->setObjectName( mLayerName +
"_CONTAINER_" + mNode->tileId().text() );
406 if ( mBillboardPositions.empty() && mTextBillboardPositions.empty() )
411 mNode->updateParentBoundingBoxesRecursively();
415 Qt3DCore::QEntity *entity =
new Qt3DCore::QEntity( parent );
416 entity->setObjectName( mLayerName +
"_" + mNode->tileId().text() );
418 QgsGeoTransform *billboardTransform =
new QgsGeoTransform;
419 billboardTransform->setGeoTranslation( mChunkOrigin );
420 entity->addComponent( billboardTransform );
422 if ( !mBillboardPositions.empty() )
427 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer =
new Qt3DRender::QGeometryRenderer;
428 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
429 billboardGeometryRenderer->setGeometry( billboardGeometry );
430 billboardGeometryRenderer->setVertexCount( billboardGeometry->
count() );
436 Qt3DCore::QEntity *billboardEntity =
new Qt3DCore::QEntity;
437 billboardEntity->addComponent( billboardMaterial );
438 billboardEntity->addComponent( billboardGeometryRenderer );
439 billboardEntity->setParent( entity );
442 if ( !mTextBillboardPositions.empty() )
447 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer =
new Qt3DRender::QGeometryRenderer;
448 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
449 billboardGeometryRenderer->setGeometry( textBillboardGeometry );
450 billboardGeometryRenderer->setVertexCount( textBillboardGeometry->
count() );
455 Qt3DCore::QEntity *billboardEntity =
new Qt3DCore::QEntity;
456 billboardEntity->addComponent( billboardMaterial );
457 billboardEntity->addComponent( billboardGeometryRenderer );
458 billboardEntity->setParent( entity );
462 if ( mFactory->mShowCallouts )
464 QgsLineVertexData lineData;
465 lineData.withAdjacency =
true;
466 lineData.geocentricCoordinates =
false;
471 lineData.addLineString( line, 0,
false );
474 QgsLineMaterial *mat =
new QgsLineMaterial;
475 mat->setLineColor( mFactory->mCalloutLineColor );
476 mat->setLineWidth( mFactory->mCalloutLineWidth );
478 Qt3DCore::QEntity *calloutEntity =
new Qt3DCore::QEntity;
479 calloutEntity->setObjectName( parent->objectName() +
"_CALLOUTS" );
482 Qt3DRender::QGeometryRenderer *calloutRenderer =
new Qt3DRender::QGeometryRenderer;
483 calloutRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
484 calloutRenderer->setGeometry( lineData.createGeometry( calloutEntity ) );
485 calloutRenderer->setVertexCount( lineData.indexes.count() );
486 calloutRenderer->setPrimitiveRestartEnabled(
true );
487 calloutRenderer->setRestartIndexValue( 0 );
490 calloutEntity->addComponent( calloutRenderer );
491 calloutEntity->addComponent( mat );
493 calloutEntity->setParent( entity );
497 if ( mZMin != std::numeric_limits<float>::max() && mZMax != std::numeric_limits<float>::lowest() )
502 mNode->setExactBox3D( box );
503 mNode->updateParentBoundingBoxesRecursively();
512QgsAnnotationLayerChunkLoaderFactory::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 )
513 : mRenderContext( context )
515 , mLeafLevel( leafLevel )
516 , mClamping( clamping )
517 , mZOffset( zOffset )
518 , mShowCallouts( showCallouts )
519 , mCalloutLineColor( calloutLineColor )
520 , mCalloutLineWidth( calloutLineWidth )
521 , mTextFormat( textFormat )
527 QgsDebugError( QStringLiteral(
"Annotation layers in globe scenes are not supported yet!" ) );
528 setupQuadtree( QgsBox3D( -1e7, -1e7, -1e7, 1e7, 1e7, 1e7 ), -1, leafLevel );
534 rootBox3D.
grow( 1.0 );
535 setupQuadtree( rootBox3D, -1, leafLevel );
538QgsChunkLoader *QgsAnnotationLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node )
const
540 return new QgsAnnotationLayerChunkLoader(
this, node );
548 : QgsChunkedEntity( map,
550 new QgsAnnotationLayerChunkLoaderFactory(
Qgs3DRenderContext::fromMapSettings( map ), layer, 3, clamping, zOffset, showCallouts, calloutLineColor, calloutLineWidth, textFormat, zMin, zMax ), true )
552 mTransform =
new Qt3DCore::QTransform;
553 if ( applyTerrainOffset() )
557 this->addComponent( mTransform );
562QgsAnnotationLayerChunkedEntity::~QgsAnnotationLayerChunkedEntity()
569bool QgsAnnotationLayerChunkedEntity::applyTerrainOffset()
const
571 if (
auto loaderFactory =
static_cast<QgsAnnotationLayerChunkLoaderFactory *
>( mChunkLoaderFactory ) )
578void QgsAnnotationLayerChunkedEntity::onTerrainElevationOffsetChanged()
580 QgsDebugMsgLevel( QStringLiteral(
"QgsAnnotationLayerChunkedEntity::onTerrainElevationOffsetChanged" ), 2 );
581 float newOffset =
static_cast<float>( qobject_cast<Qgs3DMapSettings *>( sender() )->terrainSettings()->elevationOffset() );
582 if ( !applyTerrainOffset() )
586 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.