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 ) );
193 data = block->data();
196 if ( block->hasNoData() )
199 float *floatData =
reinterpret_cast<float *
>( data.data() );
200 Q_ASSERT( data.count() %
sizeof(
float ) == 0 );
201 int count = data.count() /
sizeof( float );
202 for (
int i = 0; i < count; ++i )
204 if ( block->isNoData( i ) )
205 floatData[i] = std::numeric_limits<float>::quiet_NaN();
210 provider->moveToThread(
nullptr );
217 return downloader->
getHeightMap( extent, res, destCrs, context );
220int QgsDemHeightMapGenerator::render(
const QgsChunkNodeId &nodeId )
222 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"DEM" ), nodeId.text() );
225 QgsRectangle extent = mTilingScheme.tileToExtent( nodeId );
226 float mapUnitsPerPixel = extent.
width() / mResolution;
227 extent.
grow( mapUnitsPerPixel / 2 );
229 QgsRectangle fullExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
233 jd.jobId = ++mLastJobId;
237 QFutureWatcher<QByteArray> *fw =
new QFutureWatcher<QByteArray>(
nullptr );
238 connect( fw, &QFutureWatcher<QByteArray>::finished,
this, &QgsDemHeightMapGenerator::onFutureFinished );
239 connect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
243 mClonedProvider->moveToThread(
nullptr );
244 jd.future = QtConcurrent::run( _readDtmData, mClonedProvider, extent, mResolution, mTilingScheme.crs() );
248 jd.future = QtConcurrent::run( _readOnlineDtm, mDownloader.get(), extent, mResolution, mTilingScheme.crs(), mTransformContext );
251 fw->setFuture( jd.future );
253 mJobs.insert( fw, jd );
258void QgsDemHeightMapGenerator::waitForFinished()
260 for ( QFutureWatcher<QByteArray> *fw : mJobs.keys() )
262 disconnect( fw, &QFutureWatcher<QByteArray>::finished,
this, &QgsDemHeightMapGenerator::onFutureFinished );
263 disconnect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
265 QVector<QFutureWatcher<QByteArray>*> toBeDeleted;
266 for ( QFutureWatcher<QByteArray> *fw : mJobs.keys() )
268 fw->waitForFinished();
269 JobData jobData = mJobs.value( fw );
270 toBeDeleted.push_back( fw );
272 QByteArray data = jobData.future.result();
273 emit heightMapReady( jobData.jobId, data );
276 for ( QFutureWatcher<QByteArray> *fw : toBeDeleted )
283void QgsDemHeightMapGenerator::lazyLoadDtmCoarseData(
int res,
const QgsRectangle &rect )
285 QMutexLocker locker( &mLazyLoadDtmCoarseDataMutex );
286 if ( mDtmCoarseData.isEmpty() )
288 std::unique_ptr< QgsRasterBlock > block( mDtm->dataProvider()->block( 1, rect, res, res ) );
290 mDtmCoarseData = block->data();
291 mDtmCoarseData.detach();
295float QgsDemHeightMapGenerator::heightAt(
double x,
double y )
303 lazyLoadDtmCoarseData( res, rect );
305 int cellX = ( int )( ( x - rect.
xMinimum() ) / rect.
width() * res + .5f );
306 int cellY = ( int )( ( rect.
yMaximum() - y ) / rect.
height() * res + .5f );
307 cellX = std::clamp( cellX, 0, res - 1 );
308 cellY = std::clamp( cellY, 0, res - 1 );
310 const float *data = (
const float * ) mDtmCoarseData.constData();
311 return data[cellX + cellY * res];
314void QgsDemHeightMapGenerator::onFutureFinished()
316 QFutureWatcher<QByteArray> *fw =
static_cast<QFutureWatcher<QByteArray>*
>( sender() );
318 Q_ASSERT( mJobs.contains( fw ) );
319 JobData jobData = mJobs.value( fw );
324 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"DEM" ), jobData.tileId.text() );
326 QByteArray data = jobData.future.result();
327 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 ...
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.
QgsRasterProjector implements approximate projection support for it calculates grid of points in sour...
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.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
void grow(double delta)
Grows the rectangle in place by the specified amount.
double height() const
Returns the height of the rectangle.
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.