27using namespace Qt::StringLiterals;
29int QgsCesiumImplicitTiling::subtreeTileCount(
int levels )
33 return (
static_cast<int>( std::pow( 4, levels ) ) - 1 ) / 3;
36int QgsCesiumImplicitTiling::mortonIndex(
int x,
int y )
42 for (
int i = 0; i < 16; ++i )
44 morton |= ( ( x & ( 1 << i ) ) << i ) | ( ( y & ( 1 << i ) ) << ( i + 1 ) );
51 int offsetForLevel = subtreeTileCount( localLevel );
52 return offsetForLevel + mortonIndex( localX, localY );
57 QString expanded = templateUri;
58 expanded.replace(
"{level}"_L1, QString::number( coord.
level ) );
59 expanded.replace(
"{x}"_L1, QString::number( coord.
x ) );
60 expanded.replace(
"{y}"_L1, QString::number( coord.
y ) );
62 const QString resolved = baseUrl.resolved( QUrl( expanded ) ).toString();
63 if ( baseUrl.hasQuery() && QUrl( expanded ).isRelative() )
69QgsBox3D QgsCesiumImplicitTiling::computeImplicitRegionBoundingVolume(
const QgsBox3D &rootRegion,
const TileCoordinate &coord )
71 const double divisor = std::pow( 2.0, coord.level );
72 const double tileWidth = rootRegion.
width() / divisor;
73 const double tileHeight = rootRegion.
height() / divisor;
75 const double xMin = rootRegion.
xMinimum() + coord.x * tileWidth;
76 const double yMin = rootRegion.
yMinimum() + coord.y * tileHeight;
84 const QgsOrientedBox3D &rootBox = rootVolume.
box();
88 const double *halfAxes = rootBox.
halfAxes();
89 const double divisor = std::pow( 2.0, coord.level );
97 const double childHalfAxes[9]
98 = { halfAxes[0] / divisor, halfAxes[1] / divisor, halfAxes[2] / divisor, halfAxes[3] / divisor, halfAxes[4] / divisor, halfAxes[5] / divisor, halfAxes[6], halfAxes[7], halfAxes[8] };
101 const double dx = ( coord.x + 0.5 ) / divisor;
102 const double dy = ( coord.y + 0.5 ) / divisor;
106 const double fx = dx * 2 - 1;
107 const double fy = dy * 2 - 1;
109 const double childCenter[3] = { rootBox.
centerX() + fx * halfAxes[0] + fy * halfAxes[3], rootBox.
centerY() + fx * halfAxes[1] + fy * halfAxes[4], rootBox.
centerZ() + fx * halfAxes[2] + fy * halfAxes[5] };
111 return QgsTiledSceneBoundingVolume( QgsOrientedBox3D(
112 QList<double>( { childCenter[0], childCenter[1], childCenter[2] } ),
113 QList<double>( { childHalfAxes[0], childHalfAxes[1], childHalfAxes[2], childHalfAxes[3], childHalfAxes[4], childHalfAxes[5], childHalfAxes[6], childHalfAxes[7], childHalfAxes[8] } )
122 if ( data.isEmpty() )
127 if ( data.size() < 24 )
133 const char *ptr = data.constData();
134 if ( memcmp( ptr,
"subt", 4 ) != 0 )
139 const auto subtreeJson = json::parse( data.toStdString() );
144 catch ( json::parse_error & )
146 QgsDebugError( u
"Invalid subtree data (not binary or JSON)"_s );
152 quint64 jsonByteLength = 0;
153 quint64 binaryByteLength = 0;
154 memcpy( &jsonByteLength, ptr + 8, 8 );
155 memcpy( &binaryByteLength, ptr + 16, 8 );
157 const int headerSize = 24;
158 if (
static_cast<quint64
>( data.size() ) < headerSize + jsonByteLength )
165 const QByteArray jsonChunk = data.mid( headerSize,
static_cast<int>( jsonByteLength ) );
169 subtreeJson = json::parse( jsonChunk.toStdString() );
171 catch ( json::parse_error & )
178 const quint64 binaryStart = headerSize + ( ( jsonByteLength + 7 ) & ~static_cast<quint64>( 7 ) );
179 const QByteArray binaryChunk = ( binaryByteLength > 0 &&
static_cast<quint64
>( data.size() ) >= binaryStart + binaryByteLength )
180 ? data.mid(
static_cast<int>( binaryStart ),
static_cast<int>( binaryByteLength ) )
184 std::vector<QPair<int, int>> bufferViews;
185 if ( subtreeJson.contains(
"bufferViews" ) )
187 for (
const auto &bv : subtreeJson[
"bufferViews"] )
189 const int byteOffset = bv.value(
"byteOffset", 0 );
190 const int byteLength = bv[
"byteLength"].get<
int>();
191 bufferViews.push_back( { byteOffset, byteLength } );
196 auto parseAvailability = [&](
const json &availJson,
int bitCount ) -> QBitArray {
197 QBitArray bits( bitCount,
false );
198 if ( availJson.contains(
"constant" ) )
200 const int constant = availJson[
"constant"].get<
int>();
205 else if ( availJson.contains(
"bitstream" ) )
207 const int bitstreamIdx = availJson[
"bitstream"].get<
int>();
208 if ( bitstreamIdx >= 0 && bitstreamIdx <
static_cast<int>( bufferViews.size() ) )
210 const auto &[byteOffset, byteLength] = bufferViews[bitstreamIdx];
211 if ( byteOffset + byteLength <= binaryChunk.size() )
213 const unsigned char *bitstreamData =
reinterpret_cast<const unsigned char *
>( binaryChunk.constData() + byteOffset );
214 for (
int i = 0; i < bitCount && ( i / 8 ) < byteLength; ++i )
216 if ( bitstreamData[i / 8] & ( 1 << ( i % 8 ) ) )
226 const int totalTiles = subtreeTileCount( tilingData.
subtreeLevels );
229 const int childSubtreeCount =
static_cast<int>( std::pow( 4, tilingData.
subtreeLevels ) );
231 if ( subtreeJson.contains(
"tileAvailability" ) )
232 result.
tileAvailability = parseAvailability( subtreeJson[
"tileAvailability"], totalTiles );
236 if ( subtreeJson.contains(
"contentAvailability" ) )
239 const auto &contentAvail = subtreeJson[
"contentAvailability"];
240 if ( contentAvail.is_array() && !contentAvail.empty() )
242 else if ( contentAvail.is_object() )
252 if ( subtreeJson.contains(
"childSubtreeAvailability" ) )
263 const int subtreeRootLevel = ( coord.
level / subtreeLevels ) * subtreeLevels;
264 const int localLevel = coord.
level - subtreeRootLevel;
265 const int subtreeRootX = coord.
x >> localLevel;
266 const int subtreeRootY = coord.
y >> localLevel;
275 QMap<TileCoordinate, QgsTiledSceneNode *> children;
279 const int childLevel = coord.
level + 1;
280 for (
int dy = 0; dy < 2; ++dy )
282 for (
int dx = 0; dx < 2; ++dx )
284 const int childX = 2 * coord.
x + dx;
285 const int childY = 2 * coord.
y + dy;
286 const int childLocalLevel = childLevel - subtreeCoord.
level;
289 bool childAvail =
false;
290 bool childHasContent =
false;
294 const int bitIdx =
subtreeBitIndex( childLocalLevel, childX - ( subtreeCoord.
x << childLocalLevel ), childY - ( subtreeCoord.
y << childLocalLevel ) );
302 const int childSubtreeIdx = mortonIndex( childX - ( subtreeCoord.
x << childLocalLevel ), childY - ( subtreeCoord.
y << childLocalLevel ) );
309 childHasContent =
false;
316 auto childTile = std::make_unique<QgsTiledSceneTile>( nextTileId++ );
317 childTile->setGeometricError( tilingData.
rootGeometricError / std::pow( 2.0, childLevel ) );
324 const QgsBox3D childRegion = computeImplicitRegionBoundingVolume( *tilingData.
rootRegion, childCoord );
329 childTile->setBoundingVolume( computeImplicitBoundingVolume( tilingData.
rootBoundingVolume, childCoord ) );
331 childTile->setBaseUrl( tilingData.
baseUrl );
332 childTile->setMetadata( {
333 { u
"gltfUpAxis"_s,
static_cast<int>( tilingData.
gltfUpAxis ) },
334 { u
"contentFormat"_s, u
"cesiumtiles"_s },
343 childTile->setResources( { { u
"content"_s, contentUri } } );
346 auto childNode = std::make_unique<QgsTiledSceneNode>( childTile.release() );
347 children.insert( childCoord, childNode.get() );
348 node->
addChild( childNode.release() );
362 const auto subtreeCacheIt = tilingData.
subtreeCache.constFind( subtreeCoord );
363 if ( subtreeCacheIt == tilingData.
subtreeCache.constEnd() )
369 const int localLevel = coord.
level - subtreeCoord.
level;
378 const Subtree &subtree = subtreeCacheIt.value();
379 const int childLocalLevel = localLevel + 1;
380 for (
int dy = 0; dy < 2; ++dy )
382 for (
int dx = 0; dx < 2; ++dx )
384 const int childX = 2 * coord.
x + dx;
385 const int childY = 2 * coord.
y + dy;
386 const int childSubtreeIdx = mortonIndex( childX - ( subtreeCoord.
x << childLocalLevel ), childY - ( subtreeCoord.
y << childLocalLevel ) );
396 const auto &implicit = json[
"implicitTiling"];
397 const std::string scheme = implicit.value(
"subdivisionScheme",
"" );
398 if ( scheme ==
"QUADTREE" )
400 if ( !implicit.contains(
"availableLevels" ) || !implicit.contains(
"subtreeLevels" ) )
402 QgsDebugError( u
"Implicit tiling is missing required availableLevels or subtreeLevels"_s );
406 tilingData.
subtreeLevels = implicit[
"subtreeLevels"].get<
int>();
408 if ( implicit.contains(
"subtrees" ) && implicit[
"subtrees"].contains(
"uri" ) )
409 tilingData.
subtreeUriTemplate = QString::fromStdString( implicit[
"subtrees"][
"uri"].get<std::string>() );
413 QgsDebugError( u
"Implicit tiling is missing required subtrees.uri"_s );
417 if ( json.contains(
"content" ) && json[
"content"].contains(
"uri" ) )
418 tilingData.
contentUriTemplate = QString::fromStdString( json[
"content"][
"uri"].get<std::string>() );
427 if ( json.contains(
"boundingVolume" ) && json[
"boundingVolume"].contains(
"region" ) )
445 QgsDebugError( u
"Unsupported implicit tiling subdivision scheme: %1"_s.arg( QString::fromStdString( scheme ) ) );
TileChildrenAvailability
Possible availability states for a tile's children.
@ NeedFetching
Tile has children, but they are not yet available and must be fetched.
@ NoChildren
Tile is known to have no children.
A 3-dimensional box composed of x, y, z coordinates.
double xMinimum() const
Returns the minimum x value.
double zMaximum() const
Returns the maximum z value.
double width() const
Returns the width of the box.
double zMinimum() const
Returns the minimum z value.
double yMinimum() const
Returns the minimum y value.
double height() const
Returns the height of the box.
bool isNull() const
Test if the box is null (holding no spatial information).
static QMap< TileCoordinate, QgsTiledSceneNode * > createImplicitTilingChildren(QgsTiledSceneNode *node, const TileCoordinate &coord, Root &tilingData, const TileCoordinate &subtreeCoord, QgsCoordinateTransformContext &transformContext, long long &nextTileId)
Creates immediate children of a node according to implicit tiling.
static TileCoordinate tileCoordinateToParentSubtree(TileCoordinate coord, int subtreeLevels)
Returns parent subtree's tile coordinates of the given tile.
static QString expandTemplateUri(const QString &templateUri, const QUrl &baseUrl, const TileCoordinate &coord)
Expands template URI (using {level}, {x}, {y} markers) to the final URI.
static Qgis::TileChildrenAvailability childAvailability(const Root &tilingData, const TileCoordinate &coord)
Returns tile availability of a child based on cached subtree data.
static bool parseImplicitTiling(const json &tileJson, QgsTiledSceneNode *newNode, const QUrl &baseUrl, Qgis::Axis gltfUpAxis, Root &tilingData)
Parses JSON definition of implicit tiling into tilingData argument and returns true on success.
static Subtree parseSubtree(const Root &tilingData, const QByteArray &data)
Parses subtree definition and returns it.
static int subtreeBitIndex(int localLevel, int localX, int localY)
Returns the bit index within a subtree's availability bitstream for a given local tile position.
static QString appendQueryFromBaseUrl(const QString &contentUri, const QUrl &baseUrl)
Copies any query items from the base URL to the content URI - to replicate undocumented Cesium JS beh...
static QgsTiledSceneBoundingVolume boundingVolumeFromRegion(const QgsBox3D ®ion, const QgsCoordinateTransformContext &transformContext)
Calculates oriented bounding box in EPSG:4978 from "region" defined with min/max lat/lon coordinates ...
static QgsBox3D parseRegion(const json ®ion)
Parses a region object from a Cesium JSON object to a 3D box.
Contains information about the context in which a coordinate transform is executed.
const double * halfAxes() const
Returns the half axes matrix;.
double centerZ() const
Returns the center z-coordinate.
bool isNull() const
Returns true if the box is a null box.
double centerX() const
Returns the center x-coordinate.
double centerY() const
Returns the center y-coordinate.
Represents a bounding volume for a tiled scene.
QgsOrientedBox3D box() const
Returns the volume's oriented box.
Allows representing QgsTiledSceneTiles in a hierarchical tree.
void addChild(QgsTiledSceneNode *child)
Adds a child to this node.
QgsTiledSceneTile * tile()
Returns the tile associated with the node.
Qgis::TileRefinementProcess refinementProcess() const
Returns the tile's refinement process.
const QgsTiledSceneBoundingVolume & boundingVolume() const
Returns the bounding volume for the tile.
const QgsMatrix4x4 * transform() const
Returns the tile's transform.
void setResources(const QVariantMap &resources)
Sets the resources attached to the tile.
double geometricError() const
Returns the tile's geometric error, which is the error, in meters, of the tile's simplified represent...
#define QgsDebugError(str)
Definition of root implicit tiling node (typically root node of the whole tileset,...
QMap< TileCoordinate, Subtree > subtreeCache
Qgis::TileRefinementProcess refinementProcess
double rootGeometricError
QString contentUriTemplate
std::optional< QgsBox3D > rootRegion
if the root node uses "region" bounding volume (in lat/lon), we use it to create child regions and th...
int subtreeLevels
how many levels are stored in a single subtree
int availableLevels
total number of available levels within the implicit tiling
std::optional< QgsMatrix4x4 > rootTransform
QgsTiledSceneBoundingVolume rootBoundingVolume
if the root node uses OBB as the bounding volume, we use it directly to create child volumes
QString subtreeUriTemplate
Data about subtree of a node - there should be subtree at least for root implicit tiling node,...
QBitArray contentAvailability
Bit array whether a tile has some content.
QBitArray tileAvailability
Bit array whether a tile is available.
QBitArray childSubtreeAvailability
Bit array to know where to look for more subtree definitions deeper in the hierarchy.
Contains ZXY coordinates of a node within implicit tiling.