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 )
63 const char *what() const noexcept
override {
return QString(
"Missing field: %1" ).arg( mField ).toLocal8Bit().constData(); }
69template<
typename T>
static T jsonGet( nlohmann::json &json,
const char *idx )
71 auto &obj = json[idx];
74 throw MissingFieldException( idx );
88 QUrl metadataUrl = dsUri.
param(
"url" );
89 QNetworkRequest requestData( metadataUrl );
90 mHeaders.updateNetworkRequest( requestData );
93 if ( !mAuthCfg.isEmpty() )
98 error.
append( QObject::tr(
"Failed to retrieve quantized mesh tiles metadata: %1" ).arg( request.
errorMessage() ) );
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" ).arg( replyJson[
"format"].dump().c_str() ) );
114 const QString 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 mBoundingVolume =
QgsOrientedBox3D::fromBox3D(
QgsBox3D( mExtent.xMinimum(), mExtent.yMinimum(), dummyZRange.lower(), mExtent.xMaximum(), mExtent.yMaximum(), dummyZRange.upper() ) );
140 if ( replyJson.find(
"scheme" ) != replyJson.end() )
141 mTileScheme = QString::fromStdString( jsonGet<std::string>( replyJson,
"scheme" ) );
142 else if ( replyJson.find(
"schema" ) != replyJson.end() )
143 mTileScheme = QString::fromStdString( jsonGet<std::string>( replyJson,
"schema" ) );
145 throw MissingFieldException(
"scheme/schema" );
147 if ( replyJson.find(
"available" ) != replyJson.end() )
149 for (
auto &aabbs : replyJson.at(
"available" ) )
151 QVector<QgsTileRange> tileRanges;
152 for (
auto &aabb : aabbs )
154 tileRanges.push_back(
QgsTileRange( jsonGet<int>( aabb,
"startX" ), jsonGet<int>( aabb,
"endX" ), jsonGet<int>( aabb,
"startY" ), jsonGet<int>( aabb,
"endY" ) ) );
156 mAvailableTiles.push_back( tileRanges );
162 mMinZoom = jsonGet<uint8_t>( replyJson,
"minzoom" );
163 mMaxZoom = jsonGet<uint8_t>( replyJson,
"maxzoom" );
165 catch ( MissingFieldException & )
168 if ( mAvailableTiles.isEmpty() )
171 error.
append( QObject::tr(
"Missing max zoom or tile availability info" ) );
175 mMaxZoom = mAvailableTiles.size() - 1;
179 QString versionStr = QString::fromStdString( jsonGet<std::string>( replyJson,
"version" ) );
180 for (
auto &urlStr : jsonGet<std::vector<std::string>>( replyJson,
"tiles" ) )
182 QUrl url = metadataUrl.resolved( QString::fromStdString( urlStr ) );
183 mTileUrls.push_back( url.toString( QUrl::DecodeReserved ).replace(
"{version}", versionStr ) );
186 int rootTileCount = 1;
187 if ( crsString ==
"EPSG:4326"_L1 )
189 else if ( crsString !=
"EPSG:3857"_L1 )
190 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 };
219bool QgsQuantizedMeshMetadata::containsTile(
QgsTileXYZ tile )
const
223 if ( mAvailableTiles.isEmpty() )
225 if ( tile.
zoomLevel() >= mAvailableTiles.size() )
229 if ( mTileScheme ==
"tms"_L1 )
230 tile = tileToTms( tile );
233 if ( range.startColumn() <= tile.
column() && range.endColumn() >= tile.
column() && 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 ) ) && ( tile.
row() < ( 2 << 27 ) ) );
255 return tile.
row() | ( (
long long ) tile.
column() << 28 ) | ( (
long long ) tile.
zoomLevel() << 56 ) | ( (
long long ) 1 << 61 );
258QgsTileXYZ QgsQuantizedMeshIndex::decodeTileId(
long long id )
260 if (
id == ROOT_TILE_ID )
263 Q_ASSERT(
id >> 61 == 1 );
264 return QgsTileXYZ( (
int ) ( (
id >> 28 ) & ( ( 2 << 27 ) - 1 ) ), (
int ) (
id & ( ( 2 << 27 ) - 1 ) ), (
int ) ( (
id >> 56 ) & ( ( 2 << 4 ) - 1 ) ) );
271 const QgsRectangle bounds = mWgs84ToCrs.transform( mMetadata.mCrs.bounds() );
276long long QgsQuantizedMeshIndex::parentTileId(
long long id )
const
278 if (
id == ROOT_TILE_ID )
285QVector<long long> QgsQuantizedMeshIndex::childTileIds(
long long id )
const
288 QVector<long long> children;
289 const int x = tile.
column();
290 const int y = tile.
row();
293 if ( mMetadata.containsTile( { x * 2, y * 2, zoom + 1 } ) )
294 children.push_back( encodeTileId( { x * 2, y * 2, zoom + 1 } ) );
295 if ( mMetadata.containsTile( { x * 2 + 1, y * 2, zoom + 1 } ) )
296 children.push_back( encodeTileId( { x * 2 + 1, y * 2, zoom + 1 } ) );
297 if ( mMetadata.containsTile( { x * 2, y * 2 + 1, zoom + 1 } ) )
298 children.push_back( encodeTileId( { x * 2, y * 2 + 1, zoom + 1 } ) );
299 if ( mMetadata.containsTile( { x * 2 + 1, y * 2 + 1, zoom + 1 } ) )
300 children.push_back( encodeTileId( { x * 2 + 1, y * 2 + 1, zoom + 1 } ) );
313 sceneTile.setGeometricError( mMetadata.geometricErrorAtZoom( xyzTile.
zoomLevel() ) );
315 if (
id == ROOT_TILE_ID )
319 if ( mMetadata.mTileScheme ==
"tms"_L1 )
320 xyzTile = tileToTms( xyzTile );
322 if ( mMetadata.mTileUrls.size() == 0 )
324 QgsDebugError(
"Quantized Mesh metadata has no URLs for tiles" );
330 sceneTile.setResources( { {
"content", tileUri } } );
331 sceneTile.setMetadata( {
333 { u
"contentFormat"_s, u
"quantizedmesh"_s },
339 QgsMatrix4x4 transform( tileExtent.
width(), 0, 0, tileExtent.
xMinimum(), 0, tileExtent.
height(), 0, tileExtent.
yMinimum(), 0, 0, 1, 0, 0, 0, 0, 1 );
340 sceneTile.setTransform( transform );
346 uint8_t zoomLevel = mMetadata.mMinZoom;
349 while ( zoomLevel < mMetadata.mMaxZoom && mMetadata.geometricErrorAtZoom( zoomLevel ) > request.
requiredGeometricError() )
354 QVector<long long> ids;
368 for (
int row = tileRange.
startRow(); row <= tileRange.
endRow(); row++ )
371 if ( mMetadata.containsTile( xyzTile ) )
372 ids.push_back( encodeTileId( xyzTile ) );
379 const QVector<long long> childIds = childTileIds(
id );
380 if ( childIds.count() == 0 )
384bool QgsQuantizedMeshIndex::fetchHierarchy(
long long id,
QgsFeedback *feedback )
391 Q_UNUSED( feedback );
395QByteArray QgsQuantizedMeshIndex::fetchContent(
const QString &uri,
QgsFeedback *feedback )
397 QNetworkRequest requestData( uri );
398 requestData.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
399 requestData.setRawHeader(
"Accept",
"application/vnd.quantized-mesh,application/octet-stream;q=0.9" );
400 mMetadata.mHeaders.updateNetworkRequest( requestData );
401 if ( !mMetadata.mAuthCfg.isEmpty() )
413 if ( reply->error() != QNetworkReply::NoError )
415 QgsDebugError( u
"Request failed (%1): %2"_s.arg( uri ).arg( reply->errorString() ) );
418 return reply->data();
424 , mProviderOptions( providerOptions )
426 if ( uri.startsWith(
"ion://"_L1 ) )
428 QString updatedUri = uriFromIon( uri );
429 mMetadata = QgsQuantizedMeshMetadata( updatedUri, transformContext(), mError );
433 mMetadata = QgsQuantizedMeshMetadata( uri, transformContext(), mError );
436 if ( mError.isEmpty() )
438 QgsCoordinateReferenceSystem wgs84( u
"EPSG:4326"_s );
439 QgsCoordinateTransform wgs84ToCrs( wgs84, mMetadata->mCrs, transformContext() );
440 mIndex.emplace( new QgsQuantizedMeshIndex( *mMetadata, wgs84ToCrs ) );
445QString QgsQuantizedMeshDataProvider::uriFromIon(
const QString &uri )
452 const QString assetId = QUrlQuery( url ).queryItemValue( u
"assetId"_s );
453 const QString accessToken = QUrlQuery( url ).queryItemValue( u
"accessToken"_s );
455 const QString CESIUM_ION_URL = u
"https://api.cesium.com/"_s;
464 const QString assetInfoEndpoint = CESIUM_ION_URL + u
"v1/assets/%1"_s.arg( assetId );
465 QNetworkRequest request = QNetworkRequest( assetInfoEndpoint );
467 if ( !accessToken.isEmpty() )
468 request.setRawHeader(
"Authorization", u
"Bearer %1"_s.arg( accessToken ).toLocal8Bit() );
471 if ( accessToken.isEmpty() )
474 switch ( networkRequest.
get( request ) )
487 const json assetInfoJson = json::parse( content.
content().toStdString() );
488 if ( assetInfoJson[
"type"] !=
"TERRAIN" )
490 appendError(
QgsErrorMessage( tr(
"Only ion TERRAIN content can be accessed, not %1" ).arg( QString::fromStdString( assetInfoJson[
"type"].get<std::string>() ) ) ) );
498 const QString tileAccessEndpoint = CESIUM_ION_URL + u
"v1/assets/%1/endpoint"_s.arg( assetId );
499 QNetworkRequest request = QNetworkRequest( tileAccessEndpoint );
501 if ( !accessToken.isEmpty() )
502 request.setRawHeader(
"Authorization", u
"Bearer %1"_s.arg( accessToken ).toLocal8Bit() );
505 if ( accessToken.isEmpty() )
508 switch ( networkRequest.
get( request ) )
521 const json tileAccessJson = json::parse( content.
content().toStdString() );
523 if ( tileAccessJson.contains(
"url" ) )
525 tileSetUri = QString::fromStdString( tileAccessJson[
"url"].get<std::string>() );
527 else if ( tileAccessJson.contains(
"options" ) )
529 const auto &optionsJson = tileAccessJson[
"options"];
530 if ( optionsJson.contains(
"url" ) )
532 tileSetUri = QString::fromStdString( optionsJson[
"url"].get<std::string>() );
536 if ( tileAccessJson.contains(
"accessToken" ) )
540 headers.
insert( u
"Authorization"_s, u
"Bearer %1"_s.arg( QString::fromStdString( tileAccessJson[
"accessToken"].get<std::string>() ) ) );
545 finalUri.
setParam(
"url", tileSetUri +
"layer.json" );
556 return new QgsQuantizedMeshDataProvider( mUri, mProviderOptions );
560 return mMetadata->mCrs;
564 return mMetadata->mBoundingVolume;
577 return mMetadata->dummyZRange;
581 return mMetadata->mCrs;
583QgsRectangle QgsQuantizedMeshDataProvider::extent()
const
585 return mMetadata->mExtent;
587bool QgsQuantizedMeshDataProvider::isValid()
const
591QString QgsQuantizedMeshDataProvider::name()
const
595QString QgsQuantizedMeshDataProvider::description()
const
597 return providerDescription;
600const QgsQuantizedMeshMetadata &QgsQuantizedMeshDataProvider::quantizedMeshMetadata()
const
605QgsQuantizedMeshProviderMetadata::QgsQuantizedMeshProviderMetadata()
606 :
QgsProviderMetadata( QgsQuantizedMeshDataProvider::providerName, QgsQuantizedMeshDataProvider::providerDescription )
611 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.