17#include "moc_qgsdemterraintileloader_p.cpp"
32#include <Qt3DRender/QGeometryRenderer>
33#include <Qt3DCore/QTransform>
34#include <QMutexLocker>
38static void _heightMapMinMax(
const QByteArray &heightMap,
float &zMin,
float &zMax )
40 const float *zBits = (
const float * ) heightMap.constData();
41 int zCount = heightMap.count() /
sizeof( float );
44 zMin = zMax = std::numeric_limits<float>::quiet_NaN();
45 for (
int i = 0; i < zCount; ++i )
48 if ( std::isnan( z ) )
55 zMin = std::min( zMin, z );
56 zMax = std::max( zMax, z );
61QgsDemTerrainTileLoader::QgsDemTerrainTileLoader( QgsTerrainEntity *terrain, QgsChunkNode *node,
QgsTerrainGenerator *terrainGenerator )
62 : QgsTerrainTileLoader( terrain, node )
65 QgsDemHeightMapGenerator *heightMapGenerator =
nullptr;
82 connect( heightMapGenerator, &QgsDemHeightMapGenerator::heightMapReady,
this, &QgsDemTerrainTileLoader::onHeightMapReady );
83 mHeightMapJobId = heightMapGenerator->render( node->tileId() );
84 mResolution = heightMapGenerator->resolution();
87Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *parent )
90 _heightMapMinMax( mHeightMap, zMin, zMax );
92 if ( std::isnan( zMin ) || std::isnan( zMax ) )
99 QgsChunkNodeId nodeId = mNode->tileId();
101 double side = extent.
width();
103 QgsTerrainTileEntity *entity =
new QgsTerrainTileEntity( nodeId );
107 Qt3DRender::QGeometryRenderer *mesh =
new Qt3DRender::QGeometryRenderer;
108 mesh->setGeometry(
new DemTerrainTileGeometry( mResolution, side, map->
terrainSettings()->
verticalScale(), mSkirtHeight, mHeightMap, mesh ) );
109 entity->addComponent( mesh );
116 QgsGeoTransform *transform =
new QgsGeoTransform;
118 entity->addComponent( transform );
121 mNode->updateParentBoundingBoxesRecursively();
123 entity->setParent( parent );
127void QgsDemTerrainTileLoader::onHeightMapReady(
int jobId,
const QByteArray &heightMap )
129 if ( mHeightMapJobId == jobId )
131 this->mHeightMap = heightMap;
132 mHeightMapJobId = -1;
144#include <QtConcurrent/QtConcurrentRun>
145#include <QFutureWatcher>
150 , mClonedProvider( dtm ? qgis::down_cast<
QgsRasterDataProvider *>( dtm->dataProvider()->clone() ) : nullptr )
151 , mTilingScheme( tilingScheme )
152 , mResolution( resolution )
155 , mTransformContext( transformContext )
159QgsDemHeightMapGenerator::~QgsDemHeightMapGenerator()
161 delete mClonedProvider;
167 provider->moveToThread( QThread::currentThread() );
169 QgsEventTracing::ScopedEvent e( QStringLiteral(
"3D" ), QStringLiteral(
"DEM" ) );
173 std::unique_ptr<QgsRasterProjector> projector;
174 if ( provider->
crs() != destCrs )
179 input = projector.get();
181 std::unique_ptr<QgsRasterBlock> block( input->
block( 1, extent, res, res ) );
190 if ( !block->hasNoDataValue() )
194 block->setNoDataValue( std::numeric_limits<float>::lowest() );
196 block->setIsNoDataExcept( subRect );
198 data = block->data();
201 if ( block->hasNoData() )
204 float *floatData =
reinterpret_cast<float *
>( data.data() );
205 Q_ASSERT( data.count() %
sizeof(
float ) == 0 );
206 int count = data.count() /
sizeof( float );
207 for (
int i = 0; i < count; ++i )
209 if ( block->isNoData( i ) )
210 floatData[i] = std::numeric_limits<float>::quiet_NaN();
221 return downloader->
getHeightMap( extent, res, destCrs, context );
224int QgsDemHeightMapGenerator::render(
const QgsChunkNodeId &nodeId )
226 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"DEM" ), nodeId.text() );
229 QgsRectangle extent = mTilingScheme.tileToExtent( nodeId );
230 float mapUnitsPerPixel = extent.
width() / mResolution;
231 extent.
grow( mapUnitsPerPixel / 2 );
233 QgsRectangle rootTileExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
234 extent = extent.
intersect( rootTileExtent );
237 jd.jobId = ++mLastJobId;
241 QFutureWatcher<QByteArray> *fw =
new QFutureWatcher<QByteArray>(
nullptr );
242 connect( fw, &QFutureWatcher<QByteArray>::finished,
this, &QgsDemHeightMapGenerator::onFutureFinished );
243 connect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
244 if ( mClonedProvider )
247 std::unique_ptr<QgsRasterDataProvider> clonedProviderClone( mClonedProvider->clone() );
248 clonedProviderClone->moveToThread(
nullptr );
249 jd.future = QtConcurrent::run( _readDtmData, clonedProviderClone.release(), extent, mResolution, mTilingScheme.crs(), mTilingScheme.fullExtent() );
253 jd.future = QtConcurrent::run( _readOnlineDtm, mDownloader.get(), extent, mResolution, mTilingScheme.crs(), mTransformContext );
256 fw->setFuture( jd.future );
258 mJobs.insert( fw, jd );
263void QgsDemHeightMapGenerator::waitForFinished()
265 for (
auto it = mJobs.keyBegin(); it != mJobs.keyEnd(); it++ )
267 QFutureWatcher<QByteArray> *fw = *it;
268 disconnect( fw, &QFutureWatcher<QByteArray>::finished,
this, &QgsDemHeightMapGenerator::onFutureFinished );
269 disconnect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
271 QVector<QFutureWatcher<QByteArray> *> toBeDeleted;
272 for (
auto it = mJobs.keyBegin(); it != mJobs.keyEnd(); it++ )
274 QFutureWatcher<QByteArray> *fw = *it;
275 fw->waitForFinished();
276 JobData jobData = mJobs.value( fw );
277 toBeDeleted.push_back( fw );
279 QByteArray data = jobData.future.result();
280 emit heightMapReady( jobData.jobId, data );
283 for ( QFutureWatcher<QByteArray> *fw : toBeDeleted )
290void QgsDemHeightMapGenerator::lazyLoadDtmCoarseData(
int res,
const QgsRectangle &rect )
292 QMutexLocker locker( &mLazyLoadDtmCoarseDataMutex );
293 if ( mDtmCoarseData.isEmpty() )
295 std::unique_ptr<QgsRasterBlock> block( mClonedProvider->block( 1, rect, res, res ) );
297 mDtmCoarseData = block->data();
298 mDtmCoarseData.detach();
302float QgsDemHeightMapGenerator::heightAt(
double x,
double y )
304 if ( !mClonedProvider )
309 lazyLoadDtmCoarseData( res, mDtmExtent );
311 int cellX = ( int ) ( ( x - mDtmExtent.xMinimum() ) / mDtmExtent.width() * res + .5f );
312 int cellY = ( int ) ( ( mDtmExtent.yMaximum() - y ) / mDtmExtent.height() * res + .5f );
313 cellX = std::clamp( cellX, 0, res - 1 );
314 cellY = std::clamp( cellY, 0, res - 1 );
316 const float *data = (
const float * ) mDtmCoarseData.constData();
317 return data[cellX + cellY * res];
320void QgsDemHeightMapGenerator::onFutureFinished()
322 QFutureWatcher<QByteArray> *fw =
static_cast<QFutureWatcher<QByteArray> *
>( sender() );
324 Q_ASSERT( mJobs.contains( fw ) );
325 JobData jobData = mJobs.value( fw );
330 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"DEM" ), jobData.tileId.text() );
332 QByteArray data = jobData.future.result();
333 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.
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.
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.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...