20#include "moc_qgsquantizedmeshdataprovider.cpp" 
   40#include <nlohmann/json.hpp> 
   42#include <qnetworkrequest.h> 
   44#include <qstringliteral.h> 
   50class MissingFieldException : 
public std::exception
 
   53    MissingFieldException( 
const char *field ) : mField( field ) { }
 
   54    const char *what() const noexcept
 
   56      return QString( 
"Missing field: %1" ).arg( mField ).toLocal8Bit().constData();
 
   63static T jsonGet( nlohmann::json &json, 
const char *idx )
 
   65  auto &obj = json[idx];
 
   68    throw MissingFieldException( idx );
 
   74QgsQuantizedMeshMetadata::QgsQuantizedMeshMetadata(
 
   85  QUrl metadataUrl = dsUri.
param( 
"url" );
 
   86  QNetworkRequest requestData( metadataUrl );
 
   87  mHeaders.updateNetworkRequest( requestData );
 
   89                               QStringLiteral( 
"QgsQuantizedMeshDataProvider" ) );
 
   91  if ( !mAuthCfg.isEmpty() )
 
   97      QObject::tr( 
"Failed to retrieve quantized mesh tiles metadata: %1" )
 
  105    auto replyJson = nlohmann::json::parse( reply.data() );
 
  108    if ( jsonGet<std::string>( replyJson, 
"format" ) != 
"quantized-mesh-1.0" )
 
  110      error.
append( QObject::tr( 
"Unexpected tile format: %1" )
 
  111                    .arg( replyJson[
"format"].dump().c_str() ) );
 
  115    const QString crsString = QString::fromStdString( jsonGet<std::string>( replyJson, 
"projection" ) );
 
  117    if ( !mCrs.isValid() )
 
  119      error.
append( QObject::tr( 
"Invalid CRS '%1'!" ).arg( crsString ) );
 
  125      std::vector<double> bounds = jsonGet<std::vector<double>>( replyJson, 
"bounds" );
 
  126      if ( bounds.size() != 4 )
 
  128        error.
append( QObject::tr( 
"Bounds array doesn't have 4 items" ) );
 
  131      mExtent = 
QgsRectangle( bounds[0], bounds[1], bounds[2], bounds[3] );
 
  133    catch ( MissingFieldException & )
 
  135      mExtent = mCrs.bounds();
 
  141          mExtent.xMinimum(), mExtent.yMinimum(), dummyZRange.lower(),
 
  142          mExtent.xMaximum(), mExtent.yMaximum(), dummyZRange.upper() ) );
 
  145    if ( replyJson.find( 
"scheme" ) != replyJson.end() )
 
  146      mTileScheme = QString::fromStdString( jsonGet<std::string>( replyJson, 
"scheme" ) );
 
  147    else if ( replyJson.find( 
"schema" ) != replyJson.end() )
 
  148      mTileScheme = QString::fromStdString( jsonGet<std::string>( replyJson, 
"schema" ) );
 
  149    else throw MissingFieldException( 
"scheme/schema" );
 
  151    if ( replyJson.find( 
"available" ) != replyJson.end() )
 
  153      for ( 
auto &aabbs : replyJson.at( 
"available" ) )
 
  155        QVector<QgsTileRange> tileRanges;
 
  156        for ( 
auto &aabb : aabbs )
 
  158          tileRanges.push_back(
 
  160              jsonGet<int>( aabb, 
"startX" ), jsonGet<int>( aabb, 
"endX" ),
 
  161              jsonGet<int>( aabb, 
"startY" ), jsonGet<int>( aabb, 
"endY" ) ) );
 
  163        mAvailableTiles.push_back( tileRanges );
 
  169      mMinZoom = jsonGet<uint8_t>( replyJson, 
"minzoom" );
 
  170      mMaxZoom = jsonGet<uint8_t>( replyJson, 
"maxzoom" );
 
  172    catch ( MissingFieldException & )
 
  175      if ( mAvailableTiles.isEmpty() )
 
  178        error.
append( QObject::tr( 
"Missing max zoom or tile availability info" ) );
 
  182        mMaxZoom = mAvailableTiles.size() - 1;
 
  187      QString::fromStdString( jsonGet<std::string>( replyJson, 
"version" ) );
 
  188    for ( 
auto &urlStr : jsonGet<std::vector<std::string>>( replyJson, 
"tiles" ) )
 
  190      QUrl url = metadataUrl.resolved( QString::fromStdString( urlStr ) );
 
  192        url.toString( QUrl::DecodeReserved ).replace( 
"{version}", versionStr ) );
 
  195    int rootTileCount = 1;
 
  196    if ( crsString == QLatin1String( 
"EPSG:4326" ) )
 
  198    else if ( crsString != QLatin1String( 
"EPSG:3857" ) )
 
  199      error.
append( QObject::tr( 
"Unhandled CRS: %1" ).arg( crsString ) );
 
  207    double z0TileSize = crsBounds.
height();
 
  211  catch ( nlohmann::json::exception &ex )
 
  213    error.
append( QObject::tr( 
"Error parsing JSON metadata: %1" ).arg( ex.what() ) );
 
  215  catch ( MissingFieldException &ex )
 
  217    error.
append( QObject::tr( 
"Error parsing JSON metadata: %1" ).arg( ex.what() ) );
 
  221const QgsDoubleRange QgsQuantizedMeshMetadata::dummyZRange = {-10000, 10000};
 
  232bool QgsQuantizedMeshMetadata::containsTile( 
QgsTileXYZ tile )
 const 
  236  if ( mAvailableTiles.isEmpty() )
 
  238  if ( tile.
zoomLevel() >= mAvailableTiles.size() )
 
  242  if ( mTileScheme == QLatin1String( 
"tms" ) )
 
  243    tile = tileToTms( tile );
 
  244  for ( 
const QgsTileRange &range : mAvailableTiles[tile.zoomLevel()] )
 
  246    if ( range.startColumn() <= tile.
column() && range.endColumn() >= tile.
column() &&
 
  247         range.startRow() <= tile.
row() && range.endRow() >= tile.
row() )
 
  253double QgsQuantizedMeshMetadata::geometricErrorAtZoom( 
int zoom )
 const 
  258  return 400000 / pow( 2, zoom );
 
  261long long QgsQuantizedMeshIndex::encodeTileId( 
QgsTileXYZ tile )
 
  265    Q_ASSERT( tile.
column() == 0 && tile.
row() == 0 );
 
  268  Q_ASSERT( tile.
zoomLevel() < ( 2 << 4 ) && ( tile.
column() < ( 2 << 27 ) ) &&
 
  269            ( tile.
row() < ( 2 << 27 ) ) );
 
  270  return tile.
row() | ( ( 
long long )tile.
column() << 28 ) |
 
  271         ( ( 
long long )tile.
zoomLevel() << 56 ) | ( ( 
long long ) 1 << 61 );
 
  274QgsTileXYZ QgsQuantizedMeshIndex::decodeTileId( 
long long id )
 
  276  if ( 
id == ROOT_TILE_ID )
 
  279  Q_ASSERT( 
id >> 61 == 1 ); 
 
  281           ( 
int )( ( 
id >> 28 ) & ( ( 2 << 27 ) - 1 ) ),
 
  282           ( 
int )( 
id & ( ( 2 << 27 ) - 1 ) ),
 
  283           ( 
int )( ( 
id >> 56 ) & ( ( 2 << 4 ) - 1 ) ) );
 
  290  const QgsRectangle bounds = mWgs84ToCrs.transform( mMetadata.mCrs.bounds() );
 
  293      QgsBox3D( bounds, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
 
  297long long QgsQuantizedMeshIndex::parentTileId( 
long long id )
 const 
  299  if ( 
id == ROOT_TILE_ID )
 
  306QVector<long long> QgsQuantizedMeshIndex::childTileIds( 
long long id )
 const 
  309  QVector<long long> children;
 
  310  const int x = tile.
column();
 
  311  const int y = tile.
row();
 
  314  if ( mMetadata.containsTile( {x * 2, y * 2, zoom + 1} ) )
 
  315    children.push_back( encodeTileId( {x * 2, y * 2, zoom + 1} ) );
 
  316  if ( mMetadata.containsTile( {x * 2 + 1, y * 2, zoom + 1} ) )
 
  317    children.push_back( encodeTileId( {x * 2 + 1, y * 2, zoom + 1} ) );
 
  318  if ( mMetadata.containsTile( {x * 2, y * 2 + 1, zoom + 1} ) )
 
  319    children.push_back( encodeTileId( {x * 2, y * 2 + 1, zoom + 1} ) );
 
  320  if ( mMetadata.containsTile( {x * 2 + 1, y * 2 + 1, zoom + 1} ) )
 
  321    children.push_back( encodeTileId( {x * 2 + 1, y * 2 + 1, zoom + 1} ) );
 
  333  sceneTile.setBoundingVolume(
 
  335      QgsBox3D( tileExtent, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
 
  336  sceneTile.setGeometricError( mMetadata.geometricErrorAtZoom( xyzTile.
zoomLevel() ) );
 
  338  if ( 
id == ROOT_TILE_ID )
 
  342  if ( mMetadata.mTileScheme == QLatin1String( 
"tms" ) )
 
  343    xyzTile = tileToTms( xyzTile );
 
  345  if ( mMetadata.mTileUrls.size() == 0 )
 
  347    QgsDebugError( 
"Quantized Mesh metadata has no URLs for tiles" );
 
  353                              mMetadata.mTileUrls[0], xyzTile, zoomedMatrix );
 
  354    sceneTile.setResources( {{
"content", tileUri}} );
 
  355    sceneTile.setMetadata(
 
  357      {QStringLiteral( 
"gltfUpAxis" ), 
static_cast<int>( 
Qgis::Axis::Z )},
 
  358      {QStringLiteral( 
"contentFormat" ), QStringLiteral( 
"quantizedmesh" )},
 
  369  sceneTile.setTransform( transform );
 
  376  uint8_t zoomLevel = mMetadata.mMinZoom;
 
  379    while ( zoomLevel < mMetadata.mMaxZoom &&
 
  385  QVector<long long> ids;
 
  399    for ( 
int row = tileRange.
startRow(); row <= tileRange.
endRow(); row++ )
 
  402      if ( mMetadata.containsTile( xyzTile ) )
 
  403        ids.push_back( encodeTileId( xyzTile ) );
 
  409QgsQuantizedMeshIndex::childAvailability( 
long long id )
 const 
  411  const QVector<long long> childIds = childTileIds( 
id );
 
  412  if ( childIds.count() == 0 )
 
  416bool QgsQuantizedMeshIndex::fetchHierarchy( 
long long id, 
QgsFeedback *feedback )
 
  423  Q_UNUSED( feedback );
 
  427QByteArray QgsQuantizedMeshIndex::fetchContent( 
const QString &uri,
 
  430  QNetworkRequest requestData( uri );
 
  431  requestData.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
 
  432  requestData.setRawHeader( 
"Accept", 
"application/vnd.quantized-mesh,application/octet-stream;q=0.9" );
 
  433  mMetadata.mHeaders.updateNetworkRequest( requestData );
 
  434  if ( !mMetadata.mAuthCfg.isEmpty() )
 
  437                               QStringLiteral( 
"QgsQuantizedMeshIndex" ) );
 
  447  if ( reply->error() != QNetworkReply::NoError )
 
  449    QgsDebugError( QStringLiteral( 
"Request failed (%1): %2" ).arg( uri ).arg( reply->errorString() ) );
 
  452  return reply->data();
 
  455QgsQuantizedMeshDataProvider::QgsQuantizedMeshDataProvider(
 
  459    mProviderOptions( providerOptions )
 
  461  if ( uri.startsWith( QLatin1String( 
"ion://" ) ) )
 
  463    QString updatedUri = uriFromIon( uri );
 
  464    mMetadata = QgsQuantizedMeshMetadata( updatedUri, transformContext(), mError );
 
  468    mMetadata = QgsQuantizedMeshMetadata( uri, transformContext(), mError );
 
  471  if ( mError.isEmpty() )
 
  475    mIndex.emplace( 
new QgsQuantizedMeshIndex( *mMetadata, wgs84ToCrs ) );
 
  480QString QgsQuantizedMeshDataProvider::uriFromIon( 
const QString &uri )
 
  487  const QString assetId = QUrlQuery( url ).queryItemValue( QStringLiteral( 
"assetId" ) );
 
  488  const QString accessToken = QUrlQuery( url ).queryItemValue( QStringLiteral( 
"accessToken" ) );
 
  490  const QString CESIUM_ION_URL = QStringLiteral( 
"https://api.cesium.com/" );
 
  499    const QString assetInfoEndpoint = CESIUM_ION_URL + QStringLiteral( 
"v1/assets/%1" ).arg( assetId );
 
  500    QNetworkRequest request = QNetworkRequest( assetInfoEndpoint );
 
  502    headers.updateNetworkRequest( request );
 
  503    if ( !accessToken.isEmpty() )
 
  504      request.setRawHeader( "Authorization", QStringLiteral( "Bearer %1" ).arg( accessToken ).toLocal8Bit() );
 
  507    if ( accessToken.isEmpty() )
 
  508      networkRequest.setAuthCfg( authCfg );
 
  510    switch ( networkRequest.get( request ) )
 
  523    const json assetInfoJson  = json::parse( content.
content().toStdString() );
 
  524    if ( assetInfoJson[
"type"] != 
"TERRAIN" )
 
  526      appendError( 
QgsErrorMessage( tr( 
"Only ion TERRAIN content can be accessed, not %1" ).arg( QString::fromStdString( assetInfoJson[
"type"].get<std::string>() ) ) ) );
 
  534    const QString tileAccessEndpoint = CESIUM_ION_URL + QStringLiteral( 
"v1/assets/%1/endpoint" ).arg( assetId );
 
  535    QNetworkRequest request = QNetworkRequest( tileAccessEndpoint );
 
  537    headers.updateNetworkRequest( request );
 
  538    if ( !accessToken.isEmpty() )
 
  539      request.setRawHeader( "Authorization", QStringLiteral( "Bearer %1" ).arg( accessToken ).toLocal8Bit() );
 
  542    if ( accessToken.isEmpty() )
 
  543      networkRequest.setAuthCfg( authCfg );
 
  545    switch ( networkRequest.get( request ) )
 
  558    const json tileAccessJson = json::parse( content.
content().toStdString() );
 
  560    if ( tileAccessJson.contains( 
"url" ) )
 
  562      tileSetUri = QString::fromStdString( tileAccessJson[
"url"].get<std::string>() );
 
  564    else if ( tileAccessJson.contains( 
"options" ) )
 
  566      const auto &optionsJson = tileAccessJson[
"options"];
 
  567      if ( optionsJson.contains( 
"url" ) )
 
  569        tileSetUri = QString::fromStdString( optionsJson[
"url"].get<std::string>() );
 
  573    if ( tileAccessJson.contains( 
"accessToken" ) )
 
  577      headers.
insert( QStringLiteral( 
"Authorization" ),
 
  578                      QStringLiteral( 
"Bearer %1" ).arg( QString::fromStdString( tileAccessJson[
"accessToken"].get<std::string>() ) ) );
 
  583  finalUri.
setParam( 
"url", tileSetUri + 
"layer.json" );
 
  589QgsQuantizedMeshDataProvider::capabilities()
 const 
  595  return new QgsQuantizedMeshDataProvider( mUri, mProviderOptions );
 
  598QgsQuantizedMeshDataProvider::sceneCrs()
 const 
  600  return mMetadata->mCrs;
 
  603QgsQuantizedMeshDataProvider::boundingVolume()
 const 
  605  return mMetadata->mBoundingVolume;
 
  618  return mMetadata->dummyZRange;
 
  622  return mMetadata->mCrs;
 
  624QgsRectangle QgsQuantizedMeshDataProvider::extent()
 const 
  626  return mMetadata->mExtent;
 
  628bool QgsQuantizedMeshDataProvider::isValid()
 const { 
return mIsValid; }
 
  629QString QgsQuantizedMeshDataProvider::name()
 const { 
return providerName; }
 
  630QString QgsQuantizedMeshDataProvider::description()
 const { 
return providerDescription; }
 
  632const QgsQuantizedMeshMetadata &QgsQuantizedMeshDataProvider::quantizedMeshMetadata()
 const 
  637QgsQuantizedMeshProviderMetadata::QgsQuantizedMeshProviderMetadata()
 
  639                         QgsQuantizedMeshDataProvider::providerDescription ) {}
 
  645  return new QgsQuantizedMeshDataProvider( uri, providerOptions, flags );
 
QFlags< TiledSceneProviderCapability > TiledSceneProviderCapabilities
Tiled scene data provider capabilities.
 
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
 
TileChildrenAvailability
Possible availability states for a tile's children.
 
@ Available
Tile children are already available.
 
@ NoChildren
Tile is known to have no children.
 
static QgsTileDownloadManager * tileDownloadManager()
Returns the application's tile download manager, used for download of map tiles when rendering.
 
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
 
bool updateNetworkRequest(QNetworkRequest &request, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkRequest with an authentication config.
 
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
 
void setAuthCfg(const QString &authCfg)
Sets the authentication config id which should be used during the request.
 
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
 
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
 
@ NetworkError
A network error occurred.
 
@ ServerExceptionError
An exception was raised by the server.
 
@ NoError
No error was encountered.
 
@ TimeoutError
Timeout was reached before a reply was received.
 
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
 
A 3-dimensional box composed of x, y, z coordinates.
 
QgsRectangle toRectangle() const
Converts the box to a 2D rectangle.
 
Represents a coordinate reference system (CRS).
 
Contains information about the context in which a coordinate transform is executed.
 
Abstract base class for spatial data provider implementations.
 
Stores the component parts of a data source URI (e.g.
 
QByteArray encodedUri() const
Returns the complete encoded URI as a byte array.
 
void setEncodedUri(const QByteArray &uri)
Sets the complete encoded uri.
 
QgsHttpHeaders httpHeaders() const
Returns http headers.
 
QString param(const QString &key) const
Returns a generic parameter value corresponding to the specified key.
 
void setParam(const QString &key, const QString &value)
Sets a generic parameter value on the URI.
 
QString authConfigId() const
Returns any associated authentication configuration ID stored in the URI.
 
void setHttpHeaders(const QgsHttpHeaders &headers)
Sets headers to headers.
 
QgsRange which stores a range of double values.
 
Represents a single error message.
 
A container for error messages.
 
void append(const QString &message, const QString &tag)
Append new error message.
 
Base class for feedback objects to be used for cancellation of something running in a worker thread.
 
void canceled()
Internal routines can connect to this signal if they use event loop.
 
A simple 4x4 matrix implementation useful for transformation in 3D space.
 
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
 
QByteArray content() const
Returns the reply content.
 
QgsBox3D extent() const
Returns the overall bounding box of the object.
 
static QgsOrientedBox3D fromBox3D(const QgsBox3D &box)
Constructs an oriented box from an axis-aligned bounding box.
 
A rectangle specified with double values.
 
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
 
void finished()
Emitted when the reply has finished (either with a success or with a failure)
 
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
 
QgsRectangle tileExtent(QgsTileXYZ id) const
Returns extent of the given tile in this matrix.
 
QgsTileRange tileRangeFromExtent(const QgsRectangle &mExtent) const
Returns tile range that fully covers the given extent.
 
static QgsTileMatrix fromTileMatrix(int zoomLevel, const QgsTileMatrix &tileMatrix)
Returns a tile matrix based on another one.
 
static QgsTileMatrix fromCustomDef(int zoomLevel, const QgsCoordinateReferenceSystem &crs, const QgsPointXY &z0TopLeftPoint, double z0Dimension, int z0MatrixWidth=1, int z0MatrixHeight=1)
Returns a tile matrix for a specific CRS, top left point, zoom level 0 dimension in CRS units.
 
A range of tiles in a tile matrix.
 
int endColumn() const
Returns index of the last column in the range.
 
int endRow() const
Returns index of the last row in the range.
 
int startRow() const
Returns index of the first row in the range.
 
int startColumn() const
Returns index of the first column in the range.
 
bool isValid() const
Returns whether the range is valid (when all row/column numbers are not negative)
 
Stores coordinates of a tile in a tile matrix set.
 
int zoomLevel() const
Returns tile's zoom level (Z)
 
int column() const
Returns tile's column index (X)
 
int row() const
Returns tile's row index (Y)
 
Represents a bounding volume for a tiled scene.
 
Base class for data providers for QgsTiledSceneLayer.
 
An index for tiled scene data providers.
 
Tiled scene data request.
 
QgsOrientedBox3D filterBox() const
Returns the box from which data will be taken.
 
long long parentTileId() const
Returns the parent tile ID, if filtering is limited to children of a specific tile.
 
double requiredGeometricError() const
Returns the required geometric error threshold for the returned tiles, in meters.
 
Represents an individual tile from a tiled scene data source.
 
void setGeometricError(double error)
Sets the tile's geometric error, which is the error, in meters, of the tile's simplified representati...
 
void setBoundingVolume(const QgsTiledSceneBoundingVolume &volume)
Sets the bounding volume for the tile.
 
static QString formatXYZUrlTemplate(const QString &url, QgsTileXYZ tile, const QgsTileMatrix &tileMatrix)
Returns formatted tile URL string replacing {x}, {y}, {z} placeholders (or {-y} instead of {y} for TM...
 
#define QgsDebugError(str)
 
#define QgsSetRequestInitiatorClass(request, _class)
 
Setting options for creating vector data providers.