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 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 ) );
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 (
const QgsTileRange &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 const QgsRectangle bounds = mWgs84ToCrs.transform( mMetadata.mCrs.bounds() );
279 QgsBox3D( bounds, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
283long long QgsQuantizedMeshIndex::parentTileId(
long long id )
const
285 if (
id == ROOT_TILE_ID )
292QVector<long long> QgsQuantizedMeshIndex::childTileIds(
long long id )
const
295 QVector<long long> children;
296 const int x = tile.
column();
297 const int y = tile.
row();
300 if ( mMetadata.containsTile( {x * 2, y * 2, zoom + 1} ) )
301 children.push_back( encodeTileId( {x * 2, y * 2, zoom + 1} ) );
302 if ( mMetadata.containsTile( {x * 2 + 1, y * 2, zoom + 1} ) )
303 children.push_back( encodeTileId( {x * 2 + 1, y * 2, zoom + 1} ) );
304 if ( mMetadata.containsTile( {x * 2, y * 2 + 1, zoom + 1} ) )
305 children.push_back( encodeTileId( {x * 2, y * 2 + 1, zoom + 1} ) );
306 if ( mMetadata.containsTile( {x * 2 + 1, y * 2 + 1, zoom + 1} ) )
307 children.push_back( encodeTileId( {x * 2 + 1, y * 2 + 1, zoom + 1} ) );
319 sceneTile.setBoundingVolume(
321 QgsBox3D( tileExtent, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
322 sceneTile.setGeometricError( mMetadata.geometricErrorAtZoom( xyzTile.
zoomLevel() ) );
324 if (
id == ROOT_TILE_ID )
328 if ( mMetadata.mTileScheme == QLatin1String(
"tms" ) )
329 xyzTile = tileToTms( xyzTile );
331 if ( mMetadata.mTileUrls.size() == 0 )
333 QgsDebugError(
"Quantized Mesh metadata has no URLs for tiles" );
339 mMetadata.mTileUrls[0], xyzTile, zoomedMatrix );
340 sceneTile.setResources( {{
"content", tileUri}} );
341 sceneTile.setMetadata(
343 {QStringLiteral(
"gltfUpAxis" ),
static_cast<int>(
Qgis::Axis::Z )},
344 {QStringLiteral(
"contentFormat" ), QStringLiteral(
"quantizedmesh" )},
355 sceneTile.setTransform( transform );
362 uint8_t zoomLevel = mMetadata.mMinZoom;
365 while ( zoomLevel < mMetadata.mMaxZoom &&
371 QVector<long long> ids;
385 for (
int row = tileRange.
startRow(); row <= tileRange.
endRow(); row++ )
388 if ( mMetadata.containsTile( xyzTile ) )
389 ids.push_back( encodeTileId( xyzTile ) );
395QgsQuantizedMeshIndex::childAvailability(
long long id )
const
397 const QVector<long long> childIds = childTileIds(
id );
398 if ( childIds.count() == 0 )
402bool QgsQuantizedMeshIndex::fetchHierarchy(
long long id,
QgsFeedback *feedback )
409 Q_UNUSED( feedback );
413QByteArray QgsQuantizedMeshIndex::fetchContent(
const QString &uri,
416 QNetworkRequest requestData( uri );
417 requestData.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
418 requestData.setRawHeader(
"Accept",
"application/vnd.quantized-mesh,application/octet-stream;q=0.9" );
419 mMetadata.mHeaders.updateNetworkRequest( requestData );
420 if ( !mMetadata.mAuthCfg.isEmpty() )
423 QStringLiteral(
"QgsQuantizedMeshIndex" ) );
433 if ( reply->error() != QNetworkReply::NoError )
435 QgsDebugError( QStringLiteral(
"Request failed (%1): %2" ).arg( uri ).arg( reply->errorString() ) );
438 return reply->data();
441QgsQuantizedMeshDataProvider::QgsQuantizedMeshDataProvider(
445 mProviderOptions( providerOptions )
447 if ( uri.startsWith( QLatin1String(
"ion://" ) ) )
449 QString updatedUri = uriFromIon( uri );
450 mMetadata = QgsQuantizedMeshMetadata( updatedUri, transformContext(), mError );
454 mMetadata = QgsQuantizedMeshMetadata( uri, transformContext(), mError );
457 if ( mError.isEmpty() )
461 mIndex.emplace(
new QgsQuantizedMeshIndex( *mMetadata, wgs84ToCrs ) );
466QString QgsQuantizedMeshDataProvider::uriFromIon(
const QString &uri )
473 const QString assetId = QUrlQuery( url ).queryItemValue( QStringLiteral(
"assetId" ) );
474 const QString accessToken = QUrlQuery( url ).queryItemValue( QStringLiteral(
"accessToken" ) );
476 const QString CESIUM_ION_URL = QStringLiteral(
"https://api.cesium.com/" );
485 const QString assetInfoEndpoint = CESIUM_ION_URL + QStringLiteral(
"v1/assets/%1" ).arg( assetId );
486 QNetworkRequest request = QNetworkRequest( assetInfoEndpoint );
488 headers.updateNetworkRequest( request );
489 if ( !accessToken.isEmpty() )
490 request.setRawHeader( "Authorization", QStringLiteral( "Bearer %1" ).arg( accessToken ).toLocal8Bit() );
493 if ( accessToken.isEmpty() )
494 networkRequest.setAuthCfg( authCfg );
496 switch ( networkRequest.get( request ) )
509 const json assetInfoJson = json::parse( content.
content().toStdString() );
510 if ( assetInfoJson[
"type"] !=
"TERRAIN" )
512 appendError(
QgsErrorMessage( tr(
"Only ion TERRAIN content can be accessed, not %1" ).arg( QString::fromStdString( assetInfoJson[
"type"].get<std::string>() ) ) ) );
520 const QString tileAccessEndpoint = CESIUM_ION_URL + QStringLiteral(
"v1/assets/%1/endpoint" ).arg( assetId );
521 QNetworkRequest request = QNetworkRequest( tileAccessEndpoint );
523 headers.updateNetworkRequest( request );
524 if ( !accessToken.isEmpty() )
525 request.setRawHeader( "Authorization", QStringLiteral( "Bearer %1" ).arg( accessToken ).toLocal8Bit() );
528 if ( accessToken.isEmpty() )
529 networkRequest.setAuthCfg( authCfg );
531 switch ( networkRequest.get( request ) )
544 const json tileAccessJson = json::parse( content.
content().toStdString() );
546 if ( tileAccessJson.contains(
"url" ) )
548 tileSetUri = QString::fromStdString( tileAccessJson[
"url"].get<std::string>() );
550 else if ( tileAccessJson.contains(
"options" ) )
552 const auto &optionsJson = tileAccessJson[
"options"];
553 if ( optionsJson.contains(
"url" ) )
555 tileSetUri = QString::fromStdString( optionsJson[
"url"].get<std::string>() );
559 if ( tileAccessJson.contains(
"accessToken" ) )
563 headers.
insert( QStringLiteral(
"Authorization" ),
564 QStringLiteral(
"Bearer %1" ).arg( QString::fromStdString( tileAccessJson[
"accessToken"].get<std::string>() ) ) );
569 finalUri.
setParam(
"url", tileSetUri +
"layer.json" );
575QgsQuantizedMeshDataProvider::capabilities()
const
581 return new QgsQuantizedMeshDataProvider( mUri, mProviderOptions );
584QgsQuantizedMeshDataProvider::sceneCrs()
const
586 return mMetadata->mCrs;
589QgsQuantizedMeshDataProvider::boundingVolume()
const
591 return mMetadata->mBoundingVolume;
604 return mMetadata->dummyZRange;
608 return mMetadata->mCrs;
610QgsRectangle QgsQuantizedMeshDataProvider::extent()
const
612 return mMetadata->mExtent;
614bool QgsQuantizedMeshDataProvider::isValid()
const {
return mIsValid; }
615QString QgsQuantizedMeshDataProvider::name()
const {
return providerName; }
616QString QgsQuantizedMeshDataProvider::description()
const {
return providerDescription; }
618const QgsQuantizedMeshMetadata &QgsQuantizedMeshDataProvider::quantizedMeshMetadata()
const
623QgsQuantizedMeshProviderMetadata::QgsQuantizedMeshProviderMetadata()
625 QgsQuantizedMeshDataProvider::providerDescription ) {}
631 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.
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.
Range of tiles in a tile matrix to be rendered.
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.