45#include <Qt3DCore/QTransform>
46#include <QtConcurrentRun>
48#include "moc_qgsannotationlayerchunkloader_p.cpp"
50using namespace Qt::StringLiterals;
55QgsAnnotationLayerChunkLoader::QgsAnnotationLayerChunkLoader(
const QgsAnnotationLayerChunkLoaderFactory *factory, QgsChunkNode *node )
56 : QgsChunkLoader( node )
58 , mRenderContext( factory->mRenderContext )
67 const QgsMarkerSymbol *markerSymbol =
nullptr;
77void QgsAnnotationLayerChunkLoader::start()
79 QgsChunkNode *node = chunk();
80 if ( node->level() < mFactory->mLeafLevel )
82 QTimer::singleShot( 0,
this, &QgsAnnotationLayerChunkLoader::finished );
87 mLayerName = mFactory->mLayer->
name();
97 mRenderContext.setExpressionContext( exprContext );
108 QgsDebugError( u
"Error transforming annotation layer extent to 3d map extent: %1"_s.arg( e.
what() ) );
112 const double zOffset = mFactory->mZOffset;
114 bool showCallouts = mFactory->mShowCallouts;
118 const QStringList itemsList = layer->queryIndex( layerExtent );
119 QSet< QString > itemIds( itemsList.begin(), itemsList.end() );
126 itemIds.unite( layer->mNonIndexedItems );
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() );
136 mFutureWatcher =
new QFutureWatcher<void>(
this );
137 connect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsChunkQueueJob::finished );
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 );
142 std::vector< Billboard > billboards;
143 billboards.reserve( mItemsToRender.size() );
144 QVector< QImage > textures;
145 textures.reserve( mItemsToRender.size() );
147 std::vector< TextBillboard > textBillboards;
148 textBillboards.reserve( mItemsToRender.size() );
149 QStringList textBillboardTexts;
150 textBillboardTexts.reserve( mItemsToRender.size() );
152 auto addTextBillboard = [layerToMapTransform, showCallouts, rect, zOffset, altitudeClamping,
this, &textBillboards, &textBillboardTexts](
const QgsPointXY &p,
const QString &annotationText,
const QgsTextFormat &annotationTextFormat ) {
153 QString text = annotationText;
154 if ( annotationTextFormat.allowHtmlFormatting() )
160 if ( !text.isEmpty() )
164 const QgsPointXY mapPoint = layerToMapTransform.transform( p );
165 if ( !rect.contains( mapPoint ) )
169 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() )
172 switch ( altitudeClamping )
181 z = terrainZ + zOffset;
185 TextBillboard billboard;
186 billboard.position = (
QgsVector3D( mapPoint.
x(), mapPoint.
y(), z ) - mChunkOrigin ).toVector3D();
187 billboard.text = text;
188 textBillboards.emplace_back( std::move( billboard ) );
189 textBillboardTexts.append( text );
193 mCalloutLines <<
QgsLineString( { mapPoint.
x(), mapPoint.
x() }, { mapPoint.
y(), mapPoint.
y() }, { terrainZ, z } );
196 mZMax = std::max( mZMax, showCallouts ? std::max( 0.0, z ) : z );
197 mZMin = std::min( mZMin, showCallouts ? std::min( 0.0, z ) : z );
206 for (
const std::unique_ptr< QgsAnnotationItem > &item : std::as_const( mItemsToRender ) )
218 if ( marker->symbol() )
223 const QgsPointXY mapPoint = layerToMapTransform.transform( p );
224 if ( !rect.contains( mapPoint ) )
228 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() )
231 switch ( altitudeClamping )
240 z = terrainZ + zOffset;
245 billboard.position = (
QgsVector3D( mapPoint.
x(), mapPoint.
y(), z ) - mChunkOrigin ).toVector3D();
246 billboard.textureId = -1;
248 for (
const Billboard &existingBillboard : billboards )
250 if ( existingBillboard.markerSymbol && marker->symbol()->rendersIdenticallyTo( existingBillboard.markerSymbol ) )
253 billboard.textureId = existingBillboard.textureId;
258 if ( billboard.textureId < 0 )
261 billboard.markerSymbol = marker->symbol();
262 billboard.textureId = textures.size();
263 textures.append( QgsPoint3DBillboardMaterial::renderSymbolToImage( marker->symbol(), mRenderContext ) );
265 billboards.emplace_back( std::move( billboard ) );
269 mCalloutLines <<
QgsLineString( { mapPoint.
x(), mapPoint.
x() }, { mapPoint.
y(), mapPoint.
y() }, { terrainZ, z } );
272 mZMax = std::max( mZMax, showCallouts ? std::max( 0.0, z ) : z );
273 mZMin = std::min( mZMin, showCallouts ? std::min( 0.0, z ) : z );
283 addTextBillboard( pointText->point(), pointText->text(), pointText->format() );
288 std::unique_ptr< QgsPoint > point(
geos.pointOnSurface() );
291 addTextBillboard( *point, lineText->text(), lineText->format() );
296 switch ( rectText->placementMode() )
301 addTextBillboard( rectText->bounds().center(), rectText->text(), rectText->format() );
311 mItemsToRender.clear();
313 if ( !textures.isEmpty() )
319 mBillboardPositions.reserve(
static_cast< int >( billboards.size() ) );
320 for ( Billboard &billboard : billboards )
322 const QRect textureRect = atlas.
rect( billboard.textureId );
324 geometry.
position = billboard.position;
325 geometry.
textureAtlasOffset = QVector2D(
static_cast< float >( textureRect.left() ) /
static_cast< float>( mBillboardAtlas.width() ), 1 - (
static_cast< float >( textureRect.bottom() ) /
static_cast< float>( mBillboardAtlas.height() ) ) );
326 geometry.
textureAtlasSize = QVector2D(
static_cast< float >( textureRect.width() ) /
static_cast< float>( mBillboardAtlas.width() ),
static_cast< float>( textureRect.height() ) /
static_cast< float>( mBillboardAtlas.height() ) );
327 geometry.
pixelOffset = QPoint( 0, textureRect.height() / 2 );
328 mBillboardPositions.append( geometry );
333 QgsDebugError( u
"Error encountered building texture atlas"_s );
334 mBillboardAtlas = QImage();
339 mBillboardAtlas = QImage();
340 mBillboardPositions.clear();
344 if ( !textBillboardTexts.isEmpty() )
350 mTextBillboardPositions.reserve(
static_cast< int >( textBillboards.size() ) );
351 for ( TextBillboard &billboard : textBillboards )
353 int graphemeIndex = 0;
354 const int graphemeCount = atlas.
graphemeCount( billboard.text );
356 const double xOffset = atlas.
totalWidth( billboard.text ) / 2.0;
357 for ( ; graphemeIndex < graphemeCount; ++graphemeIndex )
361 geometry.
position = billboard.position;
362 geometry.
textureAtlasOffset = QVector2D(
static_cast< float >( textureRect.left() ) /
static_cast< float>( mTextBillboardAtlas.width() ), 1 - (
static_cast< float >( textureRect.bottom() ) /
static_cast< float>( mTextBillboardAtlas.height() ) ) );
363 geometry.
textureAtlasSize = QVector2D(
static_cast< float >( textureRect.width() ) /
static_cast< float>( mTextBillboardAtlas.width() ),
static_cast< float>( textureRect.height() ) /
static_cast< float>( mTextBillboardAtlas.height() ) );
365 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() ) ) );
366 mTextBillboardPositions.append( geometry );
372 QgsDebugError( u
"Error encountered building font texture atlas"_s );
373 mTextBillboardAtlas = QImage();
378 mTextBillboardAtlas = QImage();
379 mTextBillboardPositions.clear();
384 mFutureWatcher->setFuture( future );
387QgsAnnotationLayerChunkLoader::~QgsAnnotationLayerChunkLoader()
389 if ( mFutureWatcher && !mFutureWatcher->isFinished() )
391 disconnect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsChunkQueueJob::finished );
392 mFutureWatcher->waitForFinished();
396void QgsAnnotationLayerChunkLoader::cancel()
401Qt3DCore::QEntity *QgsAnnotationLayerChunkLoader::createEntity( Qt3DCore::QEntity *parent )
403 if ( mNode->level() < mFactory->mLeafLevel )
405 Qt3DCore::QEntity *entity =
new Qt3DCore::QEntity( parent );
406 entity->setObjectName( mLayerName +
"_CONTAINER_" + mNode->tileId().text() );
410 if ( mBillboardPositions.empty() && mTextBillboardPositions.empty() )
415 mNode->updateParentBoundingBoxesRecursively();
419 Qt3DCore::QEntity *entity =
new Qt3DCore::QEntity( parent );
420 entity->setObjectName( mLayerName +
"_" + mNode->tileId().text() );
422 QgsGeoTransform *billboardTransform =
new QgsGeoTransform;
423 billboardTransform->setGeoTranslation( mChunkOrigin );
424 entity->addComponent( billboardTransform );
426 if ( !mBillboardPositions.empty() )
431 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer =
new Qt3DRender::QGeometryRenderer;
432 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
433 billboardGeometryRenderer->setGeometry( billboardGeometry );
434 billboardGeometryRenderer->setVertexCount( billboardGeometry->
count() );
440 Qt3DCore::QEntity *billboardEntity =
new Qt3DCore::QEntity;
441 billboardEntity->addComponent( billboardMaterial );
442 billboardEntity->addComponent( billboardGeometryRenderer );
443 billboardEntity->setParent( entity );
446 if ( !mTextBillboardPositions.empty() )
451 Qt3DRender::QGeometryRenderer *billboardGeometryRenderer =
new Qt3DRender::QGeometryRenderer;
452 billboardGeometryRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Points );
453 billboardGeometryRenderer->setGeometry( textBillboardGeometry );
454 billboardGeometryRenderer->setVertexCount( textBillboardGeometry->
count() );
459 Qt3DCore::QEntity *billboardEntity =
new Qt3DCore::QEntity;
460 billboardEntity->addComponent( billboardMaterial );
461 billboardEntity->addComponent( billboardGeometryRenderer );
462 billboardEntity->setParent( entity );
466 if ( mFactory->mShowCallouts )
468 QgsLineVertexData lineData;
469 lineData.withAdjacency =
true;
470 lineData.geocentricCoordinates =
false;
475 lineData.addLineString( line, 0,
false );
478 QgsLineMaterial *mat =
new QgsLineMaterial;
479 mat->setLineColor( mFactory->mCalloutLineColor );
480 mat->setLineWidth( mFactory->mCalloutLineWidth );
482 Qt3DCore::QEntity *calloutEntity =
new Qt3DCore::QEntity;
483 calloutEntity->setObjectName( parent->objectName() +
"_CALLOUTS" );
486 Qt3DRender::QGeometryRenderer *calloutRenderer =
new Qt3DRender::QGeometryRenderer;
487 calloutRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
488 calloutRenderer->setGeometry( lineData.createGeometry( calloutEntity ) );
489 calloutRenderer->setVertexCount( lineData.indexes.count() );
490 calloutRenderer->setPrimitiveRestartEnabled(
true );
491 calloutRenderer->setRestartIndexValue( 0 );
494 calloutEntity->addComponent( calloutRenderer );
495 calloutEntity->addComponent( mat );
497 calloutEntity->setParent( entity );
501 if ( mZMin != std::numeric_limits<float>::max() && mZMax != std::numeric_limits<float>::lowest() )
506 mNode->setExactBox3D( box );
507 mNode->updateParentBoundingBoxesRecursively();
516QgsAnnotationLayerChunkLoaderFactory::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 )
517 : mRenderContext( context )
519 , mLeafLevel( leafLevel )
520 , mClamping( clamping )
521 , mZOffset( zOffset )
522 , mShowCallouts( showCallouts )
523 , mCalloutLineColor( calloutLineColor )
524 , mCalloutLineWidth( calloutLineWidth )
525 , mTextFormat( textFormat )
531 QgsDebugError( u
"Annotation layers in globe scenes are not supported yet!"_s );
532 setupQuadtree( QgsBox3D( -1e7, -1e7, -1e7, 1e7, 1e7, 1e7 ), -1, leafLevel );
538 rootBox3D.
grow( 1.0 );
539 setupQuadtree( rootBox3D, -1, leafLevel );
542QgsChunkLoader *QgsAnnotationLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node )
const
544 return new QgsAnnotationLayerChunkLoader(
this, node );
552 : QgsChunkedEntity( map,
554 new QgsAnnotationLayerChunkLoaderFactory(
Qgs3DRenderContext::fromMapSettings( map ), layer, 3, clamping, zOffset, showCallouts, calloutLineColor, calloutLineWidth, textFormat, zMin, zMax ), true )
556 mTransform =
new Qt3DCore::QTransform;
557 if ( applyTerrainOffset() )
561 this->addComponent( mTransform );
566QgsAnnotationLayerChunkedEntity::~QgsAnnotationLayerChunkedEntity()
573bool QgsAnnotationLayerChunkedEntity::applyTerrainOffset()
const
575 if (
auto loaderFactory =
static_cast<QgsAnnotationLayerChunkLoaderFactory *
>( mChunkLoaderFactory ) )
582void QgsAnnotationLayerChunkedEntity::onTerrainElevationOffsetChanged()
584 QgsDebugMsgLevel( u
"QgsAnnotationLayerChunkedEntity::onTerrainElevationOffsetChanged"_s, 2 );
585 float newOffset =
static_cast<float>( qobject_cast<Qgs3DMapSettings *>( sender() )->terrainSettings()->elevationOffset() );
586 if ( !applyTerrainOffset() )
590 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.