QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgsdemterraintileloader_p.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsdemterraintileloader_p.cpp
3 --------------------------------------
4 Date : July 2017
5 Copyright : (C) 2017 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include "qgs3dmapsettings.h"
19#include "qgschunknode_p.h"
22#include "qgseventtracing.h"
24#include "qgsterrainentity_p.h"
27#include "qgsterraingenerator.h"
28
29#include <Qt3DRender/QGeometryRenderer>
30#include <Qt3DCore/QTransform>
31#include <QMutexLocker>
32
34
35static void _heightMapMinMax( const QByteArray &heightMap, float &zMin, float &zMax )
36{
37 const float *zBits = ( const float * ) heightMap.constData();
38 int zCount = heightMap.count() / sizeof( float );
39 bool first = true;
40
41 zMin = zMax = std::numeric_limits<float>::quiet_NaN();
42 for ( int i = 0; i < zCount; ++i )
43 {
44 float z = zBits[i];
45 if ( std::isnan( z ) )
46 continue;
47 if ( first )
48 {
49 zMin = zMax = z;
50 first = false;
51 }
52 zMin = std::min( zMin, z );
53 zMax = std::max( zMax, z );
54 }
55}
56
57
58QgsDemTerrainTileLoader::QgsDemTerrainTileLoader( QgsTerrainEntity *terrain, QgsChunkNode *node, QgsTerrainGenerator *terrainGenerator )
59 : QgsTerrainTileLoader( terrain, node )
60 , mResolution( 0 )
61{
62
63 QgsDemHeightMapGenerator *heightMapGenerator = nullptr;
64 if ( terrainGenerator->type() == QgsTerrainGenerator::Dem )
65 {
66 QgsDemTerrainGenerator *generator = static_cast<QgsDemTerrainGenerator *>( terrainGenerator );
67 heightMapGenerator = generator->heightMapGenerator();
68 mSkirtHeight = generator->skirtHeight();
69 }
70 else if ( terrainGenerator->type() == QgsTerrainGenerator::Online )
71 {
72 QgsOnlineTerrainGenerator *generator = static_cast<QgsOnlineTerrainGenerator *>( terrainGenerator );
73 heightMapGenerator = generator->heightMapGenerator();
74 mSkirtHeight = generator->skirtHeight();
75 }
76 else
77 Q_ASSERT( false );
78
79 // get heightmap asynchronously
80 connect( heightMapGenerator, &QgsDemHeightMapGenerator::heightMapReady, this, &QgsDemTerrainTileLoader::onHeightMapReady );
81 mHeightMapJobId = heightMapGenerator->render( node->tileId() );
82 mResolution = heightMapGenerator->resolution();
83}
84
85Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *parent )
86{
87 float zMin, zMax;
88 _heightMapMinMax( mHeightMap, zMin, zMax );
89
90 if ( std::isnan( zMin ) || std::isnan( zMax ) )
91 {
92 // no data available for this tile
93 return nullptr;
94 }
95
96 const Qgs3DMapSettings &map = terrain()->map3D();
97 QgsChunkNodeId nodeId = mNode->tileId();
98 QgsRectangle extent = map.terrainGenerator()->tilingScheme().tileToExtent( nodeId );
99 double x0 = extent.xMinimum() - map.origin().x();
100 double y0 = extent.yMinimum() - map.origin().y();
101 double side = extent.width();
102 double half = side / 2;
103
104
105 QgsTerrainTileEntity *entity = new QgsTerrainTileEntity( nodeId );
106
107 // create geometry renderer
108
109 Qt3DRender::QGeometryRenderer *mesh = new Qt3DRender::QGeometryRenderer;
110 mesh->setGeometry( new DemTerrainTileGeometry( mResolution, side, map.terrainVerticalScale(), mSkirtHeight, mHeightMap, mesh ) );
111 entity->addComponent( mesh ); // takes ownership if the component has no parent
112
113 // create material
114
115 createTextureComponent( entity, map.isTerrainShadingEnabled(), map.terrainShadingMaterial(), !map.layers().empty() );
116
117 // create transform
118
119 Qt3DCore::QTransform *transform = nullptr;
120 transform = new Qt3DCore::QTransform();
121 entity->addComponent( transform );
122
123 transform->setScale( side );
124 transform->setTranslation( QVector3D( x0 + half, 0, - ( y0 + half ) ) );
125
126 mNode->setExactBbox( QgsAABB( x0, zMin * map.terrainVerticalScale(), -y0, x0 + side, zMax * map.terrainVerticalScale(), -( y0 + side ) ) );
127 mNode->updateParentBoundingBoxesRecursively();
128
129 entity->setEnabled( false );
130 entity->setParent( parent );
131 return entity;
132}
133
134void QgsDemTerrainTileLoader::onHeightMapReady( int jobId, const QByteArray &heightMap )
135{
136 if ( mHeightMapJobId == jobId )
137 {
138 this->mHeightMap = heightMap;
139 mHeightMapJobId = -1;
140
141 // continue loading - texture
142 loadTexture();
143 }
144}
145
146
147// ---------------------
148
149#include <qgsrasterlayer.h>
150#include <qgsrasterprojector.h>
151#include <QtConcurrent/QtConcurrentRun>
152#include <QFutureWatcher>
153#include "qgsterraindownloader.h"
154
155QgsDemHeightMapGenerator::QgsDemHeightMapGenerator( QgsRasterLayer *dtm, const QgsTilingScheme &tilingScheme, int resolution, const QgsCoordinateTransformContext &transformContext )
156 : mDtm( dtm )
157 , mClonedProvider( dtm ? qgis::down_cast<QgsRasterDataProvider *>( dtm->dataProvider()->clone() ) : nullptr )
158 , mTilingScheme( tilingScheme )
159 , mResolution( resolution )
160 , mLastJobId( 0 )
161 , mDownloader( dtm ? nullptr : new QgsTerrainDownloader( transformContext ) )
162 , mTransformContext( transformContext )
163{
164}
165
166QgsDemHeightMapGenerator::~QgsDemHeightMapGenerator()
167{
168 delete mClonedProvider;
169}
170
171
172static QByteArray _readDtmData( QgsRasterDataProvider *provider, const QgsRectangle &extent, int res, const QgsCoordinateReferenceSystem &destCrs )
173{
174 QgsEventTracing::ScopedEvent e( QStringLiteral( "3D" ), QStringLiteral( "DEM" ) );
175
176 // TODO: use feedback object? (but GDAL currently does not support cancellation anyway)
177 QgsRasterInterface *input = provider;
178 std::unique_ptr<QgsRasterProjector> projector;
179 if ( provider->crs() != destCrs )
180 {
181 projector.reset( new QgsRasterProjector );
182 projector->setCrs( provider->crs(), destCrs, provider->transformContext() );
183 projector->setInput( provider );
184 input = projector.get();
185 }
186 std::unique_ptr< QgsRasterBlock > block( input->block( 1, extent, res, res ) );
187
188 QByteArray data;
189 if ( block )
190 {
191 block->convert( Qgis::DataType::Float32 ); // currently we expect just floats
192 data = block->data();
193 data.detach(); // this should make a deep copy
194
195 if ( block->hasNoData() )
196 {
197 // turn all no-data values into NaN in the output array
198 float *floatData = reinterpret_cast<float *>( data.data() );
199 Q_ASSERT( data.count() % sizeof( float ) == 0 );
200 int count = data.count() / sizeof( float );
201 for ( int i = 0; i < count; ++i )
202 {
203 if ( block->isNoData( i ) )
204 floatData[i] = std::numeric_limits<float>::quiet_NaN();
205 }
206 }
207 }
208 return data;
209}
210
211static QByteArray _readOnlineDtm( QgsTerrainDownloader *downloader, const QgsRectangle &extent, int res, const QgsCoordinateReferenceSystem &destCrs, const QgsCoordinateTransformContext &context )
212{
213 return downloader->getHeightMap( extent, res, destCrs, context );
214}
215
216int QgsDemHeightMapGenerator::render( const QgsChunkNodeId &nodeId )
217{
218 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral( "3D" ), QStringLiteral( "DEM" ), nodeId.text() );
219
220 // extend the rect by half-pixel on each side? to get the values in "corners"
221 QgsRectangle extent = mTilingScheme.tileToExtent( nodeId );
222 float mapUnitsPerPixel = extent.width() / mResolution;
223 extent.grow( mapUnitsPerPixel / 2 );
224 // but make sure not to go beyond the full extent (returns invalid values)
225 QgsRectangle fullExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
226 extent = extent.intersect( fullExtent );
227
228 JobData jd;
229 jd.jobId = ++mLastJobId;
230 jd.tileId = nodeId;
231 jd.extent = extent;
232 jd.timer.start();
233 QFutureWatcher<QByteArray> *fw = new QFutureWatcher<QByteArray>( nullptr );
234 connect( fw, &QFutureWatcher<QByteArray>::finished, this, &QgsDemHeightMapGenerator::onFutureFinished );
235 connect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
236 // make a clone of the data provider so it is safe to use in worker thread
237 if ( mDtm )
238 jd.future = QtConcurrent::run( _readDtmData, mClonedProvider, extent, mResolution, mTilingScheme.crs() );
239 else
240 jd.future = QtConcurrent::run( _readOnlineDtm, mDownloader.get(), extent, mResolution, mTilingScheme.crs(), mTransformContext );
241
242 fw->setFuture( jd.future );
243
244 mJobs.insert( fw, jd );
245
246 return jd.jobId;
247}
248
249void QgsDemHeightMapGenerator::waitForFinished()
250{
251 for ( QFutureWatcher<QByteArray> *fw : mJobs.keys() )
252 {
253 disconnect( fw, &QFutureWatcher<QByteArray>::finished, this, &QgsDemHeightMapGenerator::onFutureFinished );
254 disconnect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
255 }
256 QVector<QFutureWatcher<QByteArray>*> toBeDeleted;
257 for ( QFutureWatcher<QByteArray> *fw : mJobs.keys() )
258 {
259 fw->waitForFinished();
260 JobData jobData = mJobs.value( fw );
261 toBeDeleted.push_back( fw );
262
263 QByteArray data = jobData.future.result();
264 emit heightMapReady( jobData.jobId, data );
265 }
266
267 for ( QFutureWatcher<QByteArray> *fw : toBeDeleted )
268 {
269 mJobs.remove( fw );
270 fw->deleteLater();
271 }
272}
273
274void QgsDemHeightMapGenerator::lazyLoadDtmCoarseData( int res, const QgsRectangle &rect )
275{
276 QMutexLocker locker( &mLazyLoadDtmCoarseDataMutex );
277 if ( mDtmCoarseData.isEmpty() )
278 {
279 std::unique_ptr< QgsRasterBlock > block( mDtm->dataProvider()->block( 1, rect, res, res ) );
280 block->convert( Qgis::DataType::Float32 );
281 mDtmCoarseData = block->data();
282 mDtmCoarseData.detach(); // make a deep copy
283 }
284}
285
286float QgsDemHeightMapGenerator::heightAt( double x, double y )
287{
288 if ( !mDtm )
289 return 0; // TODO: calculate heights for online DTM
290
291 // TODO: this is quite a primitive implementation: better to use heightmaps currently in use
292 int res = 1024;
293 QgsRectangle rect = mDtm->extent();
294 lazyLoadDtmCoarseData( res, rect );
295
296 int cellX = ( int )( ( x - rect.xMinimum() ) / rect.width() * res + .5f );
297 int cellY = ( int )( ( rect.yMaximum() - y ) / rect.height() * res + .5f );
298 cellX = std::clamp( cellX, 0, res - 1 );
299 cellY = std::clamp( cellY, 0, res - 1 );
300
301 const float *data = ( const float * ) mDtmCoarseData.constData();
302 return data[cellX + cellY * res];
303}
304
305void QgsDemHeightMapGenerator::onFutureFinished()
306{
307 QFutureWatcher<QByteArray> *fw = static_cast<QFutureWatcher<QByteArray>*>( sender() );
308 Q_ASSERT( fw );
309 Q_ASSERT( mJobs.contains( fw ) );
310 JobData jobData = mJobs.value( fw );
311
312 mJobs.remove( fw );
313 fw->deleteLater();
314
315 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( "3D" ), QStringLiteral( "DEM" ), jobData.tileId.text() );
316
317 QByteArray data = jobData.future.result();
318 emit heightMapReady( jobData.jobId, data );
319}
320
@ 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)
3
Definition: qgsaabb.h:34
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.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:296
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:333
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.
Definition: qgsvector3d.h:51
double x() const
Returns X coordinate.
Definition: qgsvector3d.h:49