39#include <nlohmann/json.hpp>
41#include <qnetworkrequest.h>
43#include <qstringliteral.h>
48class MissingFieldException :
public std::exception
51 MissingFieldException(
const char *field ) : mField( field ) { }
52 const char *what() const noexcept
54 return QString(
"Missing field: %1" ).arg( mField ).toLocal8Bit().data();
61static T jsonGet( nlohmann::json &json,
const char *idx )
63 auto &obj = json[idx];
66 throw MissingFieldException( idx );
72QgsQuantizedMeshMetadata::QgsQuantizedMeshMetadata(
83 QUrl metadataUrl = dsUri.
param(
"url" );
84 QNetworkRequest requestData( metadataUrl );
85 mHeaders.updateNetworkRequest( requestData );
87 QStringLiteral(
"QgsQuantizedMeshDataProvider" ) );
90 auto respCode = request.
get( requestData );
94 QObject::tr(
"Failed to retrieve quantized mesh tiles metadata: %1" )
102 auto replyJson = nlohmann::json::parse( reply.data() );
105 if ( jsonGet<std::string>( replyJson,
"format" ) !=
"quantized-mesh-1.0" )
107 error.
append( QObject::tr(
"Unexpected tile format: %1" )
108 .arg( replyJson[
"format"].dump().c_str() ) );
112 auto crsString = QString::fromStdString( jsonGet<std::string>( replyJson,
"projection" ) );
114 if ( !mCrs.isValid() )
116 error.
append( QObject::tr(
"Invalid CRS '%1'!" ).arg( crsString ) );
122 std::vector<double> bounds = jsonGet<std::vector<double>>( replyJson,
"bounds" );
123 if ( bounds.size() != 4 )
125 error.
append( QObject::tr(
"Bounds array doesn't have 4 items" ) );
128 mExtent =
QgsRectangle( bounds[0], bounds[1], bounds[2], bounds[3] );
130 catch ( MissingFieldException & )
132 mExtent = mCrs.bounds();
135 auto zRange = dummyZRange;
139 mExtent.xMinimum(), mExtent.yMinimum(), zRange.lower(),
140 mExtent.xMaximum(), mExtent.yMaximum(), zRange.upper() ) );
143 if ( replyJson.find(
"scheme" ) != replyJson.end() )
144 mTileScheme = QString::fromStdString( jsonGet<std::string>( replyJson,
"scheme" ) );
145 else if ( replyJson.find(
"schema" ) != replyJson.end() )
146 mTileScheme = QString::fromStdString( jsonGet<std::string>( replyJson,
"schema" ) );
147 else throw MissingFieldException(
"scheme/schema" );
149 for (
auto &aabbs : replyJson.at(
"available" ) )
151 QVector<QgsTileRange> tileRanges;
152 for (
auto &aabb : aabbs )
154 tileRanges.push_back(
156 jsonGet<int>( aabb,
"startX" ), jsonGet<int>( aabb,
"endX" ),
157 jsonGet<int>( aabb,
"startY" ), jsonGet<int>( aabb,
"endY" ) ) );
159 mAvailableTiles.push_back( tileRanges );
164 mMinZoom = jsonGet<uint8_t>( replyJson,
"minzoom" );
165 mMaxZoom = jsonGet<uint8_t>( replyJson,
"maxzoom" );
167 catch ( MissingFieldException & )
170 mMaxZoom = mAvailableTiles.size() - 1;
174 QString::fromStdString( jsonGet<std::string>( replyJson,
"version" ) );
175 for (
auto &urlStr : jsonGet<std::vector<std::string>>( replyJson,
"tiles" ) )
177 QUrl url = metadataUrl.resolved( QString::fromStdString( urlStr ) );
179 url.toString( QUrl::DecodeReserved ).replace(
"{version}", versionStr ) );
182 int rootTileCount = 1;
183 if ( crsString == QLatin1String(
"EPSG:4326" ) )
185 else if ( crsString != QLatin1String(
"EPSG:3857" ) )
186 error.
append( QObject::tr(
"Unhandled CRS: %1" ).arg( crsString ) );
193 QgsPointXY topLeft( crsBounds.xMinimum(), crsBounds.yMaximum() );
194 double z0TileSize = crsBounds.height();
198 catch ( nlohmann::json::exception &ex )
200 error.
append( QObject::tr(
"Error parsing JSON metadata: %1" ).arg( ex.what() ) );
202 catch ( MissingFieldException &ex )
204 error.
append( QObject::tr(
"Error parsing JSON metadata: %1" ).arg( ex.what() ) );
208const QgsDoubleRange QgsQuantizedMeshMetadata::dummyZRange = {-10000, 10000};
219bool QgsQuantizedMeshMetadata::containsTile(
QgsTileXYZ tile )
const
222 tile.
zoomLevel() >= mAvailableTiles.size() )
226 if ( mTileScheme == QLatin1String(
"tms" ) )
227 tile = tileToTms( tile );
228 for (
auto &range : mAvailableTiles[tile.zoomLevel()] )
230 if ( range.startColumn() <= tile.
column() && range.endColumn() >= tile.
column() &&
231 range.startRow() <= tile.
row() && range.endRow() >= tile.
row() )
237double QgsQuantizedMeshMetadata::geometricErrorAtZoom(
int zoom )
const
242 return 400000 / pow( 2, zoom );
245long long QgsQuantizedMeshIndex::encodeTileId(
QgsTileXYZ tile )
249 Q_ASSERT( tile.
column() == 0 && tile.
row() == 0 );
252 Q_ASSERT( tile.
zoomLevel() < ( 2 << 4 ) && ( tile.
column() < ( 2 << 27 ) ) &&
253 ( tile.
row() < ( 2 << 27 ) ) );
254 return tile.
row() | ( (
long long )tile.
column() << 28 ) |
255 ( (
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 );
265 (
int )( (
id >> 28 ) & ( ( 2 << 27 ) - 1 ) ),
266 (
int )(
id & ( ( 2 << 27 ) - 1 ) ),
267 (
int )( (
id >> 56 ) & ( ( 2 << 4 ) - 1 ) ) );
274 auto bounds = mWgs84ToCrs.transform( mMetadata.mCrs.bounds() );
275 tile.setBoundingVolume(
277 QgsBox3D( bounds, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
278 tile.setGeometricError( std::numeric_limits<double>::max() );
281long long QgsQuantizedMeshIndex::parentTileId(
long long id )
const
283 if (
id == ROOT_TILE_ID )
285 auto tile = decodeTileId(
id );
290QVector<long long> QgsQuantizedMeshIndex::childTileIds(
long long id )
const
292 auto tile = decodeTileId(
id );
293 QVector<long long> children;
296 if ( mMetadata.containsTile( {x * 2, y * 2, zoom + 1} ) )
297 children.push_back( encodeTileId( {x * 2, y * 2, zoom + 1} ) );
298 if ( mMetadata.containsTile( {x * 2 + 1, y * 2, zoom + 1} ) )
299 children.push_back( encodeTileId( {x * 2 + 1, y * 2, zoom + 1} ) );
300 if ( mMetadata.containsTile( {x * 2, y * 2 + 1, zoom + 1} ) )
301 children.push_back( encodeTileId( {x * 2, y * 2 + 1, zoom + 1} ) );
302 if ( mMetadata.containsTile( {x * 2 + 1, y * 2 + 1, zoom + 1} ) )
303 children.push_back( encodeTileId( {x * 2 + 1, y * 2 + 1, zoom + 1} ) );
309 auto xyzTile = decodeTileId(
id );
313 auto tileExtent = zoomedMatrix.tileExtent( xyzTile );
315 sceneTile.setBoundingVolume(
317 QgsBox3D( tileExtent, mMetadata.dummyZRange.lower(), mMetadata.dummyZRange.upper() ) ) );
318 sceneTile.setGeometricError( mMetadata.geometricErrorAtZoom( xyzTile.
zoomLevel() ) );
320 if (
id == ROOT_TILE_ID )
324 if ( mMetadata.mTileScheme == QLatin1String(
"tms" ) )
325 xyzTile = tileToTms( xyzTile );
327 if ( mMetadata.mTileUrls.size() == 0 )
329 QgsDebugError(
"Quantized Mesh metadata has no URLs for tiles" );
335 mMetadata.mTileUrls[0], xyzTile, zoomedMatrix );
336 sceneTile.setResources( {{
"content", tileUri}} );
337 sceneTile.setMetadata(
339 {QStringLiteral(
"gltfUpAxis" ),
static_cast<int>(
Qgis::Axis::Z )},
340 {QStringLiteral(
"contentFormat" ), QStringLiteral(
"quantizedmesh" )},
347 tileExtent.width(), 0, 0, tileExtent.xMinimum(),
348 0, tileExtent.height(), 0, tileExtent.yMinimum(),
351 sceneTile.setTransform( transform );
358 uint8_t zoomLevel = mMetadata.mMinZoom;
361 while ( zoomLevel < mMetadata.mMaxZoom &&
367 QVector<long long> ids;
372 auto parentTile = decodeTileId( request.
parentTileId() );
373 extent.intersect( tileMatrix.tileExtent( parentTile ) );
376 auto tileRange = tileMatrix.tileRangeFromExtent( extent );
377 if ( !tileRange.isValid() )
380 for (
int col = tileRange.startColumn(); col <= tileRange.endColumn(); col++ )
381 for (
int row = tileRange.startRow(); row <= tileRange.endRow(); row++ )
383 auto xyzTile =
QgsTileXYZ( col, row, zoomLevel );
384 if ( mMetadata.containsTile( xyzTile ) )
385 ids.push_back( encodeTileId( xyzTile ) );
391QgsQuantizedMeshIndex::childAvailability(
long long id )
const
393 auto childIds = childTileIds(
id );
394 if ( childIds.count() == 0 )
398bool QgsQuantizedMeshIndex::fetchHierarchy(
long long id,
QgsFeedback *feedback )
405 Q_UNUSED( feedback );
409QByteArray QgsQuantizedMeshIndex::fetchContent(
const QString &uri,
412 QNetworkRequest requestData( uri );
413 requestData.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
414 requestData.setRawHeader(
"Accept",
"application/vnd.quantized-mesh,application/octet-stream;q=0.9" );
415 mMetadata.mHeaders.updateNetworkRequest( requestData );
418 QStringLiteral(
"QgsQuantizedMeshIndex" ) );
428 if ( reply->error() != QNetworkReply::NoError )
430 QgsDebugError( QStringLiteral(
"Request failed: %1" ).arg( uri ) );
433 return reply->data();
436QgsQuantizedMeshDataProvider::QgsQuantizedMeshDataProvider(
440 mProviderOptions( providerOptions )
442 mMetadata = QgsQuantizedMeshMetadata( uri, transformContext(), mError );
443 if ( mError.isEmpty() )
447 mIndex.emplace(
new QgsQuantizedMeshIndex( *mMetadata, wgs84ToCrs ) );
453QgsQuantizedMeshDataProvider::capabilities()
const
459 return new QgsQuantizedMeshDataProvider( mUri, mProviderOptions );
462QgsQuantizedMeshDataProvider::sceneCrs()
const
464 return mMetadata->mCrs;
467QgsQuantizedMeshDataProvider::boundingVolume()
const
469 return mMetadata->mBoundingVolume;
482 return mMetadata->dummyZRange;
486 return mMetadata->mCrs;
488QgsRectangle QgsQuantizedMeshDataProvider::extent()
const
490 return mMetadata->mExtent;
492bool QgsQuantizedMeshDataProvider::isValid()
const {
return mIsValid; }
493QString QgsQuantizedMeshDataProvider::name()
const {
return providerName; }
494QString QgsQuantizedMeshDataProvider::description()
const {
return providerDescription; }
496const QgsQuantizedMeshMetadata &QgsQuantizedMeshDataProvider::quantizedMeshMetadata()
const
501QgsQuantizedMeshProviderMetadata::QgsQuantizedMeshProviderMetadata()
503 QgsQuantizedMeshDataProvider::providerDescription ) {}
509 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.
@ NoError
No error was encountered.
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.
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.
QString authConfigId() const
Returns any associated authentication configuration ID stored in the URI.
QgsRange which stores a range of double values.
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.
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.