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.