mirror of https://github.com/CesiumGS/cesium.git
489 lines
16 KiB
JavaScript
489 lines
16 KiB
JavaScript
import defined from "../../Core/defined.js";
|
|
import Rectangle from "../../Core/Rectangle.js";
|
|
|
|
import CartesianRectangle from "./CartesianRectangle.js";
|
|
|
|
const imageryBoundsScratch = new Rectangle();
|
|
const overlappedRectangleScratch = new Rectangle();
|
|
const clippedRectangleScratch = new Rectangle();
|
|
const nativeInputRectangleScratch = new Rectangle();
|
|
const nativeImageryBoundsScratch = new Rectangle();
|
|
const nativeClippedImageryBoundsScratch = new Rectangle();
|
|
|
|
/**
|
|
* A class containing information about a piece of imagery.
|
|
*
|
|
* This represents the result of computing the imagery tiles that
|
|
* are covered by a given <code>Rectangle</code> (and which part
|
|
* of that imagery is covered, in terms of texture coordinates).
|
|
*
|
|
* This class represents a plain structure, without member functions.
|
|
* Instances are created with the <code>createImageryCoverages</code>
|
|
* function.
|
|
*
|
|
* The instances are used by the <code>ModelPrimitiveImagery</code>, to
|
|
* represent the imagery tiles that are covered by the cartographic
|
|
* bounding rectangle of the primitive positions.
|
|
*
|
|
* Implementation note for ImageryCoverage:
|
|
*
|
|
* Some of the static functions in this class have been extracted from
|
|
* <code>ImageryLayer.prototype._createTileImagerySkeletons</code>
|
|
* See https://github.com/CesiumGS/cesium/blob/5eaa2280f495d8f300d9e1f0497118c97aec54c8/packages/engine/Source/Scene/ImageryLayer.js#L700
|
|
* An instance of this class roughly corresponds to the <code>TileImagery</code>
|
|
* that is created there.
|
|
*
|
|
* @private
|
|
*/
|
|
class ImageryCoverage {
|
|
/**
|
|
* Creates a new instance.
|
|
*
|
|
* @param {number} x x-coordinate of the imagery tile
|
|
* @param {number} y y-coordinate of the imagery tile
|
|
* @param {number} level level of the imagery tile
|
|
* @param {CartesianRectangle} textureCoordinateRectangle The texture coordinate
|
|
* rectangle from the imagery tile that is covered
|
|
* @param {Imagery} imagery The imagery
|
|
*/
|
|
constructor(x, y, level, textureCoordinateRectangle, imagery) {
|
|
this._x = x;
|
|
this._y = y;
|
|
this._level = level;
|
|
this._textureCoordinateRectangle = textureCoordinateRectangle;
|
|
this._imagery = imagery;
|
|
}
|
|
|
|
/**
|
|
* The x-coordinate of the imagery tile, typically correlated with longitude
|
|
*
|
|
* @type {number}
|
|
* @readonly
|
|
*/
|
|
get x() {
|
|
return this._x;
|
|
}
|
|
|
|
/**
|
|
* The y-coordinate of the imagery tile, typically correlated with latitude
|
|
*
|
|
* @type {number}
|
|
* @readonly
|
|
*/
|
|
get y() {
|
|
return this._y;
|
|
}
|
|
|
|
/**
|
|
* The level of the imagery tile
|
|
*
|
|
* @type {number}
|
|
* @readonly
|
|
*/
|
|
get level() {
|
|
return this._level;
|
|
}
|
|
|
|
/**
|
|
* The texture coordinate range that is covered from the
|
|
* imagery tile.
|
|
*
|
|
* This is a <code>CartesianRectangle</code> that contains the
|
|
* (minU, minV, maxU, maxV) coordinate range.
|
|
*
|
|
* Clients may not modify the returned instance.
|
|
*
|
|
* @type {CartesianRectangle}
|
|
* @readonly
|
|
*/
|
|
get textureCoordinateRectangle() {
|
|
return this._textureCoordinateRectangle;
|
|
}
|
|
|
|
/**
|
|
* Returns the imagery
|
|
*
|
|
* @type {Imagery}
|
|
* @readonly
|
|
*/
|
|
get imagery() {
|
|
return this._imagery;
|
|
}
|
|
|
|
/**
|
|
* Computes the <code>ImageryCoverage</code> objects that describe the imagery
|
|
* tiles and the respective texture coordinates that are covered by the given
|
|
* input rectangle in the given imagery data.
|
|
*
|
|
* The given imagery level will be clamped if necessary, to be in the valid
|
|
* range for the imagery provider of the given imagery layer.
|
|
*
|
|
* @param {Rectangle} inputRectangle The input rectangle (e.g. tile bounds)
|
|
* @param {ImageryLayer} imageryLayer The imagery layer
|
|
* @param {number} inputImageryLevel The level for which the imagery coverage
|
|
* should be computed.
|
|
* @returns {ImageryCoverage[]} The objects describing the covered imagery
|
|
* and the respective texture coordinates
|
|
*/
|
|
static createImageryCoverages(
|
|
inputRectangle,
|
|
imageryLayer,
|
|
inputImageryLevel,
|
|
) {
|
|
if (!imageryLayer.show) {
|
|
return [];
|
|
}
|
|
|
|
const imageryProvider = imageryLayer.imageryProvider;
|
|
const imageryLevel = ImageryCoverage._clampImageryLevel(
|
|
imageryProvider,
|
|
inputImageryLevel,
|
|
);
|
|
|
|
// Compute the range, in integer coordinates, of imagery
|
|
// tiles that are covered by the input rectangle
|
|
const imageryBounds = Rectangle.intersection(
|
|
imageryProvider.rectangle,
|
|
imageryLayer.rectangle,
|
|
imageryBoundsScratch,
|
|
);
|
|
const imageryTilingScheme = imageryProvider.tilingScheme;
|
|
const imageryRange = ImageryCoverage._computeImageryRange(
|
|
inputRectangle,
|
|
imageryBounds,
|
|
imageryTilingScheme,
|
|
imageryLevel,
|
|
);
|
|
|
|
// Convert the input rectangle and the imagery bounds into
|
|
// the native coordinate system of the tiling scheme
|
|
const nativeInputRectangle = nativeInputRectangleScratch;
|
|
imageryTilingScheme.rectangleToNativeRectangle(
|
|
inputRectangle,
|
|
nativeInputRectangle,
|
|
);
|
|
const nativeImageryBounds = nativeImageryBoundsScratch;
|
|
imageryTilingScheme.rectangleToNativeRectangle(
|
|
imageryBounds,
|
|
nativeImageryBounds,
|
|
);
|
|
|
|
// A function that returns an imagery rectangle, based on (x, y, level),
|
|
// clipped to the imagery bounds (or undefined if there is no intersection
|
|
// between the imagery rectangle and the bounds)
|
|
const computeClippedImageryRectangle = (x, y, level) => {
|
|
const localImageryRectangle = imageryTilingScheme.tileXYToRectangle(
|
|
x,
|
|
y,
|
|
level,
|
|
);
|
|
const localClippedImageryRectangle = Rectangle.intersection(
|
|
localImageryRectangle,
|
|
imageryBounds,
|
|
clippedRectangleScratch,
|
|
);
|
|
if (!defined(localClippedImageryRectangle)) {
|
|
return undefined;
|
|
}
|
|
const nativeClippedImageryBounds = nativeClippedImageryBoundsScratch;
|
|
imageryTilingScheme.rectangleToNativeRectangle(
|
|
localClippedImageryRectangle,
|
|
nativeClippedImageryBounds,
|
|
);
|
|
return nativeClippedImageryBounds;
|
|
};
|
|
|
|
const imageryCoverages = ImageryCoverage._computeImageryCoverages(
|
|
imageryLayer,
|
|
imageryRange,
|
|
imageryLevel,
|
|
nativeInputRectangle,
|
|
computeClippedImageryRectangle,
|
|
);
|
|
return imageryCoverages;
|
|
}
|
|
|
|
/**
|
|
* Validate the given imagery level against the constraints of the
|
|
* given imagery provider.
|
|
*
|
|
* This will clamp the given level to be in the range
|
|
* <code>[minimumLevel, maximumLevel)</code> that is
|
|
* defined by the given imagery provider (and cut off
|
|
* any fractional part that the input may have)
|
|
*
|
|
* @param {ImageryProvider} imageryProvider The imagery provider
|
|
* @param {number} imageryLevel The imagery level
|
|
* @returns {number} The validated level
|
|
*/
|
|
static _clampImageryLevel(imageryProvider, imageryLevel) {
|
|
const minimumLevel = imageryProvider.minimumLevel ?? 0;
|
|
const maximumLevel =
|
|
imageryProvider.maximumLevel ?? Number.POSITIVE_INFINITY;
|
|
const clampedImageryLevel = Math.min(
|
|
maximumLevel - 1,
|
|
Math.max(minimumLevel, imageryLevel),
|
|
);
|
|
const validImageryLevel = Math.floor(clampedImageryLevel);
|
|
return validImageryLevel;
|
|
}
|
|
|
|
/**
|
|
* Compute the rectangle describing the range of imagery that is covered
|
|
* with the given rectangle.
|
|
*
|
|
* This will compute a rectangle with integer coordinates that describe
|
|
* the X/Y coordinates of the imagery that is overlapped by the given
|
|
* input rectangle, based on the given imagery rectangle.
|
|
*
|
|
* Extracted from _createTileImagerySkeletons.
|
|
*
|
|
* @param {Rectangle} inputRectangle The input rectangle
|
|
* @param {Rectangle} imageryBounds The imagery bounds
|
|
* @param {TilingScheme} imageryTilingScheme The tiling scheme
|
|
* @param {number} imageryLevel The imagery level
|
|
* @returns {CartesianRectangle} The rectangle
|
|
*/
|
|
static _computeImageryRange(
|
|
inputRectangle,
|
|
imageryBounds,
|
|
imageryTilingScheme,
|
|
imageryLevel,
|
|
) {
|
|
const overlappedRectangle = ImageryCoverage._computeOverlappedRectangle(
|
|
inputRectangle,
|
|
imageryBounds,
|
|
);
|
|
const northwestTileCoordinates = imageryTilingScheme.positionToTileXY(
|
|
Rectangle.northwest(overlappedRectangle),
|
|
imageryLevel,
|
|
);
|
|
const southeastTileCoordinates = imageryTilingScheme.positionToTileXY(
|
|
Rectangle.southeast(overlappedRectangle),
|
|
imageryLevel,
|
|
);
|
|
|
|
const result = new CartesianRectangle();
|
|
result.minX = northwestTileCoordinates.x;
|
|
result.minY = northwestTileCoordinates.y;
|
|
result.maxX = southeastTileCoordinates.x;
|
|
result.maxY = southeastTileCoordinates.y;
|
|
|
|
// As extracted from _createTileImagerySkeletons:
|
|
// If the southeast corner of the rectangle lies very close to the north or west side
|
|
// of the southeast tile, we don't actually need the southernmost or easternmost
|
|
// tiles.
|
|
// Similarly, if the northwest corner of the rectangle lies very close to the south or east side
|
|
// of the northwest tile, we don't actually need the northernmost or westernmost tiles.
|
|
// We define "very close" as being within 1/512 of the width of the tile.
|
|
const veryCloseX = inputRectangle.width / 512.0;
|
|
const veryCloseY = inputRectangle.height / 512.0;
|
|
|
|
const northwestTileRectangle = imageryTilingScheme.tileXYToRectangle(
|
|
result.minX,
|
|
result.minY,
|
|
imageryLevel,
|
|
);
|
|
const deltaNorth = Math.abs(
|
|
northwestTileRectangle.south - inputRectangle.north,
|
|
);
|
|
if (deltaNorth < veryCloseY && result.minY < result.maxY) {
|
|
++result.minY;
|
|
}
|
|
const deltaWest = Math.abs(
|
|
northwestTileRectangle.east - inputRectangle.west,
|
|
);
|
|
if (deltaWest < veryCloseX && result.minX < result.maxX) {
|
|
++result.minX;
|
|
}
|
|
|
|
const southeastTileRectangle = imageryTilingScheme.tileXYToRectangle(
|
|
result.maxX,
|
|
result.maxY,
|
|
imageryLevel,
|
|
);
|
|
const deltaSouth = Math.abs(
|
|
southeastTileRectangle.north - inputRectangle.south,
|
|
);
|
|
if (deltaSouth < veryCloseY && result.maxY > result.minY) {
|
|
--result.maxY;
|
|
}
|
|
const deltaEast = Math.abs(
|
|
southeastTileRectangle.west - inputRectangle.east,
|
|
);
|
|
if (deltaEast < veryCloseX && result.maxX > result.minX) {
|
|
--result.maxX;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Clamp the given input rectangle to the given clamp rectangle.
|
|
*
|
|
* If the input rectangle is completely above/below or left/right
|
|
* of the clamp rectangle, then the north/south or east/east
|
|
* if the clamp rectangle will be used in the result.
|
|
*
|
|
* @param {Rectangle} input The input rectangle
|
|
* @param {Rectangle} clamp The clamping rectangle
|
|
* @param {Rectangle} [result] The result
|
|
* @returns {Rectangle} The result
|
|
*/
|
|
static _clampRectangle(input, clamp, result) {
|
|
if (!defined(result)) {
|
|
result = new Rectangle();
|
|
}
|
|
if (input.south >= clamp.north) {
|
|
result.north = result.south = clamp.north;
|
|
} else if (input.north <= clamp.south) {
|
|
result.north = result.south = clamp.south;
|
|
} else {
|
|
result.south = Math.max(input.south, clamp.south);
|
|
result.north = Math.min(input.north, clamp.north);
|
|
}
|
|
|
|
if (input.west >= clamp.east) {
|
|
result.west = result.east = clamp.east;
|
|
} else if (input.east <= clamp.west) {
|
|
result.west = result.east = clamp.west;
|
|
} else {
|
|
result.west = Math.max(input.west, clamp.west);
|
|
result.east = Math.min(input.east, clamp.east);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Compute overlap between the given input rectangle, and the given
|
|
* bounds that have been obtained from the imagery provider.
|
|
*
|
|
* @param {Rectangle} inputRectangle The input
|
|
* @param {Rectangle} imageryBounds The imagery bounds
|
|
* @returns {Rectangle} The rectangle
|
|
*/
|
|
static _computeOverlappedRectangle(inputRectangle, imageryBounds) {
|
|
const overlappedRectangle = Rectangle.intersection(
|
|
inputRectangle,
|
|
imageryBounds,
|
|
overlappedRectangleScratch,
|
|
);
|
|
if (defined(overlappedRectangle)) {
|
|
return overlappedRectangle;
|
|
}
|
|
return ImageryCoverage._clampRectangle(
|
|
inputRectangle,
|
|
imageryBounds,
|
|
overlappedRectangleScratch,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Computes the <code>ImageryCoverage</code> objects that describe the imagery and
|
|
* the texture coordinates that are contained in the given range of
|
|
* imagery tile coordinates, referring to the given input rectangle.
|
|
*
|
|
* @param {ImageryLayer} imageryLayer The imagery layer
|
|
* @param {CartesianRectangle} imageryRange The range of imagery tile coordinates
|
|
* @param {number} imageryLevel The imagery level
|
|
* @param {Rectangle} nativeInputRectangle The input rectangle, in coordinates
|
|
* that are native for the tiling scheme
|
|
* @param {Function} computeClippedImageryRectangle A function that returns
|
|
* an imagery rectangle, based on (x, y, level), clipped to the imagery bounds
|
|
* (or undefined if there is no intersection between the imagery rectangle
|
|
* and the bounds)
|
|
* @returns {ImageryCoverage[]} The objects describing the covered imagery
|
|
* and the respective texture coordinates
|
|
*/
|
|
static _computeImageryCoverages(
|
|
imageryLayer,
|
|
imageryRange,
|
|
imageryLevel,
|
|
nativeInputRectangle,
|
|
computeClippedImageryRectangle,
|
|
) {
|
|
const imageryCoverages = [];
|
|
|
|
for (let i = imageryRange.minX; i <= imageryRange.maxX; i++) {
|
|
const clippedImageryRectangleU = computeClippedImageryRectangle(
|
|
i,
|
|
imageryRange.maxY,
|
|
imageryLevel,
|
|
);
|
|
|
|
if (!defined(clippedImageryRectangleU)) {
|
|
continue;
|
|
}
|
|
|
|
for (let j = imageryRange.minY; j <= imageryRange.maxY; j++) {
|
|
const clippedImageryRectangleV = computeClippedImageryRectangle(
|
|
i,
|
|
j,
|
|
imageryLevel,
|
|
);
|
|
|
|
if (!defined(clippedImageryRectangleV)) {
|
|
continue;
|
|
}
|
|
|
|
const textureCoordinateRectangle =
|
|
ImageryCoverage._localizeToCartesianRectangle(
|
|
clippedImageryRectangleV,
|
|
nativeInputRectangle,
|
|
undefined,
|
|
);
|
|
|
|
// Note: The getImageryFromCache function will create the whole "chain"
|
|
// of ancestor imageries, up to the root, and increases the reference
|
|
// counter for each of them, even though it is not called
|
|
// getImageryFromCacheAndCreateAllAncestorsAndAddReferences.
|
|
// There is currently no way to have a single imagery, because
|
|
// somewhere in TileImagery, the parent is assumed to be present.
|
|
const imagery = imageryLayer.getImageryFromCache(i, j, imageryLevel);
|
|
const imageryCoverage = new ImageryCoverage(
|
|
i,
|
|
j,
|
|
imageryLevel,
|
|
textureCoordinateRectangle,
|
|
imagery,
|
|
);
|
|
imageryCoverages.push(imageryCoverage);
|
|
}
|
|
}
|
|
return imageryCoverages;
|
|
}
|
|
|
|
/**
|
|
* Compute the coordinates of the first rectangle relative to the
|
|
* second rectangle.
|
|
*
|
|
* The result will describe the bounds of the first rectangle
|
|
* in coordinates that are relative to the (south,west) and
|
|
* (width, height) of the second rectangle. This is suitable
|
|
* for describing the texture coordinates of the first
|
|
* rectangle within the second one.
|
|
*
|
|
* The result will be stored in the given result parameter, or
|
|
* in a new rectangle if the result was undefined.
|
|
*
|
|
* @param {Rectangle} rectangleA The first rectangle
|
|
* @param {Rectangle} rectangleB The second rectangle
|
|
* @param {CartesianRectangle} [result] The result
|
|
* @returns {CartesianRectangle} The result
|
|
*/
|
|
static _localizeToCartesianRectangle(rectangleA, rectangleB, result) {
|
|
if (!defined(result)) {
|
|
result = new CartesianRectangle();
|
|
}
|
|
const invX = 1.0 / rectangleB.width;
|
|
const invY = 1.0 / rectangleB.height;
|
|
result.minX = (rectangleA.west - rectangleB.west) * invX;
|
|
result.minY = (rectangleA.south - rectangleB.south) * invY;
|
|
result.maxX = (rectangleA.east - rectangleB.west) * invX;
|
|
result.maxY = (rectangleA.north - rectangleB.south) * invY;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
export default ImageryCoverage;
|