22#include <nlohmann/json.hpp>
46#include <qnetworkrequest.h>
48#include <qstringliteral.h>
51#include "moc_qgsquantizedmeshdataprovider.cpp"
53using namespace Qt::StringLiterals;
57class MissingFieldException :
public std::exception
60 MissingFieldException(
const char *field ) : mField( field ) { }
61 const char *what() const noexcept
override
63 return QString(
"Missing field: %1" ).arg( mField ).toLocal8Bit().constData();
70static T jsonGet( nlohmann::json &json,
const char *idx )
72 auto &obj = json[idx];
75 throw MissingFieldException( idx );
81QgsQuantizedMeshMetadata::QgsQuantizedMeshMetadata(
92 QUrl metadataUrl = dsUri.
param(
"url" );
93 QNetworkRequest requestData( metadataUrl );
94 mHeaders.updateNetworkRequest( requestData );
96 u
"QgsQuantizedMeshDataProvider"_s );
98 if ( !mAuthCfg.isEmpty() )
104 QObject::tr(
"Failed to retrieve quantized mesh tiles metadata: %1" )
112 auto replyJson = nlohmann::json::parse( reply.data() );
115 if ( jsonGet<std::string>( replyJson,
"format" ) !=
"quantized-mesh-1.0" )
117 error.
append( QObject::tr(
"Unexpected tile format: %1" )
118 .arg( replyJson[
"format"].dump().c_str() ) );
122 const QString crsString = QString::fromStdString( jsonGet<std::string>( replyJson,
"projection" ) );
124 if ( !mCrs.isValid() )
126 error.
append( QObject::tr(
"Invalid CRS '%1'!" ).arg( crsString ) );
132 std::vector<double> bounds = jsonGet<std::vector<double>>( replyJson,
"bounds" );
133 if ( bounds.size() != 4 )
135 error.
append( QObject::tr(
"Bounds array doesn't have 4 items" ) );
138 mExtent =
QgsRectangle( bounds[0], bounds[1], bounds[2], bounds[3] );
140 catch ( MissingFieldException & )
142 mExtent = mCrs.bounds();
148 mExtent.xMinimum(), mExtent.yMinimum(), dummyZRange.lower(),
149 mExtent.xMaximum(), mExtent.yMaximum(), dummyZRange.upper() ) );
152 if ( replyJson.find(
"scheme" ) != replyJson.end() )
153 mTileScheme = QString::fromStdString( jsonGet<std::string>( replyJson,
"scheme" ) );
154 else if ( replyJson.find(
"schema" ) != replyJson.end() )
155 mTileScheme = QString::fromStdString( jsonGet<std::string>( replyJson,
"schema" ) );
156 else throw MissingFieldException(
"scheme/schema" );
158 if ( replyJson.find(
"available" ) != replyJson.end() )
160 for (
auto &aabbs : replyJson.at(
"available" ) )
162 QVector<QgsTileRange> tileRanges;
163 for (
auto &aabb : aabbs )
165 tileRanges.push_back(
167 jsonGet<int>( aabb,
"startX" ), jsonGet<int>( aabb,
"endX" ),
168 jsonGet<int>( aabb,
"startY" ), jsonGet<int>( aabb,
"endY" ) ) );
170 mAvailableTiles.push_back( tileRanges );
176 mMinZoom = jsonGet<uint8_t>( replyJson,
"minzoom" );
177 mMaxZoom = jsonGet<uint8_t>( replyJson,
"maxzoom" );
179 catch ( MissingFieldException & )
182 if ( mAvailableTiles.isEmpty() )
185 error.
append( QObject::tr(
"Missing max zoom or tile availability info" ) );
189 mMaxZoom = mAvailableTiles.size() - 1;
194 QString::fromStdString( jsonGet<std::string>( replyJson,
"version" ) );
195 for (
auto &urlStr : jsonGet<std::vector<std::string>>( replyJson,
"tiles" ) )
197 QUrl url = metadataUrl.resolved( QString::fromStdString( urlStr ) );
199 url.toString( QUrl::DecodeReserved ).replace(
"{version}", versionStr ) );
202 int rootTileCount = 1;
203 if ( crsString ==
"EPSG:4326"_L1 )
205 else if ( crsString !=
"EPSG:3857"_L1 )
206 error.
append( QObject::tr(
"Unhandled CRS: %1" ).arg( crsString ) );
214 double z0TileSize = crsBounds.
height();
218 catch ( nlohmann::json::exception &ex )
220 error.
append( QObject::tr(
"Error parsing JSON metadata: %1" ).arg( ex.what() ) );
222 catch ( MissingFieldException &ex )
224 error.
append( QObject::tr(
"Error parsing JSON metadata: %1" ).arg( ex.what() ) );
228const QgsDoubleRange QgsQuantizedMeshMetadata::dummyZRange = {-10000, 10000};
239bool QgsQuantizedMeshMetadata::containsTile(
QgsTileXYZ tile )
const
243 if ( mAvailableTiles.isEmpty() )
245 if ( tile.
zoomLevel() >= mAvailableTiles.size() )
249 if ( mTileScheme ==
"tms"_L1 )
250 tile = tileToTms( tile );
253 if ( range.startColumn() <= tile.
column() && range.endColumn() >= tile.
column() &&
254 range.startRow() <= tile.
row() && range.endRow() >= tile.
row() )
260double QgsQuantizedMeshMetadata::geometricErrorAtZoom(
int zoom )
const
265 return 400000 / pow( 2, zoom );
268long long QgsQuantizedMeshIndex::encodeTileId(
QgsTileXYZ tile )
272 Q_ASSERT( tile.
column() == 0 && tile.
row() == 0 );
275 Q_ASSERT( tile.
zoomLevel() < ( 2 << 4 ) && ( tile.
column() < ( 2 << 27 ) ) &&
276 ( tile.
row() < ( 2 << 27 ) ) );
277 return tile.
row() | ( (
long long )tile.
column() << 28 ) |
278 ( (
long long )tile.
zoomLevel() << 56 ) | ( (
long long ) 1 << 61 );
281QgsTileXYZ QgsQuantizedMeshIndex::decodeTileId(
long long id )
283 if (
id == ROOT_TILE_ID )
286 Q_ASSERT(
id >> 61 == 1 );
288 (
int )( (
id >> 28 ) & ( ( 2 << 27 ) - 1 ) ),
289 (
int )(
id & ( ( 2 << 27 ) - 1 ) ),
290 (
int )( (
id >> 56 ) & ( ( 2 << 4 ) - 1 ) ) );
297 const QgsRectangle bounds = mWgs84ToCrs.transform( mMetadata.mCrs.bounds() );
300 QgsBox3D( bounds, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
304long long QgsQuantizedMeshIndex::parentTileId(
long long id )
const
306 if (
id == ROOT_TILE_ID )
313QVector<long long> QgsQuantizedMeshIndex::childTileIds(
long long id )
const
316 QVector<long long> children;
317 const int x = tile.
column();
318 const int y = tile.
row();
321 if ( mMetadata.containsTile( {x * 2, y * 2, zoom + 1} ) )
322 children.push_back( encodeTileId( {x * 2, y * 2, zoom + 1} ) );
323 if ( mMetadata.containsTile( {x * 2 + 1, y * 2, zoom + 1} ) )
324 children.push_back( encodeTileId( {x * 2 + 1, y * 2, zoom + 1} ) );
325 if ( mMetadata.containsTile( {x * 2, y * 2 + 1, zoom + 1} ) )
326 children.push_back( encodeTileId( {x * 2, y * 2 + 1, zoom + 1} ) );
327 if ( mMetadata.containsTile( {x * 2 + 1, y * 2 + 1, zoom + 1} ) )
328 children.push_back( encodeTileId( {x * 2 + 1, y * 2 + 1, zoom + 1} ) );
340 sceneTile.setBoundingVolume(
342 QgsBox3D( tileExtent, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
343 sceneTile.setGeometricError( mMetadata.geometricErrorAtZoom( xyzTile.
zoomLevel() ) );
345 if (
id == ROOT_TILE_ID )
349 if ( mMetadata.mTileScheme ==
"tms"_L1 )
350 xyzTile = tileToTms( xyzTile );
352 if ( mMetadata.mTileUrls.size() == 0 )
354 QgsDebugError(
"Quantized Mesh metadata has no URLs for tiles" );
360 mMetadata.mTileUrls[0], xyzTile, zoomedMatrix );
361 sceneTile.setResources( {{
"content", tileUri}} );
362 sceneTile.setMetadata(
365 {u
"contentFormat"_s, u
"quantizedmesh"_s},
376 sceneTile.setTransform( transform );
383 uint8_t zoomLevel = mMetadata.mMinZoom;
386 while ( zoomLevel < mMetadata.mMaxZoom &&
392 QVector<long long> ids;
406 for (
int row = tileRange.
startRow(); row <= tileRange.
endRow(); row++ )
409 if ( mMetadata.containsTile( xyzTile ) )
410 ids.push_back( encodeTileId( xyzTile ) );
416QgsQuantizedMeshIndex::childAvailability(
long long id )
const
418 const QVector<long long> childIds = childTileIds(
id );
419 if ( childIds.count() == 0 )
423bool QgsQuantizedMeshIndex::fetchHierarchy(
long long id,
QgsFeedback *feedback )
430 Q_UNUSED( feedback );
434QByteArray QgsQuantizedMeshIndex::fetchContent(
const QString &uri,
437 QNetworkRequest requestData( uri );
438 requestData.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
439 requestData.setRawHeader(
"Accept",
"application/vnd.quantized-mesh,application/octet-stream;q=0.9" );
440 mMetadata.mHeaders.updateNetworkRequest( requestData );
441 if ( !mMetadata.mAuthCfg.isEmpty() )
444 u
"QgsQuantizedMeshIndex"_s );
454 if ( reply->error() != QNetworkReply::NoError )
456 QgsDebugError( u
"Request failed (%1): %2"_s.arg( uri ).arg( reply->errorString() ) );
459 return reply->data();
462QgsQuantizedMeshDataProvider::QgsQuantizedMeshDataProvider(
466 mProviderOptions( providerOptions )
468 if ( uri.startsWith(
"ion://"_L1 ) )
470 QString updatedUri = uriFromIon( uri );
471 mMetadata = QgsQuantizedMeshMetadata( updatedUri, transformContext(), mError );
475 mMetadata = QgsQuantizedMeshMetadata( uri, transformContext(), mError );
478 if ( mError.isEmpty() )
480 QgsCoordinateReferenceSystem wgs84( u
"EPSG:4326"_s );
481 QgsCoordinateTransform wgs84ToCrs( wgs84, mMetadata->mCrs, transformContext() );
482 mIndex.emplace( new QgsQuantizedMeshIndex( *mMetadata, wgs84ToCrs ) );
487QString QgsQuantizedMeshDataProvider::uriFromIon(
const QString &uri )
494 const QString assetId = QUrlQuery( url ).queryItemValue( u
"assetId"_s );
495 const QString accessToken = QUrlQuery( url ).queryItemValue( u
"accessToken"_s );
497 const QString CESIUM_ION_URL = u
"https://api.cesium.com/"_s;
506 const QString assetInfoEndpoint = CESIUM_ION_URL + u
"v1/assets/%1"_s.arg( assetId );
507 QNetworkRequest request = QNetworkRequest( assetInfoEndpoint );
510 if ( !accessToken.isEmpty() )
511 request.setRawHeader(
"Authorization", u
"Bearer %1"_s.arg( accessToken ).toLocal8Bit() );
514 if ( accessToken.isEmpty() )
517 switch ( networkRequest.
get( request ) )
530 const json assetInfoJson = json::parse( content.
content().toStdString() );
531 if ( assetInfoJson[
"type"] !=
"TERRAIN" )
533 appendError(
QgsErrorMessage( tr(
"Only ion TERRAIN content can be accessed, not %1" ).arg( QString::fromStdString( assetInfoJson[
"type"].get<std::string>() ) ) ) );
541 const QString tileAccessEndpoint = CESIUM_ION_URL + u
"v1/assets/%1/endpoint"_s.arg( assetId );
542 QNetworkRequest request = QNetworkRequest( tileAccessEndpoint );
545 if ( !accessToken.isEmpty() )
546 request.setRawHeader(
"Authorization", u
"Bearer %1"_s.arg( accessToken ).toLocal8Bit() );
549 if ( accessToken.isEmpty() )
552 switch ( networkRequest.
get( request ) )
565 const json tileAccessJson = json::parse( content.
content().toStdString() );
567 if ( tileAccessJson.contains(
"url" ) )
569 tileSetUri = QString::fromStdString( tileAccessJson[
"url"].get<std::string>() );
571 else if ( tileAccessJson.contains(
"options" ) )
573 const auto &optionsJson = tileAccessJson[
"options"];
574 if ( optionsJson.contains(
"url" ) )
576 tileSetUri = QString::fromStdString( optionsJson[
"url"].get<std::string>() );
580 if ( tileAccessJson.contains(
"accessToken" ) )
584 headers.
insert( u
"Authorization"_s,
585 u
"Bearer %1"_s.arg( QString::fromStdString( tileAccessJson[
"accessToken"].get<std::string>() ) ) );
590 finalUri.
setParam(
"url", tileSetUri +
"layer.json" );
596QgsQuantizedMeshDataProvider::capabilities()
const
602 return new QgsQuantizedMeshDataProvider( mUri, mProviderOptions );
605QgsQuantizedMeshDataProvider::sceneCrs()
const
607 return mMetadata->mCrs;
610QgsQuantizedMeshDataProvider::boundingVolume()
const
612 return mMetadata->mBoundingVolume;
625 return mMetadata->dummyZRange;
629 return mMetadata->mCrs;
631QgsRectangle QgsQuantizedMeshDataProvider::extent()
const
633 return mMetadata->mExtent;
635bool QgsQuantizedMeshDataProvider::isValid()
const {
return mIsValid; }
636QString QgsQuantizedMeshDataProvider::name()
const {
return providerName; }
637QString QgsQuantizedMeshDataProvider::description()
const {
return providerDescription; }
639const QgsQuantizedMeshMetadata &QgsQuantizedMeshDataProvider::quantizedMeshMetadata()
const
644QgsQuantizedMeshProviderMetadata::QgsQuantizedMeshProviderMetadata()
646 QgsQuantizedMeshDataProvider::providerDescription ) {}
652 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.