22#include <nlohmann/json.hpp>
45#include <qnetworkrequest.h>
47#include <qstringliteral.h>
50#include "moc_qgsquantizedmeshdataprovider.cpp"
54class MissingFieldException :
public std::exception
57 MissingFieldException(
const char *field ) : mField( field ) { }
58 const char *what() const noexcept
override
60 return QString(
"Missing field: %1" ).arg( mField ).toLocal8Bit().constData();
67static T jsonGet( nlohmann::json &json,
const char *idx )
69 auto &obj = json[idx];
72 throw MissingFieldException( idx );
78QgsQuantizedMeshMetadata::QgsQuantizedMeshMetadata(
89 QUrl metadataUrl = dsUri.
param(
"url" );
90 QNetworkRequest requestData( metadataUrl );
91 mHeaders.updateNetworkRequest( requestData );
93 QStringLiteral(
"QgsQuantizedMeshDataProvider" ) );
95 if ( !mAuthCfg.isEmpty() )
101 QObject::tr(
"Failed to retrieve quantized mesh tiles metadata: %1" )
109 auto replyJson = nlohmann::json::parse( reply.data() );
112 if ( jsonGet<std::string>( replyJson,
"format" ) !=
"quantized-mesh-1.0" )
114 error.
append( QObject::tr(
"Unexpected tile format: %1" )
115 .arg( replyJson[
"format"].dump().c_str() ) );
119 const QString crsString = QString::fromStdString( jsonGet<std::string>( replyJson,
"projection" ) );
121 if ( !mCrs.isValid() )
123 error.
append( QObject::tr(
"Invalid CRS '%1'!" ).arg( crsString ) );
129 std::vector<double> bounds = jsonGet<std::vector<double>>( replyJson,
"bounds" );
130 if ( bounds.size() != 4 )
132 error.
append( QObject::tr(
"Bounds array doesn't have 4 items" ) );
135 mExtent =
QgsRectangle( bounds[0], bounds[1], bounds[2], bounds[3] );
137 catch ( MissingFieldException & )
139 mExtent = mCrs.bounds();
145 mExtent.xMinimum(), mExtent.yMinimum(), dummyZRange.lower(),
146 mExtent.xMaximum(), mExtent.yMaximum(), dummyZRange.upper() ) );
149 if ( replyJson.find(
"scheme" ) != replyJson.end() )
150 mTileScheme = QString::fromStdString( jsonGet<std::string>( replyJson,
"scheme" ) );
151 else if ( replyJson.find(
"schema" ) != replyJson.end() )
152 mTileScheme = QString::fromStdString( jsonGet<std::string>( replyJson,
"schema" ) );
153 else throw MissingFieldException(
"scheme/schema" );
155 if ( replyJson.find(
"available" ) != replyJson.end() )
157 for (
auto &aabbs : replyJson.at(
"available" ) )
159 QVector<QgsTileRange> tileRanges;
160 for (
auto &aabb : aabbs )
162 tileRanges.push_back(
164 jsonGet<int>( aabb,
"startX" ), jsonGet<int>( aabb,
"endX" ),
165 jsonGet<int>( aabb,
"startY" ), jsonGet<int>( aabb,
"endY" ) ) );
167 mAvailableTiles.push_back( tileRanges );
173 mMinZoom = jsonGet<uint8_t>( replyJson,
"minzoom" );
174 mMaxZoom = jsonGet<uint8_t>( replyJson,
"maxzoom" );
176 catch ( MissingFieldException & )
179 if ( mAvailableTiles.isEmpty() )
182 error.
append( QObject::tr(
"Missing max zoom or tile availability info" ) );
186 mMaxZoom = mAvailableTiles.size() - 1;
191 QString::fromStdString( jsonGet<std::string>( replyJson,
"version" ) );
192 for (
auto &urlStr : jsonGet<std::vector<std::string>>( replyJson,
"tiles" ) )
194 QUrl url = metadataUrl.resolved( QString::fromStdString( urlStr ) );
196 url.toString( QUrl::DecodeReserved ).replace(
"{version}", versionStr ) );
199 int rootTileCount = 1;
200 if ( crsString == QLatin1String(
"EPSG:4326" ) )
202 else if ( crsString != QLatin1String(
"EPSG:3857" ) )
203 error.
append( QObject::tr(
"Unhandled CRS: %1" ).arg( crsString ) );
211 double z0TileSize = crsBounds.
height();
215 catch ( nlohmann::json::exception &ex )
217 error.
append( QObject::tr(
"Error parsing JSON metadata: %1" ).arg( ex.what() ) );
219 catch ( MissingFieldException &ex )
221 error.
append( QObject::tr(
"Error parsing JSON metadata: %1" ).arg( ex.what() ) );
225const QgsDoubleRange QgsQuantizedMeshMetadata::dummyZRange = {-10000, 10000};
236bool QgsQuantizedMeshMetadata::containsTile(
QgsTileXYZ tile )
const
240 if ( mAvailableTiles.isEmpty() )
242 if ( tile.
zoomLevel() >= mAvailableTiles.size() )
246 if ( mTileScheme == QLatin1String(
"tms" ) )
247 tile = tileToTms( tile );
250 if ( range.startColumn() <= tile.
column() && range.endColumn() >= tile.
column() &&
251 range.startRow() <= tile.
row() && range.endRow() >= tile.
row() )
257double QgsQuantizedMeshMetadata::geometricErrorAtZoom(
int zoom )
const
262 return 400000 / pow( 2, zoom );
265long long QgsQuantizedMeshIndex::encodeTileId(
QgsTileXYZ tile )
269 Q_ASSERT( tile.
column() == 0 && tile.
row() == 0 );
272 Q_ASSERT( tile.
zoomLevel() < ( 2 << 4 ) && ( tile.
column() < ( 2 << 27 ) ) &&
273 ( tile.
row() < ( 2 << 27 ) ) );
274 return tile.
row() | ( (
long long )tile.
column() << 28 ) |
275 ( (
long long )tile.
zoomLevel() << 56 ) | ( (
long long ) 1 << 61 );
278QgsTileXYZ QgsQuantizedMeshIndex::decodeTileId(
long long id )
280 if (
id == ROOT_TILE_ID )
283 Q_ASSERT(
id >> 61 == 1 );
285 (
int )( (
id >> 28 ) & ( ( 2 << 27 ) - 1 ) ),
286 (
int )(
id & ( ( 2 << 27 ) - 1 ) ),
287 (
int )( (
id >> 56 ) & ( ( 2 << 4 ) - 1 ) ) );
294 const QgsRectangle bounds = mWgs84ToCrs.transform( mMetadata.mCrs.bounds() );
297 QgsBox3D( bounds, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
301long long QgsQuantizedMeshIndex::parentTileId(
long long id )
const
303 if (
id == ROOT_TILE_ID )
310QVector<long long> QgsQuantizedMeshIndex::childTileIds(
long long id )
const
313 QVector<long long> children;
314 const int x = tile.
column();
315 const int y = tile.
row();
318 if ( mMetadata.containsTile( {x * 2, y * 2, zoom + 1} ) )
319 children.push_back( encodeTileId( {x * 2, y * 2, zoom + 1} ) );
320 if ( mMetadata.containsTile( {x * 2 + 1, y * 2, zoom + 1} ) )
321 children.push_back( encodeTileId( {x * 2 + 1, y * 2, zoom + 1} ) );
322 if ( mMetadata.containsTile( {x * 2, y * 2 + 1, zoom + 1} ) )
323 children.push_back( encodeTileId( {x * 2, y * 2 + 1, zoom + 1} ) );
324 if ( mMetadata.containsTile( {x * 2 + 1, y * 2 + 1, zoom + 1} ) )
325 children.push_back( encodeTileId( {x * 2 + 1, y * 2 + 1, zoom + 1} ) );
337 sceneTile.setBoundingVolume(
339 QgsBox3D( tileExtent, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
340 sceneTile.setGeometricError( mMetadata.geometricErrorAtZoom( xyzTile.
zoomLevel() ) );
342 if (
id == ROOT_TILE_ID )
346 if ( mMetadata.mTileScheme == QLatin1String(
"tms" ) )
347 xyzTile = tileToTms( xyzTile );
349 if ( mMetadata.mTileUrls.size() == 0 )
351 QgsDebugError(
"Quantized Mesh metadata has no URLs for tiles" );
357 mMetadata.mTileUrls[0], xyzTile, zoomedMatrix );
358 sceneTile.setResources( {{
"content", tileUri}} );
359 sceneTile.setMetadata(
361 {QStringLiteral(
"gltfUpAxis" ),
static_cast<int>(
Qgis::Axis::Z )},
362 {QStringLiteral(
"contentFormat" ), QStringLiteral(
"quantizedmesh" )},
373 sceneTile.setTransform( transform );
380 uint8_t zoomLevel = mMetadata.mMinZoom;
383 while ( zoomLevel < mMetadata.mMaxZoom &&
389 QVector<long long> ids;
403 for (
int row = tileRange.
startRow(); row <= tileRange.
endRow(); row++ )
406 if ( mMetadata.containsTile( xyzTile ) )
407 ids.push_back( encodeTileId( xyzTile ) );
413QgsQuantizedMeshIndex::childAvailability(
long long id )
const
415 const QVector<long long> childIds = childTileIds(
id );
416 if ( childIds.count() == 0 )
420bool QgsQuantizedMeshIndex::fetchHierarchy(
long long id,
QgsFeedback *feedback )
427 Q_UNUSED( feedback );
431QByteArray QgsQuantizedMeshIndex::fetchContent(
const QString &uri,
434 QNetworkRequest requestData( uri );
435 requestData.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
436 requestData.setRawHeader(
"Accept",
"application/vnd.quantized-mesh,application/octet-stream;q=0.9" );
437 mMetadata.mHeaders.updateNetworkRequest( requestData );
438 if ( !mMetadata.mAuthCfg.isEmpty() )
441 QStringLiteral(
"QgsQuantizedMeshIndex" ) );
451 if ( reply->error() != QNetworkReply::NoError )
453 QgsDebugError( QStringLiteral(
"Request failed (%1): %2" ).arg( uri ).arg( reply->errorString() ) );
456 return reply->data();
459QgsQuantizedMeshDataProvider::QgsQuantizedMeshDataProvider(
463 mProviderOptions( providerOptions )
465 if ( uri.startsWith( QLatin1String(
"ion://" ) ) )
467 QString updatedUri = uriFromIon( uri );
468 mMetadata = QgsQuantizedMeshMetadata( updatedUri, transformContext(), mError );
472 mMetadata = QgsQuantizedMeshMetadata( uri, transformContext(), mError );
475 if ( mError.isEmpty() )
477 QgsCoordinateReferenceSystem wgs84( QStringLiteral(
"EPSG:4326" ) );
478 QgsCoordinateTransform wgs84ToCrs( wgs84, mMetadata->mCrs, transformContext() );
479 mIndex.emplace( new QgsQuantizedMeshIndex( *mMetadata, wgs84ToCrs ) );
484QString QgsQuantizedMeshDataProvider::uriFromIon(
const QString &uri )
491 const QString assetId = QUrlQuery( url ).queryItemValue( QStringLiteral(
"assetId" ) );
492 const QString accessToken = QUrlQuery( url ).queryItemValue( QStringLiteral(
"accessToken" ) );
494 const QString CESIUM_ION_URL = QStringLiteral(
"https://api.cesium.com/" );
503 const QString assetInfoEndpoint = CESIUM_ION_URL + QStringLiteral(
"v1/assets/%1" ).arg( assetId );
504 QNetworkRequest request = QNetworkRequest( assetInfoEndpoint );
506 headers.updateNetworkRequest( request );
507 if ( !accessToken.isEmpty() )
508 request.setRawHeader(
"Authorization", QStringLiteral(
"Bearer %1" ).arg( accessToken ).toLocal8Bit() );
511 if ( accessToken.isEmpty() )
512 networkRequest.setAuthCfg( authCfg );
514 switch ( networkRequest.get( request ) )
527 const json assetInfoJson = json::parse( content.
content().toStdString() );
528 if ( assetInfoJson[
"type"] !=
"TERRAIN" )
530 appendError(
QgsErrorMessage( tr(
"Only ion TERRAIN content can be accessed, not %1" ).arg( QString::fromStdString( assetInfoJson[
"type"].get<std::string>() ) ) ) );
538 const QString tileAccessEndpoint = CESIUM_ION_URL + QStringLiteral(
"v1/assets/%1/endpoint" ).arg( assetId );
539 QNetworkRequest request = QNetworkRequest( tileAccessEndpoint );
541 headers.updateNetworkRequest( request );
542 if ( !accessToken.isEmpty() )
543 request.setRawHeader(
"Authorization", QStringLiteral(
"Bearer %1" ).arg( accessToken ).toLocal8Bit() );
546 if ( accessToken.isEmpty() )
547 networkRequest.setAuthCfg( authCfg );
549 switch ( networkRequest.get( request ) )
562 const json tileAccessJson = json::parse( content.
content().toStdString() );
564 if ( tileAccessJson.contains(
"url" ) )
566 tileSetUri = QString::fromStdString( tileAccessJson[
"url"].get<std::string>() );
568 else if ( tileAccessJson.contains(
"options" ) )
570 const auto &optionsJson = tileAccessJson[
"options"];
571 if ( optionsJson.contains(
"url" ) )
573 tileSetUri = QString::fromStdString( optionsJson[
"url"].get<std::string>() );
577 if ( tileAccessJson.contains(
"accessToken" ) )
581 headers.
insert( QStringLiteral(
"Authorization" ),
582 QStringLiteral(
"Bearer %1" ).arg( QString::fromStdString( tileAccessJson[
"accessToken"].get<std::string>() ) ) );
587 finalUri.
setParam(
"url", tileSetUri +
"layer.json" );
593QgsQuantizedMeshDataProvider::capabilities()
const
599 return new QgsQuantizedMeshDataProvider( mUri, mProviderOptions );
602QgsQuantizedMeshDataProvider::sceneCrs()
const
604 return mMetadata->mCrs;
607QgsQuantizedMeshDataProvider::boundingVolume()
const
609 return mMetadata->mBoundingVolume;
622 return mMetadata->dummyZRange;
626 return mMetadata->mCrs;
628QgsRectangle QgsQuantizedMeshDataProvider::extent()
const
630 return mMetadata->mExtent;
632bool QgsQuantizedMeshDataProvider::isValid()
const {
return mIsValid; }
633QString QgsQuantizedMeshDataProvider::name()
const {
return providerName; }
634QString QgsQuantizedMeshDataProvider::description()
const {
return providerDescription; }
636const QgsQuantizedMeshMetadata &QgsQuantizedMeshDataProvider::quantizedMeshMetadata()
const
641QgsQuantizedMeshProviderMetadata::QgsQuantizedMeshProviderMetadata()
643 QgsQuantizedMeshDataProvider::providerDescription ) {}
649 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}...
#define QgsDebugError(str)
#define QgsSetRequestInitiatorClass(request, _class)
Setting options for creating vector data providers.