QGIS API Documentation 4.3.0-Master (9dce731a7a1)
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 <limits>
19#include <memory>
20
21#include "qgs3dmapsettings.h"
22#include "qgschunknode.h"
24#include "qgseventtracing.h"
26#include "qgsrasterlayer.h"
27#include "qgsrasterprojector.h"
29#include "qgsterraingenerator.h"
31
32#include <QFutureWatcher>
33#include <QMutexLocker>
34#include <QString>
35#include <Qt3DCore/QTransform>
36#include <Qt3DRender/QGeometryRenderer>
37#include <QtConcurrentRun>
38
39#include "moc_qgsdemterraintileloader_p.cpp"
40
41using namespace Qt::StringLiterals;
42
44
45QgsDemHeightMapGenerator::QgsDemHeightMapGenerator( QgsRasterLayer *dtm, const QgsTilingScheme &tilingScheme, int resolution, const QgsCoordinateTransformContext &transformContext )
46 : mDtmExtent( dtm ? dtm->extent() : QgsRectangle() )
47 , mClonedProvider( dtm ? qgis::down_cast<QgsRasterDataProvider *>( dtm->dataProvider()->clone() ) : nullptr )
48 , mTilingScheme( tilingScheme )
49 , mResolution( resolution )
50 , mDownloader( dtm ? nullptr : new QgsTerrainDownloader( transformContext ) )
51 , mTransformContext( transformContext )
52{}
53
54QgsDemHeightMapGenerator::~QgsDemHeightMapGenerator()
55{}
56
57
58static QByteArray readDtmData( QgsRasterDataProvider *provider, const QgsRectangle &extent, int res, const QgsCoordinateReferenceSystem &destCrs, const QgsRectangle &clippingExtent )
59{
60 provider->moveToThread( QThread::currentThread() );
61
62 QgsScopedEvent e( u"3D"_s, u"DEM"_s );
63
64 // TODO: use feedback object? (but GDAL currently does not support cancellation anyway)
65 QgsRasterInterface *input = provider;
66 std::unique_ptr<QgsRasterProjector> projector;
67 if ( provider->crs() != destCrs )
68 {
69 projector = std::make_unique<QgsRasterProjector>();
70 projector->setCrs( provider->crs(), destCrs, provider->transformContext() );
71 projector->setInput( provider );
72 input = projector.get();
73 }
74 std::unique_ptr<QgsRasterBlock> block( input->block( 1, extent, res, res ) );
75
76 QByteArray data;
77 if ( block )
78 {
79 block->convert( Qgis::DataType::Float32 ); // currently we expect just floats
80
81 // set noData outside our clippingExtent
82 const QRect subRect = QgsRasterBlock::subRect( extent, block->width(), block->height(), clippingExtent );
83 if ( !block->hasNoDataValue() )
84 {
85 // QgsRasterBlock::setIsNoDataExcept() misbehaves without a defined no data value
86 // see https://github.com/qgis/QGIS/issues/51285
87 block->setNoDataValue( std::numeric_limits<float>::lowest() );
88 }
89 block->setIsNoDataExcept( subRect );
90
91 data = block->data();
92 data.detach(); // this should make a deep copy
93
94 if ( block->hasNoData() )
95 {
96 // turn all no-data values into NaN in the output array
97 float *floatData = reinterpret_cast<float *>( data.data() );
98 Q_ASSERT( data.count() % sizeof( float ) == 0 );
99 int count = data.count() / sizeof( float );
100 for ( int i = 0; i < count; ++i )
101 {
102 if ( block->isNoData( i ) )
103 floatData[i] = std::numeric_limits<float>::quiet_NaN();
104 }
105 }
106 }
107
108 delete provider;
109 return data;
110}
111
112static QByteArray readOnlineDtm( QgsTerrainDownloader *downloader, const QgsRectangle &extent, int res, const QgsCoordinateReferenceSystem &destCrs, const QgsCoordinateTransformContext &context )
113{
114 return downloader->getHeightMap( extent, res, destCrs, context );
115}
116
117QFuture<QByteArray> QgsDemHeightMapGenerator::render( const QgsChunkNodeId &nodeId )
118{
119 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, u"3D"_s, u"DEM"_s, nodeId.text() );
120
121 // extend the rect by half-pixel on each side? to get the values in "corners"
122 QgsRectangle extent = mTilingScheme.tileToExtent( nodeId );
123 float mapUnitsPerPixel = extent.width() / mResolution;
124 extent.grow( mapUnitsPerPixel / 2 );
125 // but make sure not to go beyond the root tile's full extent (returns invalid values)
126 QgsRectangle rootTileExtent = mTilingScheme.tileToExtent( 0, 0, 0 );
127 extent = extent.intersect( rootTileExtent );
128
129 QFutureWatcher<QByteArray> *fw = new QFutureWatcher<QByteArray>( nullptr );
130 connect( fw, &QFutureWatcher<QByteArray>::finished, this, [this, fw]() { mJobs.remove( fw ); } );
131 connect( fw, &QFutureWatcher<QByteArray>::finished, fw, &QObject::deleteLater );
132
133 QFuture<QByteArray> future;
134 if ( mClonedProvider )
135 {
136 // make a clone of the data provider so it is safe to use in worker thread
137 std::unique_ptr<QgsRasterDataProvider> clonedProviderClone( mClonedProvider->clone() );
138 clonedProviderClone->moveToThread( nullptr );
139 future = QtConcurrent::run( readDtmData, clonedProviderClone.release(), extent, mResolution, mTilingScheme.crs(), mTilingScheme.fullExtent() );
140 }
141 else
142 {
143 future = QtConcurrent::run( readOnlineDtm, mDownloader.get(), extent, mResolution, mTilingScheme.crs(), mTransformContext );
144 }
145 mJobs[fw] = future;
146 fw->setFuture( future );
147 return future;
148}
149
150void QgsDemHeightMapGenerator::waitForFinished()
151{
152 QVector<QFutureWatcher<QByteArray> *> toBeDeleted;
153 for ( auto it = mJobs.keyBegin(); it != mJobs.keyEnd(); it++ )
154 {
155 toBeDeleted.push_back( *it );
156 }
157
158 for ( QFutureWatcher<QByteArray> *fw : toBeDeleted )
159 {
160 fw->waitForFinished(); // Signals will delete the job and the FutureWatcher
161 }
162}
163
164void QgsDemHeightMapGenerator::lazyLoadDtmCoarseData( int res, const QgsRectangle &rect )
165{
166 QMutexLocker locker( &mLazyLoadDtmCoarseDataMutex );
167 if ( !mDtmCoarseRasterBlock )
168 {
169 mDtmCoarseRasterBlock.reset( mClonedProvider->block( 1, rect, res, res ) );
170 }
171}
172
173float QgsDemHeightMapGenerator::heightAt( double x, double y )
174{
175 if ( !mClonedProvider )
176 return std::numeric_limits<float>::quiet_NaN(); // TODO: calculate heights for online DTM
177
178 // TODO: this is quite a primitive implementation: better to use heightmaps currently in use
179 int res = 1024;
180 lazyLoadDtmCoarseData( res, mDtmExtent );
181
182 int cellX = ( int ) ( ( x - mDtmExtent.xMinimum() ) / mDtmExtent.width() * res + .5f );
183 int cellY = ( int ) ( ( mDtmExtent.yMaximum() - y ) / mDtmExtent.height() * res + .5f );
184 cellX = std::clamp( cellX, 0, res - 1 );
185 cellY = std::clamp( cellY, 0, res - 1 );
186
187 bool isNoData = false;
188 const double val = mDtmCoarseRasterBlock->valueAndNoData( cellY, cellX, isNoData );
189
190 return isNoData ? std::numeric_limits<float>::quiet_NaN() : static_cast<float>( val );
191}
192
@ Float32
Thirty two bit floating point (float).
Definition qgis.h:421
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.
static QRect subRect(const QgsRectangle &extent, int width, int height, const QgsRectangle &subExtent)
For extent and width, height find rectangle covered by subextent.
Base class for raster data providers.
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.
A rectangle specified with double values.
void grow(double delta)
Grows the rectangle in place by the specified amount.
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Takes care of downloading terrain data from a publicly available data source.
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,...
Encapsulates tiling schemes (just like with WMTS / TMS / XYZ layers).