29#include <Qt3DRender/QGeometryRenderer>
30#include <Qt3DCore/QTransform>
31#include <QMutexLocker>
35static void _heightMapMinMax(
const QByteArray &heightMap,
float &zMin,
float &zMax )
37 const float *zBits = (
const float * ) heightMap.constData();
38 int zCount = heightMap.count() /
sizeof( float );
41 zMin = zMax = std::numeric_limits<float>::quiet_NaN();
42 for (
int i = 0; i < zCount; ++i )
45 if ( std::isnan( z ) )
52 zMin = std::min( zMin, z );
53 zMax = std::max( zMax, z );
58QgsDemTerrainTileLoader::QgsDemTerrainTileLoader( QgsTerrainEntity *terrain, QgsChunkNode *node,
QgsTerrainGenerator *terrainGenerator )
59 : QgsTerrainTileLoader( terrain, node )
63 QgsDemHeightMapGenerator *heightMapGenerator =
nullptr;
80 connect( heightMapGenerator, &QgsDemHeightMapGenerator::heightMapReady,
this, &QgsDemTerrainTileLoader::onHeightMapReady );
81 mHeightMapJobId = heightMapGenerator->render( node->tileId() );
82 mResolution = heightMapGenerator->resolution();
85Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *parent )
88 _heightMapMinMax( mHeightMap, zMin, zMax );
90 if ( std::isnan( zMin ) || std::isnan( zMax ) )
97 QgsChunkNodeId nodeId = mNode->tileId();
101 double side = extent.
width();
102 double half = side / 2;
105 QgsTerrainTileEntity *entity =
new QgsTerrainTileEntity( nodeId );
109 Qt3DRender::QGeometryRenderer *mesh =
new Qt3DRender::QGeometryRenderer;
110 mesh->setGeometry(
new DemTerrainTileGeometry( mResolution, side, map->
terrainVerticalScale(), mSkirtHeight, mHeightMap, mesh ) );
111 entity->addComponent( mesh );
119 Qt3DCore::QTransform *transform =
nullptr;
120 transform =
new Qt3DCore::QTransform();
121 entity->addComponent( transform );
123 transform->setScale( side );
124 transform->setTranslation( QVector3D( x0 + half, 0, - ( y0 + half ) ) );
127 mNode->updateParentBoundingBoxesRecursively();
129 entity->setParent( parent );
133void QgsDemTerrainTileLoader::onHeightMapReady(
int jobId,
const QByteArray &heightMap )
135 if ( mHeightMapJobId == jobId )
137 this->mHeightMap = heightMap;
138 mHeightMapJobId = -1;
150#include <QtConcurrent/QtConcurrentRun>
151#include <QFutureWatcher>
156 , mClonedProvider( dtm ? qgis::down_cast<
QgsRasterDataProvider *>( dtm->dataProvider()->clone() ) : nullptr )
157 , mTilingScheme( tilingScheme )
158 , mResolution( resolution )
161 , mTransformContext( transformContext )
165QgsDemHeightMapGenerator::~QgsDemHeightMapGenerator()
167 delete mClonedProvider;
173 provider->moveToThread( QThread::currentThread() );
175 QgsEventTracing::ScopedEvent e( QStringLiteral(
"3D" ), QStringLiteral(
"DEM" ) );
179 std::unique_ptr<QgsRasterProjector> projector;
180 if ( provider->
crs() != destCrs )
185 input = projector.get();
187 std::unique_ptr< QgsRasterBlock > block( input->
block( 1, extent, res, res ) );
196 if ( !block->hasNoDataValue() )
200 block->setNoDataValue( std::numeric_limits<float>::lowest() );
202 block->setIsNoDataExcept( subRect );
204 data = block->data();
207 if ( block->hasNoData() )
210 float *floatData =
reinterpret_cast<float *
>( data.data() );
211 Q_ASSERT( data.count() %
sizeof(
float ) == 0 );
212 int count = data.count() /
sizeof( float );
213 for (
int i = 0; i < count; ++i )
215 if ( block->isNoData( i ) )
216 floatData[i] = std::numeric_limits<float>::quiet_NaN();
227 return downloader->
getHeightMap( extent, res, destCrs, context );
230int QgsDemHeightMapGenerator::render(
const QgsChunkNodeId &nodeId )
232 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"DEM" ), nodeId.text() );
235 QgsRectangle extent = mTilingScheme.tileToExtent( nodeId );
236 float mapUnitsPerPixel = extent.
width() / mResolution;
237 extent.
grow( mapUnitsPerPixel / 2 );
239 QgsRectangle rootTileExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
240 extent = extent.
intersect( rootTileExtent );
243 jd.jobId = ++mLastJobId;
247 QFutureWatcher<QByteArray> *fw =
new QFutureWatcher<QByteArray>(
nullptr );
248 connect( fw, &QFutureWatcher<QByteArray>::finished,
this, &QgsDemHeightMapGenerator::onFutureFinished );
249 connect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
250 if ( mClonedProvider )
253 std::unique_ptr< QgsRasterDataProvider > clonedProviderClone( mClonedProvider->clone() );
254 clonedProviderClone->moveToThread(
nullptr );
255 jd.future = QtConcurrent::run( _readDtmData, clonedProviderClone.release(), extent, mResolution, mTilingScheme.crs(), mTilingScheme.fullExtent() );
259 jd.future = QtConcurrent::run( _readOnlineDtm, mDownloader.get(), extent, mResolution, mTilingScheme.crs(), mTransformContext );
262 fw->setFuture( jd.future );
264 mJobs.insert( fw, jd );
269void QgsDemHeightMapGenerator::waitForFinished()
271 for (
auto it = mJobs.keyBegin(); it != mJobs.keyEnd(); it++ )
273 QFutureWatcher<QByteArray> *fw = *it;
274 disconnect( fw, &QFutureWatcher<QByteArray>::finished,
this, &QgsDemHeightMapGenerator::onFutureFinished );
275 disconnect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
277 QVector<QFutureWatcher<QByteArray>*> toBeDeleted;
278 for (
auto it = mJobs.keyBegin(); it != mJobs.keyEnd(); it++ )
280 QFutureWatcher<QByteArray> *fw = *it;
281 fw->waitForFinished();
282 JobData jobData = mJobs.value( fw );
283 toBeDeleted.push_back( fw );
285 QByteArray data = jobData.future.result();
286 emit heightMapReady( jobData.jobId, data );
289 for ( QFutureWatcher<QByteArray> *fw : toBeDeleted )
296void QgsDemHeightMapGenerator::lazyLoadDtmCoarseData(
int res,
const QgsRectangle &rect )
298 QMutexLocker locker( &mLazyLoadDtmCoarseDataMutex );
299 if ( mDtmCoarseData.isEmpty() )
301 std::unique_ptr< QgsRasterBlock > block( mClonedProvider->block( 1, rect, res, res ) );
303 mDtmCoarseData = block->data();
304 mDtmCoarseData.detach();
308float QgsDemHeightMapGenerator::heightAt(
double x,
double y )
310 if ( !mClonedProvider )
315 lazyLoadDtmCoarseData( res, mDtmExtent );
317 int cellX = ( int )( ( x - mDtmExtent.xMinimum() ) / mDtmExtent.width() * res + .5f );
318 int cellY = ( int )( ( mDtmExtent.yMaximum() - y ) / mDtmExtent.height() * res + .5f );
319 cellX = std::clamp( cellX, 0, res - 1 );
320 cellY = std::clamp( cellY, 0, res - 1 );
322 const float *data = (
const float * ) mDtmCoarseData.constData();
323 return data[cellX + cellY * res];
326void QgsDemHeightMapGenerator::onFutureFinished()
328 QFutureWatcher<QByteArray> *fw =
static_cast<QFutureWatcher<QByteArray>*
>( sender() );
330 Q_ASSERT( mJobs.contains( fw ) );
331 JobData jobData = mJobs.value( fw );
336 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"DEM" ), jobData.tileId.text() );
338 QByteArray data = jobData.future.result();
339 emit heightMapReady( jobData.jobId, data );
@ Float32
Thirty two bit floating point (float)
double terrainVerticalScale() const
Returns vertical scale (exaggeration) of terrain.
QgsTerrainGenerator * terrainGenerator() const
Returns the terrain generator.
bool isTerrainShadingEnabled() const
Returns whether terrain shading is enabled.
QgsPhongMaterialSettings terrainShadingMaterial() const
Returns terrain shading material.
QList< QgsMapLayer * > layers() const
Returns the list of 3D map layers to be rendered in the scene.
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0).
This class represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
QgsCoordinateTransformContext transformContext() const
Returns data provider coordinate transform context.
virtual QgsCoordinateReferenceSystem crs() const =0
Returns the coordinate system for the data source.
QgsDemHeightMapGenerator * heightMapGenerator()
Returns height map generator object - takes care of extraction of elevations from the layer)
float skirtHeight() const
Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between ...
QgsDemHeightMapGenerator * heightMapGenerator()
Returns height map generator object - takes care of extraction of elevations from the layer)
float skirtHeight() const
Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between ...
static QRect subRect(const QgsRectangle &extent, int width, int height, const QgsRectangle &subExtent)
For extent and width, height find rectangle covered by subextent.
Base class for raster data providers.
bool setInput(QgsRasterInterface *input) override
Set input.
Base class for processing filters like renderers, reprojector, resampler etc.
virtual QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr)=0
Read block of data using given extent and size.
Represents a raster layer.
Implements approximate projection support for optimised raster transformation.
A rectangle specified with double values.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double width() const
Returns the width of the rectangle.
void grow(double delta)
Grows the rectangle in place by the specified amount.
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
QByteArray getHeightMap(const QgsRectangle &extentOrig, int res, const QgsCoordinateReferenceSystem &destCrs, const QgsCoordinateTransformContext &context=QgsCoordinateTransformContext(), QString tmpFilenameImg=QString(), QString tmpFilenameTif=QString())
For given extent and resolution (number of pixels for width/height) in specified CRS,...
@ Dem
Terrain is built from raster layer with digital elevation model.
@ Online
Terrain is built from downloaded tiles with digital elevation model.
virtual Type type() const =0
What texture generator implementation is this.
const QgsTilingScheme & tilingScheme() const
Returns tiling scheme of the terrain.
QgsRectangle tileToExtent(int x, int y, int z) const
Returns map coordinates of the extent of a tile.
double y() const
Returns Y coordinate.
double x() const
Returns X coordinate.