33#include <QMutexLocker>
35#include <Qt3DCore/QTransform>
36#include <Qt3DRender/QGeometryRenderer>
38#include "moc_qgsdemterraintileloader_p.cpp"
40using namespace Qt::StringLiterals;
44static void _heightMapMinMax(
const QByteArray &heightMap,
float &zMin,
float &zMax )
46 const float *zBits = (
const float * ) heightMap.constData();
47 int zCount = heightMap.count() /
sizeof( float );
50 zMin = zMax = std::numeric_limits<float>::quiet_NaN();
51 for (
int i = 0; i < zCount; ++i )
54 if ( std::isnan( z ) )
61 zMin = std::min( zMin, z );
62 zMax = std::max( zMax, z );
67QgsDemTerrainTileLoader::QgsDemTerrainTileLoader( QgsTerrainEntity *terrain, QgsChunkNode *node,
QgsTerrainGenerator *terrainGenerator )
68 : QgsTerrainTileLoader( terrain, node )
69 , mTerrainGenerator( terrainGenerator )
72void QgsDemTerrainTileLoader::start()
74 QgsChunkNode *node = chunk();
76 QgsDemHeightMapGenerator *heightMapGenerator =
nullptr;
93 connect( heightMapGenerator, &QgsDemHeightMapGenerator::heightMapReady,
this, &QgsDemTerrainTileLoader::onHeightMapReady );
94 mHeightMapJobId = heightMapGenerator->render( node->tileId() );
95 mResolution = heightMapGenerator->resolution();
98Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *parent )
101 _heightMapMinMax( mHeightMap, zMin, zMax );
103 if ( std::isnan( zMin ) || std::isnan( zMax ) )
110 QgsChunkNodeId nodeId = mNode->tileId();
112 double side = extent.
width();
114 QgsTerrainTileEntity *entity =
new QgsTerrainTileEntity( nodeId );
118 Qt3DRender::QGeometryRenderer *mesh =
new Qt3DRender::QGeometryRenderer;
119 mesh->setGeometry(
new DemTerrainTileGeometry( mResolution, side, map->
terrainSettings()->
verticalScale(), mSkirtHeight, mHeightMap, mesh ) );
120 entity->addComponent( mesh );
127 QgsGeoTransform *transform =
new QgsGeoTransform;
129 entity->addComponent( transform );
132 mNode->setExactBox3D(
137 mNode->updateParentBoundingBoxesRecursively();
139 entity->setParent( parent );
143void QgsDemTerrainTileLoader::onHeightMapReady(
int jobId,
const QByteArray &heightMap )
145 if ( mHeightMapJobId == jobId )
147 this->mHeightMap = heightMap;
148 mHeightMapJobId = -1;
160#include <QtConcurrentRun>
161#include <QFutureWatcher>
167 , mClonedProvider( dtm ? qgis::down_cast<
QgsRasterDataProvider *>( dtm->dataProvider()->clone() ) : nullptr )
168 , mTilingScheme( tilingScheme )
169 , mResolution( resolution )
171 , mTransformContext( transformContext )
174QgsDemHeightMapGenerator::~QgsDemHeightMapGenerator()
176 delete mClonedProvider;
182 provider->moveToThread( QThread::currentThread() );
184 QgsEventTracing::ScopedEvent e( u
"3D"_s, u
"DEM"_s );
188 std::unique_ptr<QgsRasterProjector> projector;
189 if ( provider->
crs() != destCrs )
191 projector = std::make_unique<QgsRasterProjector>();
193 projector->setInput( provider );
194 input = projector.get();
196 std::unique_ptr<QgsRasterBlock> block( input->
block( 1, extent, res, res ) );
205 if ( !block->hasNoDataValue() )
209 block->setNoDataValue( std::numeric_limits<float>::lowest() );
211 block->setIsNoDataExcept( subRect );
213 data = block->data();
216 if ( block->hasNoData() )
219 float *floatData =
reinterpret_cast<float *
>( data.data() );
220 Q_ASSERT( data.count() %
sizeof(
float ) == 0 );
221 int count = data.count() /
sizeof( float );
222 for (
int i = 0; i < count; ++i )
224 if ( block->isNoData( i ) )
225 floatData[i] = std::numeric_limits<float>::quiet_NaN();
236 return downloader->
getHeightMap( extent, res, destCrs, context );
239int QgsDemHeightMapGenerator::render(
const QgsChunkNodeId &nodeId )
241 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, u
"3D"_s, u
"DEM"_s, nodeId.text() );
244 QgsRectangle extent = mTilingScheme.tileToExtent( nodeId );
245 float mapUnitsPerPixel = extent.
width() / mResolution;
246 extent.
grow( mapUnitsPerPixel / 2 );
248 QgsRectangle rootTileExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
249 extent = extent.
intersect( rootTileExtent );
252 jd.jobId = ++mLastJobId;
256 QFutureWatcher<QByteArray> *fw =
new QFutureWatcher<QByteArray>(
nullptr );
257 connect( fw, &QFutureWatcher<QByteArray>::finished,
this, &QgsDemHeightMapGenerator::onFutureFinished );
258 connect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
259 if ( mClonedProvider )
262 std::unique_ptr<QgsRasterDataProvider> clonedProviderClone( mClonedProvider->clone() );
263 clonedProviderClone->moveToThread(
nullptr );
264 jd.future = QtConcurrent::run( _readDtmData, clonedProviderClone.release(), extent, mResolution, mTilingScheme.crs(), mTilingScheme.fullExtent() );
268 jd.future = QtConcurrent::run( _readOnlineDtm, mDownloader.get(), extent, mResolution, mTilingScheme.crs(), mTransformContext );
271 fw->setFuture( jd.future );
273 mJobs.insert( fw, jd );
278void QgsDemHeightMapGenerator::waitForFinished()
280 for (
auto it = mJobs.keyBegin(); it != mJobs.keyEnd(); it++ )
282 QFutureWatcher<QByteArray> *fw = *it;
283 disconnect( fw, &QFutureWatcher<QByteArray>::finished,
this, &QgsDemHeightMapGenerator::onFutureFinished );
284 disconnect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
286 QVector<QFutureWatcher<QByteArray> *> toBeDeleted;
287 for (
auto it = mJobs.keyBegin(); it != mJobs.keyEnd(); it++ )
289 QFutureWatcher<QByteArray> *fw = *it;
290 fw->waitForFinished();
291 JobData jobData = mJobs.value( fw );
292 toBeDeleted.push_back( fw );
294 QByteArray data = jobData.future.result();
295 emit heightMapReady( jobData.jobId, data );
298 for ( QFutureWatcher<QByteArray> *fw : toBeDeleted )
305void QgsDemHeightMapGenerator::lazyLoadDtmCoarseData(
int res,
const QgsRectangle &rect )
307 QMutexLocker locker( &mLazyLoadDtmCoarseDataMutex );
308 if ( !mDtmCoarseRasterBlock )
310 mDtmCoarseRasterBlock.reset( mClonedProvider->block( 1, rect, res, res ) );
314float QgsDemHeightMapGenerator::heightAt(
double x,
double y )
316 if ( !mClonedProvider )
317 return std::numeric_limits<float>::quiet_NaN();
321 lazyLoadDtmCoarseData( res, mDtmExtent );
323 int cellX = ( int ) ( ( x - mDtmExtent.xMinimum() ) / mDtmExtent.width() * res + .5f );
324 int cellY = ( int ) ( ( mDtmExtent.yMaximum() - y ) / mDtmExtent.height() * res + .5f );
325 cellX = std::clamp( cellX, 0, res - 1 );
326 cellY = std::clamp( cellY, 0, res - 1 );
328 bool isNoData =
false;
329 const double val = mDtmCoarseRasterBlock->valueAndNoData( cellY, cellX, isNoData );
331 return isNoData ? std::numeric_limits<float>::quiet_NaN() : static_cast<float>( val );
334void QgsDemHeightMapGenerator::onFutureFinished()
336 QFutureWatcher<QByteArray> *fw =
static_cast<QFutureWatcher<QByteArray> *
>( sender() );
338 Q_ASSERT( mJobs.contains( fw ) );
339 JobData jobData = mJobs.value( fw );
344 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"DEM"_s, jobData.tileId.text() );
346 QByteArray data = jobData.future.result();
347 emit heightMapReady( jobData.jobId, data );
@ Float32
Thirty two bit floating point (float).
const QgsAbstractTerrainSettings * terrainSettings() const
Returns the terrain settings.
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.
double verticalScale() const
Returns the vertical scale (exaggeration) for terrain.
A 3-dimensional box composed of x, y, z coordinates.
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.
Implementation of terrain generator that uses a raster layer with DEM to build terrain.
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 ...
Implementation of terrain generator that uses online resources to download heightmaps.
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.
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.
A rectangle specified with double values.
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.
Takes care of downloading terrain data from a publicly available data source.
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,...
Base class for generators of terrain.
@ Dem
Terrain is built from raster layer with digital elevation model.
@ Online
Terrain is built from downloaded tiles with digital elevation model.
const QgsTilingScheme & tilingScheme() const
Returns tiling scheme of the terrain.
Encapsulates tiling schemes (just like with WMTS / TMS / XYZ layers).
QgsRectangle tileToExtent(int x, int y, int z) const
Returns map coordinates of the extent of a tile.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...