QGIS API Documentation  3.6.0-Noosa (5873452)
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 "qgsterrainentity_p.h"
24 #include "qgsterraintileentity_p.h"
25 
26 #include <Qt3DRender/QGeometryRenderer>
27 
29 
30 static void _heightMapMinMax( const QByteArray &heightMap, float &zMin, float &zMax )
31 {
32  const float *zBits = ( const float * ) heightMap.constData();
33  int zCount = heightMap.count() / sizeof( float );
34  bool first = true;
35 
36  zMin = zMax = std::numeric_limits<float>::quiet_NaN();
37  for ( int i = 0; i < zCount; ++i )
38  {
39  float z = zBits[i];
40  if ( std::isnan( z ) )
41  continue;
42  if ( first )
43  {
44  zMin = zMax = z;
45  first = false;
46  }
47  zMin = std::min( zMin, z );
48  zMax = std::max( zMax, z );
49  }
50 }
51 
52 
53 QgsDemTerrainTileLoader::QgsDemTerrainTileLoader( QgsTerrainEntity *terrain, QgsChunkNode *node )
54  : QgsTerrainTileLoader( terrain, node )
55  , mResolution( 0 )
56 {
57 
58  const Qgs3DMapSettings &map = terrain->map3D();
59  QgsDemTerrainGenerator *generator = static_cast<QgsDemTerrainGenerator *>( map.terrainGenerator() );
60 
61  // get heightmap asynchronously
62  connect( generator->heightMapGenerator(), &QgsDemHeightMapGenerator::heightMapReady, this, &QgsDemTerrainTileLoader::onHeightMapReady );
63  mHeightMapJobId = generator->heightMapGenerator()->render( node->tileX(), node->tileY(), node->tileZ() );
64  mResolution = generator->heightMapGenerator()->resolution();
65  mSkirtHeight = generator->skirtHeight();
66 }
67 
68 Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *parent )
69 {
70  float zMin, zMax;
71  _heightMapMinMax( mHeightMap, zMin, zMax );
72 
73  if ( std::isnan( zMin ) || std::isnan( zMax ) )
74  {
75  // no data available for this tile
76  return nullptr;
77  }
78 
79  const Qgs3DMapSettings &map = terrain()->map3D();
80  QgsRectangle extent = map.terrainGenerator()->tilingScheme().tileToExtent( mNode->tileX(), mNode->tileY(), mNode->tileZ() ); //node->extent;
81  double x0 = extent.xMinimum() - map.origin().x();
82  double y0 = extent.yMinimum() - map.origin().y();
83  double side = extent.width();
84  double half = side / 2;
85 
86 
87  QgsTerrainTileEntity *entity = new QgsTerrainTileEntity;
88 
89  // create geometry renderer
90 
91  Qt3DRender::QGeometryRenderer *mesh = new Qt3DRender::QGeometryRenderer;
92  mesh->setGeometry( new DemTerrainTileGeometry( mResolution, side, map.terrainVerticalScale(), mSkirtHeight, mHeightMap, mesh ) );
93  entity->addComponent( mesh ); // takes ownership if the component has no parent
94 
95  // create material
96 
97  createTextureComponent( entity, map.isTerrainShadingEnabled(), map.terrainShadingMaterial() );
98 
99  // create transform
100 
101  Qt3DCore::QTransform *transform = nullptr;
102  transform = new Qt3DCore::QTransform();
103  entity->addComponent( transform );
104 
105  transform->setScale( side );
106  transform->setTranslation( QVector3D( x0 + half, 0, - ( y0 + half ) ) );
107 
108  mNode->setExactBbox( QgsAABB( x0, zMin * map.terrainVerticalScale(), -y0, x0 + side, zMax * map.terrainVerticalScale(), -( y0 + side ) ) );
109 
110  entity->setEnabled( false );
111  entity->setParent( parent );
112  return entity;
113 }
114 
115 void QgsDemTerrainTileLoader::onHeightMapReady( int jobId, const QByteArray &heightMap )
116 {
117  if ( mHeightMapJobId == jobId )
118  {
119  this->mHeightMap = heightMap;
120  mHeightMapJobId = -1;
121 
122  // continue loading - texture
123  loadTexture();
124  }
125 }
126 
127 
128 // ---------------------
129 
130 #include <qgsrasterlayer.h>
131 #include <qgsrasterprojector.h>
132 #include <QtConcurrent/QtConcurrentRun>
133 #include <QFutureWatcher>
134 
135 QgsDemHeightMapGenerator::QgsDemHeightMapGenerator( QgsRasterLayer *dtm, const QgsTilingScheme &tilingScheme, int resolution )
136  : mDtm( dtm )
137  , mClonedProvider( ( QgsRasterDataProvider * )dtm->dataProvider()->clone() )
138  , mTilingScheme( tilingScheme )
139  , mResolution( resolution )
140  , mLastJobId( 0 )
141 {
142 }
143 
144 QgsDemHeightMapGenerator::~QgsDemHeightMapGenerator()
145 {
146  delete mClonedProvider;
147 }
148 
149 #include <QElapsedTimer>
150 
151 static QByteArray _readDtmData( QgsRasterDataProvider *provider, const QgsRectangle &extent, int res, const QgsCoordinateReferenceSystem &destCrs )
152 {
153  QElapsedTimer t;
154  t.start();
155 
156  // TODO: use feedback object? (but GDAL currently does not support cancelation anyway)
157  QgsRasterInterface *input = provider;
158  std::unique_ptr<QgsRasterProjector> projector;
159  if ( provider->crs() != destCrs )
160  {
161  projector.reset( new QgsRasterProjector );
162  projector->setCrs( provider->crs(), destCrs );
163  projector->setInput( provider );
164  input = projector.get();
165  }
166  std::unique_ptr< QgsRasterBlock > block( input->block( 1, extent, res, res ) );
167 
168  QByteArray data;
169  if ( block )
170  {
171  block->convert( Qgis::Float32 ); // currently we expect just floats
172  data = block->data();
173  data.detach(); // this should make a deep copy
174 
175  if ( block->hasNoData() )
176  {
177  // turn all no-data values into NaN in the output array
178  float *floatData = reinterpret_cast<float *>( data.data() );
179  Q_ASSERT( data.count() % sizeof( float ) == 0 );
180  int count = data.count() / sizeof( float );
181  for ( int i = 0; i < count; ++i )
182  {
183  if ( block->isNoData( i ) )
184  floatData[i] = std::numeric_limits<float>::quiet_NaN();
185  }
186  }
187  }
188  return data;
189 }
190 
191 int QgsDemHeightMapGenerator::render( int x, int y, int z )
192 {
193  Q_ASSERT( mJobs.isEmpty() ); // should be always just one active job...
194 
195  // extend the rect by half-pixel on each side? to get the values in "corners"
196  QgsRectangle extent = mTilingScheme.tileToExtent( x, y, z );
197  float mapUnitsPerPixel = extent.width() / mResolution;
198  extent.grow( mapUnitsPerPixel / 2 );
199  // but make sure not to go beyond the full extent (returns invalid values)
200  QgsRectangle fullExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
201  extent = extent.intersect( fullExtent );
202 
203  JobData jd;
204  jd.jobId = ++mLastJobId;
205  jd.extent = extent;
206  jd.timer.start();
207  // make a clone of the data provider so it is safe to use in worker thread
208  jd.future = QtConcurrent::run( _readDtmData, mClonedProvider, extent, mResolution, mTilingScheme.crs() );
209 
210  QFutureWatcher<QByteArray> *fw = new QFutureWatcher<QByteArray>( nullptr );
211  fw->setFuture( jd.future );
212  connect( fw, &QFutureWatcher<QByteArray>::finished, this, &QgsDemHeightMapGenerator::onFutureFinished );
213 
214  mJobs.insert( fw, jd );
215 
216  return jd.jobId;
217 }
218 
219 QByteArray QgsDemHeightMapGenerator::renderSynchronously( int x, int y, int z )
220 {
221  // extend the rect by half-pixel on each side? to get the values in "corners"
222  QgsRectangle extent = mTilingScheme.tileToExtent( x, y, z );
223  float mapUnitsPerPixel = extent.width() / mResolution;
224  extent.grow( mapUnitsPerPixel / 2 );
225  // but make sure not to go beyond the full extent (returns invalid values)
226  QgsRectangle fullExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
227  extent = extent.intersect( fullExtent );
228 
229  std::unique_ptr< QgsRasterBlock > block( mDtm->dataProvider()->block( 1, extent, mResolution, mResolution ) );
230 
231  QByteArray data;
232  if ( block )
233  {
234  block->convert( Qgis::Float32 ); // currently we expect just floats
235  data = block->data();
236  data.detach(); // this should make a deep copy
237  }
238 
239  return data;
240 }
241 
242 float QgsDemHeightMapGenerator::heightAt( double x, double y )
243 {
244  // TODO: this is quite a primitive implementation: better to use heightmaps currently in use
245  int res = 1024;
246  QgsRectangle rect = mDtm->extent();
247  if ( mDtmCoarseData.isEmpty() )
248  {
249  std::unique_ptr< QgsRasterBlock > block( mDtm->dataProvider()->block( 1, rect, res, res ) );
250  block->convert( Qgis::Float32 );
251  mDtmCoarseData = block->data();
252  mDtmCoarseData.detach(); // make a deep copy
253  }
254 
255  int cellX = ( int )( ( x - rect.xMinimum() ) / rect.width() * res + .5f );
256  int cellY = ( int )( ( rect.yMaximum() - y ) / rect.height() * res + .5f );
257  cellX = qBound( 0, cellX, res - 1 );
258  cellY = qBound( 0, cellY, res - 1 );
259 
260  const float *data = ( const float * ) mDtmCoarseData.constData();
261  return data[cellX + cellY * res];
262 }
263 
264 void QgsDemHeightMapGenerator::onFutureFinished()
265 {
266  QFutureWatcher<QByteArray> *fw = static_cast<QFutureWatcher<QByteArray>*>( sender() );
267  Q_ASSERT( fw );
268  Q_ASSERT( mJobs.contains( fw ) );
269  JobData jobData = mJobs.value( fw );
270 
271  mJobs.remove( fw );
272  fw->deleteLater();
273 
274  QByteArray data = jobData.future.result();
275  emit heightMapReady( jobData.jobId, data );
276 }
277 
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.
QgsPhongMaterialSettings terrainShadingMaterial() const
Returns terrain shading material.
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
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:87
double y() const
Returns Y coordinate.
Definition: qgsvector3d.h:51
3 Definition of the world
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0)
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.
Base class for processing filters like renderers, reprojector, resampler etc.
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...
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
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...