39#include <nlohmann/json.hpp>
41#include <qnetworkrequest.h>
43#include <qstringliteral.h>
49class MissingFieldException :
public std::exception
52 MissingFieldException(
const char *field ) : mField( field ) { }
53 const char *what() const noexcept
55 return QString(
"Missing field: %1" ).arg( mField ).toLocal8Bit().data();
62static T jsonGet( nlohmann::json &json,
const char *idx )
64 auto &obj = json[idx];
67 throw MissingFieldException( idx );
73QgsQuantizedMeshMetadata::QgsQuantizedMeshMetadata(
84 QUrl metadataUrl = dsUri.
param(
"url" );
85 QNetworkRequest requestData( metadataUrl );
86 mHeaders.updateNetworkRequest( requestData );
88 QStringLiteral(
"QgsQuantizedMeshDataProvider" ) );
90 if ( !mAuthCfg.isEmpty() )
92 auto respCode = request.
get( requestData );
96 QObject::tr(
"Failed to retrieve quantized mesh tiles metadata: %1" )
104 auto replyJson = nlohmann::json::parse( reply.data() );
107 if ( jsonGet<std::string>( replyJson,
"format" ) !=
"quantized-mesh-1.0" )
109 error.
append( QObject::tr(
"Unexpected tile format: %1" )
110 .arg( replyJson[
"format"].dump().c_str() ) );
114 auto crsString = QString::fromStdString( jsonGet<std::string>( replyJson,
"projection" ) );
116 if ( !mCrs.isValid() )
118 error.
append( QObject::tr(
"Invalid CRS '%1'!" ).arg( crsString ) );
124 std::vector<double> bounds = jsonGet<std::vector<double>>( replyJson,
"bounds" );
125 if ( bounds.size() != 4 )
127 error.
append( QObject::tr(
"Bounds array doesn't have 4 items" ) );
130 mExtent =
QgsRectangle( bounds[0], bounds[1], bounds[2], bounds[3] );
132 catch ( MissingFieldException & )
134 mExtent = mCrs.bounds();
137 auto zRange = dummyZRange;
141 mExtent.xMinimum(), mExtent.yMinimum(), zRange.lower(),
142 mExtent.xMaximum(), mExtent.yMaximum(), zRange.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 for (
auto &aabbs : replyJson.at(
"available" ) )
153 QVector<QgsTileRange> tileRanges;
154 for (
auto &aabb : aabbs )
156 tileRanges.push_back(
158 jsonGet<int>( aabb,
"startX" ), jsonGet<int>( aabb,
"endX" ),
159 jsonGet<int>( aabb,
"startY" ), jsonGet<int>( aabb,
"endY" ) ) );
161 mAvailableTiles.push_back( tileRanges );
166 mMinZoom = jsonGet<uint8_t>( replyJson,
"minzoom" );
167 mMaxZoom = jsonGet<uint8_t>( replyJson,
"maxzoom" );
169 catch ( MissingFieldException & )
172 mMaxZoom = mAvailableTiles.size() - 1;
176 QString::fromStdString( jsonGet<std::string>( replyJson,
"version" ) );
177 for (
auto &urlStr : jsonGet<std::vector<std::string>>( replyJson,
"tiles" ) )
179 QUrl url = metadataUrl.resolved( QString::fromStdString( urlStr ) );
181 url.toString( QUrl::DecodeReserved ).replace(
"{version}", versionStr ) );
184 int rootTileCount = 1;
185 if ( crsString == QLatin1String(
"EPSG:4326" ) )
187 else if ( crsString != QLatin1String(
"EPSG:3857" ) )
188 error.
append( QObject::tr(
"Unhandled CRS: %1" ).arg( crsString ) );
195 QgsPointXY topLeft( crsBounds.xMinimum(), crsBounds.yMaximum() );
196 double z0TileSize = crsBounds.height();
200 catch ( nlohmann::json::exception &ex )
202 error.
append( QObject::tr(
"Error parsing JSON metadata: %1" ).arg( ex.what() ) );
204 catch ( MissingFieldException &ex )
206 error.
append( QObject::tr(
"Error parsing JSON metadata: %1" ).arg( ex.what() ) );
210const QgsDoubleRange QgsQuantizedMeshMetadata::dummyZRange = {-10000, 10000};
221bool QgsQuantizedMeshMetadata::containsTile(
QgsTileXYZ tile )
const
224 tile.
zoomLevel() >= mAvailableTiles.size() )
228 if ( mTileScheme == QLatin1String(
"tms" ) )
229 tile = tileToTms( tile );
230 for (
auto &range : mAvailableTiles[tile.zoomLevel()] )
232 if ( range.startColumn() <= tile.
column() && range.endColumn() >= tile.
column() &&
233 range.startRow() <= tile.
row() && range.endRow() >= tile.
row() )
239double QgsQuantizedMeshMetadata::geometricErrorAtZoom(
int zoom )
const
244 return 400000 / pow( 2, zoom );
247long long QgsQuantizedMeshIndex::encodeTileId(
QgsTileXYZ tile )
251 Q_ASSERT( tile.
column() == 0 && tile.
row() == 0 );
254 Q_ASSERT( tile.
zoomLevel() < ( 2 << 4 ) && ( tile.
column() < ( 2 << 27 ) ) &&
255 ( tile.
row() < ( 2 << 27 ) ) );
256 return tile.
row() | ( (
long long )tile.
column() << 28 ) |
257 ( (
long long )tile.
zoomLevel() << 56 ) | ( (
long long ) 1 << 61 );
260QgsTileXYZ QgsQuantizedMeshIndex::decodeTileId(
long long id )
262 if (
id == ROOT_TILE_ID )
265 Q_ASSERT(
id >> 61 == 1 );
267 (
int )( (
id >> 28 ) & ( ( 2 << 27 ) - 1 ) ),
268 (
int )(
id & ( ( 2 << 27 ) - 1 ) ),
269 (
int )( (
id >> 56 ) & ( ( 2 << 4 ) - 1 ) ) );
276 auto bounds = mWgs84ToCrs.transform( mMetadata.mCrs.bounds() );
277 tile.setBoundingVolume(
279 QgsBox3D( bounds, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
280 tile.setGeometricError( std::numeric_limits<double>::max() );
283long long QgsQuantizedMeshIndex::parentTileId(
long long id )
const
285 if (
id == ROOT_TILE_ID )
287 auto tile = decodeTileId(
id );
292QVector<long long> QgsQuantizedMeshIndex::childTileIds(
long long id )
const
294 auto tile = decodeTileId(
id );
295 QVector<long long> children;
298 if ( mMetadata.containsTile( {x * 2, y * 2, zoom + 1} ) )
299 children.push_back( encodeTileId( {x * 2, y * 2, zoom + 1} ) );
300 if ( mMetadata.containsTile( {x * 2 + 1, y * 2, zoom + 1} ) )
301 children.push_back( encodeTileId( {x * 2 + 1, y * 2, zoom + 1} ) );
302 if ( mMetadata.containsTile( {x * 2, y * 2 + 1, zoom + 1} ) )
303 children.push_back( encodeTileId( {x * 2, y * 2 + 1, zoom + 1} ) );
304 if ( mMetadata.containsTile( {x * 2 + 1, y * 2 + 1, zoom + 1} ) )
305 children.push_back( encodeTileId( {x * 2 + 1, y * 2 + 1, zoom + 1} ) );
311 auto xyzTile = decodeTileId(
id );
315 auto tileExtent = zoomedMatrix.tileExtent( xyzTile );
317 sceneTile.setBoundingVolume(
319 QgsBox3D( tileExtent, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
320 sceneTile.setGeometricError( mMetadata.geometricErrorAtZoom( xyzTile.
zoomLevel() ) );
322 if (
id == ROOT_TILE_ID )
326 if ( mMetadata.mTileScheme == QLatin1String(
"tms" ) )
327 xyzTile = tileToTms( xyzTile );
329 if ( mMetadata.mTileUrls.size() == 0 )
331 QgsDebugError(
"Quantized Mesh metadata has no URLs for tiles" );
337 mMetadata.mTileUrls[0], xyzTile, zoomedMatrix );
338 sceneTile.setResources( {{
"content", tileUri}} );
339 sceneTile.setMetadata(
341 {QStringLiteral(
"gltfUpAxis" ),
static_cast<int>(
Qgis::Axis::Z )},
342 {QStringLiteral(
"contentFormat" ), QStringLiteral(
"quantizedmesh" )},
349 tileExtent.width(), 0, 0, tileExtent.xMinimum(),
350 0, tileExtent.height(), 0, tileExtent.yMinimum(),
353 sceneTile.setTransform( transform );
360 uint8_t zoomLevel = mMetadata.mMinZoom;
363 while ( zoomLevel < mMetadata.mMaxZoom &&
369 QVector<long long> ids;
374 auto parentTile = decodeTileId( request.
parentTileId() );
375 extent.intersect( tileMatrix.tileExtent( parentTile ) );
378 auto tileRange = tileMatrix.tileRangeFromExtent( extent );
379 if ( !tileRange.isValid() )
382 for (
int col = tileRange.startColumn(); col <= tileRange.endColumn(); col++ )
383 for (
int row = tileRange.startRow(); row <= tileRange.endRow(); row++ )
385 auto xyzTile =
QgsTileXYZ( col, row, zoomLevel );
386 if ( mMetadata.containsTile( xyzTile ) )
387 ids.push_back( encodeTileId( xyzTile ) );
393QgsQuantizedMeshIndex::childAvailability(
long long id )
const
395 auto childIds = childTileIds(
id );
396 if ( childIds.count() == 0 )
400bool QgsQuantizedMeshIndex::fetchHierarchy(
long long id,
QgsFeedback *feedback )
407 Q_UNUSED( feedback );
411QByteArray QgsQuantizedMeshIndex::fetchContent(
const QString &uri,
414 QNetworkRequest requestData( uri );
415 requestData.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
416 requestData.setRawHeader(
"Accept",
"application/vnd.quantized-mesh,application/octet-stream;q=0.9" );
417 mMetadata.mHeaders.updateNetworkRequest( requestData );
418 if ( !mMetadata.mAuthCfg.isEmpty() )
421 QStringLiteral(
"QgsQuantizedMeshIndex" ) );
431 if ( reply->error() != QNetworkReply::NoError )
433 QgsDebugError( QStringLiteral(
"Request failed (%1): %2" ).arg( uri ).arg( reply->errorString() ) );
436 return reply->data();
439QgsQuantizedMeshDataProvider::QgsQuantizedMeshDataProvider(
443 mProviderOptions( providerOptions )
445 if ( uri.startsWith( QLatin1String(
"ion://" ) ) )
447 QString updatedUri = uriFromIon( uri );
448 mMetadata = QgsQuantizedMeshMetadata( updatedUri, transformContext(), mError );
452 mMetadata = QgsQuantizedMeshMetadata( uri, transformContext(), mError );
455 if ( mError.isEmpty() )
459 mIndex.emplace(
new QgsQuantizedMeshIndex( *mMetadata, wgs84ToCrs ) );
464QString QgsQuantizedMeshDataProvider::uriFromIon(
const QString &uri )
471 const QString assetId = QUrlQuery( url ).queryItemValue( QStringLiteral(
"assetId" ) );
472 const QString accessToken = QUrlQuery( url ).queryItemValue( QStringLiteral(
"accessToken" ) );
474 const QString CESIUM_ION_URL = QStringLiteral(
"https://api.cesium.com/" );
483 const QString assetInfoEndpoint = CESIUM_ION_URL + QStringLiteral(
"v1/assets/%1" ).arg( assetId );
484 QNetworkRequest request = QNetworkRequest( assetInfoEndpoint );
486 headers.updateNetworkRequest( request );
487 if ( !accessToken.isEmpty() )
488 request.setRawHeader( "Authorization", QStringLiteral( "Bearer %1" ).arg( accessToken ).toLocal8Bit() );
491 if ( accessToken.isEmpty() )
492 networkRequest.setAuthCfg( authCfg );
494 switch ( networkRequest.get( request ) )
507 const json assetInfoJson = json::parse( content.
content().toStdString() );
508 if ( assetInfoJson[
"type"] !=
"TERRAIN" )
510 appendError(
QgsErrorMessage( tr(
"Only ion TERRAIN content can be accessed, not %1" ).arg( QString::fromStdString( assetInfoJson[
"type"].get<std::string>() ) ) ) );
518 const QString tileAccessEndpoint = CESIUM_ION_URL + QStringLiteral(
"v1/assets/%1/endpoint" ).arg( assetId );
519 QNetworkRequest request = QNetworkRequest( tileAccessEndpoint );
521 headers.updateNetworkRequest( request );
522 if ( !accessToken.isEmpty() )
523 request.setRawHeader( "Authorization", QStringLiteral( "Bearer %1" ).arg( accessToken ).toLocal8Bit() );
526 if ( accessToken.isEmpty() )
527 networkRequest.setAuthCfg( authCfg );
529 switch ( networkRequest.get( request ) )
542 const json tileAccessJson = json::parse( content.
content().toStdString() );
544 if ( tileAccessJson.contains(
"url" ) )
546 tileSetUri = QString::fromStdString( tileAccessJson[
"url"].get<std::string>() );
548 else if ( tileAccessJson.contains(
"options" ) )
550 const auto &optionsJson = tileAccessJson[
"options"];
551 if ( optionsJson.contains(
"url" ) )
553 tileSetUri = QString::fromStdString( optionsJson[
"url"].get<std::string>() );
557 if ( tileAccessJson.contains(
"accessToken" ) )
561 headers.
insert( QStringLiteral(
"Authorization" ),
562 QStringLiteral(
"Bearer %1" ).arg( QString::fromStdString( tileAccessJson[
"accessToken"].get<std::string>() ) ) );
567 finalUri.
setParam(
"url", tileSetUri +
"layer.json" );
573QgsQuantizedMeshDataProvider::capabilities()
const
579 return new QgsQuantizedMeshDataProvider( mUri, mProviderOptions );
582QgsQuantizedMeshDataProvider::sceneCrs()
const
584 return mMetadata->mCrs;
587QgsQuantizedMeshDataProvider::boundingVolume()
const
589 return mMetadata->mBoundingVolume;
602 return mMetadata->dummyZRange;
606 return mMetadata->mCrs;
608QgsRectangle QgsQuantizedMeshDataProvider::extent()
const
610 return mMetadata->mExtent;
612bool QgsQuantizedMeshDataProvider::isValid()
const {
return mIsValid; }
613QString QgsQuantizedMeshDataProvider::name()
const {
return providerName; }
614QString QgsQuantizedMeshDataProvider::description()
const {
return providerDescription; }
616const QgsQuantizedMeshMetadata &QgsQuantizedMeshDataProvider::quantizedMeshMetadata()
const
621QgsQuantizedMeshProviderMetadata::QgsQuantizedMeshProviderMetadata()
623 QgsQuantizedMeshDataProvider::providerDescription ) {}
629 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.