Skip to content

Commit

Permalink
Merge pull request #9549 from wonder-sk/online-3d-terrain
Browse files Browse the repository at this point in the history
[3d] Add option to use terrain data from online service
  • Loading branch information
wonder-sk committed Mar 20, 2019
2 parents 13a74ae + 1010522 commit f85d494
Show file tree
Hide file tree
Showing 16 changed files with 872 additions and 83 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
117 changes: 117 additions & 0 deletions src/3d/terrain/qgsonlineterraingenerator.cpp
@@ -0,0 +1,117 @@
/***************************************************************************
qgsonlineterraingenerator.cpp
--------------------------------------
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. *
* *
***************************************************************************/

#include "qgsonlineterraingenerator.h"

#include "qgsdemterraintileloader_p.h"


QgsOnlineTerrainGenerator::QgsOnlineTerrainGenerator() = default;

QgsOnlineTerrainGenerator::~QgsOnlineTerrainGenerator() = default;

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 );
}

mHeightMapGenerator.reset( 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();
~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.get(); }

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;

std::unique_ptr<QgsDemHeightMapGenerator> mHeightMapGenerator;
};

#endif // QGSONLINETERRAINGENERATOR_H

0 comments on commit f85d494

Please sign in to comment.