QGIS API Documentation 3.28.14-Firenze (exported)
Loading...
Searching...
No Matches
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"
22#include "qgseventtracing.h"
24#include "qgsterrainentity_p.h"
27#include "qgsterraingenerator.h"
28
29#include <Qt3DRender/QGeometryRenderer>
30#include <Qt3DCore/QTransform>
31#include <QMutexLocker>
32
34
35static 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
58QgsDemTerrainTileLoader::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 );
67 heightMapGenerator = generator->heightMapGenerator();
68 mSkirtHeight = generator->skirtHeight();
69 }
70 else if ( terrainGenerator->type() == QgsTerrainGenerator::Online )
71 {
72 QgsOnlineTerrainGenerator *generator = static_cast<QgsOnlineTerrainGenerator *>( terrainGenerator );
73 heightMapGenerator = generator->heightMapGenerator();
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
85Qt3DCore::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->setParent( parent );
130 return entity;
131}
132
133void QgsDemTerrainTileLoader::onHeightMapReady( int jobId, const QByteArray &heightMap )
134{
135 if ( mHeightMapJobId == jobId )
136 {
137 this->mHeightMap = heightMap;
138 mHeightMapJobId = -1;
139
140 // continue loading - texture
141 loadTexture();
142 }
143}
144
145
146// ---------------------
147
148#include <qgsrasterlayer.h>
149#include <qgsrasterprojector.h>
150#include <QtConcurrent/QtConcurrentRun>
151#include <QFutureWatcher>
152#include "qgsterraindownloader.h"
153
154QgsDemHeightMapGenerator::QgsDemHeightMapGenerator( QgsRasterLayer *dtm, const QgsTilingScheme &tilingScheme, int resolution, const QgsCoordinateTransformContext &transformContext )
155 : mDtm( dtm )
156 , mClonedProvider( dtm ? qgis::down_cast<QgsRasterDataProvider *>( dtm->dataProvider()->clone() ) : nullptr )
157 , mTilingScheme( tilingScheme )
158 , mResolution( resolution )
159 , mLastJobId( 0 )
160 , mDownloader( dtm ? nullptr : new QgsTerrainDownloader( transformContext ) )
161 , mTransformContext( transformContext )
162{
163}
164
165QgsDemHeightMapGenerator::~QgsDemHeightMapGenerator()
166{
167 delete mClonedProvider;
168}
169
170
171static QByteArray _readDtmData( QgsRasterDataProvider *provider, const QgsRectangle &extent, int res, const QgsCoordinateReferenceSystem &destCrs )
172{
173 provider->moveToThread( QThread::currentThread() );
174
175 QgsEventTracing::ScopedEvent e( QStringLiteral( "3D" ), QStringLiteral( "DEM" ) );
176
177 // TODO: use feedback object? (but GDAL currently does not support cancellation anyway)
178 QgsRasterInterface *input = provider;
179 std::unique_ptr<QgsRasterProjector> projector;
180 if ( provider->crs() != destCrs )
181 {
182 projector.reset( new QgsRasterProjector );
183 projector->setCrs( provider->crs(), destCrs, provider->transformContext() );
184 projector->setInput( provider );
185 input = projector.get();
186 }
187 std::unique_ptr< QgsRasterBlock > block( input->block( 1, extent, res, res ) );
188
189 QByteArray data;
190 if ( block )
191 {
192 block->convert( Qgis::DataType::Float32 ); // currently we expect just floats
193 data = block->data();
194 data.detach(); // this should make a deep copy
195
196 if ( block->hasNoData() )
197 {
198 // turn all no-data values into NaN in the output array
199 float *floatData = reinterpret_cast<float *>( data.data() );
200 Q_ASSERT( data.count() % sizeof( float ) == 0 );
201 int count = data.count() / sizeof( float );
202 for ( int i = 0; i < count; ++i )
203 {
204 if ( block->isNoData( i ) )
205 floatData[i] = std::numeric_limits<float>::quiet_NaN();
206 }
207 }
208 }
209
210 provider->moveToThread( nullptr );
211
212 return data;
213}
214
215static QByteArray _readOnlineDtm( QgsTerrainDownloader *downloader, const QgsRectangle &extent, int res, const QgsCoordinateReferenceSystem &destCrs, const QgsCoordinateTransformContext &context )
216{
217 return downloader->getHeightMap( extent, res, destCrs, context );
218}
219
220int QgsDemHeightMapGenerator::render( const QgsChunkNodeId &nodeId )
221{
222 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral( "3D" ), QStringLiteral( "DEM" ), nodeId.text() );
223
224 // extend the rect by half-pixel on each side? to get the values in "corners"
225 QgsRectangle extent = mTilingScheme.tileToExtent( nodeId );
226 float mapUnitsPerPixel = extent.width() / mResolution;
227 extent.grow( mapUnitsPerPixel / 2 );
228 // but make sure not to go beyond the full extent (returns invalid values)
229 QgsRectangle fullExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
230 extent = extent.intersect( fullExtent );
231
232 JobData jd;
233 jd.jobId = ++mLastJobId;
234 jd.tileId = nodeId;
235 jd.extent = extent;
236 jd.timer.start();
237 QFutureWatcher<QByteArray> *fw = new QFutureWatcher<QByteArray>( nullptr );
238 connect( fw, &QFutureWatcher<QByteArray>::finished, this, &QgsDemHeightMapGenerator::onFutureFinished );
239 connect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
240 // make a clone of the data provider so it is safe to use in worker thread
241 if ( mDtm )
242 {
243 mClonedProvider->moveToThread( nullptr );
244 jd.future = QtConcurrent::run( _readDtmData, mClonedProvider, extent, mResolution, mTilingScheme.crs() );
245 }
246 else
247 {
248 jd.future = QtConcurrent::run( _readOnlineDtm, mDownloader.get(), extent, mResolution, mTilingScheme.crs(), mTransformContext );
249 }
250
251 fw->setFuture( jd.future );
252
253 mJobs.insert( fw, jd );
254
255 return jd.jobId;
256}
257
258void QgsDemHeightMapGenerator::waitForFinished()
259{
260 for ( QFutureWatcher<QByteArray> *fw : mJobs.keys() )
261 {
262 disconnect( fw, &QFutureWatcher<QByteArray>::finished, this, &QgsDemHeightMapGenerator::onFutureFinished );
263 disconnect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
264 }
265 QVector<QFutureWatcher<QByteArray>*> toBeDeleted;
266 for ( QFutureWatcher<QByteArray> *fw : mJobs.keys() )
267 {
268 fw->waitForFinished();
269 JobData jobData = mJobs.value( fw );
270 toBeDeleted.push_back( fw );
271
272 QByteArray data = jobData.future.result();
273 emit heightMapReady( jobData.jobId, data );
274 }
275
276 for ( QFutureWatcher<QByteArray> *fw : toBeDeleted )
277 {
278 mJobs.remove( fw );
279 fw->deleteLater();
280 }
281}
282
283void QgsDemHeightMapGenerator::lazyLoadDtmCoarseData( int res, const QgsRectangle &rect )
284{
285 QMutexLocker locker( &mLazyLoadDtmCoarseDataMutex );
286 if ( mDtmCoarseData.isEmpty() )
287 {
288 std::unique_ptr< QgsRasterBlock > block( mDtm->dataProvider()->block( 1, rect, res, res ) );
289 block->convert( Qgis::DataType::Float32 );
290 mDtmCoarseData = block->data();
291 mDtmCoarseData.detach(); // make a deep copy
292 }
293}
294
295float QgsDemHeightMapGenerator::heightAt( double x, double y )
296{
297 if ( !mDtm )
298 return 0; // TODO: calculate heights for online DTM
299
300 // TODO: this is quite a primitive implementation: better to use heightmaps currently in use
301 int res = 1024;
302 QgsRectangle rect = mDtm->extent();
303 lazyLoadDtmCoarseData( res, rect );
304
305 int cellX = ( int )( ( x - rect.xMinimum() ) / rect.width() * res + .5f );
306 int cellY = ( int )( ( rect.yMaximum() - y ) / rect.height() * res + .5f );
307 cellX = std::clamp( cellX, 0, res - 1 );
308 cellY = std::clamp( cellY, 0, res - 1 );
309
310 const float *data = ( const float * ) mDtmCoarseData.constData();
311 return data[cellX + cellY * res];
312}
313
314void QgsDemHeightMapGenerator::onFutureFinished()
315{
316 QFutureWatcher<QByteArray> *fw = static_cast<QFutureWatcher<QByteArray>*>( sender() );
317 Q_ASSERT( fw );
318 Q_ASSERT( mJobs.contains( fw ) );
319 JobData jobData = mJobs.value( fw );
320
321 mJobs.remove( fw );
322 fw->deleteLater();
323
324 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( "3D" ), QStringLiteral( "DEM" ), jobData.tileId.text() );
325
326 QByteArray data = jobData.future.result();
327 emit heightMapReady( jobData.jobId, data );
328}
329
@ Float32
Thirty two bit floating point (float)
double terrainVerticalScale() const
Returns vertical scale (exaggeration) of terrain.
QgsTerrainGenerator * terrainGenerator() const
Returns the terrain generator.
bool isTerrainShadingEnabled() const
Returns whether terrain shading is enabled.
QgsPhongMaterialSettings terrainShadingMaterial() const
Returns terrain shading material.
QList< QgsMapLayer * > layers() const
Returns the list of 3D map layers to be rendered in the scene.
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0)
This class represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
QgsCoordinateTransformContext transformContext() const
Returns data provider coordinate transform context.
virtual QgsCoordinateReferenceSystem crs() const =0
Returns the coordinate system for the data source.
QgsDemHeightMapGenerator * heightMapGenerator()
Returns height map generator object - takes care of extraction of elevations from the layer)
float skirtHeight() const
Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between ...
QgsDemHeightMapGenerator * heightMapGenerator()
Returns height map generator object - takes care of extraction of elevations from the layer)
float skirtHeight() const
Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between ...
Base class for raster data providers.
bool setInput(QgsRasterInterface *input) override
Set input.
Base class for processing filters like renderers, reprojector, resampler etc.
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.
Represents a raster layer.
QgsRasterProjector implements approximate projection support for it calculates grid of points in sour...
A rectangle specified with double values.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double width() const
Returns the width of the rectangle.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
void grow(double delta)
Grows the rectangle in place by the specified amount.
double height() const
Returns the height of the rectangle.
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
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,...
@ Dem
Terrain is built from raster layer with digital elevation model.
@ Online
Terrain is built from downloaded tiles with digital elevation model.
virtual Type type() const =0
What texture generator implementation is this.
const QgsTilingScheme & tilingScheme() const
Returns tiling scheme of the terrain.
QgsRectangle tileToExtent(int x, int y, int z) const
Returns map coordinates of the extent of a tile.
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:51
double x() const
Returns X coordinate.
Definition qgsvector3d.h:49