QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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  QgsTerrainTileEntity *entity = new QgsTerrainTileEntity;
80 
81  // create geometry renderer
82 
83  Qt3DRender::QGeometryRenderer *mesh = new Qt3DRender::QGeometryRenderer;
84  mesh->setGeometry( new DemTerrainTileGeometry( mResolution, mSkirtHeight, mHeightMap, mesh ) );
85  entity->addComponent( mesh ); // takes ownership if the component has no parent
86 
87  // create material
88 
89  createTextureComponent( entity );
90 
91  // create transform
92 
93  Qt3DCore::QTransform *transform = nullptr;
94  transform = new Qt3DCore::QTransform();
95  entity->addComponent( transform );
96 
97  const Qgs3DMapSettings &map = terrain()->map3D();
98  QgsRectangle extent = map.terrainGenerator()->tilingScheme().tileToExtent( mNode->tileX(), mNode->tileY(), mNode->tileZ() ); //node->extent;
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  transform->setScale3D( QVector3D( side, map.terrainVerticalScale(), side ) );
105  transform->setTranslation( QVector3D( x0 + half, 0, - ( y0 + half ) ) );
106 
107  mNode->setExactBbox( QgsAABB( x0, zMin * map.terrainVerticalScale(), -y0, x0 + side, zMax * map.terrainVerticalScale(), -( y0 + side ) ) );
108 
109  entity->setEnabled( false );
110  entity->setParent( parent );
111  return entity;
112 }
113 
114 void QgsDemTerrainTileLoader::onHeightMapReady( int jobId, const QByteArray &heightMap )
115 {
116  if ( mHeightMapJobId == jobId )
117  {
118  this->mHeightMap = heightMap;
119  mHeightMapJobId = -1;
120 
121  // continue loading - texture
122  loadTexture();
123  }
124 }
125 
126 
127 // ---------------------
128 
129 #include <qgsrasterlayer.h>
130 #include <qgsrasterprojector.h>
131 #include <QtConcurrent/QtConcurrentRun>
132 #include <QFutureWatcher>
133 
134 QgsDemHeightMapGenerator::QgsDemHeightMapGenerator( QgsRasterLayer *dtm, const QgsTilingScheme &tilingScheme, int resolution )
135  : mDtm( dtm )
136  , mClonedProvider( ( QgsRasterDataProvider * )dtm->dataProvider()->clone() )
137  , mTilingScheme( tilingScheme )
138  , mResolution( resolution )
139  , mLastJobId( 0 )
140 {
141 }
142 
143 QgsDemHeightMapGenerator::~QgsDemHeightMapGenerator()
144 {
145  delete mClonedProvider;
146 }
147 
148 #include <QElapsedTimer>
149 
150 static QByteArray _readDtmData( QgsRasterDataProvider *provider, const QgsRectangle &extent, int res, const QgsCoordinateReferenceSystem &destCrs )
151 {
152  QElapsedTimer t;
153  t.start();
154 
155  // TODO: use feedback object? (but GDAL currently does not support cancellation anyway)
156  QgsRasterInterface *input = provider;
157  std::unique_ptr<QgsRasterProjector> projector;
158  if ( provider->crs() != destCrs )
159  {
160  projector.reset( new QgsRasterProjector );
161  projector->setCrs( provider->crs(), destCrs );
162  projector->setInput( provider );
163  input = projector.get();
164  }
165  std::unique_ptr< QgsRasterBlock > block( input->block( 1, extent, res, res ) );
166 
167  QByteArray data;
168  if ( block )
169  {
170  block->convert( Qgis::Float32 ); // currently we expect just floats
171  data = block->data();
172  data.detach(); // this should make a deep copy
173 
174  if ( block->hasNoData() )
175  {
176  // turn all no-data values into NaN in the output array
177  float *floatData = reinterpret_cast<float *>( data.data() );
178  Q_ASSERT( data.count() % sizeof( float ) == 0 );
179  int count = data.count() / sizeof( float );
180  for ( int i = 0; i < count; ++i )
181  {
182  if ( block->isNoData( i ) )
183  floatData[i] = std::numeric_limits<float>::quiet_NaN();
184  }
185  }
186  }
187  return data;
188 }
189 
190 int QgsDemHeightMapGenerator::render( int x, int y, int z )
191 {
192  Q_ASSERT( mJobs.isEmpty() ); // should be always just one active job...
193 
194  // extend the rect by half-pixel on each side? to get the values in "corners"
195  QgsRectangle extent = mTilingScheme.tileToExtent( x, y, z );
196  float mapUnitsPerPixel = extent.width() / mResolution;
197  extent.grow( mapUnitsPerPixel / 2 );
198  // but make sure not to go beyond the full extent (returns invalid values)
199  QgsRectangle fullExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
200  extent = extent.intersect( fullExtent );
201 
202  JobData jd;
203  jd.jobId = ++mLastJobId;
204  jd.extent = extent;
205  jd.timer.start();
206  // make a clone of the data provider so it is safe to use in worker thread
207  jd.future = QtConcurrent::run( _readDtmData, mClonedProvider, extent, mResolution, mTilingScheme.crs() );
208 
209  QFutureWatcher<QByteArray> *fw = new QFutureWatcher<QByteArray>( nullptr );
210  fw->setFuture( jd.future );
211  connect( fw, &QFutureWatcher<QByteArray>::finished, this, &QgsDemHeightMapGenerator::onFutureFinished );
212 
213  mJobs.insert( fw, jd );
214 
215  return jd.jobId;
216 }
217 
218 QByteArray QgsDemHeightMapGenerator::renderSynchronously( int x, int y, int z )
219 {
220  // extend the rect by half-pixel on each side? to get the values in "corners"
221  QgsRectangle extent = mTilingScheme.tileToExtent( x, y, z );
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  std::unique_ptr< QgsRasterBlock > block( mDtm->dataProvider()->block( 1, extent, mResolution, mResolution ) );
229 
230  QByteArray data;
231  if ( block )
232  {
233  block->convert( Qgis::Float32 ); // currently we expect just floats
234  data = block->data();
235  data.detach(); // this should make a deep copy
236  }
237 
238  return data;
239 }
240 
241 float QgsDemHeightMapGenerator::heightAt( double x, double y )
242 {
243  // TODO: this is quite a primitive implementation: better to use heightmaps currently in use
244  int res = 1024;
245  QgsRectangle rect = mDtm->extent();
246  if ( mDtmCoarseData.isEmpty() )
247  {
248  std::unique_ptr< QgsRasterBlock > block( mDtm->dataProvider()->block( 1, rect, res, res ) );
249  block->convert( Qgis::Float32 );
250  mDtmCoarseData = block->data();
251  mDtmCoarseData.detach(); // make a deep copy
252  }
253 
254  int cellX = ( int )( ( x - rect.xMinimum() ) / rect.width() * res + .5f );
255  int cellY = ( int )( ( rect.yMaximum() - y ) / rect.height() * res + .5f );
256  cellX = qBound( 0, cellX, res - 1 );
257  cellY = qBound( 0, cellY, res - 1 );
258 
259  const float *data = ( const float * ) mDtmCoarseData.constData();
260  return data[cellX + cellY * res];
261 }
262 
263 void QgsDemHeightMapGenerator::onFutureFinished()
264 {
265  QFutureWatcher<QByteArray> *fw = static_cast<QFutureWatcher<QByteArray>*>( sender() );
266  Q_ASSERT( fw );
267  Q_ASSERT( mJobs.contains( fw ) );
268  JobData jobData = mJobs.value( fw );
269 
270  mJobs.remove( fw );
271  fw->deleteLater();
272 
273  QByteArray data = jobData.future.result();
274  emit heightMapReady( jobData.jobId, data );
275 }
276 
3 Axis-aligned bounding box - in world coords.
Definition: qgsaabb.h:30
A rectangle specified with double values.
Definition: qgsrectangle.h:40
bool setInput(QgsRasterInterface *input) override
Set input.
QgsTerrainGenerator * clone() const override
Makes a copy of the current instance.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:171
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
Thirty two bit floating point (float)
Definition: qgis.h:100
const QgsTilingScheme & tilingScheme() const
Returns tiling scheme of the terrain.
3 Definition of the world
double x() const
Returns X coordinate.
Definition: qgsvector3d.h:49
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:176
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:274
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.
QgsRectangle tileToExtent(int x, int y, int z) const
Returns map coordinates of the extent of a tile.
QgsRectangle extent() const override
extent of the terrain in terrain&#39;s CRS
int resolution() const
Returns resolution of the generator (how many elevation samples on one side of a terrain tile) ...
Base class for processing filters like renderers, reprojector, resampler etc.
double y() const
Returns Y coordinate.
Definition: qgsvector3d.h:51
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:311
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) ...
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0)
3 Implementation of terrain generator that uses a raster layer with DEM to build terrain.
This class represents a coordinate reference system (CRS).
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:201
3 The class encapsulates tiling scheme (just like with WMTS / TMS / XYZ layers).
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:166
double terrainVerticalScale() const
Returns vertical scale (exaggeration) of terrain.
QgsTerrainGenerator * terrainGenerator() const
Returns terrain generator. It takes care of producing terrain tiles from the input data...
float skirtHeight() const
Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between ...
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:208
Base class for raster data providers.