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