"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Tiles
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getGcsConverterAvailable = exports.MapTileTreeReference = exports.MapTileTree = exports.MapTileTreeScaleRangeVisibility = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const core_geometry_1 = require("@itwin/core-geometry");
const ApproximateTerrainHeights_1 = require("../../ApproximateTerrainHeights");
const IModelApp_1 = require("../../IModelApp");
const PlanarClipMaskState_1 = require("../../PlanarClipMaskState");
const FeatureSymbology_1 = require("../../render/FeatureSymbology");
const internal_1 = require("../internal");
const scratchPoint = core_geometry_1.Point3d.create();
const scratchCorners = [core_geometry_1.Point3d.createZero(), core_geometry_1.Point3d.createZero(), core_geometry_1.Point3d.createZero(), core_geometry_1.Point3d.createZero(), core_geometry_1.Point3d.createZero(), core_geometry_1.Point3d.createZero(), core_geometry_1.Point3d.createZero(), core_geometry_1.Point3d.createZero()];
const scratchCorner = core_geometry_1.Point3d.createZero();
const scratchZNormal = core_geometry_1.Vector3d.create(0, 0, 1);
/** Map tile tree scale range visibility values.
 * @beta */
var MapTileTreeScaleRangeVisibility;
(function (MapTileTreeScaleRangeVisibility) {
    /** state is currently unknown (i.e. never been displayed)  */
    MapTileTreeScaleRangeVisibility[MapTileTreeScaleRangeVisibility["Unknown"] = 0] = "Unknown";
    /** all currently selected tree tiles are visible (i.e within the scale range)  */
    MapTileTreeScaleRangeVisibility[MapTileTreeScaleRangeVisibility["Visible"] = 1] = "Visible";
    /** all currently selected tree tiles are hidden (i.e outside the scale range)  */
    MapTileTreeScaleRangeVisibility[MapTileTreeScaleRangeVisibility["Hidden"] = 2] = "Hidden";
    /** currently selected tree tiles are partially visible (i.e some tiles are within the scale range, and some are outside.) */
    MapTileTreeScaleRangeVisibility[MapTileTreeScaleRangeVisibility["Partial"] = 3] = "Partial";
})(MapTileTreeScaleRangeVisibility || (exports.MapTileTreeScaleRangeVisibility = MapTileTreeScaleRangeVisibility = {}));
/** A [quad tree](https://en.wikipedia.org/wiki/Quadtree) consisting of [[MapTile]]s representing the map imagery draped onto the surface of the Earth.
 * A `MapTileTree` enables display of a globe or planar map with [map imagery](https://en.wikipedia.org/wiki/Tiled_web_map) obtained from any number of sources, such as
 * [Bing](https://learn.microsoft.com/en-us/bingmaps/), [OpenStreetMap](https://wiki.openstreetmap.org/wiki/API), and [GIS servers](https://wiki.openstreetmap.org/wiki/API).
 * The specific imagery displayed is determined by a [[Viewport]]'s [MapImagerySettings]($common) and [BackgroundMapSettings]($common).
 *
 * The map or globe may be smooth, or feature 3d geometry supplied by a [[TerrainProvider]].
 * The terrain displayed in a [[Viewport]] is determined by its [TerrainSettings]($common).
 * @public
 */
class MapTileTree extends internal_1.RealityTileTree {
    /** @internal */
    constructor(params, ecefToDb, bimElevationBias, geodeticOffset, sourceTilingScheme, id, applyTerrain) {
        super(params);
        /** @internal */
        this.layerImageryTrees = [];
        this._layerSettings = new Map();
        this._imageryTreeState = new Map();
        this._modelIdToIndex = new Map();
        /** @internal */
        this.layerClassifiers = new Map();
        this.ecefToDb = ecefToDb;
        this.bimElevationBias = bimElevationBias;
        this.geodeticOffset = geodeticOffset;
        this.sourceTilingScheme = sourceTilingScheme;
        this._mercatorTilingScheme = new internal_1.WebMercatorTilingScheme();
        this._mercatorFractionToDb = this._mercatorTilingScheme.computeMercatorFractionToDb(ecefToDb, bimElevationBias, params.iModel, applyTerrain);
        const quadId = new internal_1.QuadId(sourceTilingScheme.rootLevel, 0, 0);
        this.globeOrigin = this.ecefToDb.getOrigin().clone();
        this.earthEllipsoid = core_geometry_1.Ellipsoid.createCenterMatrixRadii(this.globeOrigin, this.ecefToDb.matrix, core_geometry_1.Constant.earthRadiusWGS84.equator, core_geometry_1.Constant.earthRadiusWGS84.equator, core_geometry_1.Constant.earthRadiusWGS84.polar);
        const globalHeightRange = applyTerrain ? ApproximateTerrainHeights_1.ApproximateTerrainHeights.instance.globalHeightRange : core_geometry_1.Range1d.createXX(0, 0);
        const globalRectangle = internal_1.MapCartoRectangle.createMaximum();
        this.globeMode = id.globeMode;
        this.isOverlay = id.isOverlay;
        this.useDepthBuffer = id.useDepthBuffer;
        this.terrainExaggeration = id.terrainExaggeration;
        this.baseColor = id.baseColor;
        this.baseTransparent = id.baseTransparent;
        this.mapTransparent = id.mapTransparent;
        if (applyTerrain) {
            this.minEarthEllipsoid = core_geometry_1.Ellipsoid.createCenterMatrixRadii(this.globeOrigin, this.ecefToDb.matrix, core_geometry_1.Constant.earthRadiusWGS84.equator + globalHeightRange.low, core_geometry_1.Constant.earthRadiusWGS84.equator + globalHeightRange.low, core_geometry_1.Constant.earthRadiusWGS84.polar + globalHeightRange.low);
            this.maxEarthEllipsoid = core_geometry_1.Ellipsoid.createCenterMatrixRadii(this.globeOrigin, this.ecefToDb.matrix, core_geometry_1.Constant.earthRadiusWGS84.equator + globalHeightRange.high, core_geometry_1.Constant.earthRadiusWGS84.equator + globalHeightRange.high, core_geometry_1.Constant.earthRadiusWGS84.polar + globalHeightRange.high);
            this.produceGeometry = id.produceGeometry;
        }
        else {
            this.minEarthEllipsoid = this.earthEllipsoid;
            this.maxEarthEllipsoid = this.earthEllipsoid;
        }
        const rootPatch = core_geometry_1.EllipsoidPatch.createCapture(this.maxEarthEllipsoid, core_geometry_1.AngleSweep.createStartSweepRadians(0, core_geometry_1.Angle.pi2Radians), core_geometry_1.AngleSweep.createStartSweepRadians(-core_geometry_1.Angle.piOver2Radians, core_geometry_1.Angle.piRadians));
        let range;
        if (this.globeMode === core_common_1.GlobeMode.Ellipsoid) {
            range = rootPatch.range();
        }
        else {
            const corners = this.getFractionalTileCorners(quadId);
            this._mercatorFractionToDb.multiplyPoint3dArrayInPlace(corners);
            range = core_geometry_1.Range3d.createArray(internal_1.MapTile.computeRangeCorners(corners, core_geometry_1.Vector3d.create(0, 0, 1), 0, scratchCorners, globalHeightRange));
        }
        this._rootTile = this.createGlobeChild({ contentId: quadId.contentId, maximumSize: 0, range }, quadId, range.corners(), globalRectangle, rootPatch, undefined);
    }
    /** @internal */
    get parentsAndChildrenExclusive() {
        // If we are not depth buffering we force parents and exclusive to false to cause the map tiles to be sorted
        // by depth so that painters algorithm will approximate correct depth display.
        return this.useDepthBuffer ? this.loader.parentsAndChildrenExclusive : false;
    }
    /** Return the imagery tile tree state of matching the provided imagery tree id.
     * @internal
     */
    getImageryTreeState(imageryTreeId) {
        return this._imageryTreeState.get(imageryTreeId);
    }
    /** Return a cloned dictionary of the imagery tile tree states
     * @internal
     */
    cloneImageryTreeState() {
        const clone = new Map();
        for (const [treeId, state] of this._imageryTreeState) {
            clone.set(treeId, state.clone());
        }
        return clone;
    }
    /** @internal */
    tileFromQuadId(quadId) {
        return this._rootTile.tileFromQuadId(quadId);
    }
    /** Add a new imagery tile tree / map-layer settings pair and initialize the imagery tile tree state.
     * @internal
     */
    addImageryLayer(tree, settings, index, baseImageryLayer) {
        this.layerImageryTrees.push({ tree, settings, baseImageryLayer });
        this._layerSettings.set(tree.modelId, settings);
        if (!this._imageryTreeState.has(tree.modelId))
            this._imageryTreeState.set(tree.modelId, new internal_1.ImageryTileTreeState());
        this._modelIdToIndex.set(tree.modelId, index);
    }
    /** @internal */
    addModelLayer(layerTreeRef, context) {
        const classifier = context.addPlanarClassifier(`MapLayer ${this.modelId}-${layerTreeRef.layerIndex}`, layerTreeRef);
        if (classifier)
            this.layerClassifiers.set(layerTreeRef.layerIndex, classifier);
    }
    /** @internal */
    collectClassifierGraphics(args, selectedTiles) {
        super.collectClassifierGraphics(args, selectedTiles);
        this.layerClassifiers.forEach((layerClassifier) => {
            if (!(args instanceof internal_1.GraphicsCollectorDrawArgs))
                layerClassifier.collectGraphics(args.context, { modelId: this.modelId, tiles: selectedTiles, location: args.location, isPointCloud: this.isPointCloud });
        });
    }
    /** @internal */
    clearImageryTreesAndClassifiers() {
        this.layerImageryTrees.length = 0;
        this._layerSettings.clear();
        this._modelIdToIndex.clear();
        this.layerClassifiers.clear();
    }
    /** @internal */
    get isTransparent() {
        return this.mapTransparent || this.baseTransparent;
    }
    /** @internal */
    get maxDepth() {
        let maxDepth = this.loader.maxDepth;
        this.layerImageryTrees?.forEach((layerImageryTree) => maxDepth = Math.max(maxDepth, layerImageryTree.tree.maxDepth));
        return maxDepth;
    }
    /** @internal */
    createPlanarChild(params, quadId, corners, normal, rectangle, chordHeight, heightRange) {
        const childAvailable = this.mapLoader.isTileAvailable(quadId);
        if (!childAvailable && this.produceGeometry)
            return undefined;
        const patch = new internal_1.PlanarTilePatch(corners, normal, chordHeight);
        const cornerNormals = this.getCornerRays(rectangle);
        if (childAvailable)
            return new internal_1.MapTile(params, this, quadId, patch, rectangle, heightRange, cornerNormals);
        (0, core_bentley_1.assert)(params.parent instanceof internal_1.MapTile);
        let loadableTile = params.parent;
        while (loadableTile?.isUpsampled)
            loadableTile = loadableTile.parent;
        (0, core_bentley_1.assert)(undefined !== loadableTile);
        return new internal_1.UpsampledMapTile(params, this, quadId, patch, rectangle, heightRange, cornerNormals, loadableTile);
    }
    /** @internal */
    createGlobeChild(params, quadId, _rangeCorners, rectangle, ellipsoidPatch, heightRange) {
        return new internal_1.MapTile(params, this, quadId, ellipsoidPatch, rectangle, heightRange, this.getCornerRays(rectangle));
    }
    /** @internal */
    getChildHeightRange(quadId, rectangle, parent) {
        return this.mapLoader.getChildHeightRange(quadId, rectangle, parent);
    }
    /** @internal */
    clearLayers() {
        this._rootTile.clearLayers();
    }
    /** @internal */
    get mapLoader() { return this.loader; }
    /** @internal */
    getBaseRealityDepth(sceneContext) {
        // If the view has ever had global scope then preload low level (global) tiles.
        return (sceneContext.viewport.view.maxGlobalScopeFactor > 1) ? MapTileTree.minDisplayableDepth : -1;
    }
    /** @internal */
    doCreateGlobeChildren(tile) {
        if (this.globeMode !== core_common_1.GlobeMode.Ellipsoid)
            return false;
        const childDepth = tile.depth + 1;
        if (childDepth < MapTileTree.maxGlobeDisplayDepth) // If the depth is too low (tile is too large) display as globe.
            return true;
        return false; // Display as globe if more than 100 KM from project.
    }
    /** @internal */
    doReprojectChildren(tile) {
        if (this._gcsConverter === undefined)
            return false;
        const childDepth = tile.depth + 1;
        if (childDepth < MapTileTree.minReprojectionDepth) // If the depth is too low (tile is too large) omit reprojection.
            return false;
        return this.cartesianRange.intersectsRange(tile.range);
    }
    /** @internal */
    getCornerRays(rectangle) {
        const rays = new Array();
        if (this.globeMode === core_common_1.GlobeMode.Ellipsoid) {
            rays.push(this.earthEllipsoid.radiansToUnitNormalRay(rectangle.low.x, core_common_1.Cartographic.parametricLatitudeFromGeodeticLatitude(rectangle.high.y)));
            rays.push(this.earthEllipsoid.radiansToUnitNormalRay(rectangle.high.x, core_common_1.Cartographic.parametricLatitudeFromGeodeticLatitude(rectangle.high.y)));
            rays.push(this.earthEllipsoid.radiansToUnitNormalRay(rectangle.low.x, core_common_1.Cartographic.parametricLatitudeFromGeodeticLatitude(rectangle.low.y)));
            rays.push(this.earthEllipsoid.radiansToUnitNormalRay(rectangle.high.x, core_common_1.Cartographic.parametricLatitudeFromGeodeticLatitude(rectangle.low.y)));
        }
        else {
            const mercatorFractionRange = rectangle.getTileFractionRange(this._mercatorTilingScheme);
            rays.push(core_geometry_1.Ray3d.createCapture(this._mercatorFractionToDb.multiplyXYZ(mercatorFractionRange.low.x, mercatorFractionRange.high.y), scratchZNormal));
            rays.push(core_geometry_1.Ray3d.createCapture(this._mercatorFractionToDb.multiplyXYZ(mercatorFractionRange.high.x, mercatorFractionRange.high.y), scratchZNormal));
            rays.push(core_geometry_1.Ray3d.createCapture(this._mercatorFractionToDb.multiplyXYZ(mercatorFractionRange.low.x, mercatorFractionRange.low.y), scratchZNormal));
            rays.push(core_geometry_1.Ray3d.createCapture(this._mercatorFractionToDb.multiplyXYZ(mercatorFractionRange.high.x, mercatorFractionRange.low.y), scratchZNormal));
        }
        return rays;
    }
    /** @internal */
    pointAboveEllipsoid(point) {
        return this.earthEllipsoid.worldToLocal(point, scratchPoint).magnitude() > 1;
    }
    getMercatorFractionChildGridPoints(tile, columnCount, rowCount) {
        const gridPoints = [];
        const quadId = tile.quadId;
        const deltaX = 1.0 / columnCount, deltaY = 1.0 / rowCount;
        for (let row = 0; row <= rowCount; row++) {
            for (let column = 0; column <= columnCount; column++) {
                const xFraction = this.sourceTilingScheme.tileXToFraction(quadId.column + column * deltaX, quadId.level);
                const yFraction = this.sourceTilingScheme.tileYToFraction(quadId.row + row * deltaY, quadId.level);
                gridPoints.push(core_geometry_1.Point3d.create(xFraction, yFraction, 0));
            }
        }
        // If not mercator already need to remap latitude...
        if (!(this.sourceTilingScheme instanceof internal_1.WebMercatorTilingScheme))
            for (const gridPoint of gridPoints)
                gridPoint.y = this._mercatorTilingScheme.latitudeToYFraction(this.sourceTilingScheme.yFractionToLatitude(gridPoint.y));
        return gridPoints;
    }
    getChildCornersFromGridPoints(gridPoints, columnCount, rowCount) {
        const childCorners = new Array();
        for (let row = 0; row < rowCount; row++) {
            for (let column = 0; column < columnCount; column++) {
                const index0 = column + row * (columnCount + 1);
                const index1 = index0 + (columnCount + 1);
                childCorners.push([gridPoints[index0], gridPoints[index0 + 1], gridPoints[index1], gridPoints[index1 + 1]]);
            }
        }
        return childCorners;
    }
    /** @internal */
    getCachedReprojectedPoints(gridPoints) {
        const requestProps = [];
        for (const gridPoint of gridPoints)
            requestProps.push({
                x: this._mercatorTilingScheme.xFractionToLongitude(gridPoint.x) * core_geometry_1.Angle.degreesPerRadian,
                y: this._mercatorTilingScheme.yFractionToLatitude(gridPoint.y) * core_geometry_1.Angle.degreesPerRadian,
                z: this.bimElevationBias,
            });
        const iModelCoordinates = this._gcsConverter.getCachedIModelCoordinatesFromGeoCoordinates(requestProps);
        if (iModelCoordinates.missing)
            return undefined;
        return iModelCoordinates.result.map((result) => !result || result.s ? undefined : core_geometry_1.Point3d.fromJSON(result.p));
    }
    /** Minimize reprojection requests by requesting this corners tile and a grid that will include all points for 4 levels of descendants.
     * This greatly reduces the number of reprojection requests which currently require a roundtrip through the backend.
     * @internal
     */
    async loadReprojectionCache(tile) {
        const quadId = tile.quadId;
        const xRange = core_geometry_1.Range1d.createXX(this.sourceTilingScheme.tileXToFraction(quadId.column, quadId.level), this.sourceTilingScheme.tileXToFraction(quadId.column + 1, quadId.level));
        const yRange = core_geometry_1.Range1d.createXX(this.sourceTilingScheme.tileYToFraction(quadId.row, quadId.level), this.sourceTilingScheme.tileYToFraction(quadId.row + 1, quadId.level));
        const cacheDepth = 4, cacheDimension = 2 ** cacheDepth;
        const delta = 1.0 / cacheDimension;
        const requestProps = [];
        for (let row = 0; row <= cacheDimension; row++) {
            for (let column = 0; column <= cacheDimension; column++) {
                let yFraction = yRange.fractionToPoint(row * delta);
                if (!(this.sourceTilingScheme instanceof internal_1.WebMercatorTilingScheme))
                    yFraction = this._mercatorTilingScheme.latitudeToYFraction(this.sourceTilingScheme.yFractionToLatitude(yFraction));
                requestProps.push({
                    x: this._mercatorTilingScheme.xFractionToLongitude(xRange.fractionToPoint(column * delta)) * core_geometry_1.Angle.degreesPerRadian,
                    y: this._mercatorTilingScheme.yFractionToLatitude(yFraction) * core_geometry_1.Angle.degreesPerRadian,
                    z: this.bimElevationBias,
                });
            }
        }
        await this._gcsConverter.getIModelCoordinatesFromGeoCoordinates(requestProps);
    }
    /** Get the corners for planar children.
     * This generally will resolve immediately, but may require an asynchronous request for reprojecting the corners.
     * @internal
     */
    getPlanarChildCorners(tile, columnCount, rowCount, resolve) {
        const resolveCorners = (points, reprojected = undefined) => {
            for (let i = 0; i < points.length; i++) {
                const gridPoint = points[i];
                this._mercatorFractionToDb.multiplyPoint3d(gridPoint, scratchCorner);
                if (this.globeMode !== core_common_1.GlobeMode.Ellipsoid || this.cartesianRange.containsPoint(scratchCorner)) {
                    if (reprojected !== undefined && reprojected[i] !== undefined)
                        reprojected[i].clone(gridPoint);
                    else
                        scratchCorner.clone(gridPoint);
                }
                else {
                    this._mercatorTilingScheme.fractionToCartographic(gridPoint.x, gridPoint.y, MapTileTree._scratchCarto);
                    this.earthEllipsoid.radiansToPoint(MapTileTree._scratchCarto.longitude, core_common_1.Cartographic.parametricLatitudeFromGeodeticLatitude(MapTileTree._scratchCarto.latitude), gridPoint);
                    const cartesianDistance = this.cartesianRange.distanceToPoint(scratchCorner);
                    if (cartesianDistance < this.cartesianTransitionDistance)
                        scratchCorner.interpolate(cartesianDistance / this.cartesianTransitionDistance, gridPoint, gridPoint);
                }
            }
            resolve(this.getChildCornersFromGridPoints(points, columnCount, rowCount));
        };
        let reprojectedPoints;
        const gridPoints = this.getMercatorFractionChildGridPoints(tile, columnCount, rowCount);
        if (this.doReprojectChildren(tile)) {
            reprojectedPoints = this.getCachedReprojectedPoints(gridPoints);
            if (reprojectedPoints) {
                // If the reprojected corners are in the cache, resolve immediately.
                resolveCorners(gridPoints, reprojectedPoints);
            }
            else {
                // If the reprojected corners are not in cache request them - but also request reprojection of a grid that will include descendent corners to ensure they can
                // be reloaded without expensive reprojection requests.
                this.loadReprojectionCache(tile).then(() => {
                    const reprojected = this.getCachedReprojectedPoints(gridPoints);
                    (0, core_bentley_1.assert)(reprojected !== undefined); // We just cached them... they better be there now.
                    resolveCorners(gridPoints, reprojected);
                }).catch((_error) => {
                    resolveCorners(gridPoints);
                });
            }
        }
        else {
            resolveCorners(gridPoints);
        }
    }
    /** Scan the list of currently selected reality tiles, and fire the viewport's 'onMapLayerScaleRangeVisibilityChanged ' event
     * if any scale range visibility change is detected for one more map-layer definition.
     * @internal
     */
    reportTileVisibility(args, selected) {
        const debugControl = args.context.target.debugControl;
        const layersVisibilityBefore = this.cloneImageryTreeState();
        const changes = new Array();
        if (!layersVisibilityBefore)
            return;
        for (const [treeId] of layersVisibilityBefore) {
            const treeVisibility = this.getImageryTreeState(treeId);
            if (treeVisibility) {
                treeVisibility.reset();
            }
        }
        for (const selectedTile of selected) {
            if (selectedTile instanceof internal_1.MapTile) {
                let selectedImageryTiles = selectedTile.imageryTiles;
                if (selectedTile.hiddenImageryTiles) {
                    selectedImageryTiles = selectedImageryTiles ? [...selectedImageryTiles, ...selectedTile.hiddenImageryTiles] : selectedTile.hiddenImageryTiles;
                }
                const leafTiles = selectedTile.highResolutionReplacementTiles;
                if (leafTiles) {
                    for (const tile of leafTiles) {
                        const treeState = this.getImageryTreeState(tile.tree.id);
                        treeState?.setScaleRangeVisibility(false);
                    }
                }
                if (selectedImageryTiles) {
                    for (const selectedImageryTile of selectedImageryTiles) {
                        const treeState = this.getImageryTreeState(selectedImageryTile.tree.id);
                        if (treeState) {
                            if (selectedImageryTile.isOutOfLodRange) {
                                treeState.setScaleRangeVisibility(false);
                            }
                            else {
                                treeState.setScaleRangeVisibility(true);
                            }
                        }
                    }
                }
            }
        }
        for (const [treeId, prevState] of layersVisibilityBefore) {
            const newState = this.getImageryTreeState(treeId);
            if (newState) {
                const prevVisibility = prevState.getScaleRangeVisibility();
                const visibility = newState.getScaleRangeVisibility();
                if (prevVisibility !== visibility) {
                    if (debugControl && debugControl.logRealityTiles) {
                        // eslint-disable-next-line no-console
                        console.log(`ImageryTileTree '${treeId}' changed prev state: '${MapTileTreeScaleRangeVisibility[prevVisibility]}' new state: '${MapTileTreeScaleRangeVisibility[visibility]}'`);
                    }
                    const mapLayersIndexes = args.context.viewport.getMapLayerIndexesFromIds(this.id, treeId);
                    for (const index of mapLayersIndexes) {
                        changes.push({ index: index.index, isOverlay: index.isOverlay, visibility });
                    }
                }
            }
        }
        if (changes.length !== 0) {
            args.context.viewport.onMapLayerScaleRangeVisibilityChanged.raiseEvent(changes);
        }
    }
    /** @internal */
    getFractionalTileCorners(quadId) {
        const corners = [];
        corners.push(core_geometry_1.Point3d.create(this.sourceTilingScheme.tileXToFraction(quadId.column, quadId.level), this.sourceTilingScheme.tileYToFraction(quadId.row, quadId.level), 0.0));
        corners.push(core_geometry_1.Point3d.create(this.sourceTilingScheme.tileXToFraction(quadId.column + 1, quadId.level), this.sourceTilingScheme.tileYToFraction(quadId.row, quadId.level), 0.0));
        corners.push(core_geometry_1.Point3d.create(this.sourceTilingScheme.tileXToFraction(quadId.column, quadId.level), this.sourceTilingScheme.tileYToFraction(quadId.row + 1, quadId.level), 0.0));
        corners.push(core_geometry_1.Point3d.create(this.sourceTilingScheme.tileXToFraction(quadId.column + 1, quadId.level), this.sourceTilingScheme.tileYToFraction(quadId.row + 1, quadId.level), 0.0));
        return corners;
    }
    /** @internal */
    getTileRectangle(quadId) {
        return this.sourceTilingScheme.tileXYToRectangle(quadId.column, quadId.row, quadId.level);
    }
    /** @internal */
    getLayerIndex(imageryTreeId) {
        const index = this._modelIdToIndex.get(imageryTreeId);
        return index === undefined ? -1 : index;
    }
    /** @internal */
    getLayerTransparency(imageryTreeId) {
        const layerSettings = this._layerSettings.get(imageryTreeId);
        (0, core_bentley_1.assert)(undefined !== layerSettings);
        return undefined === layerSettings || !layerSettings.transparency ? 0.0 : layerSettings.transparency;
    }
}
exports.MapTileTree = MapTileTree;
/** Reprojection does not work with very large tiles so just do linear transform.
 * @internal
 */
MapTileTree.minReprojectionDepth = 8;
/** @internal */
MapTileTree.maxGlobeDisplayDepth = 8;
/** @internal */
MapTileTree.minDisplayableDepth = 3;
MapTileTree._scratchCarto = core_common_1.Cartographic.createZero();
/** @internal */
class MapTileTreeProps {
    get priority() { return this.loader.priority; }
    constructor(modelId, loader, iModel, gcsConverterAvailable) {
        this.gcsConverterAvailable = gcsConverterAvailable;
        this.location = core_geometry_1.Transform.createIdentity();
        this.yAxisUp = true;
        this.is3d = true;
        this.rootTile = { contentId: "", range: core_geometry_1.Range3d.createNull(), maximumSize: 0 };
        this.id = this.modelId = modelId;
        this.loader = loader;
        this.iModel = iModel;
    }
}
function createViewFlagOverrides(wantLighting, wantThematic) {
    return (0, internal_1.createDefaultViewFlagOverrides)({ clipVolume: false, lighting: wantLighting, thematic: wantThematic });
}
class MapTreeSupplier {
    constructor() {
        this.isEcefDependent = true;
    }
    compareTileTreeIds(lhs, rhs) {
        let cmp = (0, core_bentley_1.compareNumbers)(lhs.tileUserId, rhs.tileUserId);
        if (0 === cmp) {
            cmp = (0, core_bentley_1.compareStringsOrUndefined)(lhs.maskModelIds, rhs.maskModelIds);
            if (0 === cmp) {
                cmp = (0, core_bentley_1.compareBooleans)(lhs.isOverlay, rhs.isOverlay);
                if (0 === cmp) {
                    cmp = (0, core_bentley_1.compareBooleans)(lhs.wantSkirts, rhs.wantSkirts);
                    if (0 === cmp) {
                        cmp = (0, core_bentley_1.compareBooleans)(lhs.wantNormals, rhs.wantNormals);
                        if (0 === cmp) {
                            cmp = (0, core_bentley_1.compareNumbers)(lhs.globeMode, rhs.globeMode);
                            if (0 === cmp) {
                                cmp = (0, core_bentley_1.compareNumbers)(lhs.baseColor ? lhs.baseColor.tbgr : -1, rhs.baseColor ? rhs.baseColor.tbgr : -1);
                                if (0 === cmp) {
                                    cmp = (0, core_bentley_1.compareBooleans)(lhs.baseTransparent, rhs.baseTransparent);
                                    if (0 === cmp) {
                                        cmp = (0, core_bentley_1.compareBooleans)(lhs.mapTransparent, rhs.mapTransparent);
                                        if (0 === cmp) {
                                            cmp = (0, core_bentley_1.compareBooleans)(lhs.applyTerrain, rhs.applyTerrain);
                                            if (0 === cmp) {
                                                if (lhs.applyTerrain) {
                                                    // Terrain-only settings.
                                                    cmp = (0, core_bentley_1.compareStrings)(lhs.terrainProviderName, rhs.terrainProviderName);
                                                    if (0 === cmp) {
                                                        cmp = (0, core_bentley_1.compareStringsOrUndefined)(lhs.terrainDataSource, rhs.terrainDataSource);
                                                        if (0 === cmp) {
                                                            cmp = (0, core_bentley_1.compareNumbers)(lhs.terrainHeightOrigin, rhs.terrainHeightOrigin);
                                                            if (0 === cmp) {
                                                                cmp = (0, core_bentley_1.compareNumbers)(lhs.terrainHeightOriginMode, rhs.terrainHeightOriginMode);
                                                                if (0 === cmp) {
                                                                    cmp = (0, core_bentley_1.compareNumbers)(lhs.terrainExaggeration, rhs.terrainExaggeration);
                                                                    if (0 === cmp)
                                                                        cmp = (0, core_bentley_1.compareBooleansOrUndefined)(lhs.produceGeometry, rhs.produceGeometry);
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                                else {
                                                    // Non-Terrain (flat) settings.
                                                    cmp = (0, core_bentley_1.compareNumbers)(lhs.mapGroundBias, rhs.mapGroundBias);
                                                    if (0 === cmp)
                                                        cmp = (0, core_bentley_1.compareBooleans)(lhs.useDepthBuffer, rhs.useDepthBuffer);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return cmp;
    }
    async computeHeightBias(heightOrigin, heightOriginMode, exaggeration, iModel, elevationProvider) {
        const projectCenter = iModel.projectExtents.center;
        switch (heightOriginMode) {
            case core_common_1.TerrainHeightOriginMode.Ground:
                return heightOrigin + exaggeration * (await elevationProvider.getHeightValue(projectCenter, iModel, true));
            case core_common_1.TerrainHeightOriginMode.Geodetic:
                return heightOrigin;
            case core_common_1.TerrainHeightOriginMode.Geoid:
                return heightOrigin + await elevationProvider.getGeodeticToSeaLevelOffset(projectCenter, iModel);
        }
    }
    async createTileTree(id, iModel) {
        let bimElevationBias = 0, terrainProvider, geodeticOffset = 0;
        let applyTerrain = id.applyTerrain;
        const modelId = iModel.transientIds.getNext();
        const gcsConverterAvailable = await getGcsConverterAvailable(iModel);
        const terrainOpts = {
            wantSkirts: id.wantSkirts,
            exaggeration: id.terrainExaggeration,
            wantNormals: id.wantNormals,
            dataSource: id.terrainDataSource,
            produceGeometry: id.produceGeometry,
        };
        if (id.applyTerrain) {
            await ApproximateTerrainHeights_1.ApproximateTerrainHeights.instance.initialize();
            const elevationProvider = new internal_1.BingElevationProvider();
            bimElevationBias = -await this.computeHeightBias(id.terrainHeightOrigin, id.terrainHeightOriginMode, id.terrainExaggeration, iModel, elevationProvider);
            geodeticOffset = await elevationProvider.getGeodeticToSeaLevelOffset(iModel.projectExtents.center, iModel);
            const provider = IModelApp_1.IModelApp.terrainProviderRegistry.find(id.terrainProviderName);
            if (provider)
                terrainProvider = await provider.createTerrainMeshProvider(terrainOpts);
            if (!terrainProvider) {
                applyTerrain = false;
                geodeticOffset = 0;
            }
        }
        if (!terrainProvider) {
            terrainProvider = new internal_1.EllipsoidTerrainProvider(terrainOpts);
            bimElevationBias = id.mapGroundBias;
        }
        const loader = new internal_1.MapTileLoader(iModel, modelId, bimElevationBias, terrainProvider);
        const ecefToDb = iModel.getMapEcefToDb(bimElevationBias);
        if (id.maskModelIds)
            await iModel.models.load(core_bentley_1.CompressedId64Set.decompressSet(id.maskModelIds));
        const treeProps = new MapTileTreeProps(modelId, loader, iModel, gcsConverterAvailable);
        return new MapTileTree(treeProps, ecefToDb, bimElevationBias, geodeticOffset, terrainProvider.tilingScheme, id, applyTerrain);
    }
}
const mapTreeSupplier = new MapTreeSupplier();
/** Specialization of tile tree that represents background map.
 * @internal
 */
class MapTileTreeReference extends internal_1.TileTreeReference {
    constructor(settings, _baseLayerSettings, _layerSettings, iModel, tileUserId, isOverlay, _isDrape, _overrideTerrainDisplay) {
        super();
        this._baseLayerSettings = _baseLayerSettings;
        this._layerSettings = _layerSettings;
        this.isOverlay = isOverlay;
        this._isDrape = _isDrape;
        this._overrideTerrainDisplay = _overrideTerrainDisplay;
        this._baseImageryLayerIncluded = false;
        this._layerTrees = new Array();
        this._baseTransparent = false;
        this._tileUserId = tileUserId;
        this._settings = settings;
        this._iModel = iModel;
        let tree;
        if (!isOverlay && this._baseLayerSettings !== undefined) {
            if (this._baseLayerSettings instanceof core_common_1.MapLayerSettings) {
                tree = (0, internal_1.createMapLayerTreeReference)(this._baseLayerSettings, 0, iModel);
                this._baseTransparent = this._baseLayerSettings.transparency > 0;
            }
            else {
                this._baseColor = this._baseLayerSettings;
                this._baseTransparent = this._baseColor?.getTransparency() > 0;
            }
        }
        if (this._baseImageryLayerIncluded = (undefined !== tree))
            this._layerTrees.push(tree);
        for (let i = 0; i < this._layerSettings.length; i++)
            if (undefined !== (tree = (0, internal_1.createMapLayerTreeReference)(this._layerSettings[i], i + 1, iModel)))
                this._layerTrees.push(tree);
        if (this._settings.planarClipMask && this._settings.planarClipMask.isValid)
            this._planarClipMask = PlanarClipMaskState_1.PlanarClipMaskState.create(this._settings.planarClipMask);
        if (this._overrideTerrainDisplay && this._overrideTerrainDisplay()?.produceGeometry)
            this.collectTileGeometry = (collector) => this._collectTileGeometry(collector);
    }
    forEachLayerTileTreeRef(func) {
        for (const layerTree of this._layerTrees) {
            (0, core_bentley_1.assert)(layerTree instanceof internal_1.MapLayerTileTreeReference);
            func(layerTree);
        }
    }
    get isGlobal() { return true; }
    get baseColor() { return this._baseColor; }
    get planarclipMaskPriority() { return core_common_1.PlanarClipMaskPriority.BackgroundMap; }
    _createGeometryTreeReference() {
        if (!this._settings.applyTerrain || this._isDrape)
            return undefined; // Don't bother generating non-terrain (flat) geometry.
        const ref = new MapTileTreeReference(this._settings, undefined, [], this._iModel, this._tileUserId, false, false, () => {
            return { produceGeometry: true };
        });
        (0, core_bentley_1.assert)(undefined !== ref.collectTileGeometry);
        return ref;
    }
    /** Terrain  tiles do not contribute to the range used by "fit view". */
    unionFitRange(_range) { }
    get settings() { return this._settings; }
    set settings(settings) {
        this._settings = settings;
        this._planarClipMask = settings.planarClipMask ? PlanarClipMaskState_1.PlanarClipMaskState.create(settings.planarClipMask) : undefined;
    }
    setBaseLayerSettings(baseLayerSettings) {
        (0, core_bentley_1.assert)(!this.isOverlay);
        let tree;
        this._baseLayerSettings = baseLayerSettings;
        if (baseLayerSettings instanceof core_common_1.MapLayerSettings) {
            tree = (0, internal_1.createMapLayerTreeReference)(baseLayerSettings, 0, this._iModel);
            this._baseColor = undefined;
            this._baseTransparent = baseLayerSettings.transparency > 0;
        }
        else {
            this._baseColor = baseLayerSettings;
            this._baseTransparent = this._baseColor.getTransparency() > 0;
        }
        if (tree) {
            if (this._baseImageryLayerIncluded)
                this._layerTrees[0] = tree;
            else
                this._layerTrees.splice(0, 0, tree);
        }
        else {
            if (this._baseImageryLayerIncluded)
                this._layerTrees.shift();
        }
        this._baseImageryLayerIncluded = tree !== undefined;
        this.clearLayers();
    }
    get layerSettings() {
        return this._layerSettings;
    }
    setLayerSettings(layerSettings) {
        this._layerSettings = layerSettings;
        const baseLayerIndex = this._baseImageryLayerIncluded ? 1 : 0;
        this._layerTrees.length = Math.min(layerSettings.length + baseLayerIndex, this._layerTrees.length); // Truncate if number of layers reduced.
        for (let i = 0; i < layerSettings.length; i++) {
            const treeIndex = i + baseLayerIndex;
            if (treeIndex >= this._layerTrees.length || !this._layerTrees[treeIndex]?.layerSettings.displayMatches(layerSettings[i]))
                this._layerTrees[treeIndex] = (0, internal_1.createMapLayerTreeReference)(layerSettings[i], treeIndex, this._iModel);
        }
        this.clearLayers();
    }
    clearLayers() {
        const tree = this.treeOwner.tileTree;
        if (undefined !== tree)
            tree.clearLayers();
    }
    get castsShadows() {
        return false;
    }
    get _isLoadingComplete() {
        // Wait until drape tree is fully loaded too.
        for (const drapeTree of this._layerTrees)
            if (drapeTree && !drapeTree.isLoadingComplete)
                return false;
        return super._isLoadingComplete;
    }
    get useDepthBuffer() {
        return !this.isOverlay && (this.settings.applyTerrain || this.settings.useDepthBuffer);
    }
    get treeOwner() {
        let wantSkirts = (this.settings.applyTerrain || this.useDepthBuffer) && !this.settings.transparency && !this._baseTransparent;
        if (wantSkirts) {
            const maskTrans = this._planarClipMask?.settings.transparency;
            wantSkirts = (undefined === maskTrans || maskTrans <= 0);
        }
        const id = {
            tileUserId: this._tileUserId,
            applyTerrain: this.settings.applyTerrain && !this._isDrape,
            terrainProviderName: this.settings.terrainSettings.providerName,
            terrainDataSource: this.settings.terrainSettings.dataSource,
            terrainHeightOrigin: this.settings.terrainSettings.heightOrigin,
            terrainHeightOriginMode: this.settings.terrainSettings.heightOriginMode,
            terrainExaggeration: this.settings.terrainSettings.exaggeration,
            mapGroundBias: this.settings.groundBias,
            wantSkirts,
            // Can set to this.settings.terrainSettings.applyLighting if we want to ever apply lighting to terrain again so that normals are retrieved when lighting is on.
            wantNormals: false,
            globeMode: this._isDrape ? core_common_1.GlobeMode.Plane : this.settings.globeMode,
            isOverlay: this.isOverlay,
            useDepthBuffer: this.useDepthBuffer,
            baseColor: this._baseColor,
            baseTransparent: this._baseTransparent,
            mapTransparent: Number(this.settings.transparency) > 0,
            maskModelIds: this._planarClipMask?.settings.compressedModelIds,
            produceGeometry: false,
        };
        if (undefined !== this._overrideTerrainDisplay) {
            const ovr = this._overrideTerrainDisplay();
            if (undefined !== ovr) {
                id.wantSkirts = ovr.wantSkirts ?? id.wantSkirts;
                id.wantNormals = ovr.wantNormals ?? id.wantNormals;
                id.produceGeometry = ovr.produceGeometry === true;
            }
        }
        return this._iModel.tiles.getTileTreeOwner(id, mapTreeSupplier);
    }
    getLayerImageryTreeRef(index) {
        const baseLayerIndex = this._baseImageryLayerIncluded ? 1 : 0;
        const treeIndex = index + baseLayerIndex;
        return index < 0 || treeIndex >= this._layerTrees.length ? undefined : this._layerTrees[treeIndex];
    }
    /** Return the map-layer scale range visibility for the provided map-layer index.
   * @internal
   */
    getMapLayerScaleRangeVisibility(index) {
        const tree = this.treeOwner.tileTree;
        if (undefined !== tree) {
            const tileTreeRef = this.getLayerImageryTreeRef(index);
            const treeId = tileTreeRef?.treeOwner.tileTree?.id;
            if (treeId !== undefined) {
                const treeState = tree.getImageryTreeState(treeId);
                if (treeState !== undefined)
                    return treeState.getScaleRangeVisibility();
            }
        }
        return MapTileTreeScaleRangeVisibility.Unknown;
    }
    initializeLayers(context) {
        let hasLoadedTileTree = false;
        const tree = this.treeOwner.load();
        if (undefined === tree) {
            return hasLoadedTileTree; // Not loaded yet.
        }
        tree.layerImageryTrees.length = 0;
        if (0 === this._layerTrees.length) {
            return !this.isOverlay;
        }
        let treeIndex = this._layerTrees.length - 1;
        // Start displaying at the highest completely opaque layer...
        for (; treeIndex >= 1; treeIndex--) {
            const layerTreeRef = this._layerTrees[treeIndex];
            if (layerTreeRef?.isOpaque)
                break; // This layer is completely opaque and will obscure all others so ignore lower ones.
        }
        for (; treeIndex < this._layerTrees.length; treeIndex++) {
            const layerTreeRef = this._layerTrees[treeIndex];
            const hasValidTileTree = layerTreeRef && internal_1.TileTreeLoadStatus.NotFound !== layerTreeRef.treeOwner.loadStatus;
            const isImageryMapLayer = layerTreeRef instanceof internal_1.ImageryMapLayerTreeReference;
            const isLayerVisible = (isImageryMapLayer || (!isImageryMapLayer && layerTreeRef?.layerSettings.visible));
            // Load tile tree for each configured layer.
            // Note: Non-visible imagery layer are always added to allow proper tile tree scale range visibility reporting.
            if (hasValidTileTree
                && isLayerVisible
                && !layerTreeRef.layerSettings.allSubLayersInvisible) {
                const layerTree = layerTreeRef.treeOwner.load();
                if (layerTree !== undefined) {
                    hasLoadedTileTree = true;
                }
                else {
                    // Let's continue, there might be loaded tile tree in the list
                    continue;
                }
                // Add loaded TileTree
                const baseImageryLayer = this._baseImageryLayerIncluded && (treeIndex === 0);
                if (layerTree instanceof internal_1.ImageryMapTileTree) {
                    tree.addImageryLayer(layerTree, layerTreeRef.layerSettings, treeIndex, baseImageryLayer);
                }
                else if (layerTreeRef instanceof internal_1.ModelMapLayerTileTreeReference)
                    tree.addModelLayer(layerTreeRef, context);
            }
        }
        return hasLoadedTileTree;
    }
    /** Adds this reference's graphics to the scene. By default this invokes [[TileTree.drawScene]] on the referenced TileTree, if it is loaded. */
    addToScene(context) {
        if (!context.viewFlags.backgroundMap)
            return;
        const tree = this.treeOwner.load();
        if (undefined === tree || !this.initializeLayers(context))
            return; // Not loaded yet.
        if (this._planarClipMask && this._planarClipMask.settings.isValid)
            context.addPlanarClassifier(tree.modelId, undefined, this._planarClipMask);
        const nonLocatable = this.settings.locatable ? undefined : true;
        const transparency = this.settings.transparency ? this.settings.transparency : undefined;
        this._symbologyOverrides = new FeatureSymbology_1.FeatureSymbology.Overrides();
        if (nonLocatable || transparency) {
            this._symbologyOverrides.override({
                modelId: tree.modelId,
                appearance: core_common_1.FeatureAppearance.fromJSON({ transparency, nonLocatable }),
            });
        }
        const args = this.createDrawArgs(context);
        if (undefined !== args)
            tree.draw(args);
        tree.clearImageryTreesAndClassifiers();
    }
    createDrawArgs(context) {
        const args = super.createDrawArgs(context);
        if (undefined === args)
            return undefined;
        const tree = this.treeOwner.load();
        return new internal_1.RealityTileDrawArgs(args, args.worldToViewMap, args.frustumPlanes, undefined, tree?.layerClassifiers);
    }
    getViewFlagOverrides(_tree) {
        return createViewFlagOverrides(false, this._settings.applyTerrain ? undefined : false);
    }
    getSymbologyOverrides(_tree) {
        return this._symbologyOverrides;
    }
    discloseTileTrees(trees) {
        super.discloseTileTrees(trees);
        for (const imageryTree of this._layerTrees)
            if (imageryTree)
                trees.disclose(imageryTree);
        if (this._planarClipMask)
            this._planarClipMask.discloseTileTrees(trees);
    }
    imageryTreeFromTreeModelIds(mapTreeModelId, layerTreeModelId) {
        const imageryTrees = [];
        const tree = this.treeOwner.tileTree;
        if (undefined === tree || tree.modelId !== mapTreeModelId)
            return imageryTrees;
        for (const imageryTree of this._layerTrees)
            if (imageryTree && imageryTree.treeOwner.tileTree && imageryTree.treeOwner.tileTree.modelId === layerTreeModelId)
                imageryTrees.push(imageryTree);
        return imageryTrees;
    }
    layerFromTreeModelIds(mapTreeModelId, layerTreeModelId) {
        const imageryTree = this.imageryTreeFromTreeModelIds(mapTreeModelId, layerTreeModelId);
        return imageryTree.map((tree) => {
            const isBaseLayer = (this._baseImageryLayerIncluded && tree.layerIndex === 0);
            return {
                isBaseLayer,
                index: isBaseLayer ? undefined : { isOverlay: this.isOverlay, index: this._baseImageryLayerIncluded ? tree.layerIndex - 1 : tree.layerIndex },
                settings: tree.layerSettings, provider: tree.imageryProvider,
            };
        });
    }
    // Utility method that execute the provided function for every *imagery* tiles under a given HitDetail object.
    async forEachImageryTileHit(hit, func) {
        const tree = this.treeOwner.tileTree;
        if (undefined === tree || hit.iModel !== tree.iModel || tree.modelId !== hit.modelId || !hit.viewport || !hit.viewport.view.is3d)
            return undefined;
        const backgroundMapGeometry = hit.viewport.displayStyle.getBackgroundMapGeometry();
        if (undefined === backgroundMapGeometry)
            return undefined;
        const worldPoint = hit.hitPoint.clone();
        let cartoGraphic;
        try {
            cartoGraphic = (await backgroundMapGeometry.dbToWGS84CartographicFromGcs([worldPoint]))[0];
        }
        catch {
        }
        if (!cartoGraphic) {
            return undefined;
        }
        const imageryTreeRef = this.imageryTreeFromTreeModelIds(hit.modelId, hit.sourceId);
        if (imageryTreeRef.length > 0) {
            if (hit.tileId !== undefined) {
                const terrainQuadId = internal_1.QuadId.createFromContentId(hit.tileId);
                const terrainTile = tree.tileFromQuadId(terrainQuadId);
                for (const treeRef of imageryTreeRef) {
                    const processedTileIds = [];
                    if (terrainTile && terrainTile.imageryTiles) {
                        const imageryTree = treeRef.treeOwner.tileTree;
                        if (imageryTree) {
                            for (const imageryTile of terrainTile.imageryTiles) {
                                if (!processedTileIds.includes(imageryTile.contentId)
                                    && imageryTree === imageryTile.imageryTree
                                    && imageryTile.rectangle.containsCartographic(cartoGraphic)) {
                                    processedTileIds.push(imageryTile.contentId);
                                    try {
                                        await func(treeRef, imageryTile.quadId, cartoGraphic, imageryTree);
                                    }
                                    catch {
                                        // continue iterating even though we got a failure.
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    canSupplyToolTip(hit) {
        const tree = this.treeOwner.tileTree;
        return undefined !== tree && tree.modelId === hit.modelId;
    }
    async getToolTip(hit) {
        const tree = this.treeOwner.tileTree;
        if (undefined === tree || tree.modelId !== hit.modelId)
            return undefined;
        let carto;
        const strings = [];
        const getTooltipFunc = async (imageryTreeRef, quadId, cartoGraphic, imageryTree) => {
            strings.push(`${IModelApp_1.IModelApp.localization.getLocalizedString("iModelJs:MapLayers.ImageryLayer")}: ${imageryTreeRef.layerSettings.name}`);
            carto = cartoGraphic;
            await imageryTree.imageryLoader.getToolTip(strings, quadId, cartoGraphic, imageryTree);
        };
        try {
            await this.forEachImageryTileHit(hit, getTooltipFunc);
        }
        catch {
            // No results added
        }
        if (carto) {
            strings.push(`${IModelApp_1.IModelApp.localization.getLocalizedString("iModelJs:MapLayers.Latitude")}: ${carto.latitudeDegrees.toFixed(4)}`);
            strings.push(`${IModelApp_1.IModelApp.localization.getLocalizedString("iModelJs:MapLayers.Longitude")}: ${carto.longitudeDegrees.toFixed(4)}`);
            if (this.settings.applyTerrain && tree.terrainExaggeration !== 0.0) {
                const geodeticHeight = (carto.height - tree.bimElevationBias) / tree.terrainExaggeration;
                strings.push(`${IModelApp_1.IModelApp.localization.getLocalizedString("iModelJs:MapLayers.Height")}: ${geodeticHeight.toFixed(1)} ${IModelApp_1.IModelApp.localization.getLocalizedString("iModelJs:MapLayers.SeaLevel")}: ${(geodeticHeight - tree.geodeticOffset).toFixed(1)}`);
            }
        }
        const div = document.createElement("div");
        div.innerHTML = strings.join("<br>");
        return div;
    }
    async getMapFeatureInfo(hit, options) {
        const tree = this.treeOwner.tileTree;
        if (undefined === tree || hit.iModel !== tree.iModel || tree.modelId !== hit.modelId || !hit.viewport || !hit.viewport.view.is3d)
            return undefined;
        const info = [];
        const imageryTreeRef = this.imageryTreeFromTreeModelIds(hit.modelId, hit.sourceId);
        if (imageryTreeRef !== undefined) {
            const getFeatureInfoFunc = async (_imageryTreeRef, quadId, cartoGraphic, imageryTree) => {
                try {
                    await imageryTree.imageryLoader.getMapFeatureInfo(info, quadId, cartoGraphic, imageryTree, hit, options);
                }
                catch {
                }
            };
            try {
                await this.forEachImageryTileHit(hit, getFeatureInfoFunc);
            }
            catch {
                // No results added
            }
        }
        return info;
    }
    /** Add logo cards to logo div. */
    addLogoCards(cards, vp) {
        const tree = this.treeOwner.tileTree;
        if (tree) {
            tree.mapLoader.terrainProvider.addLogoCards(cards, vp);
            for (const imageryTreeRef of this._layerTrees) {
                if (imageryTreeRef?.layerSettings.visible) {
                    const imageryTree = imageryTreeRef.treeOwner.tileTree;
                    if (imageryTree instanceof internal_1.ImageryMapTileTree)
                        imageryTree.addLogoCards(cards, vp);
                }
            }
        }
    }
}
exports.MapTileTreeReference = MapTileTreeReference;
/** Returns whether a GCS converter is available.
 * @internal
 */
async function getGcsConverterAvailable(iModel) {
    if (iModel.noGcsDefined)
        return false;
    // Determine if we have a usable GCS.
    const converter = iModel.geoServices.getConverter("WGS84");
    if (undefined === converter)
        return false;
    const requestProps = [{ x: 0, y: 0, z: 0 }];
    let haveConverter;
    try {
        const responseProps = await converter.getIModelCoordinatesFromGeoCoordinates(requestProps);
        haveConverter = responseProps.iModelCoords.length === 1 && responseProps.iModelCoords[0].s !== core_common_1.GeoCoordStatus.NoGCSDefined;
    }
    catch {
        haveConverter = false;
    }
    return haveConverter;
}
exports.getGcsConverterAvailable = getGcsConverterAvailable;
//# sourceMappingURL=MapTileTree.js.map