17#include "moc_qgsdemterraintileloader_p.cpp"
31#include <Qt3DRender/QGeometryRenderer>
32#include <Qt3DCore/QTransform>
33#include <QMutexLocker>
37static void _heightMapMinMax(
const QByteArray &heightMap,
float &zMin,
float &zMax )
39 const float *zBits = (
const float * ) heightMap.constData();
40 int zCount = heightMap.count() /
sizeof( float );
43 zMin = zMax = std::numeric_limits<float>::quiet_NaN();
44 for (
int i = 0; i < zCount; ++i )
47 if ( std::isnan( z ) )
54 zMin = std::min( zMin, z );
55 zMax = std::max( zMax, z );
60QgsDemTerrainTileLoader::QgsDemTerrainTileLoader( QgsTerrainEntity *terrain, QgsChunkNode *node,
QgsTerrainGenerator *terrainGenerator )
61 : 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->
terrainVerticalScale(), mSkirtHeight, mHeightMap, mesh ) );
109 entity->addComponent( mesh );
117 Qt3DCore::QTransform *transform =
new Qt3DCore::QTransform();
119 transform->setTranslation( translation.
toVector3D() );
120 entity->addComponent( transform );
124 mNode->updateParentBoundingBoxesRecursively();
126 entity->setParent( parent );
130void QgsDemTerrainTileLoader::onHeightMapReady(
int jobId,
const QByteArray &heightMap )
132 if ( mHeightMapJobId == jobId )
134 this->mHeightMap = heightMap;
135 mHeightMapJobId = -1;
147#include <QtConcurrent/QtConcurrentRun>
148#include <QFutureWatcher>
153 , mClonedProvider( dtm ? qgis::down_cast<
QgsRasterDataProvider *>( dtm->dataProvider()->clone() ) : nullptr )
154 , mTilingScheme( tilingScheme )
155 , mResolution( resolution )
158 , mTransformContext( transformContext )
162QgsDemHeightMapGenerator::~QgsDemHeightMapGenerator()
164 delete mClonedProvider;
170 provider->moveToThread( QThread::currentThread() );
172 QgsEventTracing::ScopedEvent e( QStringLiteral(
"3D" ), QStringLiteral(
"DEM" ) );
176 std::unique_ptr<QgsRasterProjector> projector;
177 if ( provider->
crs() != destCrs )
182 input = projector.get();
184 std::unique_ptr< QgsRasterBlock > block( input->
block( 1, extent, res, res ) );
193 if ( !block->hasNoDataValue() )
197 block->setNoDataValue( std::numeric_limits<float>::lowest() );
199 block->setIsNoDataExcept( subRect );
201 data = block->data();
204 if ( block->hasNoData() )
207 float *floatData =
reinterpret_cast<float *
>( data.data() );
208 Q_ASSERT( data.count() %
sizeof(
float ) == 0 );
209 int count = data.count() /
sizeof( float );
210 for (
int i = 0; i < count; ++i )
212 if ( block->isNoData( i ) )
213 floatData[i] = std::numeric_limits<float>::quiet_NaN();
224 return downloader->
getHeightMap( extent, res, destCrs, context );
227int QgsDemHeightMapGenerator::render(
const QgsChunkNodeId &nodeId )
229 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"DEM" ), nodeId.text() );
232 QgsRectangle extent = mTilingScheme.tileToExtent( nodeId );
233 float mapUnitsPerPixel = extent.
width() / mResolution;
234 extent.
grow( mapUnitsPerPixel / 2 );
236 QgsRectangle rootTileExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
237 extent = extent.
intersect( rootTileExtent );
240 jd.jobId = ++mLastJobId;
244 QFutureWatcher<QByteArray> *fw =
new QFutureWatcher<QByteArray>(
nullptr );
245 connect( fw, &QFutureWatcher<QByteArray>::finished,
this, &QgsDemHeightMapGenerator::onFutureFinished );
246 connect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
247 if ( mClonedProvider )
250 std::unique_ptr< QgsRasterDataProvider > clonedProviderClone( mClonedProvider->clone() );
251 clonedProviderClone->moveToThread(
nullptr );
252 jd.future = QtConcurrent::run( _readDtmData, clonedProviderClone.release(), extent, mResolution, mTilingScheme.crs(), mTilingScheme.fullExtent() );
256 jd.future = QtConcurrent::run( _readOnlineDtm, mDownloader.get(), extent, mResolution, mTilingScheme.crs(), mTransformContext );
259 fw->setFuture( jd.future );
261 mJobs.insert( fw, jd );
266void QgsDemHeightMapGenerator::waitForFinished()
268 for (
auto it = mJobs.keyBegin(); it != mJobs.keyEnd(); it++ )
270 QFutureWatcher<QByteArray> *fw = *it;
271 disconnect( fw, &QFutureWatcher<QByteArray>::finished,
this, &QgsDemHeightMapGenerator::onFutureFinished );
272 disconnect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
274 QVector<QFutureWatcher<QByteArray>*> toBeDeleted;
275 for (
auto it = mJobs.keyBegin(); it != mJobs.keyEnd(); it++ )
277 QFutureWatcher<QByteArray> *fw = *it;
278 fw->waitForFinished();
279 JobData jobData = mJobs.value( fw );
280 toBeDeleted.push_back( fw );
282 QByteArray data = jobData.future.result();
283 emit heightMapReady( jobData.jobId, data );
286 for ( QFutureWatcher<QByteArray> *fw : toBeDeleted )
293void QgsDemHeightMapGenerator::lazyLoadDtmCoarseData(
int res,
const QgsRectangle &rect )
295 QMutexLocker locker( &mLazyLoadDtmCoarseDataMutex );
296 if ( mDtmCoarseData.isEmpty() )
298 std::unique_ptr< QgsRasterBlock > block( mClonedProvider->block( 1, rect, res, res ) );
300 mDtmCoarseData = block->data();
301 mDtmCoarseData.detach();
305float QgsDemHeightMapGenerator::heightAt(
double x,
double y )
307 if ( !mClonedProvider )
312 lazyLoadDtmCoarseData( res, mDtmExtent );
314 int cellX = ( int )( ( x - mDtmExtent.xMinimum() ) / mDtmExtent.width() * res + .5f );
315 int cellY = ( int )( ( mDtmExtent.yMaximum() - y ) / mDtmExtent.height() * res + .5f );
316 cellX = std::clamp( cellX, 0, res - 1 );
317 cellY = std::clamp( cellY, 0, res - 1 );
319 const float *data = (
const float * ) mDtmCoarseData.constData();
320 return data[cellX + cellY * res];
323void QgsDemHeightMapGenerator::onFutureFinished()
325 QFutureWatcher<QByteArray> *fw =
static_cast<QFutureWatcher<QByteArray>*
>( sender() );
327 Q_ASSERT( mJobs.contains( fw ) );
328 JobData jobData = mJobs.value( fw );
333 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"DEM" ), jobData.tileId.text() );
335 QByteArray data = jobData.future.result();
336 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).
static QgsVector3D mapToWorldCoordinates(const QgsVector3D &mapCoords, const QgsVector3D &origin)
Converts map coordinates to 3D world coordinates (applies offset)
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...
QVector3D toVector3D() const
Converts the current object to QVector3D.