QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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() );
66  heightMapGenerator = generator->heightMapGenerator();
67  mSkirtHeight = generator->skirtHeight();
68  }
69  else if ( map.terrainGenerator()->type() == QgsTerrainGenerator::Online )
70  {
71  QgsOnlineTerrainGenerator *generator = static_cast<QgsOnlineTerrainGenerator *>( map.terrainGenerator() );
72  heightMapGenerator = generator->heightMapGenerator();
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 
3 Axis-aligned bounding box - in world coords.
Definition: qgsaabb.h:30
A rectangle specified with double values.
Definition: qgsrectangle.h:41
bool setInput(QgsRasterInterface *input) override
Set input.
QgsTerrainGenerator * clone() const override
Makes a copy of the current instance.
bool isTerrainShadingEnabled() const
Returns whether terrain shading is enabled.
QgsDemHeightMapGenerator * heightMapGenerator()
Returns height map generator object - takes care of extraction of elevations from the layer) ...
QgsPhongMaterialSettings terrainShadingMaterial() const
Returns terrain shading material.
Represents a raster layer.
3 Implementation of terrain generator that uses online resources to download heightmaps.
virtual Type type() const =0
What texture generator implementation is this.
int resolution() const
Returns resolution of the generator (how many elevation samples on one side of a terrain tile) ...
Thirty two bit floating point (float)
Definition: qgis.h:109
float skirtHeight() const
Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between ...
double y() const
Returns Y coordinate.
Definition: qgsvector3d.h:51
3 Definition of the world
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, download necessary tile images (if not cached already) and produce height map out of them (byte array of res*res float values)
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0)
Terrain is built from raster layer with digital elevation model.
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:275
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.
virtual QgsCoordinateReferenceSystem crs() const =0
Returns the coordinate system for the data source.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:312
float skirtHeight() const
Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between ...
QgsRectangle extent() const override
extent of the terrain in terrain&#39;s CRS
const QgsTilingScheme & tilingScheme() const
Returns tiling scheme of the terrain.
Contains information about the context in which a coordinate transform is executed.
Base class for processing filters like renderers, reprojector, resampler etc.
3 Takes care of downloading terrain data from a publicly available data source.
double terrainVerticalScale() const
Returns vertical scale (exaggeration) of terrain.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
QgsRasterProjector implements approximate projection support for it calculates grid of points in sour...
QgsCoordinateTransformContext transformContext() const
Returns data provider coordinate transform context.
QgsDemHeightMapGenerator * heightMapGenerator()
Returns height map generator object - takes care of extraction of elevations from the layer) ...
3 Implementation of terrain generator that uses a raster layer with DEM to build terrain.
This class represents a coordinate reference system (CRS).
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:172
Terrain is built from downloaded tiles with digital elevation model.
3 The class encapsulates tiling scheme (just like with WMTS / TMS / XYZ layers).
QgsRectangle tileToExtent(int x, int y, int z) const
Returns map coordinates of the extent of a tile.
double x() const
Returns X coordinate.
Definition: qgsvector3d.h:49
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
Base class for raster data providers.
QgsTerrainGenerator * terrainGenerator() const
Returns terrain generator. It takes care of producing terrain tiles from the input data...