// @ts-check
import Axis from "./Axis.js";
import Empty3DTileContent from "./Empty3DTileContent.js";
import RuntimeError from "../Core/RuntimeError.js";
import UrlTemplate3DTilesDataProvider from "./UrlTemplate3DTilesDataProvider.js";
import VectorGltf3DTileContent from "./VectorGltf3DTileContent.js";
import buildVectorGltfFromMVT from "./buildVectorGltfFromMVT.js";
import decodeMVT from "./decodeMVT.js";
import oneTimeWarning from "../Core/oneTimeWarning.js";
import defined from "../Core/defined.js";
/** @import Cesium3DTile from "./Cesium3DTile.js"; */
/** @import Cesium3DTileset from "./Cesium3DTileset.js"; */
/** @import Rectangle from "../Core/Rectangle.js"; */
/** @import Resource from "../Core/Resource.js"; */
/**
* A Mapbox Vector Tiles (MVT) data provider. Loads .mvt or .pbf tiles, converting tiles
* dynamically (at runtime) into 3D Tiles.
*
*
* This object is normally not instantiated directly, use {@link MVTDataProvider.fromUrl}.
*
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
class MVTDataProvider extends UrlTemplate3DTilesDataProvider {
/**
* Creates an MVTDataProvider from the specified URL template and options.
*
* @param {Resource|string} url URL template, containing {z}, {x}, and {y} placeholders.
* @param {object} [options] Provider options.
* @param {number} [options.minZoom=0] Minimum zoom level represented in the generated tileset.
* @param {number} [options.maxZoom=14] Maximum zoom level represented in the generated tileset.
* @param {Rectangle} [options.extent] Optional geographic extent in radians to constrain the generated tile tree.
* @param {string} [options.featureIdProperty] MVT property name to use as feature ID.
*/
static async fromUrl(url, options) {
return /** @type {Promise} */ (
super.fromUrl(url, options)
);
}
/**
* @returns {object}
* @protected
* @ignore
*/
_createTilesetLoadOptions() {
return {
skipLevelOfDetail: false,
enablePick: true,
featureIdLabel: "featureId_0",
instanceFeatureIdLabel: "instanceFeatureId_0",
};
}
/**
* @param {Cesium3DTileset} tileset
* @protected
* @ignore
*/
_configureTileset(tileset) {
tileset._modelUpAxis = Axis.Z;
tileset._modelForwardAxis = Axis.X;
}
/**
* @returns {object}
* @protected
* @ignore
*/
_createCodec() {
const featureIdProperty = this._featureIdProperty;
return {
contentType: "mvt",
missingTilePolicy: { statusCodes: [404, 204] },
/**
* @param {Cesium3DTileset} tileset
* @param {Cesium3DTile} tile
* @param {Resource} resource
* @param {ArrayBuffer} arrayBuffer
* @ignore
*/
createContent: async (tileset, tile, resource, arrayBuffer) => {
const decodedTile = decodeMVT(arrayBuffer);
const tileCoordinates = parseTileCoordinates(
resource.getUrlComponent(true),
);
const glb = buildVectorGltfFromMVT(decodedTile, tileCoordinates, {
featureIdProperty: featureIdProperty,
});
if (!defined(glb)) {
if (!hasAnyDecodedFeatures(decodedTile)) {
return new Empty3DTileContent(tileset, tile);
}
throw new RuntimeError(
"Decoded MVT tile did not produce vector glTF content.",
);
}
return VectorGltf3DTileContent.fromGltf(tileset, tile, resource, glb);
},
};
}
}
/**
* @param {string} url
* @returns {{tileZ:number, tileX:number, tileY:number}}
* @ignore
*/
function parseTileCoordinates(url) {
const match = url.match(/\/(\d+)\/(\d+)\/(\d+)(?:\.[^/?#]+)?(?:[?#]|$)/i);
if (!match) {
oneTimeWarning(
"MVTDataProvider.parseTileCoordinates",
`MVT tile URL did not match /{z}/{x}/{y} pattern. Falling back to z/x/y = 0/0/0. URL: ${url}`,
);
return { tileZ: 0, tileX: 0, tileY: 0 };
}
return {
tileZ: parseInt(match[1], 10),
tileX: parseInt(match[2], 10),
tileY: parseInt(match[3], 10),
};
}
/**
* @param {{layers:Array<{features:Array<*>}>}} decodedTile
* @returns {boolean}
* @ignore
*/
function hasAnyDecodedFeatures(decodedTile) {
const layers = decodedTile.layers;
for (let i = 0; i < layers.length; i++) {
if (layers[i].features.length > 0) {
return true;
}
}
return false;
}
MVTDataProvider._parseTileCoordinates = parseTileCoordinates;
export default MVTDataProvider;