Skip to content

Commit c9ab24f

Browse files
committedMar 18, 2019
[FEATURE][3d] Add option to use terrain data from online service
This adds support for elevation tiles (using web mercator tiling) in "terrarium" format produced by Mapzen tools and publicly hosted by AWS. Terrain tiles are downloaded just like ordinary XYZ tiles, then the elevations are decoded from RGB colors and finally resampled to whatever terrain tile resolution and CRS is used by the project.
1 parent 8b621b6 commit c9ab24f

15 files changed

+715
-67
lines changed
 

‎src/3d/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ SET(QGIS_3D_SRCS
4646
terrain/qgsdemterraintilegeometry_p.cpp
4747
terrain/qgsdemterraintileloader_p.cpp
4848
terrain/qgsflatterraingenerator.cpp
49+
terrain/qgsonlineterraingenerator.cpp
50+
terrain/qgsterraindownloader.cpp
4951
terrain/qgsterrainentity_p.cpp
5052
terrain/qgsterraingenerator.cpp
5153
terrain/qgsterraintexturegenerator_p.cpp
@@ -130,6 +132,8 @@ SET(QGIS_3D_HDRS
130132
terrain/qgsdemterraingenerator.h
131133
terrain/qgsdemterraintilegeometry_p.h
132134
terrain/qgsdemterraintileloader_p.h
135+
terrain/qgsonlineterraingenerator.h
136+
terrain/qgsterraindownloader.h
133137
terrain/qgsterrainentity_p.h
134138
terrain/qgsterraingenerator.h
135139
terrain/qgsterraintexturegenerator_p.h

‎src/3d/qgs3dmapsettings.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include "qgs3dutils.h"
1919
#include "qgsflatterraingenerator.h"
2020
#include "qgsdemterraingenerator.h"
21-
//#include "quantizedmeshterraingenerator.h"
21+
#include "qgsonlineterraingenerator.h"
2222
#include "qgsvectorlayer3drenderer.h"
2323
#include "qgsmeshlayer3drenderer.h"
2424

@@ -141,12 +141,11 @@ void Qgs3DMapSettings::readXml( const QDomElement &elem, const QgsReadWriteConte
141141
demTerrainGenerator->setCrs( mCrs, mTransformContext );
142142
mTerrainGenerator.reset( demTerrainGenerator );
143143
}
144-
else if ( terrainGenType == QLatin1String( "quantized-mesh" ) )
144+
else if ( terrainGenType == QLatin1String( "online" ) )
145145
{
146-
#if 0
147-
terrainGenerator.reset( new QuantizedMeshTerrainGenerator );
148-
#endif
149-
Q_ASSERT( false ); // currently disabled
146+
QgsOnlineTerrainGenerator *onlineTerrainGenerator = new QgsOnlineTerrainGenerator;
147+
onlineTerrainGenerator->setCrs( mCrs, mTransformContext );
148+
mTerrainGenerator.reset( onlineTerrainGenerator );
150149
}
151150
else // "flat"
152151
{

‎src/3d/terrain/qgsdemterraintileloader_p.cpp

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "qgschunknode_p.h"
2020
#include "qgsdemterraingenerator.h"
2121
#include "qgsdemterraintilegeometry_p.h"
22+
#include "qgsonlineterraingenerator.h"
2223
#include "qgsterrainentity_p.h"
2324
#include "qgsterraintexturegenerator_p.h"
2425
#include "qgsterraintileentity_p.h"
@@ -56,13 +57,27 @@ QgsDemTerrainTileLoader::QgsDemTerrainTileLoader( QgsTerrainEntity *terrain, Qgs
5657
{
5758

5859
const Qgs3DMapSettings &map = terrain->map3D();
59-
QgsDemTerrainGenerator *generator = static_cast<QgsDemTerrainGenerator *>( map.terrainGenerator() );
60+
61+
QgsDemHeightMapGenerator *heightMapGenerator = nullptr;
62+
if ( map.terrainGenerator()->type() == QgsTerrainGenerator::Dem )
63+
{
64+
QgsDemTerrainGenerator *generator = static_cast<QgsDemTerrainGenerator *>( map.terrainGenerator() );
65+
heightMapGenerator = generator->heightMapGenerator();
66+
mSkirtHeight = generator->skirtHeight();
67+
}
68+
else if ( map.terrainGenerator()->type() == QgsTerrainGenerator::Online )
69+
{
70+
QgsOnlineTerrainGenerator *generator = static_cast<QgsOnlineTerrainGenerator *>( map.terrainGenerator() );
71+
heightMapGenerator = generator->heightMapGenerator();
72+
mSkirtHeight = generator->skirtHeight();
73+
}
74+
else
75+
Q_ASSERT( false );
6076

6177
// 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();
78+
connect( heightMapGenerator, &QgsDemHeightMapGenerator::heightMapReady, this, &QgsDemTerrainTileLoader::onHeightMapReady );
79+
mHeightMapJobId = heightMapGenerator->render( node->tileX(), node->tileY(), node->tileZ() );
80+
mResolution = heightMapGenerator->resolution();
6681
}
6782

6883
Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *parent )
@@ -131,13 +146,15 @@ void QgsDemTerrainTileLoader::onHeightMapReady( int jobId, const QByteArray &hei
131146
#include <qgsrasterprojector.h>
132147
#include <QtConcurrent/QtConcurrentRun>
133148
#include <QFutureWatcher>
149+
#include "qgsterraindownloader.h"
134150

135151
QgsDemHeightMapGenerator::QgsDemHeightMapGenerator( QgsRasterLayer *dtm, const QgsTilingScheme &tilingScheme, int resolution )
136152
: mDtm( dtm )
137-
, mClonedProvider( ( QgsRasterDataProvider * )dtm->dataProvider()->clone() )
153+
, mClonedProvider( dtm ? ( QgsRasterDataProvider * )dtm->dataProvider()->clone() : nullptr )
138154
, mTilingScheme( tilingScheme )
139155
, mResolution( resolution )
140156
, mLastJobId( 0 )
157+
, mDownloader( dtm ? nullptr : new QgsTerrainDownloader )
141158
{
142159
}
143160

@@ -188,6 +205,12 @@ static QByteArray _readDtmData( QgsRasterDataProvider *provider, const QgsRectan
188205
return data;
189206
}
190207

208+
209+
static QByteArray _readOnlineDtm( QgsTerrainDownloader *downloader, const QgsRectangle &extent, int res, const QgsCoordinateReferenceSystem &destCrs )
210+
{
211+
return downloader->getHeightMap( extent, res, destCrs );
212+
}
213+
191214
int QgsDemHeightMapGenerator::render( int x, int y, int z )
192215
{
193216
Q_ASSERT( mJobs.isEmpty() ); // should be always just one active job...
@@ -205,7 +228,10 @@ int QgsDemHeightMapGenerator::render( int x, int y, int z )
205228
jd.extent = extent;
206229
jd.timer.start();
207230
// 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() );
231+
if ( mDtm )
232+
jd.future = QtConcurrent::run( _readDtmData, mClonedProvider, extent, mResolution, mTilingScheme.crs() );
233+
else
234+
jd.future = QtConcurrent::run( _readOnlineDtm, mDownloader.get(), extent, mResolution, mTilingScheme.crs() );
209235

210236
QFutureWatcher<QByteArray> *fw = new QFutureWatcher<QByteArray>( nullptr );
211237
fw->setFuture( jd.future );
@@ -241,6 +267,9 @@ QByteArray QgsDemHeightMapGenerator::renderSynchronously( int x, int y, int z )
241267

242268
float QgsDemHeightMapGenerator::heightAt( double x, double y )
243269
{
270+
if ( !mDtm )
271+
return 0; // TODO: calculate heights for online DTM
272+
244273
// TODO: this is quite a primitive implementation: better to use heightmaps currently in use
245274
int res = 1024;
246275
QgsRectangle rect = mDtm->extent();

‎src/3d/terrain/qgsdemterraintileloader_p.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class QgsDemTerrainTileLoader : public QgsTerrainTileLoader
6464
};
6565

6666

67+
class QgsTerrainDownloader;
6768

6869
/**
6970
* \ingroup 3d
@@ -114,6 +115,8 @@ class QgsDemHeightMapGenerator : public QObject
114115

115116
int mLastJobId;
116117

118+
std::unique_ptr<QgsTerrainDownloader> mDownloader;
119+
117120
struct JobData
118121
{
119122
int jobId;
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#include "qgsonlineterraingenerator.h"
2+
3+
#include "qgsdemterraintileloader_p.h"
4+
5+
6+
QgsOnlineTerrainGenerator::~QgsOnlineTerrainGenerator()
7+
{
8+
delete mHeightMapGenerator;
9+
}
10+
11+
QgsChunkLoader *QgsOnlineTerrainGenerator::createChunkLoader( QgsChunkNode *node ) const
12+
{
13+
return new QgsDemTerrainTileLoader( mTerrain, node );
14+
}
15+
16+
QgsTerrainGenerator *QgsOnlineTerrainGenerator::clone() const
17+
{
18+
QgsOnlineTerrainGenerator *cloned = new QgsOnlineTerrainGenerator;
19+
cloned->mCrs = mCrs;
20+
cloned->mExtent = mExtent;
21+
cloned->mResolution = mResolution;
22+
cloned->mSkirtHeight = mSkirtHeight;
23+
cloned->updateGenerator();
24+
return cloned;
25+
}
26+
27+
QgsTerrainGenerator::Type QgsOnlineTerrainGenerator::type() const
28+
{
29+
return QgsTerrainGenerator::Online;
30+
}
31+
32+
QgsRectangle QgsOnlineTerrainGenerator::extent() const
33+
{
34+
return mTerrainTilingScheme.tileToExtent( 0, 0, 0 );
35+
}
36+
37+
float QgsOnlineTerrainGenerator::heightAt( double x, double y, const Qgs3DMapSettings &map ) const
38+
{
39+
Q_UNUSED( map );
40+
if ( mHeightMapGenerator )
41+
return mHeightMapGenerator->heightAt( x, y );
42+
else
43+
return 0;
44+
}
45+
46+
void QgsOnlineTerrainGenerator::writeXml( QDomElement &elem ) const
47+
{
48+
QgsRectangle r = mExtent;
49+
QDomElement elemExtent = elem.ownerDocument().createElement( QStringLiteral( "extent" ) );
50+
elemExtent.setAttribute( QStringLiteral( "xmin" ), QString::number( r.xMinimum() ) );
51+
elemExtent.setAttribute( QStringLiteral( "xmax" ), QString::number( r.xMaximum() ) );
52+
elemExtent.setAttribute( QStringLiteral( "ymin" ), QString::number( r.yMinimum() ) );
53+
elemExtent.setAttribute( QStringLiteral( "ymax" ), QString::number( r.yMaximum() ) );
54+
55+
elem.setAttribute( QStringLiteral( "resolution" ), mResolution );
56+
elem.setAttribute( QStringLiteral( "skirt-height" ), mSkirtHeight );
57+
58+
// crs is not read/written - it should be the same as destination crs of the map
59+
}
60+
61+
void QgsOnlineTerrainGenerator::readXml( const QDomElement &elem )
62+
{
63+
QDomElement elemExtent = elem.firstChildElement( QStringLiteral( "extent" ) );
64+
double xmin = elemExtent.attribute( QStringLiteral( "xmin" ) ).toDouble();
65+
double xmax = elemExtent.attribute( QStringLiteral( "xmax" ) ).toDouble();
66+
double ymin = elemExtent.attribute( QStringLiteral( "ymin" ) ).toDouble();
67+
double ymax = elemExtent.attribute( QStringLiteral( "ymax" ) ).toDouble();
68+
69+
setExtent( QgsRectangle( xmin, ymin, xmax, ymax ) );
70+
71+
mResolution = elem.attribute( QStringLiteral( "resolution" ) ).toInt();
72+
mSkirtHeight = elem.attribute( QStringLiteral( "skirt-height" ) ).toFloat();
73+
74+
// crs is not read/written - it should be the same as destination crs of the map
75+
}
76+
77+
void QgsOnlineTerrainGenerator::setCrs( const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context )
78+
{
79+
mCrs = crs;
80+
mTransformContext = context;
81+
updateGenerator();
82+
}
83+
84+
void QgsOnlineTerrainGenerator::setExtent( const QgsRectangle &extent )
85+
{
86+
mExtent = extent;
87+
updateGenerator();
88+
}
89+
90+
void QgsOnlineTerrainGenerator::updateGenerator()
91+
{
92+
if ( mExtent.isNull() )
93+
{
94+
mTerrainTilingScheme = QgsTilingScheme();
95+
}
96+
else
97+
{
98+
// the real extent will be a square where the given extent fully fits
99+
mTerrainTilingScheme = QgsTilingScheme( mExtent, mCrs );
100+
}
101+
102+
delete mHeightMapGenerator;
103+
mHeightMapGenerator = new QgsDemHeightMapGenerator( nullptr, mTerrainTilingScheme, mResolution );
104+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/***************************************************************************
2+
qgsonlineterraingenerator.h
3+
--------------------------------------
4+
Date : March 2019
5+
Copyright : (C) 2019 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+
16+
#ifndef QGSONLINETERRAINGENERATOR_H
17+
#define QGSONLINETERRAINGENERATOR_H
18+
19+
#include "qgis_3d.h"
20+
21+
#include "qgsterraingenerator.h"
22+
23+
#include "qgscoordinatetransformcontext.h"
24+
25+
class QgsDemHeightMapGenerator;
26+
27+
/**
28+
* \ingroup 3d
29+
* Implementation of terrain generator that uses online resources to download heightmaps.
30+
* \since QGIS 3.8
31+
*/
32+
class _3D_EXPORT QgsOnlineTerrainGenerator : public QgsTerrainGenerator
33+
{
34+
public:
35+
//! Constructor for QgsOnlineTerrainGenerator
36+
QgsOnlineTerrainGenerator() = default;
37+
~QgsOnlineTerrainGenerator() override;
38+
39+
//! Sets extent of the terrain
40+
void setExtent( const QgsRectangle &extent );
41+
42+
//! Sets CRS of the terrain
43+
void setCrs( const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context );
44+
//! Returns CRS of the terrain
45+
QgsCoordinateReferenceSystem crs() const { return mCrs; }
46+
47+
//! Sets resolution of the generator (how many elevation samples on one side of a terrain tile)
48+
void setResolution( int resolution ) { mResolution = resolution; updateGenerator(); }
49+
//! Returns resolution of the generator (how many elevation samples on one side of a terrain tile)
50+
int resolution() const { return mResolution; }
51+
52+
//! Sets skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between adjacent tiles.
53+
void setSkirtHeight( float skirtHeight ) { mSkirtHeight = skirtHeight; }
54+
//! Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between adjacent tiles.
55+
float skirtHeight() const { return mSkirtHeight; }
56+
57+
//! Returns height map generator object - takes care of extraction of elevations from the layer)
58+
QgsDemHeightMapGenerator *heightMapGenerator() { return mHeightMapGenerator; }
59+
60+
QgsTerrainGenerator *clone() const override SIP_FACTORY;
61+
Type type() const override;
62+
QgsRectangle extent() const override;
63+
float heightAt( double x, double y, const Qgs3DMapSettings &map ) const override;
64+
void writeXml( QDomElement &elem ) const override;
65+
void readXml( const QDomElement &elem ) override;
66+
//void resolveReferences( const QgsProject &project ) override;
67+
68+
QgsChunkLoader *createChunkLoader( QgsChunkNode *node ) const override SIP_FACTORY;
69+
70+
private:
71+
72+
void updateGenerator();
73+
74+
QgsRectangle mExtent;
75+
QgsCoordinateReferenceSystem mCrs;
76+
QgsCoordinateTransformContext mTransformContext;
77+
78+
//! how many vertices to place on one side of the tile
79+
int mResolution = 16;
80+
//! height of the "skirts" at the edges of tiles to hide cracks between adjacent cracks
81+
float mSkirtHeight = 10.f;
82+
83+
QgsDemHeightMapGenerator *mHeightMapGenerator = nullptr;
84+
};
85+
86+
#endif // QGSONLINETERRAINGENERATOR_H

0 commit comments

Comments
 (0)
Please sign in to comment.