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().data();
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() )
93 auto respCode = request.
get( requestData );
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 auto 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();
138 auto zRange = dummyZRange;
142 mExtent.xMinimum(), mExtent.yMinimum(), zRange.lower(),
143 mExtent.xMaximum(), mExtent.yMaximum(), zRange.upper() ) );
146 if ( replyJson.find(
"scheme" ) != replyJson.end() )
147 mTileScheme = QString::fromStdString( jsonGet<std::string>( replyJson,
"scheme" ) );
148 else if ( replyJson.find(
"schema" ) != replyJson.end() )
149 mTileScheme = QString::fromStdString( jsonGet<std::string>( replyJson,
"schema" ) );
150 else throw MissingFieldException(
"scheme/schema" );
152 for (
auto &aabbs : replyJson.at(
"available" ) )
154 QVector<QgsTileRange> tileRanges;
155 for (
auto &aabb : aabbs )
157 tileRanges.push_back(
159 jsonGet<int>( aabb,
"startX" ), jsonGet<int>( aabb,
"endX" ),
160 jsonGet<int>( aabb,
"startY" ), jsonGet<int>( aabb,
"endY" ) ) );
162 mAvailableTiles.push_back( tileRanges );
167 mMinZoom = jsonGet<uint8_t>( replyJson,
"minzoom" );
168 mMaxZoom = jsonGet<uint8_t>( replyJson,
"maxzoom" );
170 catch ( MissingFieldException & )
173 mMaxZoom = mAvailableTiles.size() - 1;
177 QString::fromStdString( jsonGet<std::string>( replyJson,
"version" ) );
178 for (
auto &urlStr : jsonGet<std::vector<std::string>>( replyJson,
"tiles" ) )
180 QUrl url = metadataUrl.resolved( QString::fromStdString( urlStr ) );
182 url.toString( QUrl::DecodeReserved ).replace(
"{version}", versionStr ) );
185 int rootTileCount = 1;
186 if ( crsString == QLatin1String(
"EPSG:4326" ) )
188 else if ( crsString != QLatin1String(
"EPSG:3857" ) )
189 error.
append( QObject::tr(
"Unhandled CRS: %1" ).arg( crsString ) );
196 QgsPointXY topLeft( crsBounds.xMinimum(), crsBounds.yMaximum() );
197 double z0TileSize = crsBounds.height();
201 catch ( nlohmann::json::exception &ex )
203 error.
append( QObject::tr(
"Error parsing JSON metadata: %1" ).arg( ex.what() ) );
205 catch ( MissingFieldException &ex )
207 error.
append( QObject::tr(
"Error parsing JSON metadata: %1" ).arg( ex.what() ) );
211const QgsDoubleRange QgsQuantizedMeshMetadata::dummyZRange = {-10000, 10000};
222bool QgsQuantizedMeshMetadata::containsTile(
QgsTileXYZ tile )
const
225 tile.
zoomLevel() >= mAvailableTiles.size() )
229 if ( mTileScheme == QLatin1String(
"tms" ) )
230 tile = tileToTms( tile );
231 for (
auto &range : mAvailableTiles[tile.zoomLevel()] )
233 if ( range.startColumn() <= tile.
column() && range.endColumn() >= tile.
column() &&
234 range.startRow() <= tile.
row() && range.endRow() >= tile.
row() )
240double QgsQuantizedMeshMetadata::geometricErrorAtZoom(
int zoom )
const
245 return 400000 / pow( 2, zoom );
248long long QgsQuantizedMeshIndex::encodeTileId(
QgsTileXYZ tile )
252 Q_ASSERT( tile.
column() == 0 && tile.
row() == 0 );
255 Q_ASSERT( tile.
zoomLevel() < ( 2 << 4 ) && ( tile.
column() < ( 2 << 27 ) ) &&
256 ( tile.
row() < ( 2 << 27 ) ) );
257 return tile.
row() | ( (
long long )tile.
column() << 28 ) |
258 ( (
long long )tile.
zoomLevel() << 56 ) | ( (
long long ) 1 << 61 );
261QgsTileXYZ QgsQuantizedMeshIndex::decodeTileId(
long long id )
263 if (
id == ROOT_TILE_ID )
266 Q_ASSERT(
id >> 61 == 1 );
268 (
int )( (
id >> 28 ) & ( ( 2 << 27 ) - 1 ) ),
269 (
int )(
id & ( ( 2 << 27 ) - 1 ) ),
270 (
int )( (
id >> 56 ) & ( ( 2 << 4 ) - 1 ) ) );
277 auto bounds = mWgs84ToCrs.transform( mMetadata.mCrs.bounds() );
278 tile.setBoundingVolume(
280 QgsBox3D( bounds, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
281 tile.setGeometricError( std::numeric_limits<double>::max() );
284long long QgsQuantizedMeshIndex::parentTileId(
long long id )
const
286 if (
id == ROOT_TILE_ID )
288 auto tile = decodeTileId(
id );
293QVector<long long> QgsQuantizedMeshIndex::childTileIds(
long long id )
const
295 auto tile = decodeTileId(
id );
296 QVector<long long> children;
299 if ( mMetadata.containsTile( {x * 2, y * 2, zoom + 1} ) )
300 children.push_back( encodeTileId( {x * 2, y * 2, zoom + 1} ) );
301 if ( mMetadata.containsTile( {x * 2 + 1, y * 2, zoom + 1} ) )
302 children.push_back( encodeTileId( {x * 2 + 1, y * 2, zoom + 1} ) );
303 if ( mMetadata.containsTile( {x * 2, y * 2 + 1, zoom + 1} ) )
304 children.push_back( encodeTileId( {x * 2, y * 2 + 1, zoom + 1} ) );
305 if ( mMetadata.containsTile( {x * 2 + 1, y * 2 + 1, zoom + 1} ) )
306 children.push_back( encodeTileId( {x * 2 + 1, y * 2 + 1, zoom + 1} ) );
312 auto xyzTile = decodeTileId(
id );
316 auto tileExtent = zoomedMatrix.tileExtent( xyzTile );
318 sceneTile.setBoundingVolume(
320 QgsBox3D( tileExtent, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
321 sceneTile.setGeometricError( mMetadata.geometricErrorAtZoom( xyzTile.
zoomLevel() ) );
323 if (
id == ROOT_TILE_ID )
327 if ( mMetadata.mTileScheme == QLatin1String(
"tms" ) )
328 xyzTile = tileToTms( xyzTile );
330 if ( mMetadata.mTileUrls.size() == 0 )
332 QgsDebugError(
"Quantized Mesh metadata has no URLs for tiles" );
338 mMetadata.mTileUrls[0], xyzTile, zoomedMatrix );
339 sceneTile.setResources( {{
"content", tileUri}} );
340 sceneTile.setMetadata(
342 {QStringLiteral(
"gltfUpAxis" ),
static_cast<int>(
Qgis::Axis::Z )},
343 {QStringLiteral(
"contentFormat" ), QStringLiteral(
"quantizedmesh" )},
350 tileExtent.width(), 0, 0, tileExtent.xMinimum(),
351 0, tileExtent.height(), 0, tileExtent.yMinimum(),
354 sceneTile.setTransform( transform );
361 uint8_t zoomLevel = mMetadata.mMinZoom;
364 while ( zoomLevel < mMetadata.mMaxZoom &&
370 QVector<long long> ids;
375 auto parentTile = decodeTileId( request.
parentTileId() );
376 extent.intersect( tileMatrix.tileExtent( parentTile ) );
379 auto tileRange = tileMatrix.tileRangeFromExtent( extent );
380 if ( !tileRange.isValid() )
383 for (
int col = tileRange.startColumn(); col <= tileRange.endColumn(); col++ )
384 for (
int row = tileRange.startRow(); row <= tileRange.endRow(); row++ )
386 auto xyzTile =
QgsTileXYZ( col, row, zoomLevel );
387 if ( mMetadata.containsTile( xyzTile ) )
388 ids.push_back( encodeTileId( xyzTile ) );
394QgsQuantizedMeshIndex::childAvailability(
long long id )
const
396 auto childIds = childTileIds(
id );
397 if ( childIds.count() == 0 )
401bool QgsQuantizedMeshIndex::fetchHierarchy(
long long id,
QgsFeedback *feedback )
408 Q_UNUSED( feedback );
412QByteArray QgsQuantizedMeshIndex::fetchContent(
const QString &uri,
415 QNetworkRequest requestData( uri );
416 requestData.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
417 requestData.setRawHeader(
"Accept",
"application/vnd.quantized-mesh,application/octet-stream;q=0.9" );
418 mMetadata.mHeaders.updateNetworkRequest( requestData );
419 if ( !mMetadata.mAuthCfg.isEmpty() )
422 QStringLiteral(
"QgsQuantizedMeshIndex" ) );
432 if ( reply->error() != QNetworkReply::NoError )
434 QgsDebugError( QStringLiteral(
"Request failed (%1): %2" ).arg( uri ).arg( reply->errorString() ) );
437 return reply->data();
440QgsQuantizedMeshDataProvider::QgsQuantizedMeshDataProvider(
444 mProviderOptions( providerOptions )
446 if ( uri.startsWith( QLatin1String(
"ion://" ) ) )
448 QString updatedUri = uriFromIon( uri );
449 mMetadata = QgsQuantizedMeshMetadata( updatedUri, transformContext(), mError );
453 mMetadata = QgsQuantizedMeshMetadata( uri, transformContext(), mError );
456 if ( mError.isEmpty() )
460 mIndex.emplace(
new QgsQuantizedMeshIndex( *mMetadata, wgs84ToCrs ) );
465QString QgsQuantizedMeshDataProvider::uriFromIon(
const QString &uri )
472 const QString assetId = QUrlQuery( url ).queryItemValue( QStringLiteral(
"assetId" ) );
473 const QString accessToken = QUrlQuery( url ).queryItemValue( QStringLiteral(
"accessToken" ) );
475 const QString CESIUM_ION_URL = QStringLiteral(
"https://api.cesium.com/" );
484 const QString assetInfoEndpoint = CESIUM_ION_URL + QStringLiteral(
"v1/assets/%1" ).arg( assetId );
485 QNetworkRequest request = QNetworkRequest( assetInfoEndpoint );
487 headers.updateNetworkRequest( request );
488 if ( !accessToken.isEmpty() )
489 request.setRawHeader( "Authorization", QStringLiteral( "Bearer %1" ).arg( accessToken ).toLocal8Bit() );
492 if ( accessToken.isEmpty() )
493 networkRequest.setAuthCfg( authCfg );
495 switch ( networkRequest.get( request ) )
508 const json assetInfoJson = json::parse( content.
content().toStdString() );
509 if ( assetInfoJson[
"type"] !=
"TERRAIN" )
511 appendError(
QgsErrorMessage( tr(
"Only ion TERRAIN content can be accessed, not %1" ).arg( QString::fromStdString( assetInfoJson[
"type"].get<std::string>() ) ) ) );
519 const QString tileAccessEndpoint = CESIUM_ION_URL + QStringLiteral(
"v1/assets/%1/endpoint" ).arg( assetId );
520 QNetworkRequest request = QNetworkRequest( tileAccessEndpoint );
522 headers.updateNetworkRequest( request );
523 if ( !accessToken.isEmpty() )
524 request.setRawHeader( "Authorization", QStringLiteral( "Bearer %1" ).arg( accessToken ).toLocal8Bit() );
527 if ( accessToken.isEmpty() )
528 networkRequest.setAuthCfg( authCfg );
530 switch ( networkRequest.get( request ) )
543 const json tileAccessJson = json::parse( content.
content().toStdString() );
545 if ( tileAccessJson.contains(
"url" ) )
547 tileSetUri = QString::fromStdString( tileAccessJson[
"url"].get<std::string>() );
549 else if ( tileAccessJson.contains(
"options" ) )
551 const auto &optionsJson = tileAccessJson[
"options"];
552 if ( optionsJson.contains(
"url" ) )
554 tileSetUri = QString::fromStdString( optionsJson[
"url"].get<std::string>() );
558 if ( tileAccessJson.contains(
"accessToken" ) )
562 headers.
insert( QStringLiteral(
"Authorization" ),
563 QStringLiteral(
"Bearer %1" ).arg( QString::fromStdString( tileAccessJson[
"accessToken"].get<std::string>() ) ) );
568 finalUri.
setParam(
"url", tileSetUri +
"layer.json" );
574QgsQuantizedMeshDataProvider::capabilities()
const
580 return new QgsQuantizedMeshDataProvider( mUri, mProviderOptions );
583QgsQuantizedMeshDataProvider::sceneCrs()
const
585 return mMetadata->mCrs;
588QgsQuantizedMeshDataProvider::boundingVolume()
const
590 return mMetadata->mBoundingVolume;
603 return mMetadata->dummyZRange;
607 return mMetadata->mCrs;
609QgsRectangle QgsQuantizedMeshDataProvider::extent()
const
611 return mMetadata->mExtent;
613bool QgsQuantizedMeshDataProvider::isValid()
const {
return mIsValid; }
614QString QgsQuantizedMeshDataProvider::name()
const {
return providerName; }
615QString QgsQuantizedMeshDataProvider::description()
const {
return providerDescription; }
617const QgsQuantizedMeshMetadata &QgsQuantizedMeshDataProvider::quantizedMeshMetadata()
const
622QgsQuantizedMeshProviderMetadata::QgsQuantizedMeshProviderMetadata()
624 QgsQuantizedMeshDataProvider::providerDescription ) {}
630 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.
This class 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.
Class for storing the component parts of a RDBMS 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.
QgsErrorMessage represents 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 class to represent a 2D point.
A rectangle specified with double values.
void finished()
Emitted when the reply has finished (either with a success or with a failure)
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.
Range of tiles in a tile matrix to be rendered.
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.
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.