Add existing to tracked
This commit is contained in:
+499
@@ -0,0 +1,499 @@
|
||||
// @ts-check
|
||||
|
||||
import Cartographic from "../Core/Cartographic.js";
|
||||
import Cesium3DTileset from "./Cesium3DTileset.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import Ellipsoid from "../Core/Ellipsoid.js";
|
||||
import Rectangle from "../Core/Rectangle.js";
|
||||
import Resource from "../Core/Resource.js";
|
||||
import getAbsoluteUri from "../Core/getAbsoluteUri.js";
|
||||
import WebMercatorTilingScheme from "../Core/WebMercatorTilingScheme.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
|
||||
/** @import FrameState from "./FrameState.js"; */
|
||||
/** @import PassState from "../Renderer/PassState.js"; */
|
||||
/** @import TilingScheme from "../Core/TilingScheme.js"; */
|
||||
|
||||
/**
|
||||
* @typedef {object} TilesetJsonRoot
|
||||
* @property {object} [boundingVolume]
|
||||
* @property {number} [geometricError]
|
||||
* @property {"REPLACE"|"ADD"} [refine]
|
||||
* @property {unknown} [content]
|
||||
* @property {unknown[]} [children]
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
const DEFAULT_MIN_ZOOM = 0;
|
||||
const DEFAULT_MAX_ZOOM = 14;
|
||||
const DEFAULT_REGION_MINIMUM_HEIGHT = -1000.0;
|
||||
const DEFAULT_REGION_MAXIMUM_HEIGHT = 10000.0;
|
||||
const EARTH_CIRCUMFERENCE_METERS =
|
||||
2.0 * Math.PI * Ellipsoid.WGS84.maximumRadius;
|
||||
const WEB_MERCATOR_TILE_SIZE = 256.0;
|
||||
|
||||
const scratchTileRectangle = new Rectangle();
|
||||
const scratchIntersectionRectangle = new Rectangle();
|
||||
|
||||
/**
|
||||
* Base provider for URL-template vector sources that are rendered through a
|
||||
* runtime-generated 3D Tiles tileset.
|
||||
*
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
class UrlTemplate3DTilesDataProvider {
|
||||
/**
|
||||
* @param {Resource|string} urlTemplate 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] Feature property name to use as feature ID when supported by content decoding.
|
||||
*/
|
||||
constructor(urlTemplate, options) {
|
||||
options = options ?? {};
|
||||
this._resource = Resource.createIfNeeded(urlTemplate);
|
||||
// @ts-expect-error Missing types.
|
||||
this._urlTemplate = this._resource.url;
|
||||
this._extent = Rectangle.clone(options.extent);
|
||||
this._minZoom = options.minZoom ?? DEFAULT_MIN_ZOOM;
|
||||
this._maxZoom = options.maxZoom ?? DEFAULT_MAX_ZOOM;
|
||||
this._featureIdProperty = options.featureIdProperty;
|
||||
this._show = true;
|
||||
this._tileset = undefined;
|
||||
this._tilesetJsonUrl = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a provider from a URL template.
|
||||
*
|
||||
* @param {Resource|string} url URL template containing {z}, {x}, and {y} placeholders.
|
||||
* @param {object} [options] Provider options.
|
||||
* @returns {Promise<UrlTemplate3DTilesDataProvider>}
|
||||
*/
|
||||
static async fromUrl(url, options) {
|
||||
const provider = new this(url, options);
|
||||
await provider._initializeTileset();
|
||||
return provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL template containing {z}/{x}/{y}.
|
||||
*
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
get urlTemplate() {
|
||||
return this._urlTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource derived from the URL template.
|
||||
*
|
||||
* @type {Resource}
|
||||
* @readonly
|
||||
*/
|
||||
get resource() {
|
||||
return this._resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional geographic extent in radians used to generate tile headers.
|
||||
*
|
||||
* @type {Rectangle|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
get extent() {
|
||||
return this._extent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backing 3D Tileset.
|
||||
*
|
||||
* @type {Cesium3DTileset|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
get tileset() {
|
||||
return this._tileset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the generated tileset is shown.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
get show() {
|
||||
return this._show;
|
||||
}
|
||||
|
||||
set show(value) {
|
||||
this._show = value;
|
||||
if (defined(this._tileset)) {
|
||||
this._tileset.show = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
_createRuntimeTilesetOptions() {
|
||||
return {
|
||||
minZoom: this._minZoom,
|
||||
maxZoom: this._maxZoom,
|
||||
extent: this._extent,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @returns {object}
|
||||
*/
|
||||
_createTilesetLoadOptions() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @param {Cesium3DTileset} _tileset
|
||||
*/
|
||||
_configureTileset(_tileset) {}
|
||||
|
||||
/**
|
||||
* Subclasses must return a runtime content codec describing how to turn
|
||||
* a downloaded tile payload into a {@link Cesium3DTileContent}. See
|
||||
* {@link Cesium3DTileset#_runtimeContentCodec} for the expected shape.
|
||||
*
|
||||
* @protected
|
||||
* @returns {object}
|
||||
*/
|
||||
_createCodec() {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
throw new DeveloperError(
|
||||
"UrlTemplate3DTilesDataProvider subclasses must implement _createCodec().",
|
||||
);
|
||||
//>>includeEnd('debug');
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _initializeTileset() {
|
||||
const tilesetJson = buildRuntimeTilesetJson(
|
||||
this._resource,
|
||||
this._createRuntimeTilesetOptions(),
|
||||
);
|
||||
const tilesetBlob = new Blob([JSON.stringify(tilesetJson)], {
|
||||
type: "application/json",
|
||||
});
|
||||
const tilesetUrl = URL.createObjectURL(tilesetBlob);
|
||||
this._tilesetJsonUrl = tilesetUrl;
|
||||
|
||||
try {
|
||||
this._tileset = await Cesium3DTileset.fromUrl(
|
||||
tilesetUrl,
|
||||
this._createTilesetLoadOptions(),
|
||||
);
|
||||
} catch (error) {
|
||||
URL.revokeObjectURL(tilesetUrl);
|
||||
this._tilesetJsonUrl = undefined;
|
||||
throw error;
|
||||
}
|
||||
URL.revokeObjectURL(tilesetUrl);
|
||||
this._tilesetJsonUrl = undefined;
|
||||
|
||||
this._configureTileset(this._tileset);
|
||||
this._tileset._runtimeContentCodec = this._createCodec();
|
||||
this._tileset.show = this._show;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
update(frameState) {
|
||||
if (defined(this._tileset)) {
|
||||
this._tileset.update(frameState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
prePassesUpdate(frameState) {
|
||||
if (defined(this._tileset)) {
|
||||
this._tileset.prePassesUpdate(frameState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
postPassesUpdate(frameState) {
|
||||
if (defined(this._tileset)) {
|
||||
this._tileset.postPassesUpdate(frameState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {FrameState} frameState
|
||||
* @param {PassState} passState
|
||||
*/
|
||||
updateForPass(frameState, passState) {
|
||||
if (defined(this._tileset)) {
|
||||
this._tileset.updateForPass(frameState, passState);
|
||||
}
|
||||
}
|
||||
|
||||
isDestroyed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (defined(this._tileset)) {
|
||||
this._tileset.destroy();
|
||||
this._tileset = undefined;
|
||||
}
|
||||
if (defined(this._tilesetJsonUrl)) {
|
||||
URL.revokeObjectURL(this._tilesetJsonUrl);
|
||||
this._tilesetJsonUrl = undefined;
|
||||
}
|
||||
this._resource = undefined;
|
||||
this._extent = undefined;
|
||||
this._featureIdProperty = undefined;
|
||||
return destroyObject(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Resource} resource
|
||||
* @param {object} options
|
||||
* @param {number} options.minZoom
|
||||
* @param {number} options.maxZoom
|
||||
* @param {Rectangle} [options.extent]
|
||||
* @returns {object}
|
||||
* @ignore
|
||||
*/
|
||||
function buildRuntimeTilesetJson(resource, options) {
|
||||
const tilingScheme = new WebMercatorTilingScheme();
|
||||
const extent = defined(options.extent)
|
||||
? Rectangle.clone(options.extent)
|
||||
: Rectangle.clone(tilingScheme.rectangle);
|
||||
const minLevelRange = computeTileRangeForExtent(
|
||||
tilingScheme,
|
||||
extent,
|
||||
options.minZoom,
|
||||
);
|
||||
|
||||
/** @type {TilesetJsonRoot} */
|
||||
const root = {
|
||||
boundingVolume: {
|
||||
region: rectangleToRegion(extent),
|
||||
},
|
||||
// Root has no renderable content, so keep a coarse error to ensure
|
||||
// refinement reaches the first renderable zoom even when minZoom is high.
|
||||
geometricError: computeGeometricError(0),
|
||||
refine: "REPLACE",
|
||||
children: [],
|
||||
};
|
||||
for (let y = minLevelRange.minY; y <= minLevelRange.maxY; y++) {
|
||||
for (let x = minLevelRange.minX; x <= minLevelRange.maxX; x++) {
|
||||
const child = buildTileNode(
|
||||
tilingScheme,
|
||||
resource,
|
||||
extent,
|
||||
options.minZoom,
|
||||
options.maxZoom,
|
||||
x,
|
||||
y,
|
||||
);
|
||||
if (defined(child)) {
|
||||
root.children.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (root.children.length === 0) {
|
||||
root.geometricError = 0.0;
|
||||
}
|
||||
return {
|
||||
asset: {
|
||||
version: "1.1",
|
||||
},
|
||||
geometricError: root.geometricError,
|
||||
root: root,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TilingScheme} tilingScheme
|
||||
* @param {Resource} resource
|
||||
* @param {Rectangle} extent
|
||||
* @param {number} level
|
||||
* @param {number} maxZoom
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @ignore
|
||||
*/
|
||||
function buildTileNode(tilingScheme, resource, extent, level, maxZoom, x, y) {
|
||||
if (
|
||||
!tileIntersectsExtent(
|
||||
tilingScheme,
|
||||
level,
|
||||
x,
|
||||
y,
|
||||
extent,
|
||||
scratchTileRectangle,
|
||||
scratchIntersectionRectangle,
|
||||
)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
const tileRectangle = tilingScheme.tileXYToRectangle(
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
new Rectangle(),
|
||||
);
|
||||
|
||||
/** @type {TilesetJsonRoot} */
|
||||
const node = {
|
||||
boundingVolume: {
|
||||
region: rectangleToRegion(tileRectangle),
|
||||
},
|
||||
geometricError: level < maxZoom ? computeGeometricError(level) : 0.0,
|
||||
refine: "REPLACE",
|
||||
content: {
|
||||
uri: resolveTileUrl(resource, level, x, y),
|
||||
},
|
||||
};
|
||||
if (level >= maxZoom) {
|
||||
return node;
|
||||
}
|
||||
const childLevel = level + 1;
|
||||
const children = [];
|
||||
for (let childY = y * 2; childY <= y * 2 + 1; childY++) {
|
||||
for (let childX = x * 2; childX <= x * 2 + 1; childX++) {
|
||||
const child = buildTileNode(
|
||||
tilingScheme,
|
||||
resource,
|
||||
extent,
|
||||
childLevel,
|
||||
maxZoom,
|
||||
childX,
|
||||
childY,
|
||||
);
|
||||
if (defined(child)) {
|
||||
children.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (children.length > 0) {
|
||||
node.children = children;
|
||||
} else {
|
||||
node.geometricError = 0.0;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Resource} resource
|
||||
* @param {number} level
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @ignore
|
||||
*/
|
||||
function resolveTileUrl(resource, level, x, y) {
|
||||
// @ts-expect-error Missing types.
|
||||
const template = resource.url;
|
||||
const tileUrl = template
|
||||
.replace(/\{z\}/gi, `${level}`)
|
||||
.replace(/\{x\}/gi, `${x}`)
|
||||
.replace(/\{y\}/gi, `${y}`);
|
||||
return getAbsoluteUri(tileUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} level
|
||||
* @ignore
|
||||
*/
|
||||
function computeGeometricError(level) {
|
||||
return EARTH_CIRCUMFERENCE_METERS / ((1 << level) * WEB_MERCATOR_TILE_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Rectangle} rectangle
|
||||
* @returns {number[]}
|
||||
* @ignore
|
||||
*/
|
||||
function rectangleToRegion(rectangle) {
|
||||
return [
|
||||
rectangle.west,
|
||||
rectangle.south,
|
||||
rectangle.east,
|
||||
rectangle.north,
|
||||
DEFAULT_REGION_MINIMUM_HEIGHT,
|
||||
DEFAULT_REGION_MAXIMUM_HEIGHT,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TilingScheme} tilingScheme
|
||||
* @param {Rectangle} extent
|
||||
* @param {number} level
|
||||
* @ignore
|
||||
*/
|
||||
function computeTileRangeForExtent(tilingScheme, extent, level) {
|
||||
const maxIndex = (1 << level) - 1;
|
||||
const nw = Cartographic.fromRadians(extent.west, extent.north);
|
||||
const se = Cartographic.fromRadians(extent.east, extent.south);
|
||||
const nwTile = tilingScheme.positionToTileXY(nw, level);
|
||||
const seTile = tilingScheme.positionToTileXY(se, level);
|
||||
if (!defined(nwTile) || !defined(seTile) || extent.west > extent.east) {
|
||||
return {
|
||||
minX: 0,
|
||||
maxX: maxIndex,
|
||||
minY: 0,
|
||||
maxY: maxIndex,
|
||||
};
|
||||
}
|
||||
return {
|
||||
minX: CesiumMath.clamp(Math.min(nwTile.x, seTile.x), 0, maxIndex),
|
||||
maxX: CesiumMath.clamp(Math.max(nwTile.x, seTile.x), 0, maxIndex),
|
||||
minY: CesiumMath.clamp(Math.min(nwTile.y, seTile.y), 0, maxIndex),
|
||||
maxY: CesiumMath.clamp(Math.max(nwTile.y, seTile.y), 0, maxIndex),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TilingScheme} tilingScheme
|
||||
* @param {number} level
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {Rectangle} extent
|
||||
* @param {Rectangle} tileRectangleScratch
|
||||
* @param {Rectangle} intersectionScratch
|
||||
* @ignore
|
||||
*/
|
||||
function tileIntersectsExtent(
|
||||
tilingScheme,
|
||||
level,
|
||||
x,
|
||||
y,
|
||||
extent,
|
||||
tileRectangleScratch,
|
||||
intersectionScratch,
|
||||
) {
|
||||
const tileRectangle = tilingScheme.tileXYToRectangle(
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
tileRectangleScratch,
|
||||
);
|
||||
return defined(
|
||||
Rectangle.intersection(tileRectangle, extent, intersectionScratch),
|
||||
);
|
||||
}
|
||||
|
||||
export default UrlTemplate3DTilesDataProvider;
|
||||
Reference in New Issue
Block a user