QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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"
20 #include "qgsdemterraingenerator.h"
22 #include "qgseventtracing.h"
24 #include "qgsterrainentity_p.h"
26 #include "qgsterraintileentity_p.h"
27 #include "qgsterraingenerator.h"
28 
29 #include <Qt3DRender/QGeometryRenderer>
30 #include <Qt3DCore/QTransform>
31 #include <QMutexLocker>
32 
34 
35 static 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 
58 QgsDemTerrainTileLoader::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 );
68  mSkirtHeight = generator->skirtHeight();
69  }
70  else if ( terrainGenerator->type() == QgsTerrainGenerator::Online )
71  {
72  QgsOnlineTerrainGenerator *generator = static_cast<QgsOnlineTerrainGenerator *>( terrainGenerator );
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 
85 Qt3DCore::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 
134 void 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 
155 QgsDemHeightMapGenerator::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 
166 QgsDemHeightMapGenerator::~QgsDemHeightMapGenerator()
167 {
168  delete mClonedProvider;
169 }
170 
171 
172 static 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 
211 static 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 
216 int 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 
249 void 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 
274 void 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 
286 float 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 
305 void 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 
qgsrasterprojector.h
QgsRectangle::height
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
QgsTerrainGenerator::Dem
@ Dem
Terrain is built from raster layer with digital elevation model.
Definition: qgsterraingenerator.h:58
QgsCoordinateTransformContext
Contains information about the context in which a coordinate transform is executed.
Definition: qgscoordinatetransformcontext.h:57
qgsrasterlayer.h
Qgs3DMapSettings::terrainShadingMaterial
QgsPhongMaterialSettings terrainShadingMaterial() const
Returns terrain shading material.
Definition: qgs3dmapsettings.h:309
QgsTerrainGenerator::type
virtual Type type() const =0
What texture generator implementation is this.
QgsVector3D::y
double y() const
Returns Y coordinate.
Definition: qgsvector3d.h:64
QgsDemTerrainGenerator::skirtHeight
float skirtHeight() const
Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between ...
Definition: qgsdemterraingenerator.h:66
QgsTerrainDownloader
Takes care of downloading terrain data from a publicly available data source.
Definition: qgsterraindownloader.h:48
QgsRectangle::yMinimum
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
qgschunknode_p.h
qgsdemterraingenerator.h
qgsterraintileentity_p.h
QgsRectangle::intersect
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:333
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QgsDataProvider::transformContext
QgsCoordinateTransformContext transformContext() const
Returns data provider coordinate transform context.
Definition: qgsdataprovider.cpp:81
QgsRasterProjector
QgsRasterProjector implements approximate projection support for it calculates grid of points in sour...
Definition: qgsrasterprojector.h:47
QgsRasterDataProvider::setInput
bool setInput(QgsRasterInterface *input) override
Set input.
Definition: qgsrasterdataprovider.h:135
qgseventtracing.h
QgsDemTerrainGenerator::heightMapGenerator
QgsDemHeightMapGenerator * heightMapGenerator()
Returns height map generator object - takes care of extraction of elevations from the layer)
Definition: qgsdemterraingenerator.h:69
qgsonlineterraingenerator.h
QgsTerrainGenerator
Base class for generators of terrain.
Definition: qgsterraingenerator.h:49
qgsterraingenerator.h
QgsAABB
Axis-aligned bounding box - in world coords.
Definition: qgsaabb.h:33
QgsTerrainDownloader::getHeightMap
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,...
Definition: qgsterraindownloader.cpp:120
qgsdemterraintilegeometry_p.h
qgsdemterraintileloader_p.h
QgsOnlineTerrainGenerator::heightMapGenerator
QgsDemHeightMapGenerator * heightMapGenerator()
Returns height map generator object - takes care of extraction of elevations from the layer)
Definition: qgsonlineterraingenerator.h:61
Qgs3DMapSettings
Definition of the world.
Definition: qgs3dmapsettings.h:57
qgsterraindownloader.h
QgsRectangle::xMinimum
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
QgsRasterLayer
Represents a raster layer.
Definition: qgsrasterlayer.h:76
Qgs3DMapSettings::origin
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0)
Definition: qgs3dmapsettings.h:89
QgsTilingScheme::tileToExtent
QgsRectangle tileToExtent(int x, int y, int z) const
Returns map coordinates of the extent of a tile.
Definition: qgstilingscheme.cpp:43
QgsCoordinateReferenceSystem
This class represents a coordinate reference system (CRS).
Definition: qgscoordinatereferencesystem.h:211
QgsDemTerrainGenerator
Implementation of terrain generator that uses a raster layer with DEM to build terrain.
Definition: qgsdemterraingenerator.h:41
Qgs3DMapSettings::terrainGenerator
QgsTerrainGenerator * terrainGenerator() const
Returns the terrain generator.
Definition: qgs3dmapsettings.h:277
qgsterrainentity_p.h
qgs3dmapsettings.h
QgsRasterInterface
Base class for processing filters like renderers, reprojector, resampler etc.
Definition: qgsrasterinterface.h:135
QgsRectangle::yMaximum
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
Qgis::DataType::Float32
@ Float32
Thirty two bit floating point (float)
QgsTerrainGenerator::Online
@ Online
Terrain is built from downloaded tiles with digital elevation model.
Definition: qgsterraingenerator.h:59
QgsRectangle::width
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
Qgs3DMapSettings::layers
QList< QgsMapLayer * > layers() const
Returns the list of 3D map layers to be rendered in the scene.
Definition: qgs3dmapsettings.cpp:562
QgsOnlineTerrainGenerator
Implementation of terrain generator that uses online resources to download heightmaps.
Definition: qgsonlineterraingenerator.h:37
QgsTilingScheme
The class encapsulates tiling scheme (just like with WMTS / TMS / XYZ layers).
Definition: qgstilingscheme.h:37
QgsRectangle::grow
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:296
qgsterraintexturegenerator_p.h
QgsRasterInterface::block
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.
QgsOnlineTerrainGenerator::skirtHeight
float skirtHeight() const
Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between ...
Definition: qgsonlineterraingenerator.h:58
QgsVector3D::x
double x() const
Returns X coordinate.
Definition: qgsvector3d.h:62
QgsRasterDataProvider
Base class for raster data providers.
Definition: qgsrasterdataprovider.h:88
Qgs3DMapSettings::isTerrainShadingEnabled
bool isTerrainShadingEnabled() const
Returns whether terrain shading is enabled.
Definition: qgs3dmapsettings.h:295
QgsTerrainGenerator::tilingScheme
const QgsTilingScheme & tilingScheme() const
Returns tiling scheme of the terrain.
Definition: qgsterraingenerator.h:103
Qgs3DMapSettings::terrainVerticalScale
double terrainVerticalScale() const
Returns vertical scale (exaggeration) of terrain.
Definition: qgs3dmapsettings.cpp:541
QgsDataProvider::crs
virtual QgsCoordinateReferenceSystem crs() const =0
Returns the coordinate system for the data source.