Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[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.
  • Loading branch information
wonder-sk committed Mar 18, 2019
1 parent 8b621b6 commit c9ab24f
Show file tree
Hide file tree
Showing 15 changed files with 715 additions and 67 deletions.
4 changes: 4 additions & 0 deletions src/3d/CMakeLists.txt
Expand Up @@ -46,6 +46,8 @@ SET(QGIS_3D_SRCS
terrain/qgsdemterraintilegeometry_p.cpp
terrain/qgsdemterraintileloader_p.cpp
terrain/qgsflatterraingenerator.cpp
terrain/qgsonlineterraingenerator.cpp
terrain/qgsterraindownloader.cpp
terrain/qgsterrainentity_p.cpp
terrain/qgsterraingenerator.cpp
terrain/qgsterraintexturegenerator_p.cpp
Expand Down Expand Up @@ -130,6 +132,8 @@ SET(QGIS_3D_HDRS
terrain/qgsdemterraingenerator.h
terrain/qgsdemterraintilegeometry_p.h
terrain/qgsdemterraintileloader_p.h
terrain/qgsonlineterraingenerator.h
terrain/qgsterraindownloader.h
terrain/qgsterrainentity_p.h
terrain/qgsterraingenerator.h
terrain/qgsterraintexturegenerator_p.h
Expand Down
11 changes: 5 additions & 6 deletions src/3d/qgs3dmapsettings.cpp
Expand Up @@ -18,7 +18,7 @@
#include "qgs3dutils.h"
#include "qgsflatterraingenerator.h"
#include "qgsdemterraingenerator.h"
//#include "quantizedmeshterraingenerator.h"
#include "qgsonlineterraingenerator.h"
#include "qgsvectorlayer3drenderer.h"
#include "qgsmeshlayer3drenderer.h"

Expand Down Expand Up @@ -141,12 +141,11 @@ void Qgs3DMapSettings::readXml( const QDomElement &elem, const QgsReadWriteConte
demTerrainGenerator->setCrs( mCrs, mTransformContext );
mTerrainGenerator.reset( demTerrainGenerator );
}
else if ( terrainGenType == QLatin1String( "quantized-mesh" ) )
else if ( terrainGenType == QLatin1String( "online" ) )
{
#if 0
terrainGenerator.reset( new QuantizedMeshTerrainGenerator );
#endif
Q_ASSERT( false ); // currently disabled
QgsOnlineTerrainGenerator *onlineTerrainGenerator = new QgsOnlineTerrainGenerator;
onlineTerrainGenerator->setCrs( mCrs, mTransformContext );
mTerrainGenerator.reset( onlineTerrainGenerator );
}
else // "flat"
{
Expand Down
43 changes: 36 additions & 7 deletions src/3d/terrain/qgsdemterraintileloader_p.cpp
Expand Up @@ -19,6 +19,7 @@
#include "qgschunknode_p.h"
#include "qgsdemterraingenerator.h"
#include "qgsdemterraintilegeometry_p.h"
#include "qgsonlineterraingenerator.h"
#include "qgsterrainentity_p.h"
#include "qgsterraintexturegenerator_p.h"
#include "qgsterraintileentity_p.h"
Expand Down Expand Up @@ -56,13 +57,27 @@ QgsDemTerrainTileLoader::QgsDemTerrainTileLoader( QgsTerrainEntity *terrain, Qgs
{

const Qgs3DMapSettings &map = terrain->map3D();
QgsDemTerrainGenerator *generator = static_cast<QgsDemTerrainGenerator *>( map.terrainGenerator() );

QgsDemHeightMapGenerator *heightMapGenerator = nullptr;
if ( map.terrainGenerator()->type() == QgsTerrainGenerator::Dem )
{
QgsDemTerrainGenerator *generator = static_cast<QgsDemTerrainGenerator *>( map.terrainGenerator() );
heightMapGenerator = generator->heightMapGenerator();
mSkirtHeight = generator->skirtHeight();
}
else if ( map.terrainGenerator()->type() == QgsTerrainGenerator::Online )
{
QgsOnlineTerrainGenerator *generator = static_cast<QgsOnlineTerrainGenerator *>( map.terrainGenerator() );
heightMapGenerator = generator->heightMapGenerator();
mSkirtHeight = generator->skirtHeight();
}
else
Q_ASSERT( false );

// get heightmap asynchronously
connect( generator->heightMapGenerator(), &QgsDemHeightMapGenerator::heightMapReady, this, &QgsDemTerrainTileLoader::onHeightMapReady );
mHeightMapJobId = generator->heightMapGenerator()->render( node->tileX(), node->tileY(), node->tileZ() );
mResolution = generator->heightMapGenerator()->resolution();
mSkirtHeight = generator->skirtHeight();
connect( heightMapGenerator, &QgsDemHeightMapGenerator::heightMapReady, this, &QgsDemTerrainTileLoader::onHeightMapReady );
mHeightMapJobId = heightMapGenerator->render( node->tileX(), node->tileY(), node->tileZ() );
mResolution = heightMapGenerator->resolution();
}

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

QgsDemHeightMapGenerator::QgsDemHeightMapGenerator( QgsRasterLayer *dtm, const QgsTilingScheme &tilingScheme, int resolution )
: mDtm( dtm )
, mClonedProvider( ( QgsRasterDataProvider * )dtm->dataProvider()->clone() )
, mClonedProvider( dtm ? ( QgsRasterDataProvider * )dtm->dataProvider()->clone() : nullptr )
, mTilingScheme( tilingScheme )
, mResolution( resolution )
, mLastJobId( 0 )
, mDownloader( dtm ? nullptr : new QgsTerrainDownloader )
{
}

Expand Down Expand Up @@ -188,6 +205,12 @@ static QByteArray _readDtmData( QgsRasterDataProvider *provider, const QgsRectan
return data;
}


static QByteArray _readOnlineDtm( QgsTerrainDownloader *downloader, const QgsRectangle &extent, int res, const QgsCoordinateReferenceSystem &destCrs )
{
return downloader->getHeightMap( extent, res, destCrs );
}

int QgsDemHeightMapGenerator::render( int x, int y, int z )
{
Q_ASSERT( mJobs.isEmpty() ); // should be always just one active job...
Expand All @@ -205,7 +228,10 @@ int QgsDemHeightMapGenerator::render( int x, int y, int z )
jd.extent = extent;
jd.timer.start();
// make a clone of the data provider so it is safe to use in worker thread
jd.future = QtConcurrent::run( _readDtmData, mClonedProvider, extent, mResolution, mTilingScheme.crs() );
if ( mDtm )
jd.future = QtConcurrent::run( _readDtmData, mClonedProvider, extent, mResolution, mTilingScheme.crs() );
else
jd.future = QtConcurrent::run( _readOnlineDtm, mDownloader.get(), extent, mResolution, mTilingScheme.crs() );

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

float QgsDemHeightMapGenerator::heightAt( double x, double y )
{
if ( !mDtm )
return 0; // TODO: calculate heights for online DTM

// TODO: this is quite a primitive implementation: better to use heightmaps currently in use
int res = 1024;
QgsRectangle rect = mDtm->extent();
Expand Down
3 changes: 3 additions & 0 deletions src/3d/terrain/qgsdemterraintileloader_p.h
Expand Up @@ -64,6 +64,7 @@ class QgsDemTerrainTileLoader : public QgsTerrainTileLoader
};


class QgsTerrainDownloader;

/**
* \ingroup 3d
Expand Down Expand Up @@ -114,6 +115,8 @@ class QgsDemHeightMapGenerator : public QObject

int mLastJobId;

std::unique_ptr<QgsTerrainDownloader> mDownloader;

struct JobData
{
int jobId;
Expand Down
104 changes: 104 additions & 0 deletions src/3d/terrain/qgsonlineterraingenerator.cpp
@@ -0,0 +1,104 @@
#include "qgsonlineterraingenerator.h"

#include "qgsdemterraintileloader_p.h"


QgsOnlineTerrainGenerator::~QgsOnlineTerrainGenerator()
{
delete mHeightMapGenerator;
}

QgsChunkLoader *QgsOnlineTerrainGenerator::createChunkLoader( QgsChunkNode *node ) const
{
return new QgsDemTerrainTileLoader( mTerrain, node );
}

QgsTerrainGenerator *QgsOnlineTerrainGenerator::clone() const
{
QgsOnlineTerrainGenerator *cloned = new QgsOnlineTerrainGenerator;
cloned->mCrs = mCrs;
cloned->mExtent = mExtent;
cloned->mResolution = mResolution;
cloned->mSkirtHeight = mSkirtHeight;
cloned->updateGenerator();
return cloned;
}

QgsTerrainGenerator::Type QgsOnlineTerrainGenerator::type() const
{
return QgsTerrainGenerator::Online;
}

QgsRectangle QgsOnlineTerrainGenerator::extent() const
{
return mTerrainTilingScheme.tileToExtent( 0, 0, 0 );
}

float QgsOnlineTerrainGenerator::heightAt( double x, double y, const Qgs3DMapSettings &map ) const
{
Q_UNUSED( map );
if ( mHeightMapGenerator )
return mHeightMapGenerator->heightAt( x, y );
else
return 0;
}

void QgsOnlineTerrainGenerator::writeXml( QDomElement &elem ) const
{
QgsRectangle r = mExtent;
QDomElement elemExtent = elem.ownerDocument().createElement( QStringLiteral( "extent" ) );
elemExtent.setAttribute( QStringLiteral( "xmin" ), QString::number( r.xMinimum() ) );
elemExtent.setAttribute( QStringLiteral( "xmax" ), QString::number( r.xMaximum() ) );
elemExtent.setAttribute( QStringLiteral( "ymin" ), QString::number( r.yMinimum() ) );
elemExtent.setAttribute( QStringLiteral( "ymax" ), QString::number( r.yMaximum() ) );

elem.setAttribute( QStringLiteral( "resolution" ), mResolution );
elem.setAttribute( QStringLiteral( "skirt-height" ), mSkirtHeight );

// crs is not read/written - it should be the same as destination crs of the map
}

void QgsOnlineTerrainGenerator::readXml( const QDomElement &elem )
{
QDomElement elemExtent = elem.firstChildElement( QStringLiteral( "extent" ) );
double xmin = elemExtent.attribute( QStringLiteral( "xmin" ) ).toDouble();
double xmax = elemExtent.attribute( QStringLiteral( "xmax" ) ).toDouble();
double ymin = elemExtent.attribute( QStringLiteral( "ymin" ) ).toDouble();
double ymax = elemExtent.attribute( QStringLiteral( "ymax" ) ).toDouble();

setExtent( QgsRectangle( xmin, ymin, xmax, ymax ) );

mResolution = elem.attribute( QStringLiteral( "resolution" ) ).toInt();
mSkirtHeight = elem.attribute( QStringLiteral( "skirt-height" ) ).toFloat();

// crs is not read/written - it should be the same as destination crs of the map
}

void QgsOnlineTerrainGenerator::setCrs( const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context )
{
mCrs = crs;
mTransformContext = context;
updateGenerator();
}

void QgsOnlineTerrainGenerator::setExtent( const QgsRectangle &extent )
{
mExtent = extent;
updateGenerator();
}

void QgsOnlineTerrainGenerator::updateGenerator()
{
if ( mExtent.isNull() )
{
mTerrainTilingScheme = QgsTilingScheme();
}
else
{
// the real extent will be a square where the given extent fully fits
mTerrainTilingScheme = QgsTilingScheme( mExtent, mCrs );
}

delete mHeightMapGenerator;
mHeightMapGenerator = new QgsDemHeightMapGenerator( nullptr, mTerrainTilingScheme, mResolution );
}
86 changes: 86 additions & 0 deletions src/3d/terrain/qgsonlineterraingenerator.h
@@ -0,0 +1,86 @@
/***************************************************************************
qgsonlineterraingenerator.h
--------------------------------------
Date : March 2019
Copyright : (C) 2019 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSONLINETERRAINGENERATOR_H
#define QGSONLINETERRAINGENERATOR_H

#include "qgis_3d.h"

#include "qgsterraingenerator.h"

#include "qgscoordinatetransformcontext.h"

class QgsDemHeightMapGenerator;

/**
* \ingroup 3d
* Implementation of terrain generator that uses online resources to download heightmaps.
* \since QGIS 3.8
*/
class _3D_EXPORT QgsOnlineTerrainGenerator : public QgsTerrainGenerator
{
public:
//! Constructor for QgsOnlineTerrainGenerator
QgsOnlineTerrainGenerator() = default;
~QgsOnlineTerrainGenerator() override;

//! Sets extent of the terrain
void setExtent( const QgsRectangle &extent );

//! Sets CRS of the terrain
void setCrs( const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context );
//! Returns CRS of the terrain
QgsCoordinateReferenceSystem crs() const { return mCrs; }

//! Sets resolution of the generator (how many elevation samples on one side of a terrain tile)
void setResolution( int resolution ) { mResolution = resolution; updateGenerator(); }
//! Returns resolution of the generator (how many elevation samples on one side of a terrain tile)
int resolution() const { return mResolution; }

//! Sets skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between adjacent tiles.
void setSkirtHeight( float skirtHeight ) { mSkirtHeight = skirtHeight; }
//! Returns skirt height (in world units). Skirts at the edges of terrain tiles help hide cracks between adjacent tiles.
float skirtHeight() const { return mSkirtHeight; }

//! Returns height map generator object - takes care of extraction of elevations from the layer)
QgsDemHeightMapGenerator *heightMapGenerator() { return mHeightMapGenerator; }

QgsTerrainGenerator *clone() const override SIP_FACTORY;
Type type() const override;
QgsRectangle extent() const override;
float heightAt( double x, double y, const Qgs3DMapSettings &map ) const override;
void writeXml( QDomElement &elem ) const override;
void readXml( const QDomElement &elem ) override;
//void resolveReferences( const QgsProject &project ) override;

QgsChunkLoader *createChunkLoader( QgsChunkNode *node ) const override SIP_FACTORY;

private:

void updateGenerator();

QgsRectangle mExtent;
QgsCoordinateReferenceSystem mCrs;
QgsCoordinateTransformContext mTransformContext;

//! how many vertices to place on one side of the tile
int mResolution = 16;
//! height of the "skirts" at the edges of tiles to hide cracks between adjacent cracks
float mSkirtHeight = 10.f;

QgsDemHeightMapGenerator *mHeightMapGenerator = nullptr;
};

#endif // QGSONLINETERRAINGENERATOR_H

0 comments on commit c9ab24f

Please sign in to comment.